diff --git a/plugins/rest/src/main/java/org/apache/struts2/rest/handler/JacksonJsonHandler.java b/plugins/rest/src/main/java/org/apache/struts2/rest/handler/JacksonJsonHandler.java index d97af08e38..bd929247e6 100644 --- a/plugins/rest/src/main/java/org/apache/struts2/rest/handler/JacksonJsonHandler.java +++ b/plugins/rest/src/main/java/org/apache/struts2/rest/handler/JacksonJsonHandler.java @@ -21,28 +21,82 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.SerializationFeature; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.apache.struts2.ActionInvocation; import org.apache.struts2.inject.Inject; import org.apache.struts2.StrutsConstants; +import org.apache.struts2.interceptor.parameter.StrutsParameter; +import java.beans.BeanInfo; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; import java.io.IOException; import java.io.Reader; import java.io.Writer; +import java.lang.reflect.Method; +import java.util.Map; /** * Handles JSON content using jackson-lib */ public class JacksonJsonHandler implements ContentTypeHandler { + private static final Logger LOG = LogManager.getLogger(JacksonJsonHandler.class); private static final String DEFAULT_CONTENT_TYPE = "application/json"; private String defaultEncoding = "ISO-8859-1"; private ObjectMapper mapper = new ObjectMapper(); + private boolean requireAnnotations = false; + + @Inject(value = StrutsConstants.STRUTS_PARAMETERS_REQUIRE_ANNOTATIONS, required = false) + public void setRequireAnnotations(String requireAnnotations) { + this.requireAnnotations = org.apache.commons.lang3.BooleanUtils.toBoolean(requireAnnotations); + } @Override public void toObject(ActionInvocation invocation, Reader in, Object target) throws IOException { mapper.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false); - ObjectReader or = mapper.readerForUpdating(target); - or.readValue(in); + if (requireAnnotations) { + // Deserialize into a map first, then filter by @StrutsParameter annotation + @SuppressWarnings("unchecked") + Map jsonMap = mapper.readValue(in, Map.class); + applyAnnotatedProperties(target, jsonMap); + } else { + ObjectReader or = mapper.readerForUpdating(target); + or.readValue(in); + } + } + + /** + * Sets only properties whose setter method is annotated with @StrutsParameter, + * consistent with ParametersInterceptor behavior for URL parameters. + */ + private void applyAnnotatedProperties(Object target, Map jsonMap) { + try { + BeanInfo info = Introspector.getBeanInfo(target.getClass()); + PropertyDescriptor[] props = info.getPropertyDescriptors(); + for (PropertyDescriptor prop : props) { + String name = prop.getName(); + if (!jsonMap.containsKey(name)) { + continue; + } + Method setter = prop.getWriteMethod(); + if (setter == null) { + continue; + } + if (setter.getAnnotation(StrutsParameter.class) == null) { + LOG.debug("REST JSON property '{}' rejected: setter [{}] missing @StrutsParameter annotation", + name, setter.getName()); + continue; + } + // Use Jackson to convert the value to the correct type and set it + Object value = jsonMap.get(name); + Object converted = mapper.convertValue(value, setter.getParameterTypes()[0]); + setter.invoke(target, converted); + } + } catch (Exception e) { + LOG.error("Error applying annotated properties from JSON", e); + } } @Override