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 = "�".toCharArray();
private static final char[] 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