View Javadoc

1   /*
2    * $Id: DebuggingInterceptor.java 651946 2008-04-27 13:41:38Z apetrelli $
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  package org.apache.struts2.interceptor.debugging;
23  
24  import java.beans.BeanInfo;
25  import java.beans.Introspector;
26  import java.beans.PropertyDescriptor;
27  import java.io.IOException;
28  import java.io.PrintWriter;
29  import java.io.StringWriter;
30  import java.lang.reflect.Array;
31  import java.lang.reflect.Method;
32  import java.util.ArrayList;
33  import java.util.Arrays;
34  import java.util.Collection;
35  import java.util.HashSet;
36  import java.util.List;
37  import java.util.Map;
38  
39  import javax.servlet.http.HttpServletResponse;
40  
41  import org.apache.struts2.ServletActionContext;
42  import org.apache.struts2.StrutsConstants;
43  import org.apache.struts2.views.freemarker.FreemarkerManager;
44  import org.apache.struts2.views.freemarker.FreemarkerResult;
45  
46  import com.opensymphony.xwork2.ActionContext;
47  import com.opensymphony.xwork2.ActionInvocation;
48  import com.opensymphony.xwork2.inject.Inject;
49  import com.opensymphony.xwork2.interceptor.Interceptor;
50  import com.opensymphony.xwork2.interceptor.PreResultListener;
51  import com.opensymphony.xwork2.util.ValueStack;
52  import com.opensymphony.xwork2.util.logging.Logger;
53  import com.opensymphony.xwork2.util.logging.LoggerFactory;
54  import com.opensymphony.xwork2.util.reflection.ReflectionProvider;
55  
56  /***
57   * <!-- START SNIPPET: description -->
58   * Provides several different debugging screens to provide insight into the
59   * data behind the page.
60   * <!-- END SNIPPET: description -->
61   * The value of the 'debug' request parameter determines
62   * the screen:
63   * <!-- START SNIPPET: parameters -->
64   * <ul>
65   * <li> <code>xml</code> - Dumps the parameters, context, session, and value
66   * stack as an XML document.</li>
67   * <li> <code>console</code> - Shows a popup 'OGNL Console' that allows the
68   * user to test OGNL expressions against the value stack. The XML data from
69   * the 'xml' mode is inserted at the top of the page.</li>
70   * <li> <code>command</code> - Tests an OGNL expression and returns the
71   * string result. Only used by the OGNL console.</li>
72   * <li><code>browser</code> Shows field values of an object specified in the 
73   * <code>object<code> parameter (#context by default). When the <code>object<code>
74   * parameters is set, the '#' character needs to be escaped to '%23'. Like
75   * debug=browser&object=%23parameters</li>
76   * </ul>
77   * <!-- END SNIPPET: parameters -->
78   * <p/>
79   *  Example:
80   * <!-- START SNIPPET: example -->
81   *  http://localhost:8080/Welcome.action?debug=xml
82   * <!-- END SNIPPET: example -->
83   * <p/>
84   * <!-- START SNIPPET: remarks -->
85   * This interceptor only is activated when devMode is enabled in
86   * struts.properties. The 'debug' parameter is removed from the parameter list
87   * before the action is executed. All operations occur before the natural
88   * Result has a chance to execute.
89   * <!-- END SNIPPET: remarks -->
90   */
91  public class DebuggingInterceptor implements Interceptor {
92  
93      private static final long serialVersionUID = -3097324155953078783L;
94  
95      private final static Logger LOG = LoggerFactory.getLogger(DebuggingInterceptor.class);
96  
97      private String[] ignorePrefixes = new String[]{"org.apache.struts.",
98              "com.opensymphony.xwork2.", "xwork."};
99      private String[] _ignoreKeys = new String[]{"application", "session",
100             "parameters", "request"};
101     private HashSet<String> ignoreKeys = new HashSet<String>(Arrays.asList(_ignoreKeys));
102 
103     private final static String XML_MODE = "xml";
104     private final static String CONSOLE_MODE = "console";
105     private final static String COMMAND_MODE = "command";
106     private final static String BROWSER_MODE = "browser";
107 
108     private final static String SESSION_KEY = "org.apache.struts2.interceptor.debugging.VALUE_STACK";
109 
110     private final static String DEBUG_PARAM = "debug";
111     private final static String OBJECT_PARAM = "object";
112     private final static String EXPRESSION_PARAM = "expression";
113     private final static String DECORATE_PARAM = "decorate";
114 
115     private boolean enableXmlWithConsole = false;
116     
117     private boolean devMode;
118     private FreemarkerManager freemarkerManager;
119     
120     private boolean consoleEnabled = false;
121     private ReflectionProvider reflectionProvider;
122 
123     @Inject(StrutsConstants.STRUTS_DEVMODE)
124     public void setDevMode(String mode) {
125         this.devMode = "true".equals(mode);
126     }
127     
128     @Inject
129     public void setFreemarkerManager(FreemarkerManager mgr) {
130         this.freemarkerManager = mgr;
131     }
132     
133     @Inject
134     public void setReflectionProvider(ReflectionProvider reflectionProvider) {
135         this.reflectionProvider = reflectionProvider;
136     }
137 
138     /***
139      * Unused.
140      */
141     public void init() {
142     }
143 
144 
145     /***
146      * Unused.
147      */
148     public void destroy() {
149     }
150 
151 
152     /*
153      * (non-Javadoc)
154      *
155      * @see com.opensymphony.xwork2.interceptor.Interceptor#invoke(com.opensymphony.xwork2.ActionInvocation)
156      */
157     public String intercept(ActionInvocation inv) throws Exception {
158         boolean actionOnly = false;
159         boolean cont = true;
160         if (devMode) {
161             final ActionContext ctx = ActionContext.getContext();
162             String type = getParameter(DEBUG_PARAM);
163             ctx.getParameters().remove(DEBUG_PARAM);
164             if (XML_MODE.equals(type)) {
165                 inv.addPreResultListener(
166                         new PreResultListener() {
167                             public void beforeResult(ActionInvocation inv, String result) {
168                                 printContext();
169                             }
170                         });
171             } else if (CONSOLE_MODE.equals(type)) {
172                 consoleEnabled = true;
173                 inv.addPreResultListener(
174                         new PreResultListener() {
175                             public void beforeResult(ActionInvocation inv, String actionResult) {
176                                 String xml = "";
177                                 if (enableXmlWithConsole) {
178                                     StringWriter writer = new StringWriter();
179                                     printContext(new PrettyPrintWriter(writer));
180                                     xml = writer.toString();
181                                     xml = xml.replaceAll("&", "&amp;");
182                                     xml = xml.replaceAll(">", "&gt;");
183                                     xml = xml.replaceAll("<", "&lt;");
184                                 }
185                                 ActionContext.getContext().put("debugXML", xml);
186 
187                                 FreemarkerResult result = new FreemarkerResult();
188                                 result.setFreemarkerManager(freemarkerManager);
189                                 result.setContentType("text/html");
190                                 result.setLocation("/org/apache/struts2/interceptor/debugging/console.ftl");
191                                 result.setParse(false);
192                                 try {
193                                     result.execute(inv);
194                                 } catch (Exception ex) {
195                                     LOG.error("Unable to create debugging console", ex);
196                                 }
197 
198                             }
199                         });
200             } else if (COMMAND_MODE.equals(type)) {
201                 ValueStack stack = (ValueStack) ctx.getSession().get(SESSION_KEY);
202                 if (stack == null) {
203                     //allows it to be embedded on another page
204                     stack = (ValueStack) ctx.get(ActionContext.VALUE_STACK);
205                     ctx.getSession().put(SESSION_KEY, stack);
206                 }
207                 String cmd = getParameter(EXPRESSION_PARAM);
208 
209                 ServletActionContext.getRequest().setAttribute("decorator", "none");
210                 HttpServletResponse res = ServletActionContext.getResponse();
211                 res.setContentType("text/plain");
212 
213                 try {
214                     PrintWriter writer =
215                             ServletActionContext.getResponse().getWriter();
216                     writer.print(stack.findValue(cmd));
217                     writer.close();
218                 } catch (IOException ex) {
219                     ex.printStackTrace();
220                 }
221                 cont = false;
222             } else if (BROWSER_MODE.equals(type)) {
223                 actionOnly = true;
224                 inv.addPreResultListener(
225                     new PreResultListener() {
226                         public void beforeResult(ActionInvocation inv, String actionResult) {
227                             String rootObjectExpression = getParameter(OBJECT_PARAM);
228                             if (rootObjectExpression == null)
229                                 rootObjectExpression = "#context";
230                             String decorate = getParameter(DECORATE_PARAM);
231                             ValueStack stack = (ValueStack) ctx.get(ActionContext.VALUE_STACK);
232                             Object rootObject = stack.findValue(rootObjectExpression);
233                             
234                             try {
235                                 StringWriter writer = new StringWriter();
236                                 ObjectToHTMLWriter htmlWriter = new ObjectToHTMLWriter(writer);
237                                 htmlWriter.write(reflectionProvider, rootObject, rootObjectExpression);
238                                 String html = writer.toString();
239                                 writer.close();
240                                 
241                                 stack.set("debugHtml", html);
242                                 
243                                 //on the first request, response can be decorated
244                                 //but we need plain text on the other ones
245                                 if ("false".equals(decorate))
246                                     ServletActionContext.getRequest().setAttribute("decorator", "none");
247                                 
248                                 FreemarkerResult result = new FreemarkerResult();
249                                 result.setFreemarkerManager(freemarkerManager);
250                                 result.setContentType("text/html");
251                                 result.setLocation("/org/apache/struts2/interceptor/debugging/browser.ftl");
252                                 result.execute(inv);
253                             } catch (Exception ex) {
254                                 LOG.error("Unable to create debugging console", ex);
255                             }
256 
257                         }
258                     });
259             }
260         } 
261         if (cont) {
262             try {
263                 if (actionOnly) {
264                     inv.invokeActionOnly();
265                     return null;
266                 } else {
267                     return inv.invoke();
268                 }
269             } finally {
270                 if (devMode && consoleEnabled) {
271                     final ActionContext ctx = ActionContext.getContext();
272                     ctx.getSession().put(SESSION_KEY, ctx.get(ActionContext.VALUE_STACK));
273                 }
274             }
275         } else {
276             return null;
277         }
278     }
279 
280 
281     /***
282      * Gets a single string from the request parameters
283      *
284      * @param key The key
285      * @return The parameter value
286      */
287     private String getParameter(String key) {
288         String[] arr = (String[]) ActionContext.getContext().getParameters().get(key);
289         if (arr != null && arr.length > 0) {
290             return arr[0];
291         }
292         return null;
293     }
294 
295 
296     /***
297      * Prints the current context to the response in XML format.
298      */
299     protected void printContext() {
300         HttpServletResponse res = ServletActionContext.getResponse();
301         res.setContentType("text/xml");
302 
303         try {
304             PrettyPrintWriter writer = new PrettyPrintWriter(
305                     ServletActionContext.getResponse().getWriter());
306             printContext(writer);
307             writer.close();
308         } catch (IOException ex) {
309             ex.printStackTrace();
310         }
311     }
312 
313 
314     /***
315      * Prints the current request to the existing writer.
316      *
317      * @param writer The XML writer
318      */
319     protected void printContext(PrettyPrintWriter writer) {
320         ActionContext ctx = ActionContext.getContext();
321         writer.startNode(DEBUG_PARAM);
322         serializeIt(ctx.getParameters(), "parameters", writer,
323                 new ArrayList<Object>());
324         writer.startNode("context");
325         String key;
326         Map ctxMap = ctx.getContextMap();
327         for (Object o : ctxMap.keySet()) {
328             key = o.toString();
329             boolean print = !ignoreKeys.contains(key);
330 
331             for (String ignorePrefixe : ignorePrefixes) {
332                 if (key.startsWith(ignorePrefixe)) {
333                     print = false;
334                     break;
335                 }
336             }
337             if (print) {
338                 serializeIt(ctxMap.get(key), key, writer, new ArrayList<Object>());
339             }
340         }
341         writer.endNode();
342         Map requestMap = (Map) ctx.get("request");
343         serializeIt(requestMap, "request", writer, filterValueStack(requestMap));
344         serializeIt(ctx.getSession(), "session", writer, new ArrayList<Object>());
345 
346         ValueStack stack = (ValueStack) ctx.get(ActionContext.VALUE_STACK);
347         serializeIt(stack.getRoot(), "valueStack", writer, new ArrayList<Object>());
348         writer.endNode();
349     }
350 
351 
352     /***
353      * Recursive function to serialize objects to XML. Currently it will
354      * serialize Collections, maps, Arrays, and JavaBeans. It maintains a stack
355      * of objects serialized already in the current functioncall. This is used
356      * to avoid looping (stack overflow) of circular linked objects. Struts and
357      * XWork objects are ignored.
358      *
359      * @param bean   The object you want serialized.
360      * @param name   The name of the object, used for element &lt;name/&gt;
361      * @param writer The XML writer
362      * @param stack  List of objects we're serializing since the first calling
363      *               of this function (to prevent looping on circular references).
364      */
365     protected void serializeIt(Object bean, String name,
366                                PrettyPrintWriter writer, List<Object> stack) {
367         writer.flush();
368         // Check stack for this object
369         if ((bean != null) && (stack.contains(bean))) {
370             if (LOG.isInfoEnabled()) {
371                 LOG.info("Circular reference detected, not serializing object: "
372                         + name);
373             }
374             return;
375         } else if (bean != null) {
376             // Push object onto stack.
377             // Don't push null objects ( handled below)
378             stack.add(bean);
379         }
380         if (bean == null) {
381             return;
382         }
383         String clsName = bean.getClass().getName();
384 
385         writer.startNode(name);
386 
387         // It depends on the object and it's value what todo next:
388         if (bean instanceof Collection) {
389             Collection col = (Collection) bean;
390 
391             // Iterate through components, and call ourselves to process
392             // elements
393             for (Object aCol : col) {
394                 serializeIt(aCol, "value", writer, stack);
395             }
396         } else if (bean instanceof Map) {
397 
398             Map<Object, Object> map = (Map) bean;
399 
400             // Loop through keys and call ourselves
401             for (Map.Entry<Object, Object> entry : map.entrySet()) {
402                 Object objValue = entry.getValue();
403                 serializeIt(objValue, entry.getKey().toString(), writer, stack);
404             }
405         } else if (bean.getClass().isArray()) {
406             // It's an array, loop through it and keep calling ourselves
407             for (int i = 0; i < Array.getLength(bean); i++) {
408                 serializeIt(Array.get(bean, i), "arrayitem", writer, stack);
409             }
410         } else {
411             if (clsName.startsWith("java.lang")) {
412                 writer.setValue(bean.toString());
413             } else {
414                 // Not java.lang, so we can call ourselves with this object's
415                 // values
416                 try {
417                     BeanInfo info = Introspector.getBeanInfo(bean.getClass());
418                     PropertyDescriptor[] props = info.getPropertyDescriptors();
419 
420                     for (PropertyDescriptor prop : props) {
421                         String n = prop.getName();
422                         Method m = prop.getReadMethod();
423 
424                         // Call ourselves with the result of the method
425                         // invocation
426                         if (m != null) {
427                             serializeIt(m.invoke(bean), n, writer, stack);
428                         }
429                     }
430                 } catch (Exception e) {
431                     LOG.error(e.toString(), e);
432                 }
433             }
434         }
435 
436         writer.endNode();
437 
438         // Remove object from stack
439         stack.remove(bean);
440     }
441 
442 
443     /***
444      * @param enableXmlWithConsole the enableXmlWithConsole to set
445      */
446     public void setEnableXmlWithConsole(boolean enableXmlWithConsole) {
447         this.enableXmlWithConsole = enableXmlWithConsole;
448     }
449 
450     
451     private List<Object> filterValueStack(Map requestMap) {
452     	List<Object> filter = new ArrayList<Object>();
453     	Object valueStack = requestMap.get("struts.valueStack");
454     	if(valueStack != null) {
455     		filter.add(valueStack);
456     	}
457     	return filter;
458     }
459 
460 
461 }
462 
463