Updated commit based on feedback, plus a few minor changes:
- Changed formatting as per review.
- Added null guard for unlikely dereference in clearTomcatCache() logging
statement.
- Added a unit test for basic correctness check and better code coverage.
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/AbstractLocalizedTextProvider.java b/core/src/main/java/com/opensymphony/xwork2/util/AbstractLocalizedTextProvider.java
index f214a92..6ed6202 100644
--- a/core/src/main/java/com/opensymphony/xwork2/util/AbstractLocalizedTextProvider.java
+++ b/core/src/main/java/com/opensymphony/xwork2/util/AbstractLocalizedTextProvider.java
@@ -336,7 +336,6 @@
*
* Note: With Java 9+, calling this method may result in "Illegal reflective access" warnings. Be aware
* its logic may fail in a future version of Java that blocks the reflection calls needed for this method.
- * {<code></code>
*/
private void clearTomcatCache() {
ClassLoader loader = getCurrentThreadContextClassLoader();
@@ -345,8 +344,8 @@
Class superCl = cl.getSuperclass();
try {
- if ( (TOMCAT_WEBAPP_CLASSLOADER.equals(cl.getName()) || TOMCAT_PARALLEL_WEBAPP_CLASSLOADER.equals(cl.getName())) &&
- (superCl != null && TOMCAT_WEBAPP_CLASSLOADER_BASE.equals(superCl.getName())) ) {
+ if ((TOMCAT_WEBAPP_CLASSLOADER.equals(cl.getName()) || TOMCAT_PARALLEL_WEBAPP_CLASSLOADER.equals(cl.getName())) &&
+ (superCl != null && TOMCAT_WEBAPP_CLASSLOADER_BASE.equals(superCl.getName()))) {
// The classloader name and superclass name match the expecations for a Tomcat classloader.
// Expect the classloader superclass to have the field, otherwise fallback to the classloader class if the field is not found.
clearMap(superCl, loader, TOMCAT_RESOURCE_ENTRIES_FIELD);
@@ -363,7 +362,7 @@
LOG.warn("Couldn't clear tomcat cache using {}", cl.getName(), e);
}
} catch (Exception e) {
- LOG.warn("Couldn't clear tomcat cache using {}", superCl.getName(), e);
+ LOG.warn("Couldn't clear tomcat cache using {}", (superCl != null ? superCl.getName() : null), e);
}
}
diff --git a/core/src/test/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProviderTest.java b/core/src/test/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProviderTest.java
index aef469d..2a3038f 100644
--- a/core/src/test/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProviderTest.java
+++ b/core/src/test/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProviderTest.java
@@ -327,6 +327,50 @@
testStrutsLocalizedTextProvider.callClearMissingBundlesCache();
}
+ /**
+ * Unit test to confirm the basic behaviour of bundle reload methods provided to
+ * StrutsLocalizedTextProvider (from AbstractLocalizedTextProvider).
+ *
+ * @since 2.6
+ */
+ public void testLocalizedTextProviderReloadMethods() {
+ TestStrutsLocalizedTextProvider testStrutsLocalizedTextProvider = new TestStrutsLocalizedTextProvider();
+ assertTrue("testStrutsLocalizedTextProvider not instance of AbstractLocalizedTextProvider ?",
+ testStrutsLocalizedTextProvider instanceof AbstractLocalizedTextProvider);
+ assertEquals("testStrutsLocalizedTextProvider starting default bundle map size not 0 before any retrievals ?",
+ 0, testStrutsLocalizedTextProvider.currentBundlesMapSize());
+
+ // Access the two default bundles to populate their cache entries and test bundle map size.
+ ResourceBundle tempBundle = testStrutsLocalizedTextProvider.findResourceBundle(
+ TestStrutsLocalizedTextProvider.XWORK_MESSAGES_BUNDLE, Locale.ENGLISH);
+ assertNotNull("XWORK_MESSAGES_BUNDLE retrieval null ?", tempBundle);
+ tempBundle = testStrutsLocalizedTextProvider.findResourceBundle(
+ TestStrutsLocalizedTextProvider.STRUTS_MESSAGES_BUNDLE, Locale.ENGLISH);
+ assertNotNull("STRUTS_MESSAGES_BUNDLE retrieval null ?", tempBundle);
+ assertEquals("testStrutsLocalizedTextProvider bundle map size not 2 after retrievals ?",
+ 2, testStrutsLocalizedTextProvider.currentBundlesMapSize());
+
+ // Force a bundle reload call for code coverage and to confirm it causes the bundle map to be emptied.
+ assertNotNull("ActionContext is somehow null ?", ActionContext.getContext());
+ boolean bundlesReloadedBeforeCall = testStrutsLocalizedTextProvider.getBundlesReloadedIndicatorValue();
+ assertFalse("Bundles reload value true before forced reload ?", bundlesReloadedBeforeCall);
+ testStrutsLocalizedTextProvider.callReloadBundlesForceReload();
+ boolean bundlesReloadedAfterCall = testStrutsLocalizedTextProvider.getBundlesReloadedIndicatorValue();
+ assertTrue("Bundles reload value false after forced reload ?", bundlesReloadedAfterCall);
+ assertEquals("testStrutsLocalizedTextProvider bundle map size not 0 after reload (which should clear it) ?",
+ 0, testStrutsLocalizedTextProvider.currentBundlesMapSize());
+
+ // Access the two default bundles again (after reload) to populate their cache entries and test bundle map size.
+ tempBundle = testStrutsLocalizedTextProvider.findResourceBundle(
+ TestStrutsLocalizedTextProvider.XWORK_MESSAGES_BUNDLE, Locale.ENGLISH);
+ assertNotNull("XWORK_MESSAGES_BUNDLE retrieval null ?", tempBundle);
+ tempBundle = testStrutsLocalizedTextProvider.findResourceBundle(
+ TestStrutsLocalizedTextProvider.STRUTS_MESSAGES_BUNDLE, Locale.ENGLISH);
+ assertNotNull("STRUTS_MESSAGES_BUNDLE retrieval null ?", tempBundle);
+ assertEquals("testStrutsLocalizedTextProvider bundle map size not 2 after retrievals ?",
+ 2, testStrutsLocalizedTextProvider.currentBundlesMapSize());
+ }
+
@Override
protected void setUp() throws Exception {
super.setUp();
@@ -352,6 +396,13 @@
*/
class TestStrutsLocalizedTextProvider extends StrutsLocalizedTextProvider {
+ /**
+ * Some test correctness depends on this {@link #RELOADED} value matching that of the private ancestor
+ * field {@link AbstractLocalizedTextProvider#RELOADED}. If the ancestor field value changes, ensure this
+ * field's value is updated to match it exactly.
+ */
+ private static final String RELOADED = "com.opensymphony.xwork2.util.LocalizedTextProvider.reloaded";
+
public void callClearBundleNoLocale(String bundleName) {
super.clearBundle(bundleName);
}
@@ -367,5 +418,32 @@
public int currentBundlesMapSize() {
return super.bundlesMap.size();
}
+
+ /**
+ * Attempt to force the resource bundles to be reloaded, even if configuration would otherwise prevent it.
+ * It will preserve the current reloadBundles state, attempt to force a reload and then restore the
+ * original reloadBundles value.
+ */
+ public void callReloadBundlesForceReload() {
+ final boolean originalReloadState = super.reloadBundles;
+ try {
+ super.setReloadBundles(Boolean.TRUE.toString());
+ super.reloadBundles();
+ } finally {
+ super.setReloadBundles(Boolean.toString(originalReloadState));
+ }
+ }
+
+ /**
+ * Returns the value of the resource bundles reloaded state from the context, provided that one was
+ * previously set. If no value is found, the result will be false (same as if bundles had not been reloaded).
+ *
+ * @return true if resource bundles reloaded indicator is true, false otherwise (including if value was never set).
+ */
+ public boolean getBundlesReloadedIndicatorValue() {
+ final ActionContext actionContext = ActionContext.getContext();
+ final Object reloadedObject = actionContext.get(RELOADED);
+ return ((reloadedObject instanceof Boolean) ? ((Boolean) reloadedObject).booleanValue() : false);
+ }
}
}