Author: musachy
Date: Fri Jul 27 07:58:05 2007
New Revision: 560261

URL: http://svn.apache.org/viewvc?view=rev&rev=560261
Log:
WW-2070 Add an object browser to the debugging interceptor

Added:
    
struts/struts2/trunk/core/src/main/java/org/apache/struts2/interceptor/debugging/ObjectToHTMLWriter.java
    
struts/struts2/trunk/core/src/main/resources/org/apache/struts2/interceptor/debugging/browser.ftl
Modified:
    
struts/struts2/trunk/core/src/main/java/org/apache/struts2/interceptor/debugging/DebuggingInterceptor.java
    
struts/struts2/trunk/core/src/main/java/org/apache/struts2/interceptor/debugging/PrettyPrintWriter.java

Modified: 
struts/struts2/trunk/core/src/main/java/org/apache/struts2/interceptor/debugging/DebuggingInterceptor.java
URL: 
http://svn.apache.org/viewvc/struts/struts2/trunk/core/src/main/java/org/apache/struts2/interceptor/debugging/DebuggingInterceptor.java?view=diff&rev=560261&r1=560260&r2=560261
==============================================================================
--- 
struts/struts2/trunk/core/src/main/java/org/apache/struts2/interceptor/debugging/DebuggingInterceptor.java
 (original)
+++ 
struts/struts2/trunk/core/src/main/java/org/apache/struts2/interceptor/debugging/DebuggingInterceptor.java
 Fri Jul 27 07:58:05 2007
@@ -40,9 +40,9 @@
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.struts2.ServletActionContext;
+import org.apache.struts2.StrutsConstants;
 import org.apache.struts2.views.freemarker.FreemarkerManager;
 import org.apache.struts2.views.freemarker.FreemarkerResult;
-import org.apache.struts2.StrutsConstants;
 
 import com.opensymphony.xwork2.ActionContext;
 import com.opensymphony.xwork2.ActionInvocation;
@@ -67,6 +67,10 @@
  * the 'xml' mode is inserted at the top of the page.</li>
  * <li> <code>command</code> - Tests an OGNL expression and returns the
  * string result. Only used by the OGNL console.</li>
+ * <li><code>browser</code> Shows field values of an object specified in the 
+ * <code>object<code> parameter (#context by default). When the 
<code>object<code>
+ * parameters is set, the '#' character needs to be escaped to '%23'. Like
+ * debug=browser&object=%23parameters</li>
  * </ul>
  * <!-- END SNIPPET: parameters -->
  * <p/>
@@ -97,11 +101,14 @@
     private final static String XML_MODE = "xml";
     private final static String CONSOLE_MODE = "console";
     private final static String COMMAND_MODE = "command";
+    private final static String BROWSER_MODE = "browser";
 
     private final static String SESSION_KEY = 
"org.apache.struts2.interceptor.debugging.VALUE_STACK";
 
     private final static String DEBUG_PARAM = "debug";
+    private final static String OBJECT_PARAM = "object";
     private final static String EXPRESSION_PARAM = "expression";
+    private final static String DECORATE_PARAM = "decorate";
 
     private boolean enableXmlWithConsole = false;
     
@@ -140,7 +147,7 @@
      * @see 
com.opensymphony.xwork2.interceptor.Interceptor#invoke(com.opensymphony.xwork2.ActionInvocation)
      */
     public String intercept(ActionInvocation inv) throws Exception {
-
+        boolean actionOnly = false;
         boolean cont = true;
         if (devMode) {
             final ActionContext ctx = ActionContext.getContext();
@@ -204,11 +211,53 @@
                     ex.printStackTrace();
                 }
                 cont = false;
+            } else if (BROWSER_MODE.equals(type)) {
+                actionOnly = true;
+                inv.addPreResultListener(
+                    new PreResultListener() {
+                        public void beforeResult(ActionInvocation inv, String 
actionResult) {
+                            String rootObjectExpression = 
getParameter(OBJECT_PARAM);
+                            if (rootObjectExpression == null)
+                                rootObjectExpression = "#context";
+                            String decorate = getParameter(DECORATE_PARAM);
+                            ValueStack stack = (ValueStack) 
ctx.get(ActionContext.VALUE_STACK);
+                            Object rootObject = 
stack.findValue(rootObjectExpression);
+                            
+                            try {
+                                StringWriter writer = new StringWriter();
+                                ObjectToHTMLWriter htmlWriter = new 
ObjectToHTMLWriter(writer);
+                                htmlWriter.write(rootObject, 
rootObjectExpression);
+                                String html = writer.toString();
+                                writer.close();
+                                
+                                stack.set("debugHtml", html);
+                                
+                                //on the first request, response can be 
decorated
+                                //but we need plain text on the other ones
+                                if ("false".equals(decorate))
+                                    
ServletActionContext.getRequest().setAttribute("decorator", "none");
+                                
+                                FreemarkerResult result = new 
FreemarkerResult();
+                                result.setFreemarkerManager(freemarkerManager);
+                                result.setContentType("text/html");
+                                
result.setLocation("/org/apache/struts2/interceptor/debugging/browser.ftl");
+                                result.execute(inv);
+                            } catch (Exception ex) {
+                                log.error("Unable to create debugging 
console", ex);
+                            }
+
+                        }
+                    });
             }
-        }
+        } 
         if (cont) {
             try {
-                return inv.invoke();
+                if (actionOnly) {
+                    inv.invokeActionOnly();
+                    return null;
+                } else {
+                    return inv.invoke();
+                }
             } finally {
                 if (devMode && consoleEnabled) {
                     final ActionContext ctx = ActionContext.getContext();

Added: 
struts/struts2/trunk/core/src/main/java/org/apache/struts2/interceptor/debugging/ObjectToHTMLWriter.java
URL: 
http://svn.apache.org/viewvc/struts/struts2/trunk/core/src/main/java/org/apache/struts2/interceptor/debugging/ObjectToHTMLWriter.java?view=auto&rev=560261
==============================================================================
--- 
struts/struts2/trunk/core/src/main/java/org/apache/struts2/interceptor/debugging/ObjectToHTMLWriter.java
 (added)
+++ 
struts/struts2/trunk/core/src/main/java/org/apache/struts2/interceptor/debugging/ObjectToHTMLWriter.java
 Fri Jul 27 07:58:05 2007
@@ -0,0 +1,175 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.struts2.interceptor.debugging;
+
+import java.beans.IntrospectionException;
+import java.io.Writer;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import ognl.OgnlException;
+
+import com.opensymphony.xwork2.util.OgnlUtil;
+
+/**
+ * Writes an object as a table, where each field can be expanded if it is an 
Object/Collection/Array
+ *
+ */
+class ObjectToHTMLWriter {
+    private PrettyPrintWriter prettyWriter;
+
+    ObjectToHTMLWriter(Writer writer) {
+        this.prettyWriter = new PrettyPrintWriter(writer);
+        this.prettyWriter.setEscape(false);
+    }
+
+    @SuppressWarnings("unchecked")
+    public void write(Object root, String expr) throws IntrospectionException,
+        OgnlException {
+        prettyWriter.startNode("table");
+        prettyWriter.addAttribute("class", "debugTable");
+
+        if (root instanceof Map) {
+            for (Iterator iterator = ((Map) root).entrySet().iterator(); 
iterator
+                .hasNext();) {
+                Map.Entry property = (Map.Entry) iterator.next();
+                String key = property.getKey().toString();
+                Object value = property.getValue();
+                writeProperty(key, value, expr);
+            }
+        } else if (root instanceof List) {
+            List list = (List) root;
+            for (int i = 0; i < list.size(); i++) {
+                Object element = list.get(i);
+                writeProperty(String.valueOf(i), element, expr);
+            }
+        } else if (root instanceof Set) {
+            Set set = (Set) root;
+            for (Iterator iterator = set.iterator(); iterator.hasNext();) {
+                writeProperty("", iterator.next(), expr);
+            }
+        } else if (root.getClass().isArray()) {
+            Object[] objects = (Object[]) root;
+            for (int i = 0; i < objects.length; i++) {
+                writeProperty(String.valueOf(i), objects[i], expr);
+            }
+        } else {
+            //print properties
+            Map<String, Object> properties = OgnlUtil.getBeanMap(root);
+            for (Map.Entry<String, Object> property : properties.entrySet()) {
+                String name = property.getKey();
+                Object value = property.getValue();
+
+                if ("class".equals(name))
+                    continue;
+
+                writeProperty(name, value, expr);
+            }
+        }
+
+        prettyWriter.endNode();
+    }
+
+    private void writeProperty(String name, Object value, String expr) {
+        prettyWriter.startNode("tr");
+
+        //name cell
+        prettyWriter.startNode("td");
+        prettyWriter.addAttribute("class", "nameColumn");
+        prettyWriter.setValue(name);
+        prettyWriter.endNode();
+
+        //value cell
+        prettyWriter.startNode("td");
+        if (value != null) {
+            //if is is an empty collection or array, don't write a link
+            if (value != null &&
+                (isEmptyCollection(value) || isEmptyMap(value) || 
(value.getClass()
+                    .isArray() && ((Object[]) value).length == 0))) {
+                prettyWriter.addAttribute("class", "emptyCollection");
+                prettyWriter.setValue("empty");
+            } else {
+                prettyWriter.addAttribute("class", "valueColumn");
+                writeValue(name, value, expr);
+            }
+        } else {
+            prettyWriter.addAttribute("class", "nullValue");
+            prettyWriter.setValue("null");
+        }
+        prettyWriter.endNode();
+
+        //type cell
+        prettyWriter.startNode("td");
+        if (value != null) {
+            prettyWriter.addAttribute("class", "typeColumn");
+            Class clazz = value.getClass();
+            prettyWriter.setValue(clazz.getName());
+        } else {
+            prettyWriter.addAttribute("class", "nullValue");
+            prettyWriter.setValue("unknown");
+        }
+        prettyWriter.endNode();
+
+        //close tr
+        prettyWriter.endNode();
+    }
+
+    /**
+     * Some maps, like AttributeMap will throw an exception when isEmpty() is 
called
+     */
+    private boolean isEmptyMap(Object value) {
+        try {
+            return value instanceof Map && ((Map) value).isEmpty();
+        } catch (Exception e) {
+            return true;
+        }
+    }
+
+    /**
+     * Some collections might throw an exception when isEmpty() is called
+     */
+    private boolean isEmptyCollection(Object value) {
+        try {
+            return value instanceof Collection && ((Collection) 
value).isEmpty();
+        } catch (Exception e) {
+            return true;
+        }
+    }
+
+    private void writeValue(String name, Object value, String expr) {
+        Class clazz = value.getClass();
+        if (clazz.isPrimitive() || Number.class.isAssignableFrom(clazz) ||
+            clazz.equals(String.class) || Boolean.class.equals(clazz)) {
+            prettyWriter.setValue(String.valueOf(value));
+        } else {
+            prettyWriter.startNode("a");
+            String path = expr.replaceAll("#", "%23") + "[\"" +
+                name.replaceAll("#", "%23") + "\"]";
+            prettyWriter.addAttribute("onclick", "expand(this, '" + path + 
"')");
+            prettyWriter.addAttribute("href", "javascript://nop/");
+            prettyWriter.setValue("Expand");
+            prettyWriter.endNode();
+        }
+    }
+}

Modified: 
struts/struts2/trunk/core/src/main/java/org/apache/struts2/interceptor/debugging/PrettyPrintWriter.java
URL: 
http://svn.apache.org/viewvc/struts/struts2/trunk/core/src/main/java/org/apache/struts2/interceptor/debugging/PrettyPrintWriter.java?view=diff&rev=560261&r1=560260&r2=560261
==============================================================================
--- 
struts/struts2/trunk/core/src/main/java/org/apache/struts2/interceptor/debugging/PrettyPrintWriter.java
 (original)
+++ 
struts/struts2/trunk/core/src/main/java/org/apache/struts2/interceptor/debugging/PrettyPrintWriter.java
 Fri Jul 27 07:58:05 2007
@@ -35,6 +35,7 @@
     private boolean readyForNewLine;
     private boolean tagIsEmpty;
     private String newLine;
+    private boolean escape = true;
 
     private static final char[] NULL = "&#x0;".toCharArray();
     private static final char[] AMP = "&amp;".toCharArray();
@@ -125,7 +126,12 @@
                     this.writer.write(QUOT);
                     break;
                 case '\'':
-                    this.writer.write(APOS);
+                    //for some reason IE just doesn't like this when we use it 
from ObjectToHtmlWriter
+                    //it works on FF and Opera
+                    if (escape)
+                        this.writer.write(APOS);
+                    else
+                        this.writer.write(c);
                     break;
                 case '\r':
                     this.writer.write(SLASH_R);
@@ -180,5 +186,13 @@
 
     public void close() {
         writer.close();
+    }
+
+    public boolean isEscape() {
+        return escape;
+    }
+
+    public void setEscape(boolean escape) {
+        this.escape = escape;
     }
 }

Added: 
struts/struts2/trunk/core/src/main/resources/org/apache/struts2/interceptor/debugging/browser.ftl
URL: 
http://svn.apache.org/viewvc/struts/struts2/trunk/core/src/main/resources/org/apache/struts2/interceptor/debugging/browser.ftl?view=auto&rev=560261
==============================================================================
--- 
struts/struts2/trunk/core/src/main/resources/org/apache/struts2/interceptor/debugging/browser.ftl
 (added)
+++ 
struts/struts2/trunk/core/src/main/resources/org/apache/struts2/interceptor/debugging/browser.ftl
 Fri Jul 27 07:58:05 2007
@@ -0,0 +1,78 @@
+<html>
+    <style>
+        .debugTable {
+            border-style: solid;
+            border-width: 1px;
+        }
+        
+        .debugTable td {
+            border-style: solid;
+            border-width: 1px;
+        }
+        
+        .nameColumn {
+            background-color:#CCDDFF;
+        }
+        
+        .valueColumn {
+            background-color: #CCFFCC;
+        }
+        
+        .nullValue {
+            background-color: #FF0000;
+        }
+        
+        .typeColumn {
+            background-color: white;
+        }
+        
+        .emptyCollection {
+            background-color: #EEEEEE;
+        }
+    </style>
+    <script language="JavaScript" type="text/javascript">
+        // Dojo configuration
+        djConfig = {
+            isDebug: false,
+            bindEncoding: "UTF-8"
+            ,baseRelativePath: "${base}/struts/dojo/"
+            ,baseScriptUri: "${base}/struts/dojo/"
+        };
+    </script>
+
+
+
+    <script language="JavaScript" type="text/javascript"  
src="${base}/struts/dojo/dojo.js"></script>
+    <script>
+        dojo.require("dojo.io.*");
+        
+        function expand(src, path) {
+          var baseUrl = location.href;
+          var i = baseUrl.indexOf("&object=");
+          baseUrl = (i > 0 ? baseUrl.substring(0, i) : baseUrl) + "&object=" + 
path;
+          if (baseUrl.indexOf("decorate") < 0) {
+             baseUrl += "&decorate=false";
+          } 
+          dojo.io.bind({
+            url: baseUrl,
+            load : function(type, data, evt) {
+              var div = document.createElement("div");
+              div.innerHTML = data;
+              src.parentNode.appendChild(div);
+              
+              src.innerHTML = "Collapse";
+              var oldonclick = src.onclick;
+              src.onclick = function() {
+                src.innerHTML = "Expand";
+                src.parentNode.removeChild(div);
+                src.onclick = oldonclick;
+              };
+            }
+          });
+        }
+    </script>
+
+<body>
+    ${debugHtml}
+</body>
+</html>
\ No newline at end of file


Reply via email to