View Javadoc

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