1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
154
155
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("&", "&");
182 xml = xml.replaceAll(">", ">");
183 xml = xml.replaceAll("<", "<");
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
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
244
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 <name/>
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
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
377
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
388 if (bean instanceof Collection) {
389 Collection col = (Collection) bean;
390
391
392
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
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
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
415
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
425
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
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