View Javadoc

1   /*
2    * $Id: DebuggingInterceptor.java 483839 2006-12-08 05:52:10Z mrdon $
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  package org.apache.struts2.interceptor.debugging;
22  
23  import java.beans.BeanInfo;
24  import java.beans.Introspector;
25  import java.beans.PropertyDescriptor;
26  import java.io.IOException;
27  import java.io.PrintWriter;
28  import java.io.StringWriter;
29  import java.lang.reflect.Array;
30  import java.lang.reflect.Method;
31  import java.util.ArrayList;
32  import java.util.Arrays;
33  import java.util.Collection;
34  import java.util.HashSet;
35  import java.util.List;
36  import java.util.Map;
37  
38  import javax.servlet.http.HttpServletResponse;
39  
40  import org.apache.commons.logging.Log;
41  import org.apache.commons.logging.LogFactory;
42  import org.apache.struts2.ServletActionContext;
43  import org.apache.struts2.views.freemarker.FreemarkerManager;
44  import org.apache.struts2.views.freemarker.FreemarkerResult;
45  import org.apache.struts2.StrutsConstants;
46  
47  import com.opensymphony.xwork2.ActionContext;
48  import com.opensymphony.xwork2.ActionInvocation;
49  import com.opensymphony.xwork2.inject.Inject;
50  import com.opensymphony.xwork2.interceptor.Interceptor;
51  import com.opensymphony.xwork2.interceptor.PreResultListener;
52  import com.opensymphony.xwork2.util.ValueStack;
53  
54  /***
55   * <!-- START SNIPPET: description -->
56   * Provides several different debugging screens to provide insight into the
57   * data behind the page.
58   * <!-- END SNIPPET: description -->
59   * The value of the 'debug' request parameter determines
60   * the screen:
61   * <!-- START SNIPPET: parameters -->
62   * <ul>
63   * <li> <code>xml</code> - Dumps the parameters, context, session, and value
64   * stack as an XML document.</li>
65   * <li> <code>console</code> - Shows a popup 'OGNL Console' that allows the
66   * user to test OGNL expressions against the value stack. The XML data from
67   * the 'xml' mode is inserted at the top of the page.</li>
68   * <li> <code>command</code> - Tests an OGNL expression and returns the
69   * string result. Only used by the OGNL console.</li>
70   * </ul>
71   * <!-- END SNIPPET: parameters -->
72   * <p/>
73   *  Example:
74   * <!-- START SNIPPET: example -->
75   *  http://localhost:8080/Welcome.action?debug=xml
76   * <!-- END SNIPPET: example -->
77   * <p/>
78   * <!-- START SNIPPET: remarks -->
79   * This interceptor only is activated when devMode is enabled in
80   * struts.properties. The 'debug' parameter is removed from the parameter list
81   * before the action is executed. All operations occur before the natural
82   * Result has a chance to execute.
83   * <!-- END SNIPPET: remarks -->
84   */
85  public class DebuggingInterceptor implements Interceptor {
86  
87      private static final long serialVersionUID = -3097324155953078783L;
88  
89      private final static Log log = LogFactory.getLog(DebuggingInterceptor.class);
90  
91      private String[] ignorePrefixes = new String[]{"org.apache.struts.",
92              "com.opensymphony.xwork2.", "xwork."};
93      private String[] _ignoreKeys = new String[]{"application", "session",
94              "parameters", "request"};
95      private HashSet<String> ignoreKeys = new HashSet<String>(Arrays.asList(_ignoreKeys));
96  
97      private final static String XML_MODE = "xml";
98      private final static String CONSOLE_MODE = "console";
99      private final static String COMMAND_MODE = "command";
100 
101     private final static String SESSION_KEY = "org.apache.struts2.interceptor.debugging.VALUE_STACK";
102 
103     private final static String DEBUG_PARAM = "debug";
104     private final static String EXPRESSION_PARAM = "expression";
105 
106     private boolean enableXmlWithConsole = false;
107     
108     private boolean devMode;
109     private FreemarkerManager freemarkerManager;
110     
111     private boolean consoleEnabled = false;
112 
113     @Inject(StrutsConstants.STRUTS_DEVMODE)
114     public void setDevMode(String mode) {
115         this.devMode = "true".equals(mode);
116     }
117     
118     @Inject
119     public void setFreemarkerManager(FreemarkerManager mgr) {
120         this.freemarkerManager = mgr;
121     }
122 
123     /***
124      * Unused.
125      */
126     public void init() {
127     }
128 
129 
130     /***
131      * Unused.
132      */
133     public void destroy() {
134     }
135 
136 
137     /*
138      * (non-Javadoc)
139      *
140      * @see com.opensymphony.xwork2.interceptor.Interceptor#invoke(com.opensymphony.xwork2.ActionInvocation)
141      */
142     public String intercept(ActionInvocation inv) throws Exception {
143 
144         boolean cont = true;
145         if (devMode) {
146             final ActionContext ctx = ActionContext.getContext();
147             String type = getParameter(DEBUG_PARAM);
148             ctx.getParameters().remove(DEBUG_PARAM);
149             if (XML_MODE.equals(type)) {
150                 inv.addPreResultListener(
151                         new PreResultListener() {
152                             public void beforeResult(ActionInvocation inv, String result) {
153                                 printContext();
154                             }
155                         });
156             } else if (CONSOLE_MODE.equals(type)) {
157                 consoleEnabled = true;
158                 inv.addPreResultListener(
159                         new PreResultListener() {
160                             public void beforeResult(ActionInvocation inv, String actionResult) {
161                                 String xml = "";
162                                 if (enableXmlWithConsole) {
163                                     StringWriter writer = new StringWriter();
164                                     printContext(new PrettyPrintWriter(writer));
165                                     xml = writer.toString();
166                                     xml = xml.replaceAll("&", "&amp;");
167                                     xml = xml.replaceAll(">", "&gt;");
168                                     xml = xml.replaceAll("<", "&lt;");
169                                 }
170                                 ActionContext.getContext().put("debugXML", xml);
171 
172                                 FreemarkerResult result = new FreemarkerResult();
173                                 result.setFreemarkerManager(freemarkerManager);
174                                 result.setContentType("text/html");
175                                 result.setLocation("/org/apache/struts2/interceptor/debugging/console.ftl");
176                                 result.setParse(false);
177                                 try {
178                                     result.execute(inv);
179                                 } catch (Exception ex) {
180                                     log.error("Unable to create debugging console", ex);
181                                 }
182 
183                             }
184                         });
185             } else if (COMMAND_MODE.equals(type)) {
186                 ValueStack stack = (ValueStack) ctx.getSession().get(SESSION_KEY);
187                 String cmd = getParameter(EXPRESSION_PARAM);
188 
189                 ServletActionContext.getRequest().setAttribute("decorator", "none");
190                 HttpServletResponse res = ServletActionContext.getResponse();
191                 res.setContentType("text/plain");
192 
193                 try {
194                     PrintWriter writer =
195                             ServletActionContext.getResponse().getWriter();
196                     writer.print(stack.findValue(cmd));
197                     writer.close();
198                 } catch (IOException ex) {
199                     ex.printStackTrace();
200                 }
201                 cont = false;
202             }
203         }
204         if (cont) {
205             try {
206                 return inv.invoke();
207             } finally {
208                 if (devMode && consoleEnabled) {
209                     final ActionContext ctx = ActionContext.getContext();
210                     ctx.getSession().put(SESSION_KEY, ctx.get(ActionContext.VALUE_STACK));
211                 }
212             }
213         } else {
214             return null;
215         }
216     }
217 
218 
219     /***
220      * Gets a single string from the request parameters
221      *
222      * @param key The key
223      * @return The parameter value
224      */
225     private String getParameter(String key) {
226         String[] arr = (String[]) ActionContext.getContext().getParameters().get(key);
227         if (arr != null && arr.length > 0) {
228             return arr[0];
229         }
230         return null;
231     }
232 
233 
234     /***
235      * Prints the current context to the response in XML format.
236      */
237     protected void printContext() {
238         HttpServletResponse res = ServletActionContext.getResponse();
239         res.setContentType("text/xml");
240 
241         try {
242             PrettyPrintWriter writer = new PrettyPrintWriter(
243                     ServletActionContext.getResponse().getWriter());
244             printContext(writer);
245             writer.close();
246         } catch (IOException ex) {
247             ex.printStackTrace();
248         }
249     }
250 
251 
252     /***
253      * Prints the current request to the existing writer.
254      *
255      * @param writer The XML writer
256      */
257     protected void printContext(PrettyPrintWriter writer) {
258         ActionContext ctx = ActionContext.getContext();
259         writer.startNode(DEBUG_PARAM);
260         serializeIt(ctx.getParameters(), "parameters", writer,
261                 new ArrayList<Object>());
262         writer.startNode("context");
263         String key;
264         Map ctxMap = ctx.getContextMap();
265         for (Object o : ctxMap.keySet()) {
266             key = o.toString();
267             boolean print = !ignoreKeys.contains(key);
268 
269             for (String ignorePrefixe : ignorePrefixes) {
270                 if (key.startsWith(ignorePrefixe)) {
271                     print = false;
272                     break;
273                 }
274             }
275             if (print) {
276                 serializeIt(ctxMap.get(key), key, writer, new ArrayList<Object>());
277             }
278         }
279         writer.endNode();
280         serializeIt(ctx.getSession(), "request", writer, new ArrayList<Object>());
281         serializeIt(ctx.getSession(), "session", writer, new ArrayList<Object>());
282 
283         ValueStack stack = (ValueStack) ctx.get(ActionContext.VALUE_STACK);
284         serializeIt(stack.getRoot(), "valueStack", writer, new ArrayList<Object>());
285         writer.endNode();
286     }
287 
288 
289     /***
290      * Recursive function to serialize objects to XML. Currently it will
291      * serialize Collections, maps, Arrays, and JavaBeans. It maintains a stack
292      * of objects serialized already in the current functioncall. This is used
293      * to avoid looping (stack overflow) of circular linked objects. Struts and
294      * XWork objects are ignored.
295      *
296      * @param bean   The object you want serialized.
297      * @param name   The name of the object, used for element &lt;name/&gt;
298      * @param writer The XML writer
299      * @param stack  List of objects we're serializing since the first calling
300      *               of this function (to prevent looping on circular references).
301      */
302     protected void serializeIt(Object bean, String name,
303                                PrettyPrintWriter writer, List<Object> stack) {
304         writer.flush();
305         // Check stack for this object
306         if ((bean != null) && (stack.contains(bean))) {
307             if (log.isInfoEnabled()) {
308                 log.info("Circular reference detected, not serializing object: "
309                         + name);
310             }
311             return;
312         } else if (bean != null) {
313             // Push object onto stack.
314             // Don't push null objects ( handled below)
315             stack.add(bean);
316         }
317         if (bean == null) {
318             return;
319         }
320         String clsName = bean.getClass().getName();
321 
322         writer.startNode(name);
323 
324         // It depends on the object and it's value what todo next:
325         if (bean instanceof Collection) {
326             Collection col = (Collection) bean;
327 
328             // Iterate through components, and call ourselves to process
329             // elements
330             for (Object aCol : col) {
331                 serializeIt(aCol, "value", writer, stack);
332             }
333         } else if (bean instanceof Map) {
334 
335             Map map = (Map) bean;
336 
337             // Loop through keys and call ourselves
338             for (Object key : map.keySet()) {
339                 Object Objvalue = map.get(key);
340                 serializeIt(Objvalue, key.toString(), writer, stack);
341             }
342         } else if (bean.getClass().isArray()) {
343             // It's an array, loop through it and keep calling ourselves
344             for (int i = 0; i < Array.getLength(bean); i++) {
345                 serializeIt(Array.get(bean, i), "arrayitem", writer, stack);
346             }
347         } else {
348             if (clsName.startsWith("java.lang")) {
349                 writer.setValue(bean.toString());
350             } else {
351                 // Not java.lang, so we can call ourselves with this object's
352                 // values
353                 try {
354                     BeanInfo info = Introspector.getBeanInfo(bean.getClass());
355                     PropertyDescriptor[] props = info.getPropertyDescriptors();
356 
357                     for (PropertyDescriptor prop : props) {
358                         String n = prop.getName();
359                         Method m = prop.getReadMethod();
360 
361                         // Call ourselves with the result of the method
362                         // invocation
363                         if (m != null) {
364                             serializeIt(m.invoke(bean), n, writer, stack);
365                         }
366                     }
367                 } catch (Exception e) {
368                     log.error(e, e);
369                 }
370             }
371         }
372 
373         writer.endNode();
374 
375         // Remove object from stack
376         stack.remove(bean);
377     }
378 
379 
380     /***
381      * @param enableXmlWithConsole the enableXmlWithConsole to set
382      */
383     public void setEnableXmlWithConsole(boolean enableXmlWithConsole) {
384         this.enableXmlWithConsole = enableXmlWithConsole;
385     }
386 
387 
388 
389 }
390