Files
struts/plugins/convention
Lukasz Lenart 2215b6873c WW-5537 Resolve classloader/memory leaks during Tomcat hot deployment (#1632)
* WW-5537 Add InternalDestroyable and ContextAwareDestroyable interfaces

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* WW-5537 ContainerHolder: ThreadLocal with AtomicLong generation counter

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* WW-5537 FinalizableReferenceQueue: volatile instance, join, classloader null

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* WW-5537 ScopeInterceptor.clearLocks: add synchronized block

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* WW-5537 CompoundRootAccessor, DefaultFileManager: implement InternalDestroyable

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* WW-5537 Add InternalDestroyable adapter classes for static cache cleanup

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* WW-5537 Register InternalDestroyable beans in struts-beans.xml

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* WW-5537 JSON plugin: add JSONCacheDestroyable for BeanInfo cache cleanup

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* WW-5537 Dispatcher.cleanup: refactor into focused methods with InternalDestroyable discovery

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* WW-5537 Rewrite DispatcherCleanupTest for InternalDestroyable discovery

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* WW-5537 Add log4j-web for proper Log4j2 lifecycle in Servlet container

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* WW-5537 Dispatcher.destroyObjectFactory: add early return on null, use pattern matching

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* WW-5537 Fix @since annotations: 7.1.0 -> 7.2.0

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* WW-5537 Add Container.destroy() to clear internal caches on undeploy

Container now exposes a destroy() method that clears factories, injectors,
constructors, and ThreadLocals. This releases Class<?> keys and JDK
DelegatingClassLoader instances that pin the webapp classloader.

DefaultConfiguration.destroy() calls container.destroy() and
reloadContainer() delegates to destroy() to avoid duplication.

Also fixes JSONCacheDestroyable referencing non-existent DefaultJSONWriter
(renamed to StrutsJSONWriter).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* WW-5537 Fix Container.destroy(): don't clear factories, don't call from reloadContainer

factories must remain intact because existing code holds direct
references to the Container after destroyConfiguration() and expects
it to still resolve dependencies (e.g. during configuration reload).

reloadContainer() reverted to clearing packageContexts/loadedFileNames
directly — calling destroy() there nulled the container reference and
cleared state needed during the bootstrap transition.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* WW-5537 Restore destroy() call in reloadContainer()

The test failures were caused by factories.clear() in
Container.destroy(), not by calling destroy() from reloadContainer().
Now that factories.clear() is removed, destroy() is safe to call
here — it clears packageContexts, loadedFileNames, and the container's
reflection caches in one place.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* WW-5537 Fix Sonar issues: thread-safe FinalizableReferenceQueue, empty method comments

- Replace volatile field with AtomicReference in FinalizableReferenceQueue
  for proper thread safety using getAndSet()
- Add comments to empty destroy() implementations in test mocks
- Replace deprecated new URL() with URI.toURL() in DispatcherCleanupTest
- Add comments to empty listener methods in DispatcherCleanupTest

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-29 07:17:57 +02:00
..

Struts 2 Convention plugin

This plugin allows to use convention over configuration approach instead of configuring everything using struts.xml. You will find more details in documentation.

Installation

Just drop this plugin JAR into WEB-INF/lib folder or add it as a Maven dependency.

Design

Action with no annotations (all the default handlings)

Action name is class name.

Action package is based on the either any Namespace annotations on the class or Java package the action class is defined in or the Java package name and the root Java package, which is determined based on the package locators or the action package list. In either case, a root package is identified and the namespace is the packages below the root up to and including the package that contains the action class.

Action method is execute in all cases.

Results are built first from the Result annotations at the class level. Next, they are built from the resources available in the web application and in the classpath based on the result location, namespace of the action and action name (not anything to do with the code element names). Results are found for common result codes including, success, error, input, failure, etc. If a specific result doesn't exist using the result code then just the action namespace and action name are used to locate results. If there are still no results found, than a redirect is added back to the index action for the namespace, if one exists.

Action with Action(s) annotations

If the annotation is located on any method other than execute, than the action configuration built from the annotation is in addition to the action configuration built using the default. Unless there is an annotation that contains no value, in which case, the method that annotation is defined for becomes the default action method. If the annotation is defined on the execute method, regardless of the value of the annotation, no default configuration is built for the class.

If multiple annotations have no value, an error should occur. If multiple annotations have the same value, an error should occur.

Action name is taken from the annotation value. If the value is empty, the default action name is used.

Action package is taken from the annotation value. If the annotation value doesn't start with a slash character, the default action namespace is used.

Action method is always the method that the annotation is defined on. Results are built first from the array of Results on the Action annotation. Next, they are built from the class level Result annotations. Lastly, all default results are created. All Results already created during this process should not be overridden later in the process.