org.apache.struts2.interceptor.debugging.DebuggingInterceptor Maven / Gradle / Ivy
/*
* 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