All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.struts2.interceptor.debugging.DebuggingInterceptor Maven / Gradle / Ivy

There is a newer version: 6.4.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.struts2.interceptor.debugging;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
import com.opensymphony.xwork2.interceptor.PreResultListener;
import com.opensymphony.xwork2.util.ValueStack;
import com.opensymphony.xwork2.util.reflection.ReflectionProvider;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.dispatcher.Parameter;
import org.apache.struts2.dispatcher.PrepareOperations;
import org.apache.struts2.views.freemarker.FreemarkerManager;
import org.apache.struts2.views.freemarker.FreemarkerResult;

import javax.servlet.http.HttpServletResponse;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.*;

/**
 * 
 * 

* Provides several different debugging screens to provide insight into the * data behind the page. *

* *

* The value of the 'debug' request parameter determines * the screen: *

* *
    *
  • xml - Dumps the parameters, context, session, and value * stack as an XML document.
  • *
  • console - Shows a popup 'OGNL Console' that allows the * user to test OGNL expressions against the value stack. The XML data from * the 'xml' mode is inserted at the top of the page.
  • *
  • command - Tests an OGNL expression and returns the * string result. Only used by the OGNL console.
  • *
  • browser Shows field values of an object specified in the * object parameter (#context by default). When the object * parameters is set, the '#' character needs to be escaped to '%23'. Like * debug=browser&object=%23parameters
  • *
* *

* Example: *

* * http://localhost:8080/Welcome.action?debug=xml * *

* * This interceptor only is activated when devMode is enabled in * struts.properties. The 'debug' parameter is removed from the parameter list * before the action is executed. All operations occur before the natural * Result has a chance to execute. * *

*/ public class DebuggingInterceptor extends AbstractInterceptor { private static final long serialVersionUID = -3097324155953078783L; private final static Logger LOG = LogManager.getLogger(DebuggingInterceptor.class); private String[] ignorePrefixes = new String[]{"org.apache.struts.", "com.opensymphony.xwork2.", "xwork."}; private String[] _ignoreKeys = new String[]{"application", "session", "parameters", "request"}; private HashSet ignoreKeys = new HashSet<>(Arrays.asList(_ignoreKeys)); private final static String XML_MODE = "xml"; private final static String CONSOLE_MODE = "console"; private final static String COMMAND_MODE = "command"; private final static String BROWSER_MODE = "browser"; private final static String SESSION_KEY = "org.apache.struts2.interceptor.debugging.VALUE_STACK"; private final static String DEBUG_PARAM = "debug"; private final static String OBJECT_PARAM = "object"; private final static String EXPRESSION_PARAM = "expression"; private final static String DECORATE_PARAM = "decorate"; private boolean enableXmlWithConsole = false; private boolean devMode; private FreemarkerManager freemarkerManager; private boolean consoleEnabled = false; private ReflectionProvider reflectionProvider; @Inject(StrutsConstants.STRUTS_DEVMODE) public void setDevMode(String mode) { this.devMode = "true".equals(mode); } @Inject public void setFreemarkerManager(FreemarkerManager mgr) { this.freemarkerManager = mgr; } @Inject public void setReflectionProvider(ReflectionProvider reflectionProvider) { this.reflectionProvider = reflectionProvider; } /* * (non-Javadoc) * * @see com.opensymphony.xwork2.interceptor.Interceptor#invoke(com.opensymphony.xwork2.ActionInvocation) */ public String intercept(ActionInvocation inv) throws Exception { boolean actionOnly = false; boolean cont = true; Boolean devModeOverride = PrepareOperations.getDevModeOverride(); boolean devMode = devModeOverride != null ? devModeOverride : this.devMode; if (devMode) { final ActionContext ctx = ActionContext.getContext(); String type = getParameter(DEBUG_PARAM); ctx.getParameters().remove(DEBUG_PARAM); if (XML_MODE.equals(type)) { inv.addPreResultListener( new PreResultListener() { public void beforeResult(ActionInvocation inv, String result) { printContext(); } }); } else if (CONSOLE_MODE.equals(type)) { consoleEnabled = true; inv.addPreResultListener( new PreResultListener() { public void beforeResult(ActionInvocation inv, String actionResult) { String xml = ""; if (enableXmlWithConsole) { StringWriter writer = new StringWriter(); printContext(new PrettyPrintWriter(writer)); xml = writer.toString(); xml = xml.replaceAll("&", "&"); xml = xml.replaceAll(">", ">"); xml = xml.replaceAll("<", "<"); } ActionContext.getContext().put("debugXML", xml); FreemarkerResult result = new FreemarkerResult(); result.setFreemarkerManager(freemarkerManager); result.setContentType("text/html"); result.setLocation("/org/apache/struts2/interceptor/debugging/console.ftl"); result.setParse(false); try { result.execute(inv); } catch (Exception ex) { LOG.error("Unable to create debugging console", ex); } } }); } else if (COMMAND_MODE.equals(type)) { ValueStack stack = (ValueStack) ctx.getSession().get(SESSION_KEY); if (stack == null) { //allows it to be embedded on another page stack = (ValueStack) ctx.get(ActionContext.VALUE_STACK); ctx.getSession().put(SESSION_KEY, stack); } String cmd = getParameter(EXPRESSION_PARAM); ServletActionContext.getRequest().setAttribute("decorator", "none"); HttpServletResponse res = ServletActionContext.getResponse(); res.setContentType("text/plain"); try (PrintWriter writer = ServletActionContext.getResponse().getWriter()) { writer.print(stack.findValue(cmd)); } catch (IOException ex) { ex.printStackTrace(); } cont = false; } else if (BROWSER_MODE.equals(type)) { actionOnly = true; inv.addPreResultListener( new PreResultListener() { public void beforeResult(ActionInvocation inv, String actionResult) { String rootObjectExpression = getParameter(OBJECT_PARAM); if (rootObjectExpression == null) rootObjectExpression = "#context"; String decorate = getParameter(DECORATE_PARAM); ValueStack stack = (ValueStack) ctx.get(ActionContext.VALUE_STACK); Object rootObject = stack.findValue(rootObjectExpression); try (StringWriter writer = new StringWriter()) { ObjectToHTMLWriter htmlWriter = new ObjectToHTMLWriter(writer); htmlWriter.write(reflectionProvider, rootObject, rootObjectExpression); String html = writer.toString(); writer.close(); stack.set("debugHtml", html); //on the first request, response can be decorated //but we need plain text on the other ones if ("false".equals(decorate)) ServletActionContext.getRequest().setAttribute("decorator", "none"); FreemarkerResult result = new FreemarkerResult(); result.setFreemarkerManager(freemarkerManager); result.setContentType("text/html"); result.setLocation("/org/apache/struts2/interceptor/debugging/browser.ftl"); result.execute(inv); } catch (Exception ex) { LOG.error("Unable to create debugging console", ex); } } }); } } if (cont) { try { if (actionOnly) { inv.invokeActionOnly(); return null; } else { return inv.invoke(); } } finally { if (devMode && consoleEnabled) { final ActionContext ctx = ActionContext.getContext(); ctx.getSession().put(SESSION_KEY, ctx.get(ActionContext.VALUE_STACK)); } } } else { return null; } } /** * Gets a single string from the request parameters * * @param key The key * @return The parameter value */ private String getParameter(String key) { Parameter parameter = ActionContext.getContext().getParameters().get(key); return parameter.getValue(); } /** * Prints the current context to the response in XML format. */ protected void printContext() { HttpServletResponse res = ServletActionContext.getResponse(); res.setContentType("text/xml"); try { PrettyPrintWriter writer = new PrettyPrintWriter( ServletActionContext.getResponse().getWriter()); printContext(writer); writer.close(); } catch (IOException ex) { ex.printStackTrace(); } } /** * Prints the current request to the existing writer. * * @param writer The XML writer */ protected void printContext(PrettyPrintWriter writer) { ActionContext ctx = ActionContext.getContext(); writer.startNode(DEBUG_PARAM); serializeIt(ctx.getParameters(), "parameters", writer, new ArrayList<>()); writer.startNode("context"); String key; Map ctxMap = ctx.getContextMap(); for (Object o : ctxMap.keySet()) { key = o.toString(); boolean print = !ignoreKeys.contains(key); for (String ignorePrefixe : ignorePrefixes) { if (key.startsWith(ignorePrefixe)) { print = false; break; } } if (print) { serializeIt(ctxMap.get(key), key, writer, new ArrayList<>()); } } writer.endNode(); Map requestMap = (Map) ctx.get("request"); serializeIt(requestMap, "request", writer, filterValueStack(requestMap)); serializeIt(ctx.getSession(), "session", writer, new ArrayList<>()); ValueStack stack = (ValueStack) ctx.get(ActionContext.VALUE_STACK); serializeIt(stack.getRoot(), "valueStack", writer, new ArrayList<>()); writer.endNode(); } /** * Recursive function to serialize objects to XML. Currently it will * serialize Collections, maps, Arrays, and JavaBeans. It maintains a stack * of objects serialized already in the current function call. This is used * to avoid looping (stack overflow) of circular linked objects. Struts and * XWork objects are ignored. * * @param bean The object you want serialized. * @param name The name of the object, used for element <name/> * @param writer The XML writer * @param stack List of objects we're serializing since the first calling * of this function (to prevent looping on circular references). */ protected void serializeIt(Object bean, String name, PrettyPrintWriter writer, List stack) { writer.flush(); // Check stack for this object if ((bean != null) && (stack.contains(bean))) { LOG.info("Circular reference detected, not serializing object: {}", name); return; } else if (bean != null) { // Push object onto stack. // Don't push null objects ( handled below) stack.add(bean); } if (bean == null) { return; } String clsName = bean.getClass().getName(); writer.startNode(name); // It depends on the object and it's value what todo next: if (bean instanceof Collection) { Collection col = (Collection) bean; // Iterate through components, and call ourselves to process // elements for (Object aCol : col) { serializeIt(aCol, "value", writer, stack); } } else if (bean instanceof Map) { Map map = (Map) bean; // Loop through keys and call ourselves for (Map.Entry entry : map.entrySet()) { Object objValue = entry.getValue(); serializeIt(objValue, entry.getKey().toString(), writer, stack); } } else if (bean.getClass().isArray()) { // It's an array, loop through it and keep calling ourselves for (int i = 0; i < Array.getLength(bean); i++) { serializeIt(Array.get(bean, i), "arrayitem", writer, stack); } } else { if (clsName.startsWith("java.lang")) { writer.setValue(bean.toString()); } else { // Not java.lang, so we can call ourselves with this object's // values try { BeanInfo info = Introspector.getBeanInfo(bean.getClass()); PropertyDescriptor[] props = info.getPropertyDescriptors(); for (PropertyDescriptor prop : props) { String n = prop.getName(); Method m = prop.getReadMethod(); // Call ourselves with the result of the method // invocation if (m != null) { serializeIt(m.invoke(bean), n, writer, stack); } } } catch (Exception e) { LOG.error(e.toString(), e); } } } writer.endNode(); // Remove object from stack stack.remove(bean); } /** * @param enableXmlWithConsole the enableXmlWithConsole to set */ public void setEnableXmlWithConsole(boolean enableXmlWithConsole) { this.enableXmlWithConsole = enableXmlWithConsole; } private List filterValueStack(Map requestMap) { List filter = new ArrayList<>(); Object valueStack = requestMap.get("struts.valueStack"); if(valueStack != null) { filter.add(valueStack); } return filter; } }