Merge pull request #1187 from apache/fix/WW-5517-debug

WW-5517 Fixes <s:debug/> to be compatible with allowlist capability
This commit is contained in:
Lukasz Lenart
2025-01-21 18:01:26 +01:00
committed by GitHub
10 changed files with 755 additions and 148 deletions
@@ -18,7 +18,10 @@
*/
package org.apache.struts2.components;
import org.apache.commons.lang3.ClassUtils;
import org.apache.struts2.inject.Inject;
import org.apache.struts2.ognl.ThreadAllowlist;
import org.apache.struts2.util.CompoundRoot;
import org.apache.struts2.util.ValueStack;
import org.apache.struts2.util.reflection.ReflectionProvider;
import jakarta.servlet.http.HttpServletRequest;
@@ -40,6 +43,7 @@ public class Debug extends UIBean {
protected ReflectionProvider reflectionProvider;
private ThreadAllowlist threadAllowlist;
public Debug(ValueStack stack, HttpServletRequest request, HttpServletResponse response) {
super(stack, request, response);
@@ -50,6 +54,11 @@ public class Debug extends UIBean {
this.reflectionProvider = prov;
}
@Inject
public void setThreadAllowlist(ThreadAllowlist threadAllowlist) {
this.threadAllowlist = threadAllowlist;
}
protected String getDefaultTemplate() {
return TEMPLATE;
}
@@ -59,16 +68,19 @@ public class Debug extends UIBean {
if (showDebug()) {
ValueStack stack = getStack();
Iterator iter = stack.getRoot().iterator();
List stackValues = new ArrayList(stack.getRoot().size());
allowList(stack.getRoot());
Iterator<Object> iter = stack.getRoot().iterator();
List<Object> stackValues = new ArrayList<>(stack.getRoot().size());
while (iter.hasNext()) {
Object o = iter.next();
Map values;
Map<String, Object> values;
try {
values = reflectionProvider.getBeanMap(o);
} catch (Exception e) {
throw new StrutsException("Caught an exception while getting the property values of " + o, e);
}
allowListClass(o);
stackValues.add(new DebugMapEntry(o.getClass().getName(), values));
}
@@ -77,6 +89,16 @@ public class Debug extends UIBean {
return result;
}
private void allowList(CompoundRoot root) {
root.forEach(this::allowListClass);
}
private void allowListClass(Object o) {
threadAllowlist.allowClass(o.getClass());
ClassUtils.getAllSuperclasses(o.getClass()).forEach(threadAllowlist::allowClass);
ClassUtils.getAllInterfaces(o.getClass()).forEach(threadAllowlist::allowClass);
}
@Override
public boolean end(Writer writer, String body) {
if (showDebug()) {
@@ -91,17 +113,17 @@ public class Debug extends UIBean {
return (devMode || Boolean.TRUE == PrepareOperations.getDevModeOverride());
}
private static class DebugMapEntry implements Map.Entry {
private final Object key;
private static class DebugMapEntry implements Map.Entry<String, Object> {
private final String key;
private Object value;
DebugMapEntry(Object key, Object value) {
DebugMapEntry(String key, Object value) {
this.key = key;
this.value = value;
}
@Override
public Object getKey() {
public String getKey() {
return key;
}
@@ -18,11 +18,14 @@
*/
package org.apache.struts2.interceptor;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.ActionInvocation;
import org.apache.struts2.config.entities.ExceptionMappingConfig;
import org.apache.struts2.dispatcher.HttpParameters;
import org.apache.struts2.inject.Inject;
import org.apache.struts2.ognl.ThreadAllowlist;
import java.util.List;
import java.util.Map;
@@ -42,11 +45,11 @@ import java.util.Map;
* you make this interceptor the first interceptor on the stack, ensuring that it has full access to catch any
* exception, even those caused by other interceptors.
* </p>
*
* <p>
* <!-- END SNIPPET: description -->
*
* <p><u>Interceptor parameters:</u></p>
*
* <p>
* <!-- START SNIPPET: parameters -->
*
* <ul>
@@ -64,11 +67,11 @@ import java.util.Map;
* The parameters above enables us to log all thrown exceptions with stacktace in our own logfile,
* and present a friendly webpage (with no stacktrace) to the end user.
* </p>
*
* <p>
* <!-- END SNIPPET: parameters -->
*
* <p><u>Extending the interceptor:</u></p>
*
* <p>
* <!-- START SNIPPET: extending -->
* <p>
* If you want to add custom handling for publishing the Exception, you may override
@@ -158,11 +161,17 @@ public class ExceptionMappingInterceptor extends AbstractInterceptor {
private static final Logger LOG = LogManager.getLogger(ExceptionMappingInterceptor.class);
private transient ThreadAllowlist threadAllowlist;
protected Logger categoryLogger;
protected boolean logEnabled = false;
protected String logCategory;
protected String logLevel;
@Inject
public void setThreadAllowlist(ThreadAllowlist threadAllowlist) {
this.threadAllowlist = threadAllowlist;
}
public boolean isLogEnabled() {
return logEnabled;
@@ -173,20 +182,20 @@ public class ExceptionMappingInterceptor extends AbstractInterceptor {
}
public String getLogCategory() {
return logCategory;
}
return logCategory;
}
public void setLogCategory(String logCatgory) {
this.logCategory = logCatgory;
}
public void setLogCategory(String logCategory) {
this.logCategory = logCategory;
}
public String getLogLevel() {
return logLevel;
}
public String getLogLevel() {
return logLevel;
}
public void setLogLevel(String logLevel) {
this.logLevel = logLevel;
}
public void setLogLevel(String logLevel) {
this.logLevel = logLevel;
}
@Override
public String intercept(ActionInvocation invocation) throws Exception {
@@ -200,13 +209,16 @@ public class ExceptionMappingInterceptor extends AbstractInterceptor {
}
List<ExceptionMappingConfig> exceptionMappings = invocation.getProxy().getConfig().getExceptionMappings();
ExceptionMappingConfig mappingConfig = this.findMappingFromExceptions(exceptionMappings, e);
if (mappingConfig != null && mappingConfig.getResult()!=null) {
if (mappingConfig != null && mappingConfig.getResult() != null) {
Map<String, String> mappingParams = mappingConfig.getParams();
// create a mutable HashMap since some interceptors will remove parameters, and parameterMap is immutable
HttpParameters parameters = HttpParameters.create(mappingParams).build();
invocation.getInvocationContext().withParameters(parameters);
result = mappingConfig.getResult();
publishException(invocation, new ExceptionHolder(e));
ExceptionHolder holder = new ExceptionHolder(e);
threadAllowlist.allowClass(holder.getClass());
threadAllowlist.allowClass(e.getClass());
publishException(invocation, holder);
} else {
throw e;
}
@@ -221,55 +233,45 @@ public class ExceptionMappingInterceptor extends AbstractInterceptor {
* @param e the exception to log.
*/
protected void handleLogging(Exception e) {
if (logCategory != null) {
if (categoryLogger == null) {
// init category logger
categoryLogger = LogManager.getLogger(logCategory);
}
doLog(categoryLogger, e);
} else {
doLog(LOG, e);
}
if (logCategory != null) {
if (categoryLogger == null) {
// init category logger
categoryLogger = LogManager.getLogger(logCategory);
}
doLog(categoryLogger, e);
} else {
doLog(LOG, e);
}
}
/**
* Performs the actual logging.
*
* @param logger the provided logger to use.
* @param e the exception to log.
* @param logger the provided logger to use.
* @param e the exception to log.
*/
protected void doLog(Logger logger, Exception e) {
if (logLevel == null) {
logger.debug(e.getMessage(), e);
return;
}
if (logLevel == null) {
logger.debug(e.getMessage(), e);
return;
}
if ("trace".equalsIgnoreCase(logLevel)) {
logger.trace(e.getMessage(), e);
} else if ("debug".equalsIgnoreCase(logLevel)) {
logger.debug(e.getMessage(), e);
} else if ("info".equalsIgnoreCase(logLevel)) {
logger.info(e.getMessage(), e);
} else if ("warn".equalsIgnoreCase(logLevel)) {
logger.warn(e.getMessage(), e);
} else if ("error".equalsIgnoreCase(logLevel)) {
logger.error(e.getMessage(), e);
} else if ("fatal".equalsIgnoreCase(logLevel)) {
logger.fatal(e.getMessage(), e);
} else {
throw new IllegalArgumentException("LogLevel [" + logLevel + "] is not supported");
}
Level level = Level.getLevel(logLevel);
if (level == null) {
throw new IllegalArgumentException("LogLevel [" + logLevel + "] is not supported");
}
logger.log(level, e.getMessage(), e);
}
/**
* Try to find appropriate {@link ExceptionMappingConfig} based on provided Throwable
*
* @param exceptionMappings list of defined exception mappings
* @param t caught exception
* @param t caught exception
* @return appropriate mapping or null
*/
protected ExceptionMappingConfig findMappingFromExceptions(List<ExceptionMappingConfig> exceptionMappings, Throwable t) {
ExceptionMappingConfig config = null;
ExceptionMappingConfig config = null;
// Check for specific exception mappings.
if (exceptionMappings != null) {
int deepest = Integer.MAX_VALUE;
@@ -288,15 +290,15 @@ public class ExceptionMappingInterceptor extends AbstractInterceptor {
* Return the depth to the superclass matching. 0 means ex matches exactly. Returns -1 if there's no match.
* Otherwise, returns depth. Lowest depth wins.
*
* @param exceptionMapping the mapping classname
* @param t the cause
* @param exceptionMapping the mapping classname
* @param t the cause
* @return the depth, if not found -1 is returned.
*/
public int getDepth(String exceptionMapping, Throwable t) {
return getDepth(exceptionMapping, t.getClass(), 0);
}
private int getDepth(String exceptionMapping, Class exceptionClass, int depth) {
private int getDepth(String exceptionMapping, Class<?> exceptionClass, int depth) {
if (exceptionClass.getName().contains(exceptionMapping)) {
// Found it!
return depth;
@@ -312,7 +314,7 @@ public class ExceptionMappingInterceptor extends AbstractInterceptor {
* Default implementation to handle ExceptionHolder publishing. Pushes given ExceptionHolder on the stack.
* Subclasses may override this to customize publishing.
*
* @param invocation The invocation to publish Exception for.
* @param invocation The invocation to publish Exception for.
* @param exceptionHolder The exceptionHolder wrapping the Exception to publish.
*/
protected void publishException(ActionInvocation invocation, ExceptionHolder exceptionHolder) {
@@ -18,21 +18,23 @@
*/
package org.apache.struts2.interceptor.debugging;
import org.apache.struts2.ActionContext;
import org.apache.struts2.ActionInvocation;
import org.apache.struts2.inject.Inject;
import org.apache.struts2.interceptor.AbstractInterceptor;
import org.apache.struts2.util.ValueStack;
import org.apache.struts2.util.reflection.ReflectionProvider;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.ClassUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.ActionContext;
import org.apache.struts2.ActionInvocation;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.dispatcher.DispatcherConstants;
import org.apache.struts2.dispatcher.Parameter;
import org.apache.struts2.dispatcher.PrepareOperations;
import org.apache.struts2.dispatcher.RequestMap;
import org.apache.struts2.inject.Inject;
import org.apache.struts2.interceptor.AbstractInterceptor;
import org.apache.struts2.ognl.ThreadAllowlist;
import org.apache.struts2.util.ValueStack;
import org.apache.struts2.util.reflection.ReflectionProvider;
import org.apache.struts2.views.freemarker.FreemarkerManager;
import org.apache.struts2.views.freemarker.FreemarkerResult;
@@ -101,10 +103,10 @@ public class DebuggingInterceptor extends AbstractInterceptor {
private final String[] ignorePrefixes = new String[]{"org.apache.struts.", "org.apache.struts2.", "xwork."};
private final Set<String> ignoreKeys = Set.of(
DispatcherConstants.APPLICATION,
DispatcherConstants.SESSION,
DispatcherConstants.PARAMETERS,
DispatcherConstants.REQUEST
DispatcherConstants.APPLICATION,
DispatcherConstants.SESSION,
DispatcherConstants.PARAMETERS,
DispatcherConstants.REQUEST
);
private final static String XML_MODE = "xml";
@@ -126,6 +128,7 @@ public class DebuggingInterceptor extends AbstractInterceptor {
private boolean consoleEnabled = false;
private ReflectionProvider reflectionProvider;
private transient ThreadAllowlist threadAllowlist;
@Inject(StrutsConstants.STRUTS_DEVMODE)
public void setDevMode(String mode) {
@@ -142,6 +145,11 @@ public class DebuggingInterceptor extends AbstractInterceptor {
this.reflectionProvider = reflectionProvider;
}
@Inject
public void setThreadAllowlist(ThreadAllowlist threadAllowlist) {
this.threadAllowlist = threadAllowlist;
}
/*
* (non-Javadoc)
*
@@ -200,7 +208,7 @@ public class DebuggingInterceptor extends AbstractInterceptor {
res.setContentType("text/plain");
try (PrintWriter writer =
ServletActionContext.getResponse().getWriter()) {
ServletActionContext.getResponse().getWriter()) {
writer.print(stack.findValue(cmd));
} catch (IOException ex) {
LOG.warn("Interceptor in: {} mode has failed!", COMMAND_MODE, ex);
@@ -217,6 +225,7 @@ public class DebuggingInterceptor extends AbstractInterceptor {
String decorate = getParameter(DECORATE_PARAM);
ValueStack stack = ctx.getValueStack();
Object rootObject = stack.findValue(rootObjectExpression);
allowListClass(rootObject);
try (StringWriter writer = new StringWriter()) {
ObjectToHTMLWriter htmlWriter = new ObjectToHTMLWriter(writer);
@@ -228,8 +237,9 @@ public class DebuggingInterceptor extends AbstractInterceptor {
//on the first request, response can be decorated
//but we need plain text on the other ones
if ("false".equals(decorate))
if ("false".equals(decorate)) {
ServletActionContext.getRequest().setAttribute("decorator", "none");
}
FreemarkerResult result = new FreemarkerResult();
result.setFreemarkerManager(freemarkerManager);
@@ -239,7 +249,6 @@ public class DebuggingInterceptor extends AbstractInterceptor {
} catch (Exception ex) {
LOG.error("Unable to create debugging console", ex);
}
});
}
}
@@ -262,6 +271,14 @@ public class DebuggingInterceptor extends AbstractInterceptor {
}
}
private void allowListClass(Object o) {
if (o != null) {
threadAllowlist.allowClass(o.getClass());
ClassUtils.getAllSuperclasses(o.getClass()).forEach(threadAllowlist::allowClass);
ClassUtils.getAllInterfaces(o.getClass()).forEach(threadAllowlist::allowClass);
}
}
/**
* Gets a single string from the request parameters
*
@@ -277,12 +294,11 @@ public class DebuggingInterceptor extends AbstractInterceptor {
* Prints the current context to the response in XML format.
*/
protected void printContext() {
HttpServletResponse res = ServletActionContext.getResponse();
res.setContentType("text/xml");
HttpServletResponse response = ActionContext.getContext().getServletResponse();
response.setContentType("text/xml");
try {
PrettyPrintWriter writer = new PrettyPrintWriter(
ServletActionContext.getResponse().getWriter());
PrettyPrintWriter writer = new PrettyPrintWriter(response.getWriter());
printContext(writer);
writer.close();
} catch (IOException ex) {
@@ -311,6 +327,7 @@ public class DebuggingInterceptor extends AbstractInterceptor {
}
}
if (print) {
allowListClass(ctxMap.get(key));
serializeIt(ctxMap.get(key), key, writer, new ArrayList<>());
}
}
@@ -426,5 +443,3 @@ public class DebuggingInterceptor extends AbstractInterceptor {
return filter;
}
}
@@ -25,8 +25,6 @@ import org.apache.commons.lang3.BooleanUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.ognl.ProviderAllowlist;
import org.apache.struts2.ognl.ThreadAllowlist;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
@@ -19,8 +19,8 @@
*/
-->
<!DOCTYPE html>
<html>
<@s.style>
<html lang="en">
<style>
.debugTable {
border-style: solid;
border-width: 1px;
@@ -50,40 +50,40 @@
.emptyCollection {
background-color: #EEEEEE;
}
</@s.style>
</style>
<@s.script>
<script>
function expand(src, path) {
var baseUrl = location.href;
var i = baseUrl.indexOf("&object=");
let baseUrl = location.href;
const i = baseUrl.indexOf('&object=');
baseUrl = (i > 0 ? baseUrl.substring(0, i) : baseUrl) + "&object=" + path;
if (baseUrl.indexOf("decorate") < 0) {
baseUrl += "&decorate=false";
}
var request = new XMLHttpRequest();
const request = new XMLHttpRequest();
request.open('GET', baseUrl, true);
request.onreadystatechange = function() {
if (this.readyState === 4) {
if (this.status >= 200 && this.status < 400) {
var div = document.createElement("div");
const div = document.createElement('div');
console.log(this.responseText);
div.innerHTML = this.responseText;
src.parentNode.appendChild(div);
src.innerHTML = "Collapse";
var oldonclick = src.onclick;
const oldOnclick = src.onclick;
src.onclick = function() {
src.innerHTML = "Expand";
src.parentNode.removeChild(div);
src.onclick = oldonclick;
src.onclick = oldOnclick;
};
}
}
};
request.send();
}
</@s.script>
</script>
<body>
${debugHtml?no_esc}
@@ -23,21 +23,25 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="webconsole.css"/>
<script src="webconsole.js"></script>
<title>OGNL Console</title>
<link rel="stylesheet" type="text/css" href="webconsole.css"/>
<script src="webconsole.js"></script>
<title>OGNL Console</title>
</head>
<body>
<div id="shell">
<div class="wc-results" id="wc-result">
Welcome to the OGNL console!
<br/>
:-&gt;
</div>
<form onsubmit="return false" id="wc-form">
<input type="hidden" name="debug" value="command"/>
<input name="expression" onkeyup="keyEvent(event)" class="wc-command" id="wc-command" type="text"/>
</form>
<div class="wc-results" id="wc-result">
Welcome to the OGNL console!
<br/>
:-&gt;
</div>
<form onsubmit="return false" id="wc-form">
<input type="hidden" name="debug" value="command"/>
<input name="expression" class="wc-command" id="wc-command" type="text"/>
<script>
const input = document.getElementById("wc-command")
input.addEventListener("keyup", keyEvent);
</script>
</form>
</div>
</body>
</html>
@@ -18,8 +18,6 @@
*/
package org.apache.struts2;
import org.apache.struts2.ActionProxyFactory;
import org.apache.struts2.XWorkJUnit4TestCase;
import org.apache.struts2.dispatcher.Dispatcher;
import org.apache.struts2.util.StrutsTestCaseHelper;
import org.apache.struts2.views.jsp.StrutsMockServletContext;
@@ -19,16 +19,17 @@
package org.apache.struts2.interceptor;
import com.mockobjects.dynamic.Mock;
import org.apache.struts2.action.Action;
import org.apache.struts2.ActionContext;
import org.apache.struts2.ActionInvocation;
import org.apache.struts2.ActionProxy;
import org.apache.struts2.StrutsException;
import org.apache.struts2.XWorkTestCase;
import org.apache.struts2.action.Action;
import org.apache.struts2.config.entities.ActionConfig;
import org.apache.struts2.config.entities.ExceptionMappingConfig;
import org.apache.struts2.ognl.ThreadAllowlist;
import org.apache.struts2.util.ValueStack;
import org.apache.struts2.validator.ValidationException;
import org.apache.struts2.StrutsException;
/**
* Unit test for ExceptionMappingInterceptor.
@@ -52,7 +53,7 @@ public class ExceptionMappingInterceptorTest extends XWorkTestCase {
String result = interceptor.intercept(invocation);
assertNotNull(stack.findValue("exception"));
assertEquals(stack.findValue("exception"), exception);
assertEquals(result, "spooky");
assertEquals("spooky", result);
ExceptionHolder holder = (ExceptionHolder) stack.getRoot().get(0); // is on top of the root
assertNotNull(holder.getExceptionStack()); // to invoke the method for unit test
}
@@ -67,7 +68,7 @@ public class ExceptionMappingInterceptorTest extends XWorkTestCase {
String result = interceptor.intercept(invocation);
assertNotNull(stack.findValue("exception"));
assertEquals(stack.findValue("exception"), exception);
assertEquals(result, "throwable");
assertEquals("throwable", result);
}
public void testNoThrownException() throws Exception {
@@ -77,7 +78,7 @@ public class ExceptionMappingInterceptorTest extends XWorkTestCase {
mockInvocation.expectAndReturn("invoke", Action.SUCCESS);
mockInvocation.matchAndReturn("getAction", action.proxy());
String result = interceptor.intercept(invocation);
assertEquals(result, Action.SUCCESS);
assertEquals(Action.SUCCESS, result);
assertNull(stack.findValue("exception"));
}
@@ -106,7 +107,7 @@ public class ExceptionMappingInterceptorTest extends XWorkTestCase {
mockInvocation.matchAndReturn("getAction", action.proxy());
try {
interceptor.setLogEnabled(true);
interceptor.setLogEnabled(true);
interceptor.intercept(invocation);
fail("Should not have reached this point.");
} catch (Exception e) {
@@ -123,8 +124,8 @@ public class ExceptionMappingInterceptorTest extends XWorkTestCase {
mockInvocation.matchAndReturn("getAction", action.proxy());
try {
interceptor.setLogEnabled(true);
interceptor.setLogCategory("showcase.unhandled");
interceptor.setLogEnabled(true);
interceptor.setLogCategory("showcase.unhandled");
interceptor.intercept(invocation);
fail("Should not have reached this point.");
} catch (Exception e) {
@@ -141,9 +142,9 @@ public class ExceptionMappingInterceptorTest extends XWorkTestCase {
mockInvocation.matchAndReturn("getAction", action.proxy());
try {
interceptor.setLogEnabled(true);
interceptor.setLogCategory("showcase.unhandled");
interceptor.setLogLevel("fatal");
interceptor.setLogEnabled(true);
interceptor.setLogCategory("showcase.unhandled");
interceptor.setLogLevel("fatal");
interceptor.intercept(invocation);
fail("Should not have reached this point.");
} catch (Exception e) {
@@ -164,9 +165,9 @@ public class ExceptionMappingInterceptorTest extends XWorkTestCase {
mockInvocation.matchAndReturn("getAction", action.proxy());
try {
interceptor.setLogEnabled(true);
interceptor.setLogCategory("showcase.unhandled");
interceptor.setLogLevel("error");
interceptor.setLogEnabled(true);
interceptor.setLogCategory("showcase.unhandled");
interceptor.setLogLevel("error");
interceptor.intercept(invocation);
fail("Should not have reached this point.");
} catch (Exception e) {
@@ -183,9 +184,9 @@ public class ExceptionMappingInterceptorTest extends XWorkTestCase {
mockInvocation.matchAndReturn("getAction", action.proxy());
try {
interceptor.setLogEnabled(true);
interceptor.setLogCategory("showcase.unhandled");
interceptor.setLogLevel("warn");
interceptor.setLogEnabled(true);
interceptor.setLogCategory("showcase.unhandled");
interceptor.setLogLevel("warn");
interceptor.intercept(invocation);
fail("Should not have reached this point.");
} catch (Exception e) {
@@ -202,9 +203,9 @@ public class ExceptionMappingInterceptorTest extends XWorkTestCase {
mockInvocation.matchAndReturn("getAction", action.proxy());
try {
interceptor.setLogEnabled(true);
interceptor.setLogCategory("showcase.unhandled");
interceptor.setLogLevel("info");
interceptor.setLogEnabled(true);
interceptor.setLogCategory("showcase.unhandled");
interceptor.setLogLevel("info");
interceptor.intercept(invocation);
fail("Should not have reached this point.");
} catch (Exception e) {
@@ -221,9 +222,9 @@ public class ExceptionMappingInterceptorTest extends XWorkTestCase {
mockInvocation.matchAndReturn("getAction", action.proxy());
try {
interceptor.setLogEnabled(true);
interceptor.setLogCategory("showcase.unhandled");
interceptor.setLogLevel("debug");
interceptor.setLogEnabled(true);
interceptor.setLogCategory("showcase.unhandled");
interceptor.setLogLevel("debug");
interceptor.intercept(invocation);
fail("Should not have reached this point.");
} catch (Exception e) {
@@ -240,9 +241,9 @@ public class ExceptionMappingInterceptorTest extends XWorkTestCase {
mockInvocation.matchAndReturn("getAction", action.proxy());
try {
interceptor.setLogEnabled(true);
interceptor.setLogCategory("showcase.unhandled");
interceptor.setLogLevel("trace");
interceptor.setLogEnabled(true);
interceptor.setLogCategory("showcase.unhandled");
interceptor.setLogLevel("trace");
interceptor.intercept(invocation);
fail("Should not have reached this point.");
} catch (Exception e) {
@@ -259,12 +260,12 @@ public class ExceptionMappingInterceptorTest extends XWorkTestCase {
mockInvocation.matchAndReturn("getAction", action.proxy());
try {
interceptor.setLogEnabled(true);
interceptor.setLogLevel("xxx");
interceptor.setLogEnabled(true);
interceptor.setLogLevel("xxx");
interceptor.intercept(invocation);
fail("Should not have reached this point.");
} catch (IllegalArgumentException e) {
// success
// success
}
}
@@ -296,6 +297,7 @@ public class ExceptionMappingInterceptorTest extends XWorkTestCase {
mockInvocation.expectAndReturn("getStack", stack);
mockInvocation.expectAndReturn("getInvocationContext", ActionContext.of().bind());
interceptor = new ExceptionMappingInterceptor();
interceptor.setThreadAllowlist(new ThreadAllowlist());
interceptor.init();
}
@@ -0,0 +1,569 @@
/*
* 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 org.apache.struts2.ActionContext;
import org.apache.struts2.StrutsJUnit4InternalTestCase;
import org.apache.struts2.TestAction;
import org.apache.struts2.dispatcher.DispatcherConstants;
import org.apache.struts2.dispatcher.HttpParameters;
import org.apache.struts2.dispatcher.RequestMap;
import org.apache.struts2.dispatcher.SessionMap;
import org.apache.struts2.mock.MockActionInvocation;
import org.apache.struts2.ognl.ThreadAllowlist;
import org.apache.struts2.util.ValueStack;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.assertj.core.util.Maps;
import org.junit.Before;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockHttpSession;
import java.util.HashMap;
import java.util.Map;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
public class DebuggingInterceptorTest extends StrutsJUnit4InternalTestCase {
private DebuggingInterceptor interceptor;
private MockActionInvocation invocation;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private ActionContext context;
private TestAction action;
@Test
public void noDevMode() throws Exception {
interceptor.intercept(invocation);
assertThat(invocation.getResultCode()).isEqualTo("mock");
assertThat(response.getContentAsString()).isEmpty();
}
@Test
public void debugXml() throws Exception {
interceptor.setDevMode("true");
context.withParameters(HttpParameters.create(Maps.newHashMap("debug", "xml")).build());
interceptor.intercept(invocation);
assertThat(response.getContentAsString()).isEqualToIgnoringWhitespace("""
<debug>
<parameters/>
<context/>
<request/>
<session/>
<valueStack>
<value>
<action>
<actionErrors/>
<actionMessages/>
<class>class org.apache.struts2.TestAction</class>
<fieldErrors/>
<locale>
<ISO3Country>USA</ISO3Country>
<ISO3Language>eng</ISO3Language>
<class>class java.util.Locale</class>
<country>US</country>
<displayCountry>United States</displayCountry>
<displayLanguage>English</displayLanguage>
<displayName>English (United States)</displayName>
<displayScript></displayScript>
<displayVariant></displayVariant>
<extensionKeys/>
<language>en</language>
<script></script>
<unicodeLocaleAttributes/>
<unicodeLocaleKeys/>
<variant></variant>
</locale>
<status>
<class>class org.apache.struts2.SomeEnum</class>
<declaringClass>class org.apache.struts2.SomeEnum</declaringClass>
<displayName>completed</displayName>
<name>COMPLETED</name>
</status>
<statusList>
<value>
<class>class org.apache.struts2.SomeEnum</class>
<declaringClass>class org.apache.struts2.SomeEnum</declaringClass>
<displayName>init</displayName>
<name>INIT</name>
</value>
<value>
<class>class org.apache.struts2.SomeEnum</class>
<declaringClass>class org.apache.struts2.SomeEnum</declaringClass>
<displayName>completed</displayName>
<name>COMPLETED</name>
</value>
</statusList>
<texts>
<baseBundleName>org.apache.struts2.TestAction</baseBundleName>
<class>class java.util.PropertyResourceBundle</class>
<keys>
<class>class sun.util.ResourceBundleEnumeration</class>
</keys>
<locale>
<ISO3Country></ISO3Country>
<ISO3Language></ISO3Language>
<class>class java.util.Locale</class>
<country></country>
<displayCountry></displayCountry>
<displayLanguage></displayLanguage>
<displayName></displayName>
<displayScript></displayScript>
<displayVariant></displayVariant>
<extensionKeys/>
<language></language>
<script></script>
<unicodeLocaleAttributes/>
<unicodeLocaleKeys/>
<variant></variant>
</locale>
</texts>
</action>
<org.apache.struts2.util.OgnlValueStack.MAP_IDENTIFIER_KEY></org.apache.struts2.util.OgnlValueStack.MAP_IDENTIFIER_KEY>
</value>
<value>
<class>class org.apache.struts2.text.DefaultTextProvider</class>
</value>
</valueStack>
</debug>
""");
}
@Test
public void debugXmlWithConsole() throws Exception {
interceptor.setDevMode("true");
context.withParameters(HttpParameters.create(Maps.newHashMap("debug", "console")).build());
interceptor.setEnableXmlWithConsole(true);
interceptor.intercept(invocation);
assertThat(response.getContentAsString()).isEqualToIgnoringWhitespace("""
<!DOCTYPE html>
<html>
<head>
<script>
var baseUrl = "/static";
window.open(baseUrl+"/webconsole.html", 'OGNL Console','width=500,height=450,status=no,toolbar=no,menubar=no');
</script>
</head>
<body>
<pre>
&amp;lt;debug&amp;gt;
&amp;lt;parameters/&amp;gt;
&amp;lt;context/&amp;gt;
&amp;lt;request/&amp;gt;
&amp;lt;session/&amp;gt;
&amp;lt;valueStack&amp;gt;
&amp;lt;value&amp;gt;
&amp;lt;action&amp;gt;
&amp;lt;actionErrors/&amp;gt;
&amp;lt;actionMessages/&amp;gt;
&amp;lt;class&amp;gt;class org.apache.struts2.TestAction&amp;lt;/class&amp;gt;
&amp;lt;fieldErrors/&amp;gt;
&amp;lt;locale&amp;gt;
&amp;lt;ISO3Country&amp;gt;USA&amp;lt;/ISO3Country&amp;gt;
&amp;lt;ISO3Language&amp;gt;eng&amp;lt;/ISO3Language&amp;gt;
&amp;lt;class&amp;gt;class java.util.Locale&amp;lt;/class&amp;gt;
&amp;lt;country&amp;gt;US&amp;lt;/country&amp;gt;
&amp;lt;displayCountry&amp;gt;United States&amp;lt;/displayCountry&amp;gt;
&amp;lt;displayLanguage&amp;gt;English&amp;lt;/displayLanguage&amp;gt;
&amp;lt;displayName&amp;gt;English (United States)&amp;lt;/displayName&amp;gt;
&amp;lt;displayScript&amp;gt;&amp;lt;/displayScript&amp;gt;
&amp;lt;displayVariant&amp;gt;&amp;lt;/displayVariant&amp;gt;
&amp;lt;extensionKeys/&amp;gt;
&amp;lt;language&amp;gt;en&amp;lt;/language&amp;gt;
&amp;lt;script&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;unicodeLocaleAttributes/&amp;gt;
&amp;lt;unicodeLocaleKeys/&amp;gt;
&amp;lt;variant&amp;gt;&amp;lt;/variant&amp;gt;
&amp;lt;/locale&amp;gt;
&amp;lt;status&amp;gt;
&amp;lt;class&amp;gt;class org.apache.struts2.SomeEnum&amp;lt;/class&amp;gt;
&amp;lt;declaringClass&amp;gt;class org.apache.struts2.SomeEnum&amp;lt;/declaringClass&amp;gt;
&amp;lt;displayName&amp;gt;completed&amp;lt;/displayName&amp;gt;
&amp;lt;name&amp;gt;COMPLETED&amp;lt;/name&amp;gt;
&amp;lt;/status&amp;gt;
&amp;lt;statusList&amp;gt;
&amp;lt;value&amp;gt;
&amp;lt;class&amp;gt;class org.apache.struts2.SomeEnum&amp;lt;/class&amp;gt;
&amp;lt;declaringClass&amp;gt;class org.apache.struts2.SomeEnum&amp;lt;/declaringClass&amp;gt;
&amp;lt;displayName&amp;gt;init&amp;lt;/displayName&amp;gt;
&amp;lt;name&amp;gt;INIT&amp;lt;/name&amp;gt;
&amp;lt;/value&amp;gt;
&amp;lt;value&amp;gt;
&amp;lt;class&amp;gt;class org.apache.struts2.SomeEnum&amp;lt;/class&amp;gt;
&amp;lt;declaringClass&amp;gt;class org.apache.struts2.SomeEnum&amp;lt;/declaringClass&amp;gt;
&amp;lt;displayName&amp;gt;completed&amp;lt;/displayName&amp;gt;
&amp;lt;name&amp;gt;COMPLETED&amp;lt;/name&amp;gt;
&amp;lt;/value&amp;gt;
&amp;lt;/statusList&amp;gt;
&amp;lt;texts&amp;gt;
&amp;lt;baseBundleName&amp;gt;org.apache.struts2.TestAction&amp;lt;/baseBundleName&amp;gt;
&amp;lt;class&amp;gt;class java.util.PropertyResourceBundle&amp;lt;/class&amp;gt;
&amp;lt;keys&amp;gt;
&amp;lt;class&amp;gt;class sun.util.ResourceBundleEnumeration&amp;lt;/class&amp;gt;
&amp;lt;/keys&amp;gt;
&amp;lt;locale&amp;gt;
&amp;lt;ISO3Country&amp;gt;&amp;lt;/ISO3Country&amp;gt;
&amp;lt;ISO3Language&amp;gt;&amp;lt;/ISO3Language&amp;gt;
&amp;lt;class&amp;gt;class java.util.Locale&amp;lt;/class&amp;gt;
&amp;lt;country&amp;gt;&amp;lt;/country&amp;gt;
&amp;lt;displayCountry&amp;gt;&amp;lt;/displayCountry&amp;gt;
&amp;lt;displayLanguage&amp;gt;&amp;lt;/displayLanguage&amp;gt;
&amp;lt;displayName&amp;gt;&amp;lt;/displayName&amp;gt;
&amp;lt;displayScript&amp;gt;&amp;lt;/displayScript&amp;gt;
&amp;lt;displayVariant&amp;gt;&amp;lt;/displayVariant&amp;gt;
&amp;lt;extensionKeys/&amp;gt;
&amp;lt;language&amp;gt;&amp;lt;/language&amp;gt;
&amp;lt;script&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;unicodeLocaleAttributes/&amp;gt;
&amp;lt;unicodeLocaleKeys/&amp;gt;
&amp;lt;variant&amp;gt;&amp;lt;/variant&amp;gt;
&amp;lt;/locale&amp;gt;
&amp;lt;/texts&amp;gt;
&amp;lt;/action&amp;gt;
&amp;lt;org.apache.struts2.util.OgnlValueStack.MAP_IDENTIFIER_KEY&amp;gt;&amp;lt;/org.apache.struts2.util.OgnlValueStack.MAP_IDENTIFIER_KEY&amp;gt;
&amp;lt;/value&amp;gt;
&amp;lt;value&amp;gt;
&amp;lt;class&amp;gt;class org.apache.struts2.text.DefaultTextProvider&amp;lt;/class&amp;gt;
&amp;lt;/value&amp;gt;
&amp;lt;/valueStack&amp;gt;
&amp;lt;/debug&amp;gt;
</pre>
</body>
</html>
""");
}
@Test
public void debugConsole() throws Exception {
interceptor.setDevMode("true");
context.withParameters(HttpParameters.create(Maps.newHashMap("debug", "console")).build());
interceptor.intercept(invocation);
assertThat(response.getContentAsString()).isEqualToIgnoringWhitespace("""
<!DOCTYPE html>
<html>
<head>
<script>
var baseUrl = "/static";
window.open(baseUrl+"/webconsole.html", 'OGNL Console','width=500,height=450,status=no,toolbar=no,menubar=no');
</script>
</head>
<body>
<pre>
</pre>
</body>
</html>
""");
}
@Test
public void debugCommand() throws Exception {
interceptor.setDevMode("true");
Map<String, Object> params = new HashMap<>() {{
put("debug", "command");
put("expression", "1+1");
}};
context.withParameters(HttpParameters.create(params).build());
interceptor.intercept(invocation);
assertThat(response.getContentAsString()).isEqualToIgnoringWhitespace("2");
}
@Test
public void debugBrowser() throws Exception {
interceptor.setDevMode("true");
context.withParameters(HttpParameters.create(Maps.newHashMap("debug", "browser")).build());
interceptor.intercept(invocation);
invocation.invoke();
assertThat(response.getContentAsString()).isEqualToIgnoringWhitespace("""
<!DOCTYPE html>
<html lang="en">
<style>
.debugTable {
border-style: solid;
border-width: 1px;
}
.debugTable td {
border-style: solid;
border-width: 1px;
}
.nameColumn {
background-color:#CCDDFF;
}
.valueColumn {
background-color: #CCFFCC;
}
.nullValue {
background-color: #FF0000;
}
.typeColumn {
background-color: white;
}
.emptyCollection {
background-color: #EEEEEE;
}
</style>
<script>
function expand(src, path) {
let baseUrl = location.href;
const i = baseUrl.indexOf('&object=');
baseUrl = (i > 0 ? baseUrl.substring(0, i) : baseUrl) + "&object=" + path;
if (baseUrl.indexOf("decorate") < 0) {
baseUrl += "&decorate=false";
}
const request = new XMLHttpRequest();
request.open('GET', baseUrl, true);
request.onreadystatechange = function() {
if (this.readyState === 4) {
if (this.status >= 200 && this.status < 400) {
const div = document.createElement('div');
console.log(this.responseText);
div.innerHTML = this.responseText;
src.parentNode.appendChild(div);
src.innerHTML = "Collapse";
const oldOnclick = src.onclick;
src.onclick = function() {
src.innerHTML = "Expand";
src.parentNode.removeChild(div);
src.onclick = oldOnclick;
};
}
}
};
request.send();
}
</script>
<body>
<table class="debugTable">
<tr>
<td class="nameColumn">container</td>
<td class="valueColumn">There is no read method for container</td>
<td class="typeColumn">java.lang.String</td>
</tr>
<tr>
<td class="nameColumn">foo</td>
<td class="nullValue">null</td>
<td class="nullValue">unknown</td>
</tr>
<tr>
<td class="nameColumn">intList</td>
<td class="nullValue">null</td>
<td class="nullValue">unknown</td>
</tr>
<tr>
<td class="nameColumn">locale</td>
<td class="valueColumn">
<a onclick="expand(this, 'action[&quot;locale&quot;]')" href="javascript://nop/">Expand</a>
</td>
<td class="typeColumn">java.util.Locale</td>
</tr>
<tr>
<td class="nameColumn">result</td>
<td class="nullValue">null</td>
<td class="nullValue">unknown</td>
</tr>
<tr>
<td class="nameColumn">collection2</td>
<td class="nullValue">null</td>
<td class="nullValue">unknown</td>
</tr>
<tr>
<td class="nameColumn">someBool</td>
<td class="nullValue">null</td>
<td class="nullValue">unknown</td>
</tr>
<tr>
<td class="nameColumn">array</td>
<td class="nullValue">null</td>
<td class="nullValue">unknown</td>
</tr>
<tr>
<td class="nameColumn">fooInt</td>
<td class="nullValue">null</td>
<td class="nullValue">unknown</td>
</tr>
<tr>
<td class="nameColumn">id</td>
<td class="nullValue">null</td>
<td class="nullValue">unknown</td>
</tr>
<tr>
<td class="nameColumn">map</td>
<td class="nullValue">null</td>
<td class="nullValue">unknown</td>
</tr>
<tr>
<td class="nameColumn">actionErrors</td>
<td class="emptyCollection">empty</td>
<td class="typeColumn">java.util.LinkedList</td>
</tr>
<tr>
<td class="nameColumn">objectArray</td>
<td class="nullValue">null</td>
<td class="nullValue">unknown</td>
</tr>
<tr>
<td class="nameColumn">fieldErrors</td>
<td class="emptyCollection">empty</td>
<td class="typeColumn">java.util.LinkedHashMap</td>
</tr>
<tr>
<td class="nameColumn">collection</td>
<td class="nullValue">null</td>
<td class="nullValue">unknown</td>
</tr>
<tr>
<td class="nameColumn">floatNumber</td>
<td class="nullValue">null</td>
<td class="nullValue">unknown</td>
</tr>
<tr>
<td class="nameColumn">list</td>
<td class="nullValue">null</td>
<td class="nullValue">unknown</td>
</tr>
<tr>
<td class="nameColumn">enumList</td>
<td class="nullValue">null</td>
<td class="nullValue">unknown</td>
</tr>
<tr>
<td class="nameColumn">actionMessages</td>
<td class="emptyCollection">empty</td>
<td class="typeColumn">java.util.LinkedList</td>
</tr>
<tr>
<td class="nameColumn">statusList</td>
<td class="valueColumn">
<a onclick="expand(this, 'action[&quot;statusList&quot;]')" href="javascript://nop/">Expand</a>
</td>
<td class="typeColumn">java.util.Arrays$ArrayList</td>
</tr>
<tr>
<td class="nameColumn">texts</td>
<td class="valueColumn">
<a onclick="expand(this, 'action[&quot;texts&quot;]')" href="javascript://nop/">Expand</a>
</td>
<td class="typeColumn">java.util.PropertyResourceBundle</td>
</tr>
<tr>
<td class="nameColumn">list3</td>
<td class="nullValue">null</td>
<td class="nullValue">unknown</td>
</tr>
<tr>
<td class="nameColumn">list2</td>
<td class="nullValue">null</td>
<td class="nullValue">unknown</td>
</tr>
<tr>
<td class="nameColumn">user</td>
<td class="nullValue">null</td>
<td class="nullValue">unknown</td>
</tr>
<tr>
<td class="nameColumn">status</td>
<td class="valueColumn">
<a onclick="expand(this, 'action[&quot;status&quot;]')" href="javascript://nop/">Expand</a>
</td>
<td class="typeColumn">org.apache.struts2.SomeEnum</td>
</tr>
</table>
</body>
</html>
""");
}
@Test
public void allowlist() throws Exception {
interceptor.setDevMode("true");
context.withParameters(HttpParameters.create(Maps.newHashMap("debug", "browser")).build());
assertThat(container.getInstance(ThreadAllowlist.class))
.extracting(ThreadAllowlist::getAllowlist).asInstanceOf(InstanceOfAssertFactories.SET)
.isEmpty();
interceptor.intercept(invocation);
invocation.invoke();
assertThat(container.getInstance(ThreadAllowlist.class))
.extracting(ThreadAllowlist::getAllowlist).asInstanceOf(InstanceOfAssertFactories.SET)
.contains(
org.apache.struts2.interceptor.ValidationAware.class,
org.apache.struts2.Validateable.class,
org.apache.struts2.action.Action.class,
org.apache.struts2.text.TextProvider.class,
org.apache.struts2.ActionSupport.class,
org.apache.struts2.locale.LocaleProvider.class,
org.apache.struts2.TestAction.class
);
}
@Before
public void before() {
request = new MockHttpServletRequest();
request.setSession(new MockHttpSession());
response = new MockHttpServletResponse();
ValueStack valueStack = dispatcher.getValueStackFactory().createValueStack();
context = valueStack.getActionContext()
.withServletContext(servletContext)
.withServletRequest(request)
.withServletResponse(response)
.withSession(new SessionMap(request))
.with(DispatcherConstants.REQUEST, new RequestMap(request));
interceptor = container.inject(DebuggingInterceptor.class);
interceptor.init();
invocation = new MockActionInvocation();
invocation.setResultCode("mock");
invocation.setInvocationContext(context);
action = new TestAction();
invocation.setAction(action);
invocation.setStack(valueStack);
valueStack.set("action", invocation.getAction());
context = context.withActionInvocation(invocation).bind();
}
}
@@ -18,13 +18,12 @@
*/
package org.apache.struts2.views.jsp.ui;
import org.apache.struts2.config.ConfigurationException;
import org.apache.commons.lang3.StringUtils;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.dispatcher.PrepareOperations;
import org.apache.struts2.views.jsp.AbstractUITagTest;
import java.util.HashMap;
import java.util.Collections;
import java.util.Map;
/**
@@ -59,7 +58,7 @@ public class DebugTagTest extends AbstractUITagTest {
freshTag.setPageContext(pageContext);
// DebugTag has no additional state, so it compares as equal with the default tag clear state as well.
assertTrue("Tag state after doEndTag() under default tag clear state is inequal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
@@ -82,7 +81,7 @@ public class DebugTagTest extends AbstractUITagTest {
freshTag.setPerformClearTagStateForTagPoolingServers(true);
freshTag.setPageContext(pageContext);
assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
@@ -98,7 +97,7 @@ public class DebugTagTest extends AbstractUITagTest {
freshTag.setPageContext(pageContext);
// DebugTag has no additional state, so it compares as equal with the default tag clear state as well.
assertTrue("Tag state after doEndTag() under default tag clear state is inequal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
@@ -116,7 +115,7 @@ public class DebugTagTest extends AbstractUITagTest {
freshTag.setPerformClearTagStateForTagPoolingServers(true);
freshTag.setPageContext(pageContext);
assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
@@ -135,7 +134,7 @@ public class DebugTagTest extends AbstractUITagTest {
freshTag.setPageContext(pageContext);
// DebugTag has no additional state, so it compares as equal with the default tag clear state as well.
assertTrue("Tag state after doEndTag() under default tag clear state is inequal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
PrepareOperations.clearDevModeOverride(); // Clear DevMode override. Avoid ThreadLocal side-effects if test thread re-used.
@@ -158,7 +157,7 @@ public class DebugTagTest extends AbstractUITagTest {
freshTag.setPerformClearTagStateForTagPoolingServers(true);
freshTag.setPageContext(pageContext);
assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
PrepareOperations.clearDevModeOverride(); // Clear DevMode override. Avoid ThreadLocal side-effects if test thread re-used.
@@ -177,7 +176,7 @@ public class DebugTagTest extends AbstractUITagTest {
freshTag.setPageContext(pageContext);
// DebugTag has no additional state, so it compares as equal with the default tag clear state as well.
assertTrue("Tag state after doEndTag() under default tag clear state is inequal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
PrepareOperations.clearDevModeOverride(); // Clear DevMode override. Avoid ThreadLocal side-effects if test thread re-used.
@@ -198,16 +197,14 @@ public class DebugTagTest extends AbstractUITagTest {
freshTag.setPerformClearTagStateForTagPoolingServers(true);
freshTag.setPageContext(pageContext);
assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
PrepareOperations.clearDevModeOverride(); // Clear DevMode override. Avoid ThreadLocal side-effects if test thread re-used.
}
private void setDevMode(final boolean devMode) {
setStrutsConstant(new HashMap<String, String>() {{
put(StrutsConstants.STRUTS_DEVMODE, Boolean.toString(devMode));
}});
setStrutsConstant(Collections.singletonMap(StrutsConstants.STRUTS_DEVMODE, Boolean.toString(devMode)));
}
/**