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>
  * &lt;!-- START SNIPPET: example --&gt;
  * &lt;result name=&quot;success&quot; type=&quot;json&quot; /&gt;
@@ -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;
+    }
+
+}


Reply via email to