Index: stripes/src/net/sourceforge/stripes/tag/DefaultTagErrorRendererFactory.java =================================================================== --- stripes/src/net/sourceforge/stripes/tag/DefaultTagErrorRendererFactory.java (revision 1000) +++ stripes/src/net/sourceforge/stripes/tag/DefaultTagErrorRendererFactory.java (revision ) @@ -53,7 +53,7 @@ this.rendererClass = configuration.getBootstrapPropertyResolver(). getClassProperty(RENDERER_CLASS_KEY, TagErrorRenderer.class); - + if (this.rendererClass == null) this.rendererClass = DefaultTagErrorRenderer.class; } @@ -86,4 +86,8 @@ { this.configuration = configuration; } + + public void terminate(Configuration configuration) { + //Nothing to clean up here -} + } +} Index: stripes/src/net/sourceforge/stripes/controller/AnnotatedClassActionResolver.java =================================================================== --- stripes/src/net/sourceforge/stripes/controller/AnnotatedClassActionResolver.java (revision 1412) +++ stripes/src/net/sourceforge/stripes/controller/AnnotatedClassActionResolver.java (revision ) @@ -59,7 +59,7 @@ * Configuration key used to lookup a comma-separated list of package names. The * packages (and their sub-packages) will be scanned for implementations of * ActionBean. - * @since Stripes 1.5 + * @since Stripes 1.5 */ public static final String PACKAGES = "ActionResolver.Packages"; @@ -348,7 +348,7 @@ * Calls {@link ActionBean#setContext(ActionBeanContext)} with the given {@code context} only if * necessary. Subclasses should use this method instead of setting the context directly because * it can be somewhat tricky to determine when it needs to be done. - * + * * @param bean The bean whose context may need to be set. * @param context The context to pass to the bean if necessary. */ @@ -408,12 +408,12 @@ * the request, then return its value. This attribute is used to handle internal forwards, when * request parameters are merged and cannot reliably determine the desired event name. *

- * + * *

* If that doesn't work, the value of a 'special' request parameter ({@link StripesConstants#URL_KEY_EVENT_NAME}) * is checked to see if contains a single value matching an event name. *

- * + * *

* Failing that, search for a parameter in the request whose name matches one of the named * events handled by the ActionBean. For example, if the ActionBean can handle events foo and @@ -422,13 +422,13 @@ * matching names, the result of this method cannot be guaranteed and a * {@link StripesRuntimeException} will be thrown. *

- * + * *

* Finally, if the event name cannot be determined through the parameter names and there is * extra path information beyond the URL binding of the ActionBean, it is checked to see if it * matches an event name. *

- * + * * @param bean the ActionBean type bound to the request * @param context the ActionBeanContect for the current request * @return String the name of the event submitted, or null if none can be found @@ -445,7 +445,7 @@ * Checks a special request attribute to get the event name. This attribute * may be set when the presence of the original request parameters on a * forwarded request makes it difficult to determine which event to fire. - * + * * @param bean the ActionBean type bound to the request * @param context the ActionBeanContect for the current request * @return the name of the event submitted, or null if none can be found @@ -641,4 +641,8 @@ public Collection> getActionBeanClasses() { return getUrlBindingFactory().getActionBeanClasses(); } + + public void terminate(Configuration configuration) { + //Nothing to clean up here -} + } +} Index: stripes/src/net/sourceforge/stripes/validation/DefaultValidationMetadataProvider.java =================================================================== --- stripes/src/net/sourceforge/stripes/validation/DefaultValidationMetadataProvider.java (revision 1187) +++ stripes/src/net/sourceforge/stripes/validation/DefaultValidationMetadataProvider.java (revision ) @@ -14,6 +14,12 @@ */ package net.sourceforge.stripes.validation; +import net.sourceforge.stripes.config.Configuration; +import net.sourceforge.stripes.controller.ParameterName; +import net.sourceforge.stripes.exception.StripesRuntimeException; +import net.sourceforge.stripes.util.Log; +import net.sourceforge.stripes.util.ReflectUtil; + import java.beans.PropertyDescriptor; import java.lang.annotation.Annotation; import java.lang.reflect.AccessibleObject; @@ -30,19 +36,13 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import net.sourceforge.stripes.config.Configuration; -import net.sourceforge.stripes.controller.ParameterName; -import net.sourceforge.stripes.exception.StripesRuntimeException; -import net.sourceforge.stripes.util.Log; -import net.sourceforge.stripes.util.ReflectUtil; - /** * An implementation of {@link ValidationMetadataProvider} that scans classes and their superclasses * for properties annotated with {@link Validate} and/or {@link ValidateNestedProperties} and * exposes the validation metadata specified by those annotations. When searching for annotations, * this implementation looks first at the property's read method (getter), then its write method * (setter), and finally at the field itself. - * + * * @author Ben Gunter, Freddy Daoud * @since Stripes 1.5 */ @@ -84,7 +84,7 @@ * the property's read method, write method, or field declaration. If a property has a * {@link ValidateNestedProperties} annotation, then the nested properties named in its * {@link Validate} annotations will be included as well. - * + * * @param beanType a class * @return A map of (possibly nested) property names to {@link ValidationMetadata} for the * property. @@ -291,7 +291,7 @@ protected Annotation findAnnotation(Class clazz, PropertyWrapper property, Class annotationClass) { - AccessibleObject accessible = property.getAccessibleObject(); + AccessibleObject accessible = property.getAccessibleObject(); if (accessible != null && property.getDeclaringClass().equals(clazz) && ( (accessible.getClass().equals(Method.class) && Modifier.isPublic(property.getModifiers())) @@ -389,4 +389,8 @@ return type; } } + + public void terminate(Configuration configuration) { + //Nothing to clean up here -} + } +} Index: stripes/src/net/sourceforge/stripes/validation/DefaultTypeConverterFactory.java =================================================================== --- stripes/src/net/sourceforge/stripes/validation/DefaultTypeConverterFactory.java (revision 1054) +++ stripes/src/net/sourceforge/stripes/validation/DefaultTypeConverterFactory.java (revision ) @@ -14,16 +14,16 @@ */ package net.sourceforge.stripes.validation; +import net.sourceforge.stripes.config.Configuration; +import net.sourceforge.stripes.util.Log; +import net.sourceforge.stripes.util.TypeHandlerCache; + import java.math.BigDecimal; import java.math.BigInteger; import java.util.Date; import java.util.Locale; import java.util.Map; -import net.sourceforge.stripes.config.Configuration; -import net.sourceforge.stripes.util.Log; -import net.sourceforge.stripes.util.TypeHandlerCache; - /** * Default TypeConverterFactory implementation that simply creates an instance level map of all the * TypeConverters included in the Stripes distribution, and their applicable classes. Can handle @@ -99,7 +99,7 @@ public void add(Class targetType, Class> converterClass) { cache.add(targetType, converterClass); } - + /** * Gets the applicable type converter for the class passed in. This is based on the default * set of type converters which are stored in a Map on this class. Enums are a special case, @@ -142,4 +142,8 @@ converter.setLocale(locale); return converter; } + + public void terminate(Configuration configuration) { + //Nothing to clean up here -} + } +} Index: tests/src/net/sourceforge/stripes/localization/MockLocalePicker.java =================================================================== --- tests/src/net/sourceforge/stripes/localization/MockLocalePicker.java (revision 1316) +++ tests/src/net/sourceforge/stripes/localization/MockLocalePicker.java (revision ) @@ -14,16 +14,15 @@ */ package net.sourceforge.stripes.localization; -import java.util.Locale; +import net.sourceforge.stripes.config.Configuration; import javax.servlet.http.HttpServletRequest; +import java.util.Locale; -import net.sourceforge.stripes.config.Configuration; - /** * Simple locale picker that just uses the locale of the passed HttpServletRequest. This should be * used for locale dependent test cases. - * + * * @author Marcus Krassmann */ public class MockLocalePicker implements LocalePicker { @@ -37,4 +36,8 @@ public void init(Configuration configuration) throws Exception { } + + public void terminate(Configuration configuration) { + //Nothing to clean up here -} \ No newline at end of file + } +} Index: stripes/src/net/sourceforge/stripes/controller/multipart/DefaultMultipartWrapperFactory.java =================================================================== --- stripes/src/net/sourceforge/stripes/controller/multipart/DefaultMultipartWrapperFactory.java (revision 1364) +++ stripes/src/net/sourceforge/stripes/controller/multipart/DefaultMultipartWrapperFactory.java (revision ) @@ -30,7 +30,7 @@ * Configuration under the key specified by {@link #WRAPPER_CLASS_NAME}. If no class * name is configured, defaults to the {@link CosMultipartWrapper}. An additional configuration * parameter is supported to specify the maximum post size allowable.

- * + * * @author Tim Fennell * @since Stripes 1.4 */ @@ -73,7 +73,7 @@ // Determine which class we're using this.multipartClass = config.getBootstrapPropertyResolver().getClassProperty(WRAPPER_CLASS_NAME, MultipartWrapper.class); - + if (this.multipartClass == null) { // It wasn't defined in web.xml so we'll try the bundled MultipartWrappers for (String className : BUNDLED_IMPLEMENTATIONS) { @@ -107,7 +107,7 @@ } else { String tmpDir = System.getProperty("java.io.tmpdir"); - + if (tmpDir != null) { this.temporaryDirectory = new File(tmpDir).getAbsoluteFile(); } @@ -168,4 +168,8 @@ ("Could not construct a MultipartWrapper for the current request.", e); } } + + public void terminate(Configuration configuration) { + //Nothing to clean up here -} + } +} Index: stripes/src/net/sourceforge/stripes/controller/DefaultActionBeanPropertyBinder.java =================================================================== --- stripes/src/net/sourceforge/stripes/controller/DefaultActionBeanPropertyBinder.java (revision 1413) +++ stripes/src/net/sourceforge/stripes/controller/DefaultActionBeanPropertyBinder.java (revision ) @@ -58,13 +58,13 @@ * expression support to perform JavaBean property binding. Several additions/enhancements are * available above and beyond the standard JavaBean syntax. These include: *

- * + * * - * + * * @author Tim Fennell * @since Stripes 1.4 */ @@ -92,12 +92,12 @@ * is attempted. Only fields which do not produce validation errors will be bound to the * ActionBean. *

- * + * *

* Individual property binding is delegated to the other interface method, bind(ActionBean, * String, Object), in order to allow for easy extension of this class. *

- * + * * @param bean the ActionBean whose properties are to be validated and bound * @param context the ActionBeanContext of the current request * @param validate true indicates that validation should be run, false indicates that only type @@ -241,12 +241,12 @@ * expression is available through the {@code getExpression()} and the ActionBean is available * through the {@code getBean()} method on the evaluation. *

- * + * *

* By default checks to ensure that the expression is not attempting to bind into the * ActionBeanContext for security reasons. *

- * + * * @param eval the expression evaluation to check for binding permission * @return true if binding can/should proceed, false to veto binding */ @@ -255,7 +255,7 @@ .isBindingAllowed(eval); if (!allowed) { String param = eval.getExpression().getSource(); - log.warn("Binding denied for parameter [", param, "]"); + log.warn("Binding denied for parameter [", param, "]. Use @Validate to allow binding in conjunction with @StrictBinding."); } return allowed; } @@ -264,7 +264,7 @@ * Invoked whenever an exception is thrown when attempting to bind a property to an ActionBean. * By default logs some information about the occurrence, but could be overridden to do more * intelligent things based on the application. - * + * * @param bean the ActionBean that was the subject of binding * @param name the ParameterName object for the parameter being bound * @param values the list of values being bound, potentially null if the error occurred when @@ -290,7 +290,7 @@ * Uses a hidden field to determine what (if any) fields were present in the form but did not get * submitted to the server. For each such field the value is "softly" set to null on the * ActionBean. This is not uncommon for checkboxes, and also for multi-selects. - * + * * @param bean the ActionBean being bound to * @param context the current ActionBeanContext */ @@ -316,7 +316,7 @@ * hidden field containing a set of field names. This is encrypted to stop the user from * monkeying with it. This method retrieves the list of field names, decrypts it and splits it * out into a Collection of field names. - * + * * @param bean the current ActionBean * @return a non-null (though possibly empty) list of field names */ @@ -349,7 +349,7 @@ * Internal helper method to bind one or more values to a single property on an ActionBean. If * the target type is an array of Collection, then all values are bound. If the target type is a * scalar type then the first value in the List of values is bound. - * + * * @param bean the ActionBean instance to which the property is being bound * @param propertyEvaluation the property evaluation to be used to set the property * @param valueOrValues a List containing one or more values @@ -389,7 +389,7 @@ * or intervening objects in a nested property are null, nothing is done. If the property is * non-null, it will be set to null. Unless the property is a collection, in which case it will * be clear()'d. - * + * * @param bean the ActionBean to which properties are being bound * @param property the name of the property being bound * @param type the declared type of the property on the ActionBean @@ -424,7 +424,7 @@ /** * Attempt to set the named property on the target bean. If the binding fails for any reason * (property does not exist, type conversion not possible etc.) an exception will be thrown. - * + * * @param bean the ActionBean on to which the property is to be bound * @param propertyName the name of the property to be bound (simple or complex) * @param propertyValue the value of the target property @@ -534,13 +534,13 @@ * having one or more values, and where each value is a non-empty String after it has had white * space trimmed from each end. *

- * + * *

* For any fields that fail validation, creates a ScopedLocaliableError that uses the stripped * name of the field to find localized info (e.g. foo.bar instead of foo[1].bar). The error is * bound to the actual field on the form though, e.g. foo[1].bar. *

- * + * * @param name the name of the parameter verbatim from the request * @param strippedName the name of the parameter with any indexing removed from it * @param values the String[] of values that was submitted in the request @@ -580,7 +580,7 @@ /** * Performs several basic validations on the String value supplied in the HttpServletRequest, * based on information provided in annotations on the ActionBean. - * + * * @param propertyName the name of the property being validated (used for constructing errors) * @param values the String[] of values from the request being validated * @param validationInfo the ValidationMetadata for the property being validated @@ -626,7 +626,7 @@ * Performs basic post-conversion validations on the properties of the ActionBean after they * have been converted to their rich type by the type conversion system. Validates single * properties in isolation from other properties. - * + * * @param bean the ActionBean that is undergoing validation and binding * @param convertedValues a map of ParameterName to all converted values for each field * @param errors the validation errors object to put errors in to @@ -681,7 +681,7 @@ * {@literal @}Validate annotation. The expression is evaluated once for each value converted. * See {@link net.sourceforge.stripes.validation.expression.ExpressionValidator} for details * on how this is implemented. - * + * * @param bean the ActionBean who's property is being validated * @param name the name of the property being validated * @param values the non-null post-conversion values for the property @@ -705,14 +705,14 @@ * no default converter, then a Constructor will be looked for on the target type which takes a * single String parameter. If such a Constructor exists it will be invoked. *

- * + * *

* Only parameter values that are non-null and do not equal the empty String will be converted * and returned. So an input array with one entry equaling the empty string, [""], will result * in an empty List being returned. Similarly, if a length three array is passed in with * one item equaling the empty String, a List of length two will be returned. *

- * + * * @param bean the ActionBean on which the property to convert exists * @param propertyName the name of the property being converted * @param values a String array of values to attempt conversion of @@ -838,7 +838,7 @@ /** * An inner class that represents a "row" of form properties that all have the same index - * so that we can validate all those properties together. + * so that we can validate all those properties together. */ protected static class Row extends HashMap { private static final long serialVersionUID = 1L; @@ -864,4 +864,8 @@ return this.hasNonEmptyValues; } } + + public void terminate(Configuration configuration) { + //Nothing to clean up here -} + } +} Index: stripes/src/net/sourceforge/stripes/controller/StripesFilter.java =================================================================== --- stripes/src/net/sourceforge/stripes/controller/StripesFilter.java (revision 1407) +++ stripes/src/net/sourceforge/stripes/controller/StripesFilter.java (revision ) @@ -77,7 +77,7 @@ * for the VM, and if so return it even when the Configuration isn't set in the thread local. */ private static final Set> configurations = - new HashSet>(); + new HashSet>(); /** * Some operations should only be done if the current invocation of @@ -312,6 +312,7 @@ /** Calls the cleanup() method on the log to release resources held by commons logging. */ public void destroy() { + configuration.terminate(); this.servletContext.removeAttribute(StripesFilter.class.getName()); Log.cleanup(); Introspector.flushCaches(); // Not 100% sure this is necessary, but it doesn't hurt Index: stripes/src/net/sourceforge/stripes/controller/DefaultObjectFactory.java =================================================================== --- stripes/src/net/sourceforge/stripes/controller/DefaultObjectFactory.java (revision 1130) +++ stripes/src/net/sourceforge/stripes/controller/DefaultObjectFactory.java (revision ) @@ -14,6 +14,13 @@ */ package net.sourceforge.stripes.controller; +import net.sourceforge.stripes.config.Configuration; +import net.sourceforge.stripes.config.TargetTypes; +import net.sourceforge.stripes.exception.StripesRuntimeException; +import net.sourceforge.stripes.util.Log; +import net.sourceforge.stripes.util.ReflectUtil; +import net.sourceforge.stripes.util.TypeHandlerCache; + import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Type; @@ -32,19 +39,12 @@ import java.util.TreeMap; import java.util.TreeSet; -import net.sourceforge.stripes.config.Configuration; -import net.sourceforge.stripes.config.TargetTypes; -import net.sourceforge.stripes.exception.StripesRuntimeException; -import net.sourceforge.stripes.util.Log; -import net.sourceforge.stripes.util.ReflectUtil; -import net.sourceforge.stripes.util.TypeHandlerCache; - /** *

* An implementation of {@link ObjectFactory} that simply calls {@link Class#newInstance()} to * obtain a new instance. *

- * + * * @author Ben Gunter * @since Stripes 1.5.1 */ @@ -60,7 +60,7 @@ /** * Wrap the given constructor. - * + * * @param factory The object factory whose * {@link ObjectFactory#newInstance(Constructor, Object...)} method will be * called when invoking the constructor. @@ -119,7 +119,7 @@ * will apply are determined by the value of the {@link TargetTypes} annotation on the class. If * there is no such annotation, then the post-processor will process all instances created by * the object factory. - * + * * @param postProcessor The post-processor to use. */ public synchronized void addPostProcessor(ObjectPostProcessor postProcessor) { @@ -169,7 +169,7 @@ /** * Calls {@link Class#newInstance()} and returns the newly created object. - * + * * @param clazz The class to instantiate. * @return The new object */ @@ -191,7 +191,7 @@ /** * Attempts to determine an implementing class for the interface provided and instantiate it * using a default constructor. - * + * * @param interfaceType an interface (or abstract class) to make an instance of * @return an instance of the interface type supplied * @throws InstantiationException if no implementation type has been configured @@ -221,7 +221,7 @@ /** * Looks up the default implementing type for the supplied interface. This is done based on a * static map of known common interface types and implementing classes. - * + * * @param iface an interface for which an implementing class is needed * @return a Class object representing the implementing type, or null if one is not found */ @@ -233,7 +233,7 @@ * Register a class as the default implementation of an interface. The implementation class will * be returned from future calls to {@link #getImplementingClass(Class)} when the argument is * {@code iface}. - * + * * @param iface The interface class * @param impl The implementation class */ @@ -247,7 +247,7 @@ /** * Create a new instance of {@code clazz} by looking up the specified constructor and passing it * and its parameters to {@link #newInstance(Constructor, Object...)}. - * + * * @param clazz The class to instantiate. * @param constructorArgTypes The type parameters of the constructor to be invoked. (See * {@link Class#getConstructor(Class...)}.) @@ -275,7 +275,7 @@ /** * Calls {@link Constructor#newInstance(Object...)} with the given parameters, passes the new * object to {@link #postProcess(Object)} and returns it. - * + * * @param constructor The constructor to invoke. * @param params The parameters to pass to the constructor. */ @@ -297,7 +297,7 @@ /** * Get a {@link ConstructorWrapper} that wraps the constructor for the given class that accepts * parameters of the given types. - * + * * @param clazz The class to look up the constructor in. * @param parameterTypes The parameter types that the constructor accepts. */ @@ -318,7 +318,7 @@ * {@link #newInstance(Class, Class[], Object[])}. Subclasses that do not need to change the way * objects are instantiated but do need to do something to the objects before returning them may * override this method to achieve that. - * + * * @param object A newly created object. * @return The given object, unchanged. */ @@ -334,4 +334,8 @@ return object; } + + public void terminate(Configuration configuration) { + //Nothing to clean up here -} + } +} Index: stripes/src/net/sourceforge/stripes/localization/DefaultLocalePicker.java =================================================================== --- stripes/src/net/sourceforge/stripes/localization/DefaultLocalePicker.java (revision 739) +++ stripes/src/net/sourceforge/stripes/localization/DefaultLocalePicker.java (revision ) @@ -197,4 +197,8 @@ public String pickCharacterEncoding(HttpServletRequest request, Locale locale) { return this.encodings.get(locale); } + + public void terminate(Configuration configuration) { + //Nothing to clean up here -} + } +} Index: stripes/src/net/sourceforge/stripes/config/DefaultConfiguration.java =================================================================== --- stripes/src/net/sourceforge/stripes/config/DefaultConfiguration.java (revision 1153) +++ stripes/src/net/sourceforge/stripes/config/DefaultConfiguration.java (revision ) @@ -14,18 +14,6 @@ */ package net.sourceforge.stripes.config; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.servlet.ServletContext; - import net.sourceforge.stripes.controller.ActionBeanContextFactory; import net.sourceforge.stripes.controller.ActionBeanPropertyBinder; import net.sourceforge.stripes.controller.ActionResolver; @@ -61,6 +49,17 @@ import net.sourceforge.stripes.validation.TypeConverterFactory; import net.sourceforge.stripes.validation.ValidationMetadataProvider; +import javax.servlet.ServletContext; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + /** *

Centralized location for defaults for all Configuration properties. This implementation does * not lookup configuration information anywhere! It returns hard-coded defaults that will result @@ -68,9 +67,9 @@ * *

Despite it's name the DefaultConfiguration is not in fact the default Configuration * implementation in Stripes! Instead it is the retainer of default configuration values. The - * Configuration implementation that is used when no alternative is configured is the - * {@link RuntimeConfiguration}, which is a direct subclass of DefaultConfiguration, and when no - * further configuration properties are supplied behaves identically to the DefaultConfiguration.

+ * Configuration implementation that is used when no alternative is configured is the {@link + * RuntimeConfiguration}, which is a direct subclass of DefaultConfiguration, and when no further + * configuration properties are supplied behaves identically to the DefaultConfiguration.

* *

The DefaultConfiguration is designed to be easily extended as needed. The init() method * ensures that components are initialized in the correct order (taking dependencies into account), @@ -97,7 +96,7 @@ private FormatterFactory formatterFactory; private TagErrorRendererFactory tagErrorRendererFactory; private PopulationStrategy populationStrategy; - private Map> interceptors; + private Map> interceptors; private ExceptionHandler exceptionHandler; private MultipartWrapperFactory multipartWrapperFactory; private ValidationMetadataProvider validationMetadataProvider; @@ -108,13 +107,13 @@ } /** - * Creates and stores instances of the objects of the type that the Configuration is - * responsible for providing, except where subclasses have already provided instances. + * Creates and stores instances of the objects of the type that the Configuration is responsible + * for providing, except where subclasses have already provided instances. */ @SuppressWarnings("unchecked") public void init() { try { - Boolean debugMode = initDebugMode(); + Boolean debugMode = initDebugMode(); if (debugMode != null) { this.debugMode = debugMode; } @@ -223,17 +222,18 @@ } // do a quick check to see if any interceptor classes are configured more than once - for (Map.Entry> entry : this.interceptors.entrySet()) { - Set> classes = new HashSet>(); + for (Map.Entry> entry : this.interceptors + .entrySet()) { + Set> classes = + new HashSet>(); Collection interceptors = entry.getValue(); - if (interceptors == null) - continue; + if (interceptors == null) { continue; } for (Interceptor interceptor : interceptors) { Class clazz = interceptor.getClass(); if (classes.contains(clazz)) { - log.warn("Interceptor ", clazz, - " is configured to run more than once for ", entry.getKey()); + log.warn("Interceptor ", clazz, " is configured to run more than once for ", entry + .getKey()); } else { classes.add(clazz); @@ -242,8 +242,7 @@ } } catch (Exception e) { - throw new StripesRuntimeException - ("Problem instantiating default configuration objects.", e); + throw new StripesRuntimeException("Problem instantiating default configuration objects.", e); } } @@ -262,25 +261,25 @@ return getBootstrapPropertyResolver().getFilterConfig().getServletContext(); } - /** Enable or disable debug mode. */ - public void setDebugMode(boolean debugMode) { - this.debugMode = debugMode; - } + /** Enable or disable debug mode. */ + public void setDebugMode(boolean debugMode) { + this.debugMode = debugMode; + } - /** Returns true if the Stripes application is running in debug mode. */ - public boolean isDebugMode() { - return debugMode; - } + /** Returns true if the Stripes application is running in debug mode. */ + public boolean isDebugMode() { + return debugMode; + } - /** Allows subclasses to initialize a non-default debug mode value. */ - protected Boolean initDebugMode() { - return null; - } + /** Allows subclasses to initialize a non-default debug mode value. */ + protected Boolean initDebugMode() { + return null; + } /** * Returns an instance of {@link ObjectFactory} that is used throughout Stripes to instantiate * classes. - * + * * @return an instance of {@link ObjectFactory}. */ public ObjectFactory getObjectFactory() { @@ -291,8 +290,9 @@ protected ObjectFactory initObjectFactory() { return null; } /** - * Returns an instance of {@link NameBasedActionResolver} unless a subclass has - * overridden the default. + * Returns an instance of {@link NameBasedActionResolver} unless a subclass has overridden the + * default. + * * @return ActionResolver an instance of the configured resolver */ public ActionResolver getActionResolver() { @@ -305,6 +305,7 @@ /** * Returns an instance of {@link DefaultActionBeanPropertyBinder} unless a subclass has * overridden the default. + * * @return ActionBeanPropertyBinder an instance of the configured binder */ public ActionBeanPropertyBinder getActionBeanPropertyBinder() { @@ -328,8 +329,9 @@ protected ActionBeanContextFactory initActionBeanContextFactory() { return null; } /** - * Returns an instance of {@link DefaultTypeConverterFactory} unless a subclass has - * overridden the default.. + * Returns an instance of {@link DefaultTypeConverterFactory} unless a subclass has overridden + * the default.. + * * @return TypeConverterFactory an instance of the configured factory. */ public TypeConverterFactory getTypeConverterFactory() { @@ -360,8 +362,8 @@ protected LocalePicker initLocalePicker() { return null; } /** - * Returns an instance of a FormatterFactory. Unless a subclass has picked another implementation - * will return an instance of DefaultFormatterFactory. + * Returns an instance of a FormatterFactory. Unless a subclass has picked another + * implementation will return an instance of DefaultFormatterFactory. */ public FormatterFactory getFormatterFactory() { return this.formatterFactory; } @@ -381,8 +383,8 @@ /** * Returns an instance of a PopulationsStrategy. Unless a subclass has picked another - * implementation, will return an instance of - * {@link net.sourceforge.stripes.tag.BeanFirstPopulationStrategy}. + * implementation, will return an instance of {@link net.sourceforge.stripes.tag.BeanFirstPopulationStrategy}. + * * @since Stripes 1.6 */ public PopulationStrategy getPopulationStrategy() { return this.populationStrategy; } @@ -392,8 +394,7 @@ /** * Returns an instance of an ExceptionHandler. Unless a subclass has picked another - * implementation, will return an instance of - * {@link net.sourceforge.stripes.exception.DefaultExceptionHandler}. + * implementation, will return an instance of {@link net.sourceforge.stripes.exception.DefaultExceptionHandler}. */ public ExceptionHandler getExceptionHandler() { return this.exceptionHandler; } @@ -411,15 +412,13 @@ return this.multipartWrapperFactory; } - /** Allows subclasses to initialize a non-default MultipartWrapperFactory. */ protected MultipartWrapperFactory initMultipartWrapperFactory() { return null; } /** * Returns an instance of {@link ValidationMetadataProvider} that can be used by Stripes to - * determine what validations need to be applied during - * {@link LifecycleStage#BindingAndValidation}. + * determine what validations need to be applied during {@link LifecycleStage#BindingAndValidation}. - * + * * @return an instance of {@link ValidationMetadataProvider} */ public ValidationMetadataProvider getValidationMetadataProvider() { @@ -430,9 +429,8 @@ protected ValidationMetadataProvider initValidationMetadataProvider() { return null; } /** - * Returns a list of interceptors that should be executed around the lifecycle stage - * indicated. By default returns a single element list containing the - * {@link BeforeAfterMethodInterceptor}. + * Returns a list of interceptors that should be executed around the lifecycle stage indicated. + * By default returns a single element list containing the {@link BeforeAfterMethodInterceptor}. */ public Collection getInterceptors(LifecycleStage stage) { Collection interceptors = this.interceptors.get(stage); @@ -441,14 +439,13 @@ } return interceptors; } - + /** - * Merges the two {@link Map}s of {@link LifecycleStage} to {@link Collection} of - * {@link Interceptor}. A simple {@link Map#putAll(Map)} does not work because it overwrites - * the collections in the map instead of adding to them. + * Merges the two {@link Map}s of {@link LifecycleStage} to {@link Collection} of {@link + * Interceptor}. A simple {@link Map#putAll(Map)} does not work because it overwrites the + * collections in the map instead of adding to them. */ - protected void mergeInterceptorMaps(Map> dst, - Map> src) { + protected void mergeInterceptorMaps(Map> dst, Map> src) { for (Map.Entry> entry : src.entrySet()) { Collection collection = dst.get(entry.getKey()); if (collection == null) { @@ -458,27 +455,23 @@ collection.addAll(entry.getValue()); } } - + /** - * Adds the interceptor to the map, associating it with the {@link LifecycleStage}s indicated - * by the {@link Intercepts} annotation. If the interceptor implements - * {@link ConfigurableComponent}, then its init() method will be called. + * Adds the interceptor to the map, associating it with the {@link LifecycleStage}s indicated by + * the {@link Intercepts} annotation. If the interceptor implements {@link + * ConfigurableComponent}, then its init() method will be called. */ - protected void addInterceptor(Map> map, - Interceptor interceptor) { + protected void addInterceptor(Map> map, Interceptor interceptor) { Class type = interceptor.getClass(); Intercepts intercepts = type.getAnnotation(Intercepts.class); if (intercepts == null) { - log.error("An interceptor of type ", type.getName(), " was configured ", - "but was not marked with an @Intercepts annotation. As a ", - "result it is not possible to determine at which ", - "lifecycle stages the interceptor should be applied. This ", - "interceptor will be ignored."); + log.error("An interceptor of type ", type + .getName(), " was configured ", "but was not marked with an @Intercepts annotation. As a ", "result it is not possible to determine at which ", "lifecycle stages the interceptor should be applied. This ", "interceptor will be ignored."); return; } else { - log.debug("Configuring interceptor '", type.getSimpleName(), - "', for lifecycle stages: ", intercepts.value()); + log.debug("Configuring interceptor '", type + .getSimpleName(), "', for lifecycle stages: ", intercepts.value()); } // call init() if the interceptor implements ConfigurableComponent @@ -504,12 +497,50 @@ /** Instantiates the core interceptors, allowing subclasses to override the default behavior */ protected Map> initCoreInterceptors() { - Map> interceptors = new HashMap>(); + Map> interceptors = + new HashMap>(); addInterceptor(interceptors, new BeforeAfterMethodInterceptor()); addInterceptor(interceptors, new HttpCacheInterceptor()); return interceptors; } /** Allows subclasses to initialize a non-default Map of Interceptor instances. */ - protected Map> initInterceptors() { return null; } + protected Map> initInterceptors() { return null; } + + public void terminate() { + tryTerminate(objectFactory); + tryTerminate(actionResolver); + tryTerminate(actionBeanPropertyBinder); + tryTerminate(actionBeanContextFactory); + tryTerminate(typeConverterFactory); + tryTerminate(localizationBundleFactory); + tryTerminate(localePicker); + tryTerminate(formatterFactory); + tryTerminate(tagErrorRendererFactory); + tryTerminate(populationStrategy); + terminateInterceptors(interceptors); + tryTerminate(exceptionHandler); + tryTerminate(multipartWrapperFactory); + tryTerminate(validationMetadataProvider); -} + } + + void tryTerminate(Object object) { + if (object != null && object instanceof ConfigurableComponent) { + ConfigurableComponent configurable = (ConfigurableComponent) object; + try { + configurable.terminate(this); + } + catch (RuntimeException e) { + log.error(e, "Unexpected error terminating ", object.getClass().getCanonicalName()); + } + } + } + + void terminateInterceptors(final Map> interceptors) { + for (Collection values : interceptors.values()) { + for (Interceptor interceptor : values) { + tryTerminate(interceptor); + } + } + } +} Index: stripes/src/net/sourceforge/stripes/controller/DefaultActionBeanContextFactory.java =================================================================== --- stripes/src/net/sourceforge/stripes/controller/DefaultActionBeanContextFactory.java (revision 1000) +++ stripes/src/net/sourceforge/stripes/controller/DefaultActionBeanContextFactory.java (revision ) @@ -85,4 +85,8 @@ { this.configuration = configuration; } + + public void terminate(Configuration configuration) { + //Nothing to clean up here -} + } +} Index: stripes/src/net/sourceforge/stripes/tag/DefaultPopulationStrategy.java =================================================================== --- stripes/src/net/sourceforge/stripes/tag/DefaultPopulationStrategy.java (revision 1295) +++ stripes/src/net/sourceforge/stripes/tag/DefaultPopulationStrategy.java (revision ) @@ -163,10 +163,14 @@ ActionBean actionBean = tag.getParentFormTag().getActionBean(); if (actionBean != null) { - ValidationErrors errors = actionBean.getContext().getValidationErrors(); + ValidationErrors errors = actionBean.getContext().getValidationErrors(); inError = (errors != null && errors.size() > 0); } return inError; } + + public void terminate(Configuration configuration) { + //Nothing to clean up here -} + } +} Index: stripes/src/net/sourceforge/stripes/localization/DefaultLocalizationBundleFactory.java =================================================================== --- stripes/src/net/sourceforge/stripes/localization/DefaultLocalizationBundleFactory.java (revision 949) +++ stripes/src/net/sourceforge/stripes/localization/DefaultLocalizationBundleFactory.java (revision ) @@ -135,4 +135,8 @@ { this.configuration = configuration; } + + public void terminate(Configuration configuration) { + //Nothing to clean up here -} + } +} Index: stripes/src/net/sourceforge/stripes/format/DefaultFormatterFactory.java =================================================================== --- stripes/src/net/sourceforge/stripes/format/DefaultFormatterFactory.java (revision 1054) +++ stripes/src/net/sourceforge/stripes/format/DefaultFormatterFactory.java (revision ) @@ -14,21 +14,21 @@ */ package net.sourceforge.stripes.format; -import java.util.Date; -import java.util.Locale; -import java.util.Map; - import net.sourceforge.stripes.config.Configuration; import net.sourceforge.stripes.util.Log; import net.sourceforge.stripes.util.TypeHandlerCache; +import java.util.Date; +import java.util.Locale; +import java.util.Map; + /** * Implementation of {@link FormatterFactory} that contains a set of built-in formatters. Additional * formatters can be registered by calling {@link #add(Class, Class)}. If there is no registered * formatter for a specific class, then it attempts to find the best available formatter by * searching for a match against the target implemented interfaces, class's superclasses, and * interface superclasses. - * + * * @author Tim Fennell */ public class DefaultFormatterFactory implements FormatterFactory { @@ -80,7 +80,7 @@ /** * Check to see if the there is a Formatter for the specified clazz. If a Formatter is found an * instance is created, configured and returned. Otherwise returns null. - * + * * @param clazz the type of object being formatted * @param locale the Locale into which the object should be formatted * @param formatType the type of output to produce (e.g. date, time etc.) @@ -122,4 +122,8 @@ formatter.init(); return formatter; } + + public void terminate(Configuration configuration) { + //Nothing to clean up here -} + } +} Index: stripes/src/net/sourceforge/stripes/exception/DefaultExceptionHandler.java =================================================================== --- stripes/src/net/sourceforge/stripes/exception/DefaultExceptionHandler.java (revision 1362) +++ stripes/src/net/sourceforge/stripes/exception/DefaultExceptionHandler.java (revision ) @@ -15,19 +15,6 @@ package net.sourceforge.stripes.exception; -import java.beans.PropertyDescriptor; -import java.io.IOException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.net.URL; -import java.text.DecimalFormat; -import java.util.HashMap; -import java.util.Map; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - import net.sourceforge.stripes.action.ActionBean; import net.sourceforge.stripes.action.ActionBeanContext; import net.sourceforge.stripes.action.FileBean; @@ -44,6 +31,18 @@ import net.sourceforge.stripes.util.ReflectUtil; import net.sourceforge.stripes.validation.LocalizableError; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.beans.PropertyDescriptor; +import java.io.IOException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.URL; +import java.text.DecimalFormat; +import java.util.HashMap; +import java.util.Map; + /** *

Default ExceptionHandler implementation that makes it easy for users to extend and * add custom handling for different types of exception. When extending this class methods @@ -184,7 +183,7 @@ * In production, most applications will provide their own handler for * {@link SourcePageNotFoundException} by extending this class and overriding this method. *

- * + * * @param exception The exception. * @param request The servlet request. * @param response The servlet response. @@ -228,7 +227,7 @@ * {@link #getFileUploadExceededExceptionPath(HttpServletRequest)} to return the path to your * global error page. *

- * + * * @param exception The exception that needs to be handled * @param request The servlet request * @param response The servlet response @@ -326,7 +325,7 @@ * from the HTTP Referer header. If it is unable to do so, it returns null. Subclasses may * override this method to return whatever they wish. The return value must be relative to the * application context root. - * + * * @param request The request that generated the exception * @return The context-relative path from which the request was submitted */ @@ -437,4 +436,8 @@ return throwable; } + + public void terminate(Configuration configuration) { + //Nothing to clean up here -} + } +} Index: stripes/src/net/sourceforge/stripes/config/Configuration.java =================================================================== --- stripes/src/net/sourceforge/stripes/config/Configuration.java (revision 1000) +++ stripes/src/net/sourceforge/stripes/config/Configuration.java (revision ) @@ -51,7 +51,7 @@ /** * Supplies the Configuration with a BootstrapPropertyResolver. This method is guaranteed to * be invoked prior to the init method. - * + * * @param resolver a BootStrapPropertyResolver which can be used to find any values required * by the Configuration in order to initialize */ @@ -65,6 +65,14 @@ void init(); /** + * Called by the DispatcherServlet after the application server perform a clean shutdown. + * In a terminate method would be possible to commit several data uncommitted + * Components are not expected to fail with a exception. Instead the should handle + * any failure on their own + */ + void terminate(); + + /** * Implementations should implement this method to simply return a reference to the * BootstrapPropertyResolver passed to the Configuration at initialization time. * @@ -89,7 +97,7 @@ /** * Returns an instance of {@link ObjectFactory} that is used throughout Stripes to instantiate * classes. - * + * * @return an instance of {@link ObjectFactory}. */ ObjectFactory getObjectFactory(); @@ -206,7 +214,7 @@ * Returns an instance of {@link ValidationMetadataProvider} that can be used by Stripes to * determine what validations need to be applied during * {@link LifecycleStage#BindingAndValidation}. - * + * * @return an instance of {@link ValidationMetadataProvider} */ ValidationMetadataProvider getValidationMetadataProvider(); Index: tests/src/net/sourceforge/stripes/config/DefaultConfigurationTest.java =================================================================== --- tests/src/net/sourceforge/stripes/config/DefaultConfigurationTest.java (revision ) +++ tests/src/net/sourceforge/stripes/config/DefaultConfigurationTest.java (revision ) @@ -0,0 +1,111 @@ +package net.sourceforge.stripes.config; + +import net.sourceforge.stripes.action.Resolution; +import net.sourceforge.stripes.controller.ExecutionContext; +import net.sourceforge.stripes.controller.Interceptor; +import net.sourceforge.stripes.controller.LifecycleStage; +import org.testng.Assert; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; + +public class DefaultConfigurationTest { + private DefaultConfiguration defaultConfiguration; + + @BeforeTest + public void setup() { + defaultConfiguration = new DefaultConfiguration(); + } + + @Test(groups = "fast") + public void testTryTerminate() { + final StringBuilder called = new StringBuilder(); + defaultConfiguration.tryTerminate(new ConfigurableComponent() { + public void init(final Configuration configuration) throws Exception { + } + + public void terminate(final Configuration configuration) { + called.append("called"); + } + }); + Assert.assertEquals(called.toString(), "called", "expected to call the terminate method"); + } + + @Test(groups = "fast") + public void testTryTerminateIgnoresNullValues() { + defaultConfiguration.tryTerminate(null); + } + + @Test(groups = "fast") + public void testTryTerminateIgnoresNonConfigurableComponentInstances() { + defaultConfiguration.tryTerminate("I am not a ConfigurableComponent"); + } + + @Test(groups = "fast") + public void testTryTerminateHandlesRuntimeExceptions() { + defaultConfiguration.tryTerminate(new ConfigurableComponent() { + public void init(final Configuration configuration) throws Exception { + } + + public void terminate(final Configuration configuration) { + throw new RuntimeException("Something went terribly wrong"); + } + }); + } + + @Test(groups = "fast") + public void testTerminateInterceptor() { + Map> interceptors = + new HashMap>(); + HashSet interceptorSetOne = new HashSet(); + interceptorSetOne.add(new MockInterceptor()); + MockInterceptorImplementingConfigurableComponent configurableOne = + new MockInterceptorImplementingConfigurableComponent(); + interceptorSetOne.add(configurableOne); + interceptors.put(LifecycleStage.ActionBeanResolution, interceptorSetOne); + + HashSet interceptorSetTwo = new HashSet(); + MockInterceptorImplementingConfigurableComponent configurableTwo = + new MockInterceptorImplementingConfigurableComponent(); + interceptorSetTwo.add(configurableTwo); + interceptorSetTwo.add(new MockInterceptor()); + interceptors.put(LifecycleStage.HandlerResolution, interceptorSetTwo); + + defaultConfiguration.terminateInterceptors(interceptors); + + Assert.assertTrue(configurableOne.terminated, "expected the configurable interceptor to be terminated"); + Assert.assertTrue(configurableTwo.terminated, "expected the configurable interceptor to be terminated"); + } + + @Test(groups = "fast") + public void testTerminateInterceptorHandlesEmptyMap() { + defaultConfiguration.terminateInterceptors(new HashMap>()); + } + + static class MockInterceptor implements Interceptor { + public Resolution intercept(final ExecutionContext context) throws Exception { + throw new RuntimeException("should not be called"); + } + } + + static class MockInterceptorImplementingConfigurableComponent implements Interceptor, + ConfigurableComponent { + boolean terminated = false; + + public Resolution intercept(final ExecutionContext context) throws Exception { + throw new RuntimeException("should not be called"); + } + + public void init(final Configuration configuration) throws Exception { + throw new RuntimeException("should not be called"); + } + + public void terminate(final Configuration configuration) { + terminated = true; + } + } +} Index: stripes/src/net/sourceforge/stripes/config/ConfigurableComponent.java =================================================================== --- stripes/src/net/sourceforge/stripes/config/ConfigurableComponent.java (revision 724) +++ stripes/src/net/sourceforge/stripes/config/ConfigurableComponent.java (revision ) @@ -25,12 +25,22 @@ public interface ConfigurableComponent { /** - * Invoked directly after instantiation to allow the configured component to perform - * one time initialization. Components are expected to fail loudly if they are not - * going to be in a valid state after initialization. + * Invoked directly after instantiation to allow the configured component to perform one time + * initialization. Components are expected to fail loudly if they are not going to be in a + * valid state after initialization. * * @param configuration the Configuration object being used by Stripes * @throws Exception should be thrown if the component cannot be configured well enough to use. */ void init(Configuration configuration) throws Exception; + + /** + * Invoked after the application server perform a clean shutdown. + * In a terminate method would be possible to commit several data uncommitted + * Components are not expected to fail with a exception. Instead the should handle + * any failure on their own + * + * @param configuration the Configuration object being used by Stripes + */ + void terminate(Configuration configuration); }