/* Copyright 2005-2006 Tim Fennell * * Licensed 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 net.sourceforge.stripes.config; import java.lang.reflect.Type; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import net.sourceforge.stripes.controller.ActionBeanContextFactory; import net.sourceforge.stripes.controller.ActionBeanPropertyBinder; import net.sourceforge.stripes.controller.ActionResolver; import net.sourceforge.stripes.controller.Interceptor; import net.sourceforge.stripes.controller.LifecycleStage; import net.sourceforge.stripes.controller.ObjectFactory; import net.sourceforge.stripes.controller.multipart.MultipartWrapperFactory; import net.sourceforge.stripes.exception.ExceptionHandler; import net.sourceforge.stripes.exception.StripesRuntimeException; import net.sourceforge.stripes.format.Formatter; import net.sourceforge.stripes.format.FormatterFactory; import net.sourceforge.stripes.localization.LocalePicker; import net.sourceforge.stripes.localization.LocalizationBundleFactory; import net.sourceforge.stripes.tag.PopulationStrategy; import net.sourceforge.stripes.tag.TagErrorRendererFactory; import net.sourceforge.stripes.util.Log; import net.sourceforge.stripes.util.ReflectUtil; import net.sourceforge.stripes.validation.TypeConverter; import net.sourceforge.stripes.validation.TypeConverterFactory; import net.sourceforge.stripes.validation.ValidationMetadataProvider; /** * <p>Configuration class that uses the BootstrapPropertyResolver to look for configuration values, * and when it cannot find a value, falls back on the DefaultConfiguration to supply default * values. In general, the RuntimeConfiguration will operate in the following pattern:</p> * * <ul> * <li>Look for the value of a configuration property in the BootstrapProperties</li> * <li>If the value exists, the configuration will attempt to use it (usually to instantiate * a class). If an exception occurs, the RuntimeConfiguration will throw an exception and * not provide a value. In most cases this will be fatal!</li> * <li>If the value does not exist, the default from DefaultConfiguration will be used.</li> * </ul> * * @author Tim Fennell */ public class RuntimeConfiguration extends DefaultConfiguration { /** Log implementation for use within this class. */ private static final Log log = Log.getInstance(RuntimeConfiguration.class); /** The Configuration Key for enabling debug mode. */ public static final String DEBUG_MODE = "Stripes.DebugMode"; /** The Configuration Key for looking up the name of the ObjectFactory class */ public static final String OBJECT_FACTORY = "ObjectFactory.Class"; /** The Configuration Key for looking up the name of the ActionResolver class. */ public static final String ACTION_RESOLVER = "ActionResolver.Class"; /** The Configuration Key for looking up the name of the ActionResolver class. */ public static final String ACTION_BEAN_PROPERTY_BINDER = "ActionBeanPropertyBinder.Class"; /** The Configuration Key for looking up the name of an ActionBeanContextFactory class. */ public static final String ACTION_BEAN_CONTEXT_FACTORY = "ActionBeanContextFactory.Class"; /** The Configuration Key for looking up the name of the TypeConverterFactory class. */ public static final String TYPE_CONVERTER_FACTORY = "TypeConverterFactory.Class"; /** The Configuration Key for looking up the name of the LocalizationBundleFactory class. */ public static final String LOCALIZATION_BUNDLE_FACTORY = "LocalizationBundleFactory.Class"; /** The Configuration Key for looking up the name of the LocalizationBundleFactory class. */ public static final String LOCALE_PICKER = "LocalePicker.Class"; /** The Configuration Key for looking up the name of the FormatterFactory class. */ public static final String FORMATTER_FACTORY = "FormatterFactory.Class"; /** The Configuration Key for looking up the name of the TagErrorRendererFactory class */ public static final String TAG_ERROR_RENDERER_FACTORY = "TagErrorRendererFactory.Class"; /** The Configuration Key for looking up the name of the PopulationStrategy class */ public static final String POPULATION_STRATEGY = "PopulationStrategy.Class"; /** The Configuration Key for looking up the name of the ExceptionHandler class */ public static final String EXCEPTION_HANDLER = "ExceptionHandler.Class"; /** The Configuration Key for looking up the name of the MultipartWrapperFactory class */ public static final String MULTIPART_WRAPPER_FACTORY = "MultipartWrapperFactory.Class"; /** The Configuration Key for looking up the name of the ValidationMetadataProvider class */ public static final String VALIDATION_METADATA_PROVIDER = "ValidationMetadataProvider.Class"; /** The Configuration Key for looking up the comma separated list of core interceptor classes. */ public static final String CORE_INTERCEPTOR_LIST = "CoreInterceptor.Classes"; /** The Configuration Key for looking up the comma separated list of interceptor classes. */ public static final String INTERCEPTOR_LIST = "Interceptor.Classes"; /** Looks for a true/false value in config. */ @Override protected Boolean initDebugMode() { try { return Boolean.valueOf(getBootstrapPropertyResolver().getProperty(DEBUG_MODE) .toLowerCase()); } catch (Exception e) { return null; } } /** Looks for a class name in config and uses that to create the component. */ @Override protected ObjectFactory initObjectFactory() { return initializeComponent(ObjectFactory.class, OBJECT_FACTORY); } /** Looks for a class name in config and uses that to create the component. */ @Override protected ActionResolver initActionResolver() { return initializeComponent(ActionResolver.class, ACTION_RESOLVER); } /** Looks for a class name in config and uses that to create the component. */ @Override protected ActionBeanPropertyBinder initActionBeanPropertyBinder() { return initializeComponent(ActionBeanPropertyBinder.class, ACTION_BEAN_PROPERTY_BINDER); } /** Looks for a class name in config and uses that to create the component. */ @Override protected ActionBeanContextFactory initActionBeanContextFactory() { return initializeComponent(ActionBeanContextFactory.class, ACTION_BEAN_CONTEXT_FACTORY); } /** Looks for a class name in config and uses that to create the component. */ @Override protected TypeConverterFactory initTypeConverterFactory() { return initializeComponent(TypeConverterFactory.class, TYPE_CONVERTER_FACTORY); } /** Looks for a class name in config and uses that to create the component. */ @Override protected LocalizationBundleFactory initLocalizationBundleFactory() { return initializeComponent(LocalizationBundleFactory.class, LOCALIZATION_BUNDLE_FACTORY); } /** Looks for a class name in config and uses that to create the component. */ @Override protected LocalePicker initLocalePicker() { return initializeComponent(LocalePicker.class, LOCALE_PICKER); } /** Looks for a class name in config and uses that to create the component. */ @Override protected FormatterFactory initFormatterFactory() { return initializeComponent(FormatterFactory.class, FORMATTER_FACTORY); } /** Looks for a class name in config and uses that to create the component. */ @Override protected TagErrorRendererFactory initTagErrorRendererFactory() { return initializeComponent(TagErrorRendererFactory.class, TAG_ERROR_RENDERER_FACTORY); } /** Looks for a class name in config and uses that to create the component. */ @Override protected PopulationStrategy initPopulationStrategy() { return initializeComponent(PopulationStrategy.class, POPULATION_STRATEGY); } /** Looks for a class name in config and uses that to create the component. */ @Override protected ExceptionHandler initExceptionHandler() { return initializeComponent(ExceptionHandler.class, EXCEPTION_HANDLER); } /** Looks for a class name in config and uses that to create the component. */ @Override protected MultipartWrapperFactory initMultipartWrapperFactory() { return initializeComponent(MultipartWrapperFactory.class, MULTIPART_WRAPPER_FACTORY); } /** Looks for a class name in config and uses that to create the component. */ @Override protected ValidationMetadataProvider initValidationMetadataProvider() { return initializeComponent(ValidationMetadataProvider.class, VALIDATION_METADATA_PROVIDER); } /** * Looks for a list of class names separated by commas under the configuration key * {@link #CORE_INTERCEPTOR_LIST}. White space surrounding the class names is trimmed, * the classes instantiated and then stored under the lifecycle stage(s) they should * intercept. * * @return a Map of {@link LifecycleStage} to Collection of {@link Interceptor} */ @Override protected Map<LifecycleStage, Collection<Interceptor>> initCoreInterceptors() { List<Class<?>> coreInterceptorClasses = getBootstrapPropertyResolver().getClassPropertyList(CORE_INTERCEPTOR_LIST); if (coreInterceptorClasses.size() == 0) return super.initCoreInterceptors(); else return initInterceptors(coreInterceptorClasses); } /** * Looks for a list of class names separated by commas under the configuration key * {@link #INTERCEPTOR_LIST}. White space surrounding the class names is trimmed, * the classes instantiated and then stored under the lifecycle stage(s) they should * intercept. * * @return a Map of {@link LifecycleStage} to Collection of {@link Interceptor} */ @Override protected Map<LifecycleStage, Collection<Interceptor>> initInterceptors() { return initInterceptors(getBootstrapPropertyResolver().getClassPropertyList(INTERCEPTOR_LIST, Interceptor.class)); } /** * Splits a comma-separated list of class names and maps each {@link LifecycleStage} to the * interceptors in the list that intercept it. Also automatically finds Interceptors in * packages listed in {@link BootstrapPropertyResolver#PACKAGES} if searchExtensionPackages is true. * * @return a Map of {@link LifecycleStage} to Collection of {@link Interceptor} */ @SuppressWarnings("unchecked") protected Map<LifecycleStage, Collection<Interceptor>> initInterceptors(List classes) { Map<LifecycleStage, Collection<Interceptor>> map = new HashMap<LifecycleStage, Collection<Interceptor>>(); for (Object type : classes) { try { Interceptor interceptor = getObjectFactory().newInstance( (Class<? extends Interceptor>) type); addInterceptor(map, interceptor); } catch (Exception e) { throw new StripesRuntimeException("Could not instantiate configured Interceptor [" + type.getClass().getName() + "].", e); } } return map; } /** * Internal utility method that is used to implement the main pattern of this class: lookup the * name of a class based on a property name, instantiate the named class and initialize it. * * @param componentType a Class object representing a subclass of ConfigurableComponent * @param propertyName the name of the property to look up for the class name * @return an instance of the component, or null if one was not configured. */ @SuppressWarnings("unchecked") protected <T extends ConfigurableComponent> T initializeComponent(Class<T> componentType, String propertyName) { Class clazz = getBootstrapPropertyResolver().getClassProperty(propertyName, componentType); if (clazz != null) { try { T component; ObjectFactory objectFactory = getObjectFactory(); if (objectFactory != null) { component = objectFactory.newInstance((Class<T>) clazz); } else { component = (T) clazz.newInstance(); } component.init(this); return component; } catch (Exception e) { throw new StripesRuntimeException("Could not instantiate configured " + componentType.getSimpleName() + " of type [" + clazz.getSimpleName() + "]. Please check " + "the configuration parameters specified in your web.xml.", e); } } else { return null; } } /** * Calls super.init() then adds Formatters and TypeConverters found in * packages listed in {@link BootstrapPropertyResolver#PACKAGES} to their respective factories. */ @SuppressWarnings("unchecked") @Override public void init() { super.init(); List<Class<? extends Formatter>> formatters = getBootstrapPropertyResolver().getClassPropertyList(Formatter.class); for (Class<? extends Formatter> formatter : formatters) { Type[] typeArguments = ReflectUtil.getActualTypeArguments(formatter, Formatter.class); log.trace("Found Formatter [", formatter, "] - type parameters: ", typeArguments); if ((typeArguments != null) && (typeArguments.length == 1) && !typeArguments[0].equals(Object.class)) { if (typeArguments[0] instanceof Class) { log.debug("Adding auto-discovered Formatter [", formatter, "] for [", typeArguments[0], "] (from type parameter)"); getFormatterFactory().add((Class<?>) typeArguments[0], (Class<? extends Formatter<?>>) formatter); } else { log.warn("Type parameter for non-abstract Formatter [", formatter, "] is not a class."); } } TargetTypes targetTypes = formatter.getAnnotation(TargetTypes.class); if (targetTypes != null) { for (Class<?> targetType : targetTypes.value()) { log.debug("Adding auto-discovered Formatter [", formatter, "] for [", targetType, "] (from TargetTypes annotation)"); getFormatterFactory().add(targetType, (Class<? extends Formatter<?>>) formatter); } } } List<Class<? extends TypeConverter>> typeConverters = getBootstrapPropertyResolver().getClassPropertyList(TypeConverter.class); for (Class<? extends TypeConverter> typeConverter : typeConverters) { Type[] typeArguments = ReflectUtil.getActualTypeArguments(typeConverter, TypeConverter.class); log.trace("Found TypeConverter [", typeConverter, "] - type parameters: ", typeArguments); if ((typeArguments != null) && (typeArguments.length == 1) && !typeArguments[0].equals(Object.class)) { if (typeArguments[0] instanceof Class) { log.debug("Adding auto-discovered TypeConverter [", typeConverter, "] for [", typeArguments[0], "] (from type parameter)"); getTypeConverterFactory().add((Class<?>) typeArguments[0], (Class<? extends TypeConverter<?>>) typeConverter); } else { log.warn("Type parameter for non-abstract TypeConverter [", typeConverter, "] is not a class."); } } TargetTypes targetTypes = typeConverter.getAnnotation(TargetTypes.class); if (targetTypes != null) { for (Class<?> targetType : targetTypes.value()) { log.debug("Adding auto-discovered TypeConverter [", typeConverter, "] for [", targetType, "] (from TargetTypes annotation)"); getTypeConverterFactory().add(targetType, (Class<? extends TypeConverter<?>>) typeConverter); } } } } }