-
Notifications
You must be signed in to change notification settings - Fork 811
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WW-5382 Fix stale injections in Dispatcher #826
Changes from all commits
32584c3
15acd72
2024d83
6f1e122
fa5b46c
ae71c46
946737c
3d25caa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -325,12 +325,8 @@ public Class<? extends Configuration> type() { | |
} | ||
|
||
protected ActionContext setContext(Container cont) { | ||
ActionContext context = ActionContext.getContext(); | ||
if (context == null) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This method is only called twice, and it is always null on the first call, and never null on the second call. I can't see why we wouldn't want to replace the bootstrap context once the final context has been constructed. Although it is also unclear if the ActionContext is even used after this point - i.e. for loading the PackageProviders. The ActionContext is cleared once the PackageProviders are loaded. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To be honest I would love to re-organise the whole proces. I assume the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah that would be ideal - if that were the case we'd be able to remove the need for the bootstrap context too it looks like |
||
ValueStack vs = cont.getInstance(ValueStackFactory.class).createValueStack(); | ||
context = ActionContext.of(vs.getContext()).bind(); | ||
} | ||
return context; | ||
ValueStack vs = cont.getInstance(ValueStackFactory.class).createValueStack(); | ||
return ActionContext.of(vs.getContext()).bind(); | ||
} | ||
|
||
protected Container createBootstrapContainer(List<ContainerProvider> providers) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -64,6 +64,7 @@ | |
import org.apache.struts2.config.StrutsJavaConfiguration; | ||
import org.apache.struts2.config.StrutsJavaConfigurationProvider; | ||
import org.apache.struts2.config.StrutsXmlConfigurationProvider; | ||
import org.apache.struts2.dispatcher.mapper.ActionMapper; | ||
import org.apache.struts2.dispatcher.mapper.ActionMapping; | ||
import org.apache.struts2.dispatcher.multipart.MultiPartRequest; | ||
import org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper; | ||
|
@@ -119,6 +120,12 @@ public class Dispatcher { | |
*/ | ||
private static final List<DispatcherListener> dispatcherListeners = new CopyOnWriteArrayList<>(); | ||
|
||
/** | ||
* This field exists so {@link #getContainer()} can determine whether to (re-)inject this instance in the case of | ||
* a {@link ConfigurationManager} reload. | ||
*/ | ||
private Container injectedContainer; | ||
|
||
/** | ||
* Store state of StrutsConstants.STRUTS_DEVMODE setting. | ||
*/ | ||
|
@@ -144,11 +151,6 @@ public class Dispatcher { | |
*/ | ||
private String multipartSaveDir; | ||
|
||
/** | ||
* Stores the value of {@link StrutsConstants#STRUTS_MULTIPART_PARSER} setting | ||
*/ | ||
private String multipartHandlerName; | ||
|
||
/** | ||
* Stores the value of {@link StrutsConstants#STRUTS_MULTIPART_ENABLED} | ||
*/ | ||
|
@@ -192,6 +194,11 @@ public class Dispatcher { | |
* Store ConfigurationManager instance, set on init. | ||
*/ | ||
protected ConfigurationManager configurationManager; | ||
private ObjectFactory objectFactory; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should save some computation by injecting these beans once when the Dispatcher is initialised instead of constantly retrieving them from the container There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe they should be moved into constructor? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since the Dispatcher itself is not container-managed, we can't use constructor injection for it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It doesn't have to be, just using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep would require a major refactor, I've got some ideas but not a high priority right now, as long as everything works |
||
private ActionProxyFactory actionProxyFactory; | ||
private LocaleProviderFactory localeProviderFactory; | ||
private StaticContentLoader staticContentLoader; | ||
private ActionMapper actionMapper; | ||
|
||
/** | ||
* Provide the dispatcher instance for the current thread. | ||
|
@@ -211,6 +218,13 @@ public static void setInstance(Dispatcher instance) { | |
Dispatcher.instance.set(instance); | ||
} | ||
|
||
/** | ||
* Removes the dispatcher instance for this thread. | ||
*/ | ||
public static void clearInstance() { | ||
Dispatcher.instance.remove(); | ||
} | ||
|
||
/** | ||
* Add a dispatcher lifecycle listener. | ||
* | ||
|
@@ -306,9 +320,12 @@ public void setMultipartSaveDir(String val) { | |
multipartSaveDir = val; | ||
} | ||
|
||
@Inject(StrutsConstants.STRUTS_MULTIPART_PARSER) | ||
/** | ||
* @deprecated since 6.4.0, no replacement. | ||
*/ | ||
@Deprecated | ||
public void setMultipartHandler(String val) { | ||
multipartHandlerName = val; | ||
// no-op | ||
} | ||
|
||
@Inject(value = StrutsConstants.STRUTS_MULTIPART_ENABLED, required = false) | ||
|
@@ -326,11 +343,21 @@ public void setValueStackFactory(ValueStackFactory valueStackFactory) { | |
this.valueStackFactory = valueStackFactory; | ||
} | ||
|
||
public ValueStackFactory getValueStackFactory() { | ||
return valueStackFactory; | ||
} | ||
|
||
@Inject(StrutsConstants.STRUTS_HANDLE_EXCEPTION) | ||
public void setHandleException(String handleException) { | ||
this.handleException = Boolean.parseBoolean(handleException); | ||
} | ||
|
||
@Inject(StrutsConstants.STRUTS_DISPATCHER_PARAMETERSWORKAROUND) | ||
public void setDispatchersParametersWorkaround(String dispatchersParametersWorkaround) { | ||
this.paramsWorkaroundEnabled = Boolean.parseBoolean(dispatchersParametersWorkaround) | ||
|| (servletContext != null && StringUtils.contains(servletContext.getServerInfo(), "WebLogic")); | ||
} | ||
|
||
public boolean isHandleException() { | ||
return handleException; | ||
} | ||
|
@@ -340,12 +367,48 @@ public void setDispatcherErrorHandler(DispatcherErrorHandler errorHandler) { | |
this.errorHandler = errorHandler; | ||
} | ||
|
||
@Inject | ||
public void setObjectFactory(ObjectFactory objectFactory) { | ||
this.objectFactory = objectFactory; | ||
} | ||
|
||
@Inject | ||
public void setActionProxyFactory(ActionProxyFactory actionProxyFactory) { | ||
this.actionProxyFactory = actionProxyFactory; | ||
} | ||
|
||
public ActionProxyFactory getActionProxyFactory() { | ||
return actionProxyFactory; | ||
} | ||
|
||
@Inject | ||
public void setLocaleProviderFactory(LocaleProviderFactory localeProviderFactory) { | ||
this.localeProviderFactory = localeProviderFactory; | ||
} | ||
|
||
@Inject | ||
public void setStaticContentLoader(StaticContentLoader staticContentLoader) { | ||
this.staticContentLoader = staticContentLoader; | ||
} | ||
|
||
public StaticContentLoader getStaticContentLoader() { | ||
return staticContentLoader; | ||
} | ||
|
||
@Inject | ||
public void setActionMapper(ActionMapper actionMapper) { | ||
this.actionMapper = actionMapper; | ||
} | ||
|
||
public ActionMapper getActionMapper() { | ||
return actionMapper; | ||
} | ||
|
||
/** | ||
* Releases all instances bound to this dispatcher instance. | ||
*/ | ||
public void cleanup() { | ||
// clean up ObjectFactory | ||
ObjectFactory objectFactory = getContainer().getInstance(ObjectFactory.class); | ||
if (objectFactory == null) { | ||
LOG.warn("Object Factory is null, something is seriously wrong, no clean up will be performed"); | ||
} | ||
|
@@ -359,7 +422,7 @@ public void cleanup() { | |
} | ||
|
||
// clean up Dispatcher itself for this thread | ||
instance.set(null); | ||
instance.remove(); | ||
servletContext.setAttribute(StrutsStatics.SERVLET_DISPATCHER, null); | ||
|
||
// clean up DispatcherListeners | ||
|
@@ -532,21 +595,6 @@ private void init_DeferredXmlConfigurations() { | |
loadConfigPaths("struts-deferred.xml"); | ||
} | ||
|
||
private Container init_PreloadConfiguration() { | ||
return getContainer(); | ||
} | ||
|
||
private void init_CheckWebLogicWorkaround(Container container) { | ||
// test whether param-access workaround needs to be enabled | ||
if (servletContext != null && StringUtils.contains(servletContext.getServerInfo(), "WebLogic")) { | ||
LOG.info("WebLogic server detected. Enabling Struts parameter access work-around."); | ||
paramsWorkaroundEnabled = true; | ||
} else { | ||
paramsWorkaroundEnabled = "true".equals(container.getInstance(String.class, | ||
StrutsConstants.STRUTS_DISPATCHER_PARAMETERSWORKAROUND)); | ||
} | ||
} | ||
|
||
/** | ||
* Load configurations, including both XML and zero-configuration strategies, | ||
* and update optional settings, including whether to reload configurations and resource files. | ||
|
@@ -567,9 +615,7 @@ public void init() { | |
init_AliasStandardObjects(); // [7] | ||
init_DeferredXmlConfigurations(); | ||
|
||
Container container = init_PreloadConfiguration(); | ||
container.inject(this); | ||
init_CheckWebLogicWorkaround(container); | ||
getContainer(); // Inject this instance | ||
|
||
if (!dispatcherListeners.isEmpty()) { | ||
for (DispatcherListener l : dispatcherListeners) { | ||
|
@@ -689,7 +735,6 @@ protected ActionProxy prepareActionProxy(Map<String, Object> extraContext, Strin | |
} | ||
|
||
protected ActionProxy createActionProxy(String namespace, String name, String method, Map<String, Object> extraContext) { | ||
ActionProxyFactory actionProxyFactory = getContainer().getInstance(ActionProxyFactory.class); | ||
return actionProxyFactory.createActionProxy(namespace, name, method, extraContext, true, false); | ||
} | ||
|
||
|
@@ -865,6 +910,7 @@ protected String getSaveDir() { | |
* @param response The response | ||
*/ | ||
public void prepare(HttpServletRequest request, HttpServletResponse response) { | ||
getContainer(); // Init ContainerHolder and reinject this instance IF ConfigurationManager was reloaded | ||
String encoding = null; | ||
if (defaultEncoding != null) { | ||
encoding = defaultEncoding; | ||
|
@@ -937,15 +983,12 @@ public HttpServletRequest wrapRequest(HttpServletRequest request) throws IOExcep | |
} | ||
|
||
if (isMultipartSupportEnabled(request) && isMultipartRequest(request)) { | ||
MultiPartRequest multiPartRequest = getMultiPartRequest(); | ||
LocaleProviderFactory localeProviderFactory = getContainer().getInstance(LocaleProviderFactory.class); | ||
|
||
request = new MultiPartRequestWrapper( | ||
multiPartRequest, | ||
request, | ||
getSaveDir(), | ||
localeProviderFactory.createLocaleProvider(), | ||
disableRequestAttributeValueStackLookup | ||
getMultiPartRequest(), | ||
request, | ||
getSaveDir(), | ||
localeProviderFactory.createLocaleProvider(), | ||
disableRequestAttributeValueStackLookup | ||
); | ||
} else { | ||
request = new StrutsRequestWrapper(request, disableRequestAttributeValueStackLookup); | ||
|
@@ -988,18 +1031,7 @@ protected boolean isMultipartRequest(HttpServletRequest request) { | |
* @return a multi part request object | ||
*/ | ||
protected MultiPartRequest getMultiPartRequest() { | ||
MultiPartRequest mpr = null; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remnants from before the bean aliasing functionality existed I presume? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm... good question, no idea :) |
||
//check for alternate implementations of MultiPartRequest | ||
Set<String> multiNames = getContainer().getInstanceNames(MultiPartRequest.class); | ||
for (String multiName : multiNames) { | ||
if (multiName.equals(multipartHandlerName)) { | ||
mpr = getContainer().getInstance(MultiPartRequest.class, multiName); | ||
} | ||
} | ||
if (mpr == null) { | ||
mpr = getContainer().getInstance(MultiPartRequest.class); | ||
} | ||
return mpr; | ||
return getContainer().getInstance(MultiPartRequest.class); | ||
lukaszlenart marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
/** | ||
|
@@ -1063,27 +1095,26 @@ public ConfigurationManager getConfigurationManager() { | |
} | ||
|
||
/** | ||
* Expose the dependency injection container. | ||
* Exposes a thread-cached reference of the dependency injection container. If the container is found to have | ||
* changed since the last time it was cached, this Dispatcher instance is re-injected to ensure no stale | ||
* configuration/dependencies persist. | ||
* <p> | ||
* A non-cached reference can be obtained by calling {@link #getConfigurationManager()}. | ||
* | ||
* @return Our dependency injection container | ||
*/ | ||
public Container getContainer() { | ||
if (ContainerHolder.get() != null) { | ||
return ContainerHolder.get(); | ||
} | ||
ConfigurationManager mgr = getConfigurationManager(); | ||
if (mgr == null) { | ||
throw new IllegalStateException("The configuration manager shouldn't be null"); | ||
} else { | ||
Configuration config = mgr.getConfiguration(); | ||
if (config == null) { | ||
throw new IllegalStateException("Unable to load configuration"); | ||
} else { | ||
Container container = config.getContainer(); | ||
ContainerHolder.store(container); | ||
return container; | ||
if (ContainerHolder.get() == null) { | ||
try { | ||
ContainerHolder.store(getConfigurationManager().getConfiguration().getContainer()); | ||
} catch (NullPointerException e) { | ||
throw new IllegalStateException("ConfigurationManager and/or Configuration should not be null", e); | ||
} | ||
} | ||
if (injectedContainer != ContainerHolder.get()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the core change of this PR - we compare the retrieved container instance against the one we last injected this with, and if it has changed, reinject |
||
injectedContainer = ContainerHolder.get(); | ||
injectedContainer.inject(this); | ||
} | ||
return ContainerHolder.get(); | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not necessary