Author: lukaszlenart Date: Sat Oct 23 19:01:25 2010 New Revision: 1026661 URL: http://svn.apache.org/viewvc?rev=1026661&view=rev Log: Solved WW-3509 - JSONResult.setIncludeProperties() constructs incorrect patterns
Added: struts/struts2/trunk/plugins/json/src/main/java/org/apache/struts2/json/smd/SMDGenerator.java Modified: struts/struts2/trunk/plugins/json/src/main/java/org/apache/struts2/json/JSONResult.java Modified: struts/struts2/trunk/plugins/json/src/main/java/org/apache/struts2/json/JSONResult.java URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/json/src/main/java/org/apache/struts2/json/JSONResult.java?rev=1026661&r1=1026660&r2=1026661&view=diff ============================================================================== --- struts/struts2/trunk/plugins/json/src/main/java/org/apache/struts2/json/JSONResult.java (original) +++ struts/struts2/trunk/plugins/json/src/main/java/org/apache/struts2/json/JSONResult.java Sat Oct 23 19:01:25 2010 @@ -20,23 +20,6 @@ */ package org.apache.struts2.json; -import java.io.IOException; -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.regex.Pattern; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.struts2.StrutsConstants; -import org.apache.struts2.StrutsStatics; -import org.apache.struts2.json.annotations.SMD; -import org.apache.struts2.json.annotations.SMDMethod; -import org.apache.struts2.json.annotations.SMDMethodParameter; - import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.Result; @@ -44,6 +27,19 @@ import com.opensymphony.xwork2.inject.In import com.opensymphony.xwork2.util.ValueStack; import com.opensymphony.xwork2.util.logging.Logger; import com.opensymphony.xwork2.util.logging.LoggerFactory; +import org.apache.struts2.StrutsConstants; +import org.apache.struts2.StrutsStatics; +import org.apache.struts2.json.smd.SMDGenerator; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; /** * <!-- START SNIPPET: description --> <p/> This result serializes an action @@ -57,7 +53,7 @@ import com.opensymphony.xwork2.util.logg * <p/> * </ul> * <p/> <!-- END SNIPPET: parameters --> <p/> <b>Example:</b> <p/> - * + * <p/> * <pre> * <!-- START SNIPPET: example --> * <result name="success" type="json" /> @@ -65,7 +61,9 @@ import com.opensymphony.xwork2.util.logg * </pre> */ public class JSONResult implements Result { + private static final long serialVersionUID = 8624350183189931165L; + private static final Logger LOG = LoggerFactory.getLogger(JSONResult.class); private String defaultEncoding = "ISO-8859-1"; @@ -96,7 +94,7 @@ public class JSONResult implements Resul /** * Gets a list of regular expressions of properties to exclude from the JSON * output. - * + * * @return A list of compiled regular expression patterns */ public List<Pattern> getExcludePropertiesList() { @@ -106,9 +104,8 @@ public class JSONResult implements Resul /** * Sets a comma-delimited list of regular expressions to match properties * that should be excluded from the JSON output. - * - * @param commaDelim - * A comma-delimited list of regular expressions + * + * @param commaDelim A comma-delimited list of regular expressions */ public void setExcludeProperties(String commaDelim) { List<String> excludePatterns = JSONUtil.asList(commaDelim); @@ -128,50 +125,62 @@ public class JSONResult implements Resul } /** - * @param includedProperties - * the includeProperties to set + * @param commaDelim comma delimited include string patterns */ public void setIncludeProperties(String commaDelim) { List<String> includePatterns = JSONUtil.asList(commaDelim); if (includePatterns != null) { - this.includeProperties = new ArrayList<Pattern>(includePatterns.size()); + processIncludePatterns(includePatterns); + } + } + + private void processIncludePatterns(List<String> includePatterns) { + includeProperties = new ArrayList<Pattern>(includePatterns.size()); + Map<String, String> existingPatterns = new HashMap<String, String>(); + for (String pattern : includePatterns) { + processPattern(existingPatterns, pattern); + } + } + + private void processPattern(Map<String, String> existingPatterns, String pattern) { + // Compile a pattern for each *unique* "level" of the object + // hierarchy specified in the regex. + String[] patternPieces = pattern.split("\\\\\\."); + + String patternExpr = ""; + for (String patternPiece : patternPieces) { + patternExpr = processPatternPiece(existingPatterns, patternExpr, patternPiece); + } + } - HashMap existingPatterns = new HashMap(); + private String processPatternPiece(Map<String, String> existingPatterns, String patternExpr, String patternPiece) { + if (patternExpr.length() > 0) { + patternExpr += "\\."; + } + patternExpr += patternPiece; - for (String pattern : includePatterns) { - // Compile a pattern for each *unique* "level" of the object - // hierarchy specified in the regex. - String[] patternPieces = pattern.split("\\\\\\."); - - String patternExpr = ""; - for (String patternPiece : patternPieces) { - if (patternExpr.length() > 0) { - patternExpr += "\\."; - } - patternExpr += patternPiece; - - // Check for duplicate patterns so that there is no overlap. - if (!existingPatterns.containsKey(patternExpr)) { - existingPatterns.put(patternExpr, patternExpr); - - // Add a pattern that does not have the indexed property - // matching (ie. list\[\d+\] becomes list). - if (patternPiece.endsWith("\\]")) { - this.includeProperties.add(Pattern.compile(patternExpr.substring(0, patternPiece - .lastIndexOf("\\[")))); - - if (LOG.isDebugEnabled()) - LOG.debug("Adding include property expression: " - + patternExpr.substring(0, patternPiece.lastIndexOf("\\["))); - } - - this.includeProperties.add(Pattern.compile(patternExpr)); - - if (LOG.isDebugEnabled()) - LOG.debug("Adding include property expression: " + patternExpr); - } - } + // Check for duplicate patterns so that there is no overlap. + if (!existingPatterns.containsKey(patternExpr)) { + existingPatterns.put(patternExpr, patternExpr); + if (isIndexedProperty(patternPiece)) { + addPattern(patternExpr.substring(0, patternExpr.lastIndexOf("\\["))); } + addPattern(patternExpr); + } + return patternExpr; + } + + /** + * Add a pattern that does not have the indexed property matching (ie. list\[\d+\] becomes list). + */ + private boolean isIndexedProperty(String patternPiece) { + return patternPiece.endsWith("\\]"); + } + + private void addPattern(String pattern) { + this.includeProperties.add(Pattern.compile(pattern)); + if (LOG.isDebugEnabled()) { + LOG.debug("Adding include property expression: " + pattern); } } @@ -181,138 +190,60 @@ public class JSONResult implements Resul HttpServletResponse response = (HttpServletResponse) actionContext.get(StrutsStatics.HTTP_RESPONSE); try { - String json; Object rootObject; - if (this.enableSMD) { - // generate SMD - rootObject = this.writeSMD(invocation); - } else { - // generate JSON - if (this.root != null) { - ValueStack stack = invocation.getStack(); - rootObject = stack.findValue(this.root); - } else { - rootObject = invocation.getAction(); - } - } - json = JSONUtil.serialize(rootObject, excludeProperties, includeProperties, ignoreHierarchy, - enumAsBean, excludeNullProperties); - json = addCallbackIfApplicable(request, json); - - boolean writeGzip = enableGZIP && JSONUtil.isGzipInRequest(request); - - writeToResponse(response, json, writeGzip); - + rootObject = readRootObject(invocation); + writeToResponse(response, createJSONString(request, rootObject), enableGzip(request)); } catch (IOException exception) { LOG.error(exception.getMessage(), exception); throw exception; } } - protected void writeToResponse(HttpServletResponse response, String json, boolean gzip) - throws IOException { - JSONUtil.writeJSONToResponse(new SerializationParams(response, getEncoding(), isWrapWithComments(), - json, false, gzip, noCache, statusCode, errorCode, prefix, contentType, wrapPrefix, - wrapSuffix)); + private Object readRootObject(ActionInvocation invocation) { + Object root = findRootObject(invocation); + if (enableSMD) { + return new SMDGenerator(root, excludeProperties, ignoreInterfaces).generate(invocation); + } + return root; } - @SuppressWarnings("unchecked") - protected org.apache.struts2.json.smd.SMD writeSMD(ActionInvocation invocation) { - ActionContext actionContext = invocation.getInvocationContext(); - HttpServletRequest request = (HttpServletRequest) actionContext.get(StrutsStatics.HTTP_REQUEST); - - // root is based on OGNL expression (action by default) - Object rootObject = null; + private Object findRootObject(ActionInvocation invocation) { + Object rootObject; if (this.root != null) { ValueStack stack = invocation.getStack(); - rootObject = stack.findValue(this.root); + rootObject = stack.findValue(root); } else { rootObject = invocation.getAction(); } + return rootObject; + } - Class clazz = rootObject.getClass(); - org.apache.struts2.json.smd.SMD smd = new org.apache.struts2.json.smd.SMD(); - // URL - smd.setServiceUrl(request.getRequestURI()); - - // customize SMD - SMD smdAnnotation = (SMD) clazz.getAnnotation(SMD.class); - if (smdAnnotation != null) { - smd.setObjectName(smdAnnotation.objectName()); - smd.setServiceType(smdAnnotation.serviceType()); - smd.setVersion(smdAnnotation.version()); - } - - // get public methods - Method[] methods = JSONUtil.listSMDMethods(clazz, ignoreInterfaces); - - for (Method method : methods) { - SMDMethod smdMethodAnnotation = method.getAnnotation(SMDMethod.class); - - // SMDMethod annotation is required - if (((smdMethodAnnotation != null) && !this.shouldExcludeProperty(method.getName()))) { - String methodName = smdMethodAnnotation.name().length() == 0 ? method.getName() - : smdMethodAnnotation.name(); - - org.apache.struts2.json.smd.SMDMethod smdMethod = new org.apache.struts2.json.smd.SMDMethod( - methodName); - smd.addSMDMethod(smdMethod); - - // find params for this method - int parametersCount = method.getParameterTypes().length; - if (parametersCount > 0) { - Annotation[][] parameterAnnotations = method.getParameterAnnotations(); - - for (int i = 0; i < parametersCount; i++) { - // are you ever going to pick shorter names? nope - SMDMethodParameter smdMethodParameterAnnotation = this - .getSMDMethodParameterAnnotation(parameterAnnotations[i]); - - String paramName = smdMethodParameterAnnotation != null ? smdMethodParameterAnnotation - .name() - : "p" + i; - - // goog thing this is the end of the hierarchy, - // oitherwise I would need that 21'' LCD ;) - smdMethod.addSMDMethodParameter(new org.apache.struts2.json.smd.SMDMethodParameter( - paramName)); - } - } - - } else { - if (LOG.isDebugEnabled()) - LOG.debug("Ignoring property " + method.getName()); - } - } - return smd; + private String createJSONString(HttpServletRequest request, Object rootObject) throws JSONException { + String json; + json = JSONUtil.serialize(rootObject, excludeProperties, includeProperties, ignoreHierarchy, + enumAsBean, excludeNullProperties); + json = addCallbackIfApplicable(request, json); + return json; } - /** - * Find an SMDethodParameter annotation on this array - */ - private org.apache.struts2.json.annotations.SMDMethodParameter getSMDMethodParameterAnnotation( - Annotation[] annotations) { - for (Annotation annotation : annotations) { - if (annotation instanceof org.apache.struts2.json.annotations.SMDMethodParameter) - return (org.apache.struts2.json.annotations.SMDMethodParameter) annotation; - } + private boolean enableGzip(HttpServletRequest request) { + return enableGZIP && JSONUtil.isGzipInRequest(request); + } - return null; + protected void writeToResponse(HttpServletResponse response, String json, boolean gzip) throws IOException { + JSONUtil.writeJSONToResponse(new SerializationParams(response, getEncoding(), isWrapWithComments(), + json, false, gzip, noCache, statusCode, errorCode, prefix, contentType, wrapPrefix, + wrapSuffix)); } - private boolean shouldExcludeProperty(String expr) { - if (this.excludeProperties != null) { - for (Pattern pattern : this.excludeProperties) { - if (pattern.matcher(expr).matches()) - return true; - } - } - return false; + @SuppressWarnings("unchecked") + protected org.apache.struts2.json.smd.SMD buildSMDObject(ActionInvocation invocation) { + return new SMDGenerator(readRootObject(invocation), excludeProperties, ignoreInterfaces).generate(invocation); } /** * Retrieve the encoding <p/> - * + * * @return The encoding associated with this template (defaults to the value * of 'struts.i18n.encoding' property) */ @@ -348,9 +279,8 @@ public class JSONResult implements Resul /** * Sets the root object to be serialized, defaults to the Action - * - * @param root - * OGNL expression of root object to be serialized + * + * @param root OGNL expression of root object to be serialized */ public void setRoot(String root) { this.root = root; @@ -365,7 +295,7 @@ public class JSONResult implements Resul /** * Wrap generated JSON with comments - * + * * @param wrapWithComments */ public void setWrapWithComments(boolean wrapWithComments) { @@ -381,7 +311,7 @@ public class JSONResult implements Resul /** * Enable SMD generation for action, which can be used for JSON-RPC - * + * * @param enableSMD */ public void setEnableSMD(boolean enableSMD) { @@ -405,7 +335,7 @@ public class JSONResult implements Resul * Controls how Enum's are serialized : If true, an Enum is serialized as a * name=value pair (name=name()) (default) If false, an Enum is serialized * as a bean with a special property _name=name() - * + * * @param enumAsBean */ public void setEnumAsBean(boolean enumAsBean) { @@ -430,7 +360,7 @@ public class JSONResult implements Resul /** * Add headers to response to prevent the browser from caching the response - * + * * @param noCache */ public void setNoCache(boolean noCache) { @@ -447,7 +377,7 @@ public class JSONResult implements Resul /** * Do not serialize properties with a null value - * + * * @param excludeNullProperties */ public void setExcludeNullProperties(boolean excludeNullProperties) { @@ -456,7 +386,7 @@ public class JSONResult implements Resul /** * Status code to be set in the response - * + * * @param statusCode */ public void setStatusCode(int statusCode) { @@ -465,7 +395,7 @@ public class JSONResult implements Resul /** * Error code to be set in the response - * + * * @param errorCode */ public void setErrorCode(int errorCode) { @@ -482,7 +412,7 @@ public class JSONResult implements Resul /** * Prefix JSON with "{} &&" - * + * * @param prefix */ public void setPrefix(boolean prefix) { @@ -491,7 +421,7 @@ public class JSONResult implements Resul /** * Content type to be set in the response - * + * * @param contentType */ public void setContentType(String contentType) { Added: struts/struts2/trunk/plugins/json/src/main/java/org/apache/struts2/json/smd/SMDGenerator.java URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/json/src/main/java/org/apache/struts2/json/smd/SMDGenerator.java?rev=1026661&view=auto ============================================================================== --- struts/struts2/trunk/plugins/json/src/main/java/org/apache/struts2/json/smd/SMDGenerator.java (added) +++ struts/struts2/trunk/plugins/json/src/main/java/org/apache/struts2/json/smd/SMDGenerator.java Sat Oct 23 19:01:25 2010 @@ -0,0 +1,132 @@ +package org.apache.struts2.json.smd; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; +import org.apache.struts2.StrutsStatics; +import org.apache.struts2.json.JSONUtil; +import org.apache.struts2.json.annotations.SMD; +import org.apache.struts2.json.annotations.SMDMethod; +import org.apache.struts2.json.annotations.SMDMethodParameter; + +import javax.servlet.http.HttpServletRequest; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.List; +import java.util.regex.Pattern; + +public class SMDGenerator { + + private static final Logger LOG = LoggerFactory.getLogger(SMDGenerator.class); + + // rootObject is based on OGNL expression (action by default) + private Object rootObject; + private List<Pattern> excludeProperties; + private boolean ignoreInterfaces; + + public SMDGenerator(Object root, List<Pattern> excludeProperties, boolean ignoreInterfaces) { + this.rootObject = root; + this.excludeProperties = excludeProperties; + this.ignoreInterfaces = ignoreInterfaces; + } + + public org.apache.struts2.json.smd.SMD generate(ActionInvocation actionInvocation) { + ActionContext actionContext = actionInvocation.getInvocationContext(); + HttpServletRequest request = (HttpServletRequest) actionContext.get(StrutsStatics.HTTP_REQUEST); + + Class clazz = rootObject.getClass(); + org.apache.struts2.json.smd.SMD smd = new org.apache.struts2.json.smd.SMD(); + // URL + smd.setServiceUrl(request.getRequestURI()); + + // customize SMD + org.apache.struts2.json.annotations.SMD smdAnnotation = (SMD) clazz.getAnnotation(SMD.class); + if (smdAnnotation != null) { + smd.setObjectName(smdAnnotation.objectName()); + smd.setServiceType(smdAnnotation.serviceType()); + smd.setVersion(smdAnnotation.version()); + } + + // get public methods + Method[] methods = JSONUtil.listSMDMethods(clazz, ignoreInterfaces); + + for (Method method : methods) { + processAnnotatedMethod(smd, method); + } + return smd; + + } + + private void processAnnotatedMethod(org.apache.struts2.json.smd.SMD smd, Method method) { + SMDMethod smdMethodAnnotation = method.getAnnotation(SMDMethod.class); + // SMDMethod annotation is required + if (shouldProcessMethod(method, smdMethodAnnotation)) { + String methodName = readMethodName(method, smdMethodAnnotation); + org.apache.struts2.json.smd.SMDMethod smdMethod = new org.apache.struts2.json.smd.SMDMethod(methodName); + smd.addSMDMethod(smdMethod); + + // find params for this method + processMethodsParametrs(method, smdMethod); + + } else { + if (LOG.isDebugEnabled()) + LOG.debug("Ignoring property " + method.getName()); + } + } + + private void processMethodsParametrs(Method method, org.apache.struts2.json.smd.SMDMethod smdMethod) { + int parametersCount = method.getParameterTypes().length; + if (parametersCount > 0) { + + Annotation[][] parameterAnnotations = method.getParameterAnnotations(); + + for (int i = 0; i < parametersCount; i++) { + processParameter(smdMethod, parameterAnnotations[i], i); + } + } + } + + private void processParameter(org.apache.struts2.json.smd.SMDMethod smdMethod, Annotation[] parameterAnnotation, int i) { + // are you ever going to pick shorter names? nope + SMDMethodParameter smdMethodParameterAnnotation = getSMDMethodParameterAnnotation(parameterAnnotation); + String paramName = buildParamName(i, smdMethodParameterAnnotation); + smdMethod.addSMDMethodParameter(new org.apache.struts2.json.smd.SMDMethodParameter(paramName)); + } + + private String buildParamName(int i, SMDMethodParameter smdMethodParameterAnnotation) { + return smdMethodParameterAnnotation != null ? smdMethodParameterAnnotation.name() : "p" + i; + } + + private String readMethodName(Method method, SMDMethod smdMethodAnnotation) { + return smdMethodAnnotation.name().length() == 0 ? method.getName() : smdMethodAnnotation.name(); + } + + private boolean shouldProcessMethod(Method method, SMDMethod smdMethodAnnotation) { + return ((smdMethodAnnotation != null) && !this.shouldExcludeProperty(method.getName())); + } + + private boolean shouldExcludeProperty(String expr) { + if (this.excludeProperties != null) { + for (Pattern pattern : this.excludeProperties) { + if (pattern.matcher(expr).matches()) + return true; + } + } + return false; + } + + /** + * Find an SMDethodParameter annotation on this array + */ + private org.apache.struts2.json.annotations.SMDMethodParameter getSMDMethodParameterAnnotation( + Annotation[] annotations) { + for (Annotation annotation : annotations) { + if (annotation instanceof org.apache.struts2.json.annotations.SMDMethodParameter) + return (org.apache.struts2.json.annotations.SMDMethodParameter) annotation; + } + + return null; + } + +}