1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
108
109
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("&", "&");
137 xml = xml.replaceAll(">", ">");
138 xml = xml.replaceAll("<", "<");
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 <name/>
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
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
282
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
293 if (bean instanceof Collection) {
294 Collection col = (Collection) bean;
295
296
297
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
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
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
320
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
330
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
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