001: /*
002: * Copyright (c) 2002-2006 by OpenSymphony
003: * All rights reserved.
004: */
005: package com.opensymphony.webwork.interceptor.debugging;
006:
007: import com.opensymphony.xwork.ActionContext;
008: import com.opensymphony.xwork.ActionInvocation;
009: import com.opensymphony.xwork.interceptor.Interceptor;
010: import com.opensymphony.xwork.interceptor.PreResultListener;
011: import com.opensymphony.xwork.util.OgnlValueStack;
012: import org.apache.commons.logging.Log;
013: import org.apache.commons.logging.LogFactory;
014: import com.opensymphony.webwork.ServletActionContext;
015: import com.opensymphony.webwork.views.freemarker.FreemarkerResult;
016:
017: import javax.servlet.http.HttpServletResponse;
018: import java.beans.BeanInfo;
019: import java.beans.Introspector;
020: import java.beans.PropertyDescriptor;
021: import java.io.*;
022: import java.lang.reflect.Array;
023: import java.lang.reflect.Method;
024: import java.util.*;
025:
026: /**
027: * <!-- START SNIPPET: description -->
028: *
029: * Provides several different debugging screens to provide insight into the
030: * data behind the page. The value of the 'debug' request parameter determines
031: * the screen.
032: * <p/>
033: * This interceptor only is activated when devMode is enabled in
034: * webwork.properties. The 'debug' parameter is removed from the parameter list
035: * before the action is executed. All operations occur before the natural
036: * Result has a chance to execute. </p>
037: *
038: * <!-- END SNIPPET: description -->
039: *
040: *
041: * <!-- START SNIPPET: parameters -->
042: * The value of the 'debug' request parameter determines
043: * the screen.
044: * <ul>
045: * <li> <code>xml</code> - Dumps the parameters, context, session, and value
046: * stack as an XML document.</li>
047: * <li> <code>console</code> - Shows a popup 'OGNL Console' that allows the
048: * user to test OGNL expressions against the value stack. The XML data from
049: * the 'xml' mode is inserted at the top of the page.</li>
050: * <li> <code>command</code> - Tests an OGNL expression and returns the
051: * string result. Only used by the OGNL console.</li>
052: * </ul>
053: * <!-- END SNIPPET: parameters -->
054: *
055: * <!-- START SNIPPET: extending -->
056: * There's no intended extension points
057: * <!-- END SNIPPET: extending -->
058: *
059: * <pre>
060: * <!-- START SNIPPET: example -->
061: * <action ...>
062: * <interceptor-ref name="debugging" />
063: * ....
064: * </action>
065: * <!-- END SNIPPET: example -->
066: * </pre>
067: */
068: public class DebuggingInterceptor implements Interceptor {
069:
070: private static final long serialVersionUID = -3097324155953078783L;
071:
072: private final static Log log = LogFactory
073: .getLog(DebuggingInterceptor.class);
074:
075: private String[] ignorePrefixes = new String[] {
076: "com.opensymphony.webwork.", "com.opensymphony.xwork.",
077: "xwork." };
078: private String[] _ignoreKeys = new String[] { "application",
079: "session", "parameters", "request" };
080: private HashSet ignoreKeys = new HashSet(Arrays.asList(_ignoreKeys));
081:
082: private final static String XML_MODE = "xml";
083: private final static String CONSOLE_MODE = "console";
084: private final static String COMMAND_MODE = "command";
085:
086: private final static String SESSION_KEY = "com.opensymphony.webwork.interceptor.debugging.VALUE_STACK";
087:
088: private final static String DEBUG_PARAM = "debug";
089: private final static String EXPRESSION_PARAM = "expression";
090:
091: /**
092: * Unused.
093: */
094: public void init() {
095: }
096:
097: /**
098: * Unused.
099: */
100: public void destroy() {
101: }
102:
103: /*
104: * (non-Javadoc)
105: *
106: * @see com.opensymphony.xwork2.interceptor.Interceptor#invoke(com.opensymphony.xwork2.ActionInvocation)
107: */
108: public String intercept(ActionInvocation inv) throws Exception {
109:
110: boolean devMode = ((Boolean) ActionContext.getContext().get(
111: ActionContext.DEV_MODE)).booleanValue();
112: boolean cont = true;
113: if (devMode) {
114: final ActionContext ctx = ActionContext.getContext();
115: String type = getParameter(DEBUG_PARAM);
116: ctx.getParameters().remove(DEBUG_PARAM);
117: if (XML_MODE.equals(type)) {
118: inv.addPreResultListener(new PreResultListener() {
119: public void beforeResult(ActionInvocation inv,
120: String result) {
121: printContext();
122: }
123: });
124: } else if (CONSOLE_MODE.equals(type)) {
125: inv.addPreResultListener(new PreResultListener() {
126: public void beforeResult(ActionInvocation inv,
127: String actionResult) {
128: StringWriter writer = new StringWriter();
129: printContext(new PrettyPrintWriter(writer));
130: String xml = writer.toString();
131: xml = xml.replaceAll("&", "&");
132: xml = xml.replaceAll(">", ">");
133: xml = xml.replaceAll("<", "<");
134: ActionContext.getContext().put("debugXML", xml);
135:
136: FreemarkerResult result = new FreemarkerResult();
137: result.setContentType("text/html");
138: result
139: .setLocation("/com/opensymphony/webwork/interceptor/debugging/console.ftl");
140: result.setParse(false);
141: try {
142: result.execute(inv);
143: } catch (Exception ex) {
144: log
145: .error(
146: "Unable to create debugging console",
147: ex);
148: }
149:
150: }
151: });
152: } else if (COMMAND_MODE.equals(type)) {
153: OgnlValueStack stack = (OgnlValueStack) ctx
154: .getSession().get(SESSION_KEY);
155: String cmd = getParameter(EXPRESSION_PARAM);
156:
157: HttpServletResponse res = ServletActionContext
158: .getResponse();
159: res.setContentType("text/plain");
160:
161: try {
162: PrintWriter writer = ServletActionContext
163: .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
178: .getContext();
179: ctx.getSession().put(SESSION_KEY,
180: ctx.get(ActionContext.VALUE_STACK));
181: }
182: }
183: } else {
184: return null;
185: }
186: }
187:
188: /**
189: * Gets a single string from the request parameters
190: *
191: * @param key The key
192: * @return The parameter value
193: */
194: private String getParameter(String key) {
195: String[] arr = (String[]) ActionContext.getContext()
196: .getParameters().get(key);
197: if (arr != null && arr.length > 0) {
198: return arr[0];
199: }
200: return null;
201: }
202:
203: /**
204: * Prints the current context to the response in XML format.
205: */
206: protected void printContext() {
207: HttpServletResponse res = ServletActionContext.getResponse();
208: res.setContentType("text/xml");
209:
210: try {
211: PrettyPrintWriter writer = new PrettyPrintWriter(
212: ServletActionContext.getResponse().getWriter());
213: printContext(writer);
214: writer.close();
215: } catch (IOException ex) {
216: ex.printStackTrace();
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());
230: writer.startNode("context");
231: String key;
232: Map ctxMap = ctx.getContextMap();
233:
234: Iterator iterator = ctxMap.keySet().iterator();
235: while (iterator.hasNext()) {
236: key = ((Object) iterator.next()).toString();
237: boolean print = !ignoreKeys.contains(key);
238: for (int i = 0; i < ignorePrefixes.length; i++) {
239: if (key.startsWith(ignorePrefixes[i])) {
240: print = false;
241: break;
242: }
243: }
244: if (print) {
245: serializeIt(ctxMap.get(key), key, writer,
246: new ArrayList());
247: }
248: }
249: writer.endNode();
250: serializeIt(ctx.getSession(), "request", writer,
251: new ArrayList());
252: serializeIt(ctx.getSession(), "session", writer,
253: new ArrayList());
254:
255: OgnlValueStack stack = (OgnlValueStack) ctx
256: .get(ActionContext.VALUE_STACK);
257: serializeIt(stack.getRoot(), "valueStack", writer,
258: new ArrayList());
259: writer.endNode();
260: }
261:
262: /**
263: * Recursive function to serialize objects to XML. Currently it will
264: * serialize Collections, maps, Arrays, and JavaBeans. It maintains a stack
265: * of objects serialized already in the current functioncall. This is used
266: * to avoid looping (stack overflow) of circular linked objects. Struts and
267: * XWork objects are ignored.
268: *
269: * @param bean The object you want serialized.
270: * @param name The name of the object, used for element <name/>
271: * @param writer The XML writer
272: * @param stack List of objects we're serializing since the first calling
273: * of this function (to prevent looping on circular references).
274: */
275: protected void serializeIt(Object bean, String name,
276: PrettyPrintWriter writer, List stack) {
277: writer.flush();
278: // Check stack for this object
279: if ((bean != null) && (stack.contains(bean))) {
280: if (log.isInfoEnabled()) {
281: log
282: .info("Circular reference detected, not serializing object: "
283: + name);
284: }
285: return;
286: } else if (bean != null) {
287: // Push object onto stack.
288: // Don't push null objects ( handled below)
289: stack.add(bean);
290: }
291: if (bean == null) {
292: return;
293: }
294: String clsName = bean.getClass().getName();
295:
296: writer.startNode(name);
297:
298: // It depends on the object and it's value what todo next:
299: if (bean instanceof Collection) {
300: Collection col = (Collection) bean;
301:
302: // Iterate through components, and call ourselves to process
303: // elements
304: Iterator iterator = col.iterator();
305: while (iterator.hasNext()) {
306: serializeIt(iterator.next(), "value", writer, stack);
307: }
308: } else if (bean instanceof Map) {
309: Map map = (Map) bean;
310: // Loop through keys and call ourselves
311: Iterator mapIterator = map.keySet().iterator();
312: while (mapIterator.hasNext()) {
313: Object key = mapIterator.next();
314: Object value = map.get(key);
315: serializeIt(value, key.toString(), writer, stack);
316: }
317:
318: } else if (bean.getClass().isArray()) {
319: // It's an array, loop through it and keep calling ourselves
320: for (int i = 0; i < Array.getLength(bean); i++) {
321: serializeIt(Array.get(bean, i), "arrayitem", writer,
322: stack);
323: }
324: } else {
325: if (clsName != null
326: && clsName.startsWith("org.opensymphony.webwork")) {
327: // ignore
328: } else if (clsName != null
329: && clsName.startsWith("com.opensymphony.xwork")) {
330: // ignore
331: } else if (clsName.startsWith("java.lang")) {
332: writer.setValue(bean.toString());
333: } else {
334: // Not java.lang, so we can call ourselves with this object's
335: // values
336: try {
337: BeanInfo info = Introspector.getBeanInfo(bean
338: .getClass());
339: PropertyDescriptor[] props = info
340: .getPropertyDescriptors();
341:
342: for (int i = 0; i < props.length; i++) {
343: String n = props[i].getName();
344: Method m = props[i].getReadMethod();
345:
346: // Call ourselves with the result of the method
347: // invocation
348: if (m != null) {
349: serializeIt(m.invoke(bean, null), n,
350: writer, stack);
351: }
352: }
353: } catch (Exception e) {
354: log.error(e, e);
355: }
356: }
357: }
358:
359: writer.endNode();
360:
361: // Remove object from stack
362: stack.remove(bean);
363: }
364:
365: }
|