/* 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);
}
}
}
}
}