View Javadoc

    1   /*
    2    * $Id: DebuggingInterceptor.java 483839 2006-12-08 05:52:10Z mrdon $
    3    *
    4    * Licensed to the Apache Software Foundation (ASF) under one
    5    * or more contributor license agreements.  See the NOTICE file
    6    * distributed with this work for additional information
    7    * regarding copyright ownership.  The ASF licenses this file
    8    * to you under the Apache License, Version 2.0 (the
    9    * "License"); you may not use this file except in compliance
    10   * with the License.  You may obtain a copy of the License at
    11   *
    12   *  http://www.apache.org/licenses/LICENSE-2.0
    13   *
    14   * Unless required by applicable law or agreed to in writing,
    15   * software distributed under the License is distributed on an
    16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    17   * KIND, either express or implied.  See the License for the
    18   * specific language governing permissions and limitations
    19   * under the License.
    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      * (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(
    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("&", "&amp;");
    167                                     xml = xml.replaceAll(">", "&gt;");
    168                                     xml = xml.replaceAll("<", "&lt;");
    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 &lt;name/&gt;
    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         // Check stack for this object
    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             // Push object onto stack.
    314             // Don't push null objects ( handled below)
    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         // It depends on the object and it's value what todo next:
    325         if (bean instanceof Collection) {
    326             Collection col = (Collection) bean;
    327
    328             // Iterate through components, and call ourselves to process
    329             // elements
    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             // Loop through keys and call ourselves
    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             // It's an array, loop through it and keep calling ourselves
    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                 // Not java.lang, so we can call ourselves with this object's
    352                 // values
    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                         // Call ourselves with the result of the method
    362                         // invocation
    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         // Remove object from stack
    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