001: /*
002: * $Id: DebuggingInterceptor.java 483839 2006-12-08 05:52:10Z mrdon $
003: *
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021: package org.apache.struts2.interceptor.debugging;
022:
023: import java.beans.BeanInfo;
024: import java.beans.Introspector;
025: import java.beans.PropertyDescriptor;
026: import java.io.IOException;
027: import java.io.PrintWriter;
028: import java.io.StringWriter;
029: import java.lang.reflect.Array;
030: import java.lang.reflect.Method;
031: import java.util.ArrayList;
032: import java.util.Arrays;
033: import java.util.Collection;
034: import java.util.HashSet;
035: import java.util.List;
036: import java.util.Map;
037:
038: import javax.servlet.http.HttpServletResponse;
039:
040: import org.apache.commons.logging.Log;
041: import org.apache.commons.logging.LogFactory;
042: import org.apache.struts2.ServletActionContext;
043: import org.apache.struts2.views.freemarker.FreemarkerManager;
044: import org.apache.struts2.views.freemarker.FreemarkerResult;
045: import org.apache.struts2.StrutsConstants;
046:
047: import com.opensymphony.xwork2.ActionContext;
048: import com.opensymphony.xwork2.ActionInvocation;
049: import com.opensymphony.xwork2.inject.Inject;
050: import com.opensymphony.xwork2.interceptor.Interceptor;
051: import com.opensymphony.xwork2.interceptor.PreResultListener;
052: import com.opensymphony.xwork2.util.ValueStack;
053:
054: /**
055: * <!-- START SNIPPET: description -->
056: * Provides several different debugging screens to provide insight into the
057: * data behind the page.
058: * <!-- END SNIPPET: description -->
059: * The value of the 'debug' request parameter determines
060: * the screen:
061: * <!-- START SNIPPET: parameters -->
062: * <ul>
063: * <li> <code>xml</code> - Dumps the parameters, context, session, and value
064: * stack as an XML document.</li>
065: * <li> <code>console</code> - Shows a popup 'OGNL Console' that allows the
066: * user to test OGNL expressions against the value stack. The XML data from
067: * the 'xml' mode is inserted at the top of the page.</li>
068: * <li> <code>command</code> - Tests an OGNL expression and returns the
069: * string result. Only used by the OGNL console.</li>
070: * </ul>
071: * <!-- END SNIPPET: parameters -->
072: * <p/>
073: * Example:
074: * <!-- START SNIPPET: example -->
075: * http://localhost:8080/Welcome.action?debug=xml
076: * <!-- END SNIPPET: example -->
077: * <p/>
078: * <!-- START SNIPPET: remarks -->
079: * This interceptor only is activated when devMode is enabled in
080: * struts.properties. The 'debug' parameter is removed from the parameter list
081: * before the action is executed. All operations occur before the natural
082: * Result has a chance to execute.
083: * <!-- END SNIPPET: remarks -->
084: */
085: public class DebuggingInterceptor implements Interceptor {
086:
087: private static final long serialVersionUID = -3097324155953078783L;
088:
089: private final static Log log = LogFactory
090: .getLog(DebuggingInterceptor.class);
091:
092: private String[] ignorePrefixes = new String[] {
093: "org.apache.struts.", "com.opensymphony.xwork2.", "xwork." };
094: private String[] _ignoreKeys = new String[] { "application",
095: "session", "parameters", "request" };
096: private HashSet<String> ignoreKeys = new HashSet<String>(Arrays
097: .asList(_ignoreKeys));
098:
099: private final static String XML_MODE = "xml";
100: private final static String CONSOLE_MODE = "console";
101: private final static String COMMAND_MODE = "command";
102:
103: private final static String SESSION_KEY = "org.apache.struts2.interceptor.debugging.VALUE_STACK";
104:
105: private final static String DEBUG_PARAM = "debug";
106: private final static String EXPRESSION_PARAM = "expression";
107:
108: private boolean enableXmlWithConsole = false;
109:
110: private boolean devMode;
111: private FreemarkerManager freemarkerManager;
112:
113: private boolean consoleEnabled = false;
114:
115: @Inject(StrutsConstants.STRUTS_DEVMODE)
116: public void setDevMode(String mode) {
117: this .devMode = "true".equals(mode);
118: }
119:
120: @Inject
121: public void setFreemarkerManager(FreemarkerManager mgr) {
122: this .freemarkerManager = mgr;
123: }
124:
125: /**
126: * Unused.
127: */
128: public void init() {
129: }
130:
131: /**
132: * Unused.
133: */
134: public void destroy() {
135: }
136:
137: /*
138: * (non-Javadoc)
139: *
140: * @see com.opensymphony.xwork2.interceptor.Interceptor#invoke(com.opensymphony.xwork2.ActionInvocation)
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(new PreResultListener() {
151: public void beforeResult(ActionInvocation inv,
152: String result) {
153: printContext();
154: }
155: });
156: } else if (CONSOLE_MODE.equals(type)) {
157: consoleEnabled = true;
158: inv.addPreResultListener(new PreResultListener() {
159: public void beforeResult(ActionInvocation inv,
160: 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
176: .setLocation("/org/apache/struts2/interceptor/debugging/console.ftl");
177: result.setParse(false);
178: try {
179: result.execute(inv);
180: } catch (Exception ex) {
181: log
182: .error(
183: "Unable to create debugging console",
184: ex);
185: }
186:
187: }
188: });
189: } else if (COMMAND_MODE.equals(type)) {
190: ValueStack stack = (ValueStack) ctx.getSession().get(
191: SESSION_KEY);
192: String cmd = getParameter(EXPRESSION_PARAM);
193:
194: ServletActionContext.getRequest().setAttribute(
195: "decorator", "none");
196: HttpServletResponse res = ServletActionContext
197: .getResponse();
198: res.setContentType("text/plain");
199:
200: try {
201: PrintWriter writer = ServletActionContext
202: .getResponse().getWriter();
203: writer.print(stack.findValue(cmd));
204: writer.close();
205: } catch (IOException ex) {
206: ex.printStackTrace();
207: }
208: cont = false;
209: }
210: }
211: if (cont) {
212: try {
213: return inv.invoke();
214: } finally {
215: if (devMode && consoleEnabled) {
216: final ActionContext ctx = ActionContext
217: .getContext();
218: ctx.getSession().put(SESSION_KEY,
219: ctx.get(ActionContext.VALUE_STACK));
220: }
221: }
222: } else {
223: return null;
224: }
225: }
226:
227: /**
228: * Gets a single string from the request parameters
229: *
230: * @param key The key
231: * @return The parameter value
232: */
233: private String getParameter(String key) {
234: String[] arr = (String[]) ActionContext.getContext()
235: .getParameters().get(key);
236: if (arr != null && arr.length > 0) {
237: return arr[0];
238: }
239: return null;
240: }
241:
242: /**
243: * Prints the current context to the response in XML format.
244: */
245: protected void printContext() {
246: HttpServletResponse res = ServletActionContext.getResponse();
247: res.setContentType("text/xml");
248:
249: try {
250: PrettyPrintWriter writer = new PrettyPrintWriter(
251: ServletActionContext.getResponse().getWriter());
252: printContext(writer);
253: writer.close();
254: } catch (IOException ex) {
255: ex.printStackTrace();
256: }
257: }
258:
259: /**
260: * Prints the current request to the existing writer.
261: *
262: * @param writer The XML writer
263: */
264: protected void printContext(PrettyPrintWriter writer) {
265: ActionContext ctx = ActionContext.getContext();
266: writer.startNode(DEBUG_PARAM);
267: serializeIt(ctx.getParameters(), "parameters", writer,
268: new ArrayList<Object>());
269: writer.startNode("context");
270: String key;
271: Map ctxMap = ctx.getContextMap();
272: for (Object o : ctxMap.keySet()) {
273: key = o.toString();
274: boolean print = !ignoreKeys.contains(key);
275:
276: for (String ignorePrefixe : ignorePrefixes) {
277: if (key.startsWith(ignorePrefixe)) {
278: print = false;
279: break;
280: }
281: }
282: if (print) {
283: serializeIt(ctxMap.get(key), key, writer,
284: new ArrayList<Object>());
285: }
286: }
287: writer.endNode();
288: serializeIt(ctx.getSession(), "request", writer,
289: new ArrayList<Object>());
290: serializeIt(ctx.getSession(), "session", writer,
291: new ArrayList<Object>());
292:
293: ValueStack stack = (ValueStack) ctx
294: .get(ActionContext.VALUE_STACK);
295: serializeIt(stack.getRoot(), "valueStack", writer,
296: new ArrayList<Object>());
297: writer.endNode();
298: }
299:
300: /**
301: * Recursive function to serialize objects to XML. Currently it will
302: * serialize Collections, maps, Arrays, and JavaBeans. It maintains a stack
303: * of objects serialized already in the current functioncall. This is used
304: * to avoid looping (stack overflow) of circular linked objects. Struts and
305: * XWork objects are ignored.
306: *
307: * @param bean The object you want serialized.
308: * @param name The name of the object, used for element <name/>
309: * @param writer The XML writer
310: * @param stack List of objects we're serializing since the first calling
311: * of this function (to prevent looping on circular references).
312: */
313: protected void serializeIt(Object bean, String name,
314: PrettyPrintWriter writer, List<Object> stack) {
315: writer.flush();
316: // Check stack for this object
317: if ((bean != null) && (stack.contains(bean))) {
318: if (log.isInfoEnabled()) {
319: log
320: .info("Circular reference detected, not serializing object: "
321: + name);
322: }
323: return;
324: } else if (bean != null) {
325: // Push object onto stack.
326: // Don't push null objects ( handled below)
327: stack.add(bean);
328: }
329: if (bean == null) {
330: return;
331: }
332: String clsName = bean.getClass().getName();
333:
334: writer.startNode(name);
335:
336: // It depends on the object and it's value what todo next:
337: if (bean instanceof Collection) {
338: Collection col = (Collection) bean;
339:
340: // Iterate through components, and call ourselves to process
341: // elements
342: for (Object aCol : col) {
343: serializeIt(aCol, "value", writer, stack);
344: }
345: } else if (bean instanceof Map) {
346:
347: Map map = (Map) bean;
348:
349: // Loop through keys and call ourselves
350: for (Object key : map.keySet()) {
351: Object Objvalue = map.get(key);
352: serializeIt(Objvalue, key.toString(), writer, stack);
353: }
354: } else if (bean.getClass().isArray()) {
355: // It's an array, loop through it and keep calling ourselves
356: for (int i = 0; i < Array.getLength(bean); i++) {
357: serializeIt(Array.get(bean, i), "arrayitem", writer,
358: stack);
359: }
360: } else {
361: if (clsName.startsWith("java.lang")) {
362: writer.setValue(bean.toString());
363: } else {
364: // Not java.lang, so we can call ourselves with this object's
365: // values
366: try {
367: BeanInfo info = Introspector.getBeanInfo(bean
368: .getClass());
369: PropertyDescriptor[] props = info
370: .getPropertyDescriptors();
371:
372: for (PropertyDescriptor prop : props) {
373: String n = prop.getName();
374: Method m = prop.getReadMethod();
375:
376: // Call ourselves with the result of the method
377: // invocation
378: if (m != null) {
379: serializeIt(m.invoke(bean), n, writer,
380: stack);
381: }
382: }
383: } catch (Exception e) {
384: log.error(e, e);
385: }
386: }
387: }
388:
389: writer.endNode();
390:
391: // Remove object from stack
392: stack.remove(bean);
393: }
394:
395: /**
396: * @param enableXmlWithConsole the enableXmlWithConsole to set
397: */
398: public void setEnableXmlWithConsole(boolean enableXmlWithConsole) {
399: this.enableXmlWithConsole = enableXmlWithConsole;
400: }
401:
402: }
|