1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
139
140
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("&", "&");
167 xml = xml.replaceAll(">", ">");
168 xml = xml.replaceAll("<", "<");
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 <name/>
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
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
314
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
325 if (bean instanceof Collection) {
326 Collection col = (Collection) bean;
327
328
329
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
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
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
352
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
362
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
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