diff --git a/core/src/main/java/org/apache/struts2/StrutsConstants.java b/core/src/main/java/org/apache/struts2/StrutsConstants.java index b2b7c57c8..d205a15b7 100644 --- a/core/src/main/java/org/apache/struts2/StrutsConstants.java +++ b/core/src/main/java/org/apache/struts2/StrutsConstants.java @@ -246,4 +246,10 @@ public final class StrutsConstants { /** Enable handling exceptions by Dispatcher - true by default **/ public static final String STRUTS_HANDLE_EXCEPTION = "struts.handle.exception"; + public static final String STRUTS_CONVERTER_PROPERTIES_PROCESSOR = "struts.converter.properties.processor"; + public static final String STRUTS_CONVERTER_FILE_PROCESSOR = "struts.converter.file.processor"; + public static final String STRUTS_CONVERTER_ANNOTATION_PROCESSOR = "struts.converter.annotation.processor"; + public static final String STRUTS_CONVERTER_CREATOR = "struts.converter.creator"; + public static final String STRUTS_CONVERTER_HOLDER = "struts..converter.holder"; + } diff --git a/core/src/main/java/org/apache/struts2/config/BeanSelectionProvider.java b/core/src/main/java/org/apache/struts2/config/BeanSelectionProvider.java index 434688dd6..ddd1c5e0d 100644 --- a/core/src/main/java/org/apache/struts2/config/BeanSelectionProvider.java +++ b/core/src/main/java/org/apache/struts2/config/BeanSelectionProvider.java @@ -32,7 +32,12 @@ import com.opensymphony.xwork2.XWorkConstants; import com.opensymphony.xwork2.config.Configuration; import com.opensymphony.xwork2.config.ConfigurationException; import com.opensymphony.xwork2.config.ConfigurationProvider; +import com.opensymphony.xwork2.conversion.ConversionAnnotationProcessor; +import com.opensymphony.xwork2.conversion.ConversionFileProcessor; +import com.opensymphony.xwork2.conversion.ConversionPropertiesProcessor; import com.opensymphony.xwork2.conversion.ObjectTypeDeterminer; +import com.opensymphony.xwork2.conversion.TypeConverterCreator; +import com.opensymphony.xwork2.conversion.TypeConverterHolder; import com.opensymphony.xwork2.conversion.impl.ArrayConverter; import com.opensymphony.xwork2.conversion.impl.CollectionConverter; import com.opensymphony.xwork2.conversion.impl.DateConverter; @@ -241,6 +246,36 @@ import java.util.StringTokenizer; * singleton * Converter used to convert any object to String and back * + * + * com.opensymphony.xwork2.conversion.ConversionPropertiesProcessor + * struts.conversion.properties.processor + * singleton + * Process Properties to create converters + * + * + * com.opensymphony.xwork2.conversion.ConversionPropertiesProcessor + * struts.converter.file.processor + * singleton + * Process -conversion.properties file create converters + * + * + * com.opensymphony.xwork2.conversion.ConversionAnnotationProcessor + * struts.converter.annotation.processor + * singleton + * Process TypeConversion annotation to create converters + * + * + * com.opensymphony.xwork2.conversion.TypeConverterCreator + * struts.converter.creator + * singleton + * Creates user converters + * + * + * com.opensymphony.xwork2.conversion.TypeConverterHolder + * struts.converter.holder + * singleton + * Holds user converters' instances + * * * * @@ -287,8 +322,22 @@ public class BeanSelectionProvider implements ConfigurationProvider { public void register(ContainerBuilder builder, LocatableProperties props) { alias(ObjectFactory.class, StrutsConstants.STRUTS_OBJECTFACTORY, builder, props); alias(FileManagerFactory.class, StrutsConstants.STRUTS_FILE_MANAGER_FACTORY, builder, props, Scope.SINGLETON); + alias(XWorkConverter.class, StrutsConstants.STRUTS_XWORKCONVERTER, builder, props); + alias(CollectionConverter.class, StrutsConstants.STRUTS_CONVERTER_COLLECTION, builder, props); + alias(ArrayConverter.class, StrutsConstants.STRUTS_CONVERTER_ARRAY, builder, props); + alias(DateConverter.class, StrutsConstants.STRUTS_CONVERTER_DATE, builder, props); + alias(NumberConverter.class, StrutsConstants.STRUTS_CONVERTER_NUMBER, builder, props); + alias(StringConverter.class, StrutsConstants.STRUTS_CONVERTER_STRING, builder, props); + + alias(ConversionPropertiesProcessor.class, StrutsConstants.STRUTS_CONVERTER_PROPERTIES_PROCESSOR, builder, props); + alias(ConversionFileProcessor.class, StrutsConstants.STRUTS_CONVERTER_FILE_PROCESSOR, builder, props); + alias(ConversionAnnotationProcessor.class, StrutsConstants.STRUTS_CONVERTER_ANNOTATION_PROCESSOR, builder, props); + alias(TypeConverterCreator.class, StrutsConstants.STRUTS_CONVERTER_CREATOR, builder, props); + alias(TypeConverterHolder.class, StrutsConstants.STRUTS_CONVERTER_HOLDER, builder, props); + alias(TextProvider.class, StrutsConstants.STRUTS_XWORKTEXTPROVIDER, builder, props, Scope.DEFAULT); + alias(LocaleProvider.class, StrutsConstants.STRUTS_LOCALE_PROVIDER, builder, props); alias(ActionProxyFactory.class, StrutsConstants.STRUTS_ACTIONPROXYFACTORY, builder, props); alias(ObjectTypeDeterminer.class, StrutsConstants.STRUTS_OBJECTTYPEDETERMINER, builder, props); @@ -306,12 +355,6 @@ public class BeanSelectionProvider implements ConfigurationProvider { alias(UnknownHandlerManager.class, StrutsConstants.STRUTS_UNKNOWN_HANDLER_MANAGER, builder, props); alias(UrlHelper.class, StrutsConstants.STRUTS_URL_HELPER, builder, props); - alias(CollectionConverter.class, StrutsConstants.STRUTS_CONVERTER_COLLECTION, builder, props); - alias(ArrayConverter.class, StrutsConstants.STRUTS_CONVERTER_ARRAY, builder, props); - alias(DateConverter.class, StrutsConstants.STRUTS_CONVERTER_DATE, builder, props); - alias(NumberConverter.class, StrutsConstants.STRUTS_CONVERTER_NUMBER, builder, props); - alias(StringConverter.class, StrutsConstants.STRUTS_CONVERTER_STRING, builder, props); - if ("true".equalsIgnoreCase(props.getProperty(StrutsConstants.STRUTS_DEVMODE))) { props.setProperty(StrutsConstants.STRUTS_I18N_RELOAD, "true"); props.setProperty(StrutsConstants.STRUTS_CONFIGURATION_XML_RELOAD, "true"); diff --git a/core/src/main/resources/struts-default.xml b/core/src/main/resources/struts-default.xml index 0b6ff544e..8a9691896 100644 --- a/core/src/main/resources/struts-default.xml +++ b/core/src/main/resources/struts-default.xml @@ -65,6 +65,12 @@ + + + + + + diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java index f72a7ece3..911c5a331 100644 --- a/xwork-core/src/main/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java @@ -37,12 +37,22 @@ import com.opensymphony.xwork2.config.entities.ResultConfig; import com.opensymphony.xwork2.config.entities.ResultTypeConfig; import com.opensymphony.xwork2.config.entities.UnknownHandlerConfig; import com.opensymphony.xwork2.config.providers.InterceptorBuilder; +import com.opensymphony.xwork2.conversion.ConversionAnnotationProcessor; +import com.opensymphony.xwork2.conversion.ConversionFileProcessor; +import com.opensymphony.xwork2.conversion.ConversionPropertiesProcessor; import com.opensymphony.xwork2.conversion.ObjectTypeDeterminer; import com.opensymphony.xwork2.conversion.TypeConverter; +import com.opensymphony.xwork2.conversion.TypeConverterCreator; +import com.opensymphony.xwork2.conversion.TypeConverterHolder; import com.opensymphony.xwork2.conversion.impl.ArrayConverter; import com.opensymphony.xwork2.conversion.impl.CollectionConverter; import com.opensymphony.xwork2.conversion.impl.DateConverter; +import com.opensymphony.xwork2.conversion.impl.DefaultConversionAnnotationProcessor; +import com.opensymphony.xwork2.conversion.impl.DefaultConversionFileProcessor; +import com.opensymphony.xwork2.conversion.impl.DefaultConversionPropertiesProcessor; import com.opensymphony.xwork2.conversion.impl.DefaultObjectTypeDeterminer; +import com.opensymphony.xwork2.conversion.impl.DefaultTypeConverterCreator; +import com.opensymphony.xwork2.conversion.impl.DefaultTypeConverterHolder; import com.opensymphony.xwork2.conversion.impl.NumberConverter; import com.opensymphony.xwork2.conversion.impl.StringConverter; import com.opensymphony.xwork2.conversion.impl.XWorkBasicConverter; @@ -289,7 +299,14 @@ public class DefaultConfiguration implements Configuration { } builder.factory(ReflectionProvider.class, OgnlReflectionProvider.class, Scope.SINGLETON); builder.factory(ValueStackFactory.class, OgnlValueStackFactory.class, Scope.SINGLETON); + builder.factory(XWorkConverter.class, Scope.SINGLETON); + builder.factory(ConversionPropertiesProcessor.class, DefaultConversionPropertiesProcessor.class, Scope.SINGLETON); + builder.factory(ConversionFileProcessor.class, DefaultConversionFileProcessor.class, Scope.SINGLETON); + builder.factory(ConversionAnnotationProcessor.class, DefaultConversionAnnotationProcessor.class, Scope.SINGLETON); + builder.factory(TypeConverterCreator.class, DefaultTypeConverterCreator.class, Scope.SINGLETON); + builder.factory(TypeConverterHolder.class, DefaultTypeConverterHolder.class, Scope.SINGLETON); + builder.factory(XWorkBasicConverter.class, Scope.SINGLETON); builder.factory(TypeConverter.class, XWorkConstants.COLLECTION_CONVERTER, CollectionConverter.class, Scope.SINGLETON); builder.factory(TypeConverter.class, XWorkConstants.ARRAY_CONVERTER, ArrayConverter.class, Scope.SINGLETON); diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/config/providers/XWorkConfigurationProvider.java b/xwork-core/src/main/java/com/opensymphony/xwork2/config/providers/XWorkConfigurationProvider.java index 49473423e..ca8074099 100644 --- a/xwork-core/src/main/java/com/opensymphony/xwork2/config/providers/XWorkConfigurationProvider.java +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/config/providers/XWorkConfigurationProvider.java @@ -15,12 +15,22 @@ import com.opensymphony.xwork2.XWorkConstants; import com.opensymphony.xwork2.config.Configuration; import com.opensymphony.xwork2.config.ConfigurationException; import com.opensymphony.xwork2.config.ConfigurationProvider; +import com.opensymphony.xwork2.conversion.ConversionAnnotationProcessor; +import com.opensymphony.xwork2.conversion.ConversionFileProcessor; +import com.opensymphony.xwork2.conversion.ConversionPropertiesProcessor; import com.opensymphony.xwork2.conversion.NullHandler; import com.opensymphony.xwork2.conversion.ObjectTypeDeterminer; +import com.opensymphony.xwork2.conversion.TypeConverterCreator; +import com.opensymphony.xwork2.conversion.TypeConverterHolder; import com.opensymphony.xwork2.conversion.impl.ArrayConverter; import com.opensymphony.xwork2.conversion.impl.CollectionConverter; import com.opensymphony.xwork2.conversion.impl.DateConverter; +import com.opensymphony.xwork2.conversion.impl.DefaultConversionAnnotationProcessor; +import com.opensymphony.xwork2.conversion.impl.DefaultConversionFileProcessor; +import com.opensymphony.xwork2.conversion.impl.DefaultConversionPropertiesProcessor; import com.opensymphony.xwork2.conversion.impl.DefaultObjectTypeDeterminer; +import com.opensymphony.xwork2.conversion.impl.DefaultTypeConverterCreator; +import com.opensymphony.xwork2.conversion.impl.DefaultTypeConverterHolder; import com.opensymphony.xwork2.conversion.impl.InstantiatingNullHandler; import com.opensymphony.xwork2.conversion.impl.NumberConverter; import com.opensymphony.xwork2.conversion.impl.StringConverter; @@ -92,7 +102,15 @@ public class XWorkConfigurationProvider implements ConfigurationProvider { builder.factory(com.opensymphony.xwork2.ObjectFactory.class) .factory(ActionProxyFactory.class, DefaultActionProxyFactory.class, Scope.SINGLETON) .factory(ObjectTypeDeterminer.class, DefaultObjectTypeDeterminer.class, Scope.SINGLETON) + .factory(XWorkConverter.class, Scope.SINGLETON) + .factory(XWorkBasicConverter.class, Scope.SINGLETON) + .factory(ConversionPropertiesProcessor.class, DefaultConversionPropertiesProcessor.class, Scope.SINGLETON) + .factory(ConversionFileProcessor.class, DefaultConversionFileProcessor.class, Scope.SINGLETON) + .factory(ConversionAnnotationProcessor.class, DefaultConversionAnnotationProcessor.class, Scope.SINGLETON) + .factory(TypeConverterCreator.class, DefaultTypeConverterCreator.class, Scope.SINGLETON) + .factory(TypeConverterHolder.class, DefaultTypeConverterHolder.class, Scope.SINGLETON) + .factory(FileManager.class, "system", DefaultFileManager.class, Scope.SINGLETON) .factory(FileManagerFactory.class, DefaultFileManagerFactory.class, Scope.SINGLETON) .factory(ValueStackFactory.class, OgnlValueStackFactory.class, Scope.SINGLETON) @@ -107,18 +125,18 @@ public class XWorkConfigurationProvider implements ConfigurationProvider { .factory(PropertyAccessor.class, Enumeration.class.getName(), XWorkEnumerationAccessor.class, Scope.SINGLETON) .factory(UnknownHandlerManager.class, DefaultUnknownHandlerManager.class, Scope.SINGLETON) - // silly workarounds for ognl since there is no way to flush its caches + // silly workarounds for ognl since there is no way to flush its caches .factory(PropertyAccessor.class, List.class.getName(), XWorkListPropertyAccessor.class, Scope.SINGLETON) .factory(PropertyAccessor.class, ArrayList.class.getName(), XWorkListPropertyAccessor.class, Scope.SINGLETON) .factory(PropertyAccessor.class, HashSet.class.getName(), XWorkCollectionPropertyAccessor.class, Scope.SINGLETON) .factory(PropertyAccessor.class, Set.class.getName(), XWorkCollectionPropertyAccessor.class, Scope.SINGLETON) .factory(PropertyAccessor.class, HashMap.class.getName(), XWorkMapPropertyAccessor.class, Scope.SINGLETON) .factory(PropertyAccessor.class, Map.class.getName(), XWorkMapPropertyAccessor.class, Scope.SINGLETON) - .factory(PropertyAccessor.class, Collection.class.getName(), XWorkCollectionPropertyAccessor.class, Scope.SINGLETON) .factory(PropertyAccessor.class, ObjectProxy.class.getName(), ObjectProxyPropertyAccessor.class, Scope.SINGLETON) .factory(MethodAccessor.class, Object.class.getName(), XWorkMethodAccessor.class, Scope.SINGLETON) .factory(MethodAccessor.class, CompoundRoot.class.getName(), CompoundRootAccessor.class, Scope.SINGLETON) + .factory(NullHandler.class, Object.class.getName(), InstantiatingNullHandler.class, Scope.SINGLETON) .factory(ActionValidatorManager.class, AnnotationActionValidatorManager.class, Scope.SINGLETON) .factory(ActionValidatorManager.class, "no-annotations", DefaultActionValidatorManager.class, Scope.SINGLETON) @@ -126,7 +144,6 @@ public class XWorkConfigurationProvider implements ConfigurationProvider { .factory(TextProvider.class, TextProviderSupport.class, Scope.SINGLETON) .factory(LocaleProvider.class, DefaultLocaleProvider.class, Scope.SINGLETON) .factory(OgnlUtil.class, Scope.SINGLETON) - .factory(XWorkBasicConverter.class, Scope.SINGLETON) .factory(CollectionConverter.class, Scope.SINGLETON) .factory(ArrayConverter.class, Scope.SINGLETON) .factory(DateConverter.class, Scope.SINGLETON) diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/ConversionAnnotationProcessor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/ConversionAnnotationProcessor.java new file mode 100644 index 000000000..159f8d524 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/ConversionAnnotationProcessor.java @@ -0,0 +1,23 @@ +package com.opensymphony.xwork2.conversion; + +import com.opensymphony.xwork2.conversion.annotations.TypeConversion; + +import java.util.Map; + +/** + * Used to process {@link com.opensymphony.xwork2.conversion.annotations.TypeConversion} + * annotation to read defined Converters + */ +public interface ConversionAnnotationProcessor { + + /** + * Process annotation and build {@link TypeConverter} base on provided annotation + * and assigning it under given key + * + * @param mapping keeps converters per given key + * @param tc annotation which keeps information about converter + * @param key key under which converter should be registered + */ + void process(Map mapping, TypeConversion tc, String key); + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/ConversionFileProcessor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/ConversionFileProcessor.java new file mode 100644 index 000000000..8d2803ff5 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/ConversionFileProcessor.java @@ -0,0 +1,19 @@ +package com.opensymphony.xwork2.conversion; + +import java.util.Map; + +/** + * Used to process -conversion.properties file to read defined Converters + */ +public interface ConversionFileProcessor { + + /** + * Process conversion file to create mapping for key (property, type) and corresponding converter + * + * @param mapping keeps converters per given key + * @param clazz class which should be converted by the converter + * @param converterFilename to read converters from + */ + void process(Map mapping, Class clazz, String converterFilename); + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/ConversionPropertiesProcessor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/ConversionPropertiesProcessor.java new file mode 100644 index 000000000..8d0bab841 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/ConversionPropertiesProcessor.java @@ -0,0 +1,22 @@ +package com.opensymphony.xwork2.conversion; + +/** + * Used to read converters from Properties file + */ +public interface ConversionPropertiesProcessor { + + /** + * Process given property to load converters as not required (Properties file doesn't have to exist) + * + * @param propsName Properties file name + */ + void process(String propsName); + + /** + * Process given property to load converters as required (Properties file must exist) + * + * @param propsName Properties file name + */ + void processRequired(String propsName); + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/TypeConverterCreator.java b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/TypeConverterCreator.java new file mode 100644 index 000000000..738bbe82f --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/TypeConverterCreator.java @@ -0,0 +1,17 @@ +package com.opensymphony.xwork2.conversion; + +/** + * Instantiate converter classes, if cannot create TypeConverter throws exception + */ +public interface TypeConverterCreator { + + /** + * Creates {@link TypeConverter} from given class + * + * @param className convert class + * @return instance of {@link TypeConverter} + * @throws Exception when cannot create/cast to {@link TypeConverter} + */ + TypeConverter createTypeConverter(String className) throws Exception; + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/TypeConverterHolder.java b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/TypeConverterHolder.java new file mode 100644 index 000000000..6a67b8bf9 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/TypeConverterHolder.java @@ -0,0 +1,82 @@ +package com.opensymphony.xwork2.conversion; + +import java.util.Map; + +/** + * Holds all mappings related to {@link TypeConverter}s + */ +public interface TypeConverterHolder { + + /** + * Adds mapping for default type converters - application scoped + * + * @param className name of the class with associated converter + * @param typeConverter {@link TypeConverter} instance for associated class + */ + void addDefaultMapping(String className, TypeConverter typeConverter); + + /** + * Checks if converter was already defined for given class + * + * @param className name of the class to check for + * @return true if default mapping was already specified + */ + boolean containsDefaultMapping(String className); + + /** + * Returns instance of {@link TypeConverter} associated with given class + * + * @param className name of the class to return converter for + * @return instance of {@link TypeConverter} to be used to convert class + */ + TypeConverter getDefaultMapping(String className); + + /** + * Target class conversion Mappings. + * + * @param clazz class to convert to/from + * @return {@link TypeConverter} for given class + */ + Map getMapping(Class clazz); + + /** + * Assign mapping of converters for given class + * + * @param clazz class to convert to/from + * @param mapping property converters + */ + void addMapping(Class clazz, Map mapping); + + /** + * Check if there is no mapping for given class to convert + * + * @param clazz class to convert to/from + * @return true if mapping couldn't be found + */ + boolean containsNoMapping(Class clazz); + + /** + * Adds no mapping flag for give class + * + * @param clazz class to register missing converter + */ + void addNoMapping(Class clazz); + + /** + * Checks if no mapping was defined for given class name + * FIXME lukaszlenart: maybe it should be merged with NoMapping + * + * @param className name of the class to check for + * @return true if converter was defined for given class name + */ + boolean containsUnknownMapping(String className); + + /** + * Adds no converter flag for given class name + * FIXME lukaszlenart: maybe it should be merged with NoMapping + * + * @param className name of the class to mark there is no converter for it + */ + void addUnknownMapping(String className); + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultConversionAnnotationProcessor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultConversionAnnotationProcessor.java new file mode 100644 index 000000000..0f1008514 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultConversionAnnotationProcessor.java @@ -0,0 +1,84 @@ +package com.opensymphony.xwork2.conversion.impl; + +import com.opensymphony.xwork2.conversion.ConversionAnnotationProcessor; +import com.opensymphony.xwork2.conversion.TypeConverter; +import com.opensymphony.xwork2.conversion.TypeConverterCreator; +import com.opensymphony.xwork2.conversion.TypeConverterHolder; +import com.opensymphony.xwork2.conversion.annotations.ConversionRule; +import com.opensymphony.xwork2.conversion.annotations.ConversionType; +import com.opensymphony.xwork2.conversion.annotations.TypeConversion; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; + +import java.util.Map; + +/** + * Default implementation of {@link ConversionAnnotationProcessor} + */ +public class DefaultConversionAnnotationProcessor implements ConversionAnnotationProcessor { + + private static final Logger LOG = LoggerFactory.getLogger(DefaultConversionAnnotationProcessor.class); + + private TypeConverterCreator converterCreator; + private TypeConverterHolder converterHolder; + + @Inject + public void setTypeConverterCreator(TypeConverterCreator converterCreator) { + this.converterCreator = converterCreator; + } + + @Inject + public void setTypeConverterHolder(TypeConverterHolder converterHolder) { + this.converterHolder = converterHolder; + } + + public void process(Map mapping, TypeConversion tc, String key) { + if (LOG.isDebugEnabled()) { + LOG.debug("TypeConversion [#0] with key: [#1]", tc.converter(), key); + } + if (key == null) { + return; + } + try { + if (tc.type() == ConversionType.APPLICATION) { + converterHolder.addDefaultMapping(key, converterCreator.createTypeConverter(tc.converter())); + } else { + if (tc.rule() == ConversionRule.KEY_PROPERTY || tc.rule() == ConversionRule.CREATE_IF_NULL) { + mapping.put(key, tc.value()); + } + //for properties of classes + else if (tc.rule() != ConversionRule.ELEMENT || tc.rule() == ConversionRule.KEY || tc.rule() == ConversionRule.COLLECTION) { + mapping.put(key, converterCreator.createTypeConverter(tc.converter())); + } + //for keys of Maps + else if (tc.rule() == ConversionRule.KEY) { + Class converterClass = Thread.currentThread().getContextClassLoader().loadClass(tc.converter()); + if (LOG.isDebugEnabled()) { + LOG.debug("Converter class: [#0]", converterClass); + } + //check if the converter is a type converter if it is one + //then just put it in the map as is. Otherwise + //put a value in for the type converter of the class + if (converterClass.isAssignableFrom(TypeConverter.class)) { + mapping.put(key, converterCreator.createTypeConverter(tc.converter())); + } else { + mapping.put(key, converterClass); + if (LOG.isDebugEnabled()) { + LOG.debug("Object placed in mapping for key [#0] is [#1]", key, mapping.get(key)); + } + } + } + //elements(values) of maps / lists + else { + mapping.put(key, Thread.currentThread().getContextClassLoader().loadClass(tc.converter())); + } + } + } catch (Exception e) { + if (LOG.isDebugEnabled()) { + LOG.debug("Got exception for #0", e, key); + } + } + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultConversionFileProcessor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultConversionFileProcessor.java new file mode 100644 index 000000000..4537b0e12 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultConversionFileProcessor.java @@ -0,0 +1,112 @@ +package com.opensymphony.xwork2.conversion.impl; + +import com.opensymphony.xwork2.FileManager; +import com.opensymphony.xwork2.FileManagerFactory; +import com.opensymphony.xwork2.conversion.ConversionFileProcessor; +import com.opensymphony.xwork2.conversion.TypeConverter; +import com.opensymphony.xwork2.conversion.TypeConverterCreator; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.ClassLoaderUtil; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; + +import java.io.InputStream; +import java.util.Map; +import java.util.Properties; + +/** + * Default implementation of {@link ConversionFileProcessor} + */ +public class DefaultConversionFileProcessor implements ConversionFileProcessor { + + private static final Logger LOG = LoggerFactory.getLogger(DefaultConversionFileProcessor.class); + + private FileManager fileManager; + private TypeConverterCreator converterCreator; + + @Inject + public void setFileManagerFactory(FileManagerFactory factory) { + fileManager = factory.getFileManager(); + } + + @Inject + public void setTypeConverterCreator(TypeConverterCreator converterCreator) { + this.converterCreator = converterCreator; + } + + public void process(Map mapping, Class clazz, String converterFilename) { + try { + InputStream is = fileManager.loadFile(ClassLoaderUtil.getResource(converterFilename, clazz)); + + if (is != null) { + if (LOG.isDebugEnabled()) { + LOG.debug("Processing conversion file [#0] for class [#1]", converterFilename, clazz); + } + + Properties prop = new Properties(); + prop.load(is); + + for (Map.Entry entry : prop.entrySet()) { + String key = (String) entry.getKey(); + + if (mapping.containsKey(key)) { + break; + } + // for keyProperty of Set + if (key.startsWith(DefaultObjectTypeDeterminer.KEY_PROPERTY_PREFIX) + || key.startsWith(DefaultObjectTypeDeterminer.CREATE_IF_NULL_PREFIX)) { + if (LOG.isDebugEnabled()) { + LOG.debug("\t" + key + ":" + entry.getValue() + "[treated as String]"); + } + mapping.put(key, entry.getValue()); + } + //for properties of classes + else if (!(key.startsWith(DefaultObjectTypeDeterminer.ELEMENT_PREFIX) || + key.startsWith(DefaultObjectTypeDeterminer.KEY_PREFIX) || + key.startsWith(DefaultObjectTypeDeterminer.DEPRECATED_ELEMENT_PREFIX)) + ) { + TypeConverter _typeConverter = converterCreator.createTypeConverter((String) entry.getValue()); + if (LOG.isDebugEnabled()) { + LOG.debug("\t" + key + ":" + entry.getValue() + "[treated as TypeConverter " + _typeConverter + "]"); + } + mapping.put(key, _typeConverter); + } + //for keys of Maps + else if (key.startsWith(DefaultObjectTypeDeterminer.KEY_PREFIX)) { + + Class converterClass = Thread.currentThread().getContextClassLoader().loadClass((String) entry.getValue()); + + //check if the converter is a type converter if it is one + //then just put it in the map as is. Otherwise + //put a value in for the type converter of the class + if (converterClass.isAssignableFrom(TypeConverter.class)) { + TypeConverter _typeConverter = converterCreator.createTypeConverter((String) entry.getValue()); + if (LOG.isDebugEnabled()) { + LOG.debug("\t" + key + ":" + entry.getValue() + "[treated as TypeConverter " + _typeConverter + "]"); + } + mapping.put(key, _typeConverter); + } else { + if (LOG.isDebugEnabled()) { + LOG.debug("\t" + key + ":" + entry.getValue() + "[treated as Class " + converterClass + "]"); + } + mapping.put(key, converterClass); + } + } + //elements(values) of maps / lists + else { + Class _c = Thread.currentThread().getContextClassLoader().loadClass((String) entry.getValue()); + if (LOG.isDebugEnabled()) { + LOG.debug("\t" + key + ":" + entry.getValue() + "[treated as Class " + _c + "]"); + } + mapping.put(key, _c); + } + } + } + } catch (Exception ex) { + if (LOG.isErrorEnabled()) { + LOG.error("Problem loading properties for #0", ex, clazz.getName()); + } + } + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultConversionPropertiesProcessor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultConversionPropertiesProcessor.java new file mode 100644 index 000000000..49c29bb1e --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultConversionPropertiesProcessor.java @@ -0,0 +1,85 @@ +package com.opensymphony.xwork2.conversion.impl; + +import com.opensymphony.xwork2.XWorkException; +import com.opensymphony.xwork2.conversion.ConversionPropertiesProcessor; +import com.opensymphony.xwork2.conversion.TypeConverter; +import com.opensymphony.xwork2.conversion.TypeConverterCreator; +import com.opensymphony.xwork2.conversion.TypeConverterHolder; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.ClassLoaderUtil; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; + +import java.io.IOException; +import java.net.URL; +import java.util.Iterator; +import java.util.Map; +import java.util.Properties; + +/** + * TODO lukaszlenart: add a comment + */ +public class DefaultConversionPropertiesProcessor implements ConversionPropertiesProcessor { + + private static final Logger LOG = LoggerFactory.getLogger(DefaultConversionPropertiesProcessor.class); + + private TypeConverterCreator converterCreator; + private TypeConverterHolder converterHolder; + + @Inject + public void setTypeConverterCreator(TypeConverterCreator converterCreator) { + this.converterCreator = converterCreator; + } + + @Inject + public void setTypeConverterHolder(TypeConverterHolder converterHolder) { + this.converterHolder = converterHolder; + } + + public void process(String propsName) { + loadConversionProperties(propsName, false); + } + + public void processRequired(String propsName) { + loadConversionProperties(propsName, true); + } + + public void loadConversionProperties(String propsName, boolean require) { + try { + Iterator resources = ClassLoaderUtil.getResources(propsName, getClass(), true); + while (resources.hasNext()) { + URL url = resources.next(); + Properties props = new Properties(); + props.load(url.openStream()); + + if (LOG.isDebugEnabled()) { + LOG.debug("processing conversion file [" + propsName + "]"); + } + + for (Object o : props.entrySet()) { + Map.Entry entry = (Map.Entry) o; + String key = (String) entry.getKey(); + + try { + TypeConverter _typeConverter = converterCreator.createTypeConverter((String) entry.getValue()); + if (LOG.isDebugEnabled()) { + LOG.debug("\t" + key + ":" + entry.getValue() + " [treated as TypeConverter " + _typeConverter + "]"); + } + converterHolder.addDefaultMapping(key, _typeConverter); + } catch (Exception e) { + LOG.error("Conversion registration error", e); + } + } + } + } catch (IOException ex) { + if (require) { + throw new XWorkException("Cannot load conversion properties file: "+propsName, ex); + } else { + if (LOG.isDebugEnabled()) { + LOG.debug("Cannot load conversion properties file: #0", ex, propsName); + } + } + } + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultTypeConverterCreator.java b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultTypeConverterCreator.java new file mode 100644 index 000000000..ed6ac2265 --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultTypeConverterCreator.java @@ -0,0 +1,35 @@ +package com.opensymphony.xwork2.conversion.impl; + +import com.opensymphony.xwork2.ObjectFactory; +import com.opensymphony.xwork2.conversion.TypeConverter; +import com.opensymphony.xwork2.conversion.TypeConverterCreator; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.ognl.XWorkTypeConverterWrapper; + +/** + * Default implementation of {@link TypeConverterCreator} + */ +public class DefaultTypeConverterCreator implements TypeConverterCreator { + + private ObjectFactory objectFactory; + + @Inject + public void setObjectFactory(ObjectFactory objectFactory) { + this.objectFactory = objectFactory; + } + + public TypeConverter createTypeConverter(String className) throws Exception { + // type converters are used across users + Object obj = objectFactory.buildBean(className, null); + if (obj instanceof TypeConverter) { + return (TypeConverter) obj; + + // For backwards compatibility + } else if (obj instanceof ognl.TypeConverter) { + return new XWorkTypeConverterWrapper((ognl.TypeConverter) obj); + } else { + throw new IllegalArgumentException("Type converter class " + obj.getClass() + " doesn't implement com.opensymphony.xwork2.conversion.TypeConverter"); + } + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultTypeConverterHolder.java b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultTypeConverterHolder.java new file mode 100644 index 000000000..b547f05ac --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultTypeConverterHolder.java @@ -0,0 +1,97 @@ +package com.opensymphony.xwork2.conversion.impl; + +import com.opensymphony.xwork2.conversion.TypeConverter; +import com.opensymphony.xwork2.conversion.TypeConverterHolder; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; + +/** + * Default implementation of {@link TypeConverterHolder} + */ +public class DefaultTypeConverterHolder implements TypeConverterHolder { + + /** + * Record class and its type converter mapping. + *
+     * - String - classname as String
+     * - TypeConverter - instance of TypeConverter
+     * 
+ */ + private HashMap defaultMappings = new HashMap(); // non-action (eg. returned value) + + /** + * Target class conversion Mappings. + *
+     * Map>
+     *  - Class -> convert to class
+     *  - Map
+     *    - String -> property name
+     *                eg. Element_property, property etc.
+     *    - Object -> String to represent properties
+     *                eg. value part of
+     *                    KeyProperty_property=id
+     *             -> TypeConverter to represent an Ognl TypeConverter
+     *                eg. value part of
+     *                    property=foo.bar.MyConverter
+     *             -> Class to represent a class
+     *                eg. value part of
+     *                    Element_property=foo.bar.MyObject
+     * 
+ */ + private HashMap> mappings = new HashMap>(); // action + + /** + * Unavailable target class conversion mappings, serves as a simple cache. + */ + private HashSet noMapping = new HashSet(); // action + + /** + * Record classes that doesn't have conversion mapping defined. + *
+     * - String -> classname as String
+     * 
+ */ + protected HashSet unknownMappings = new HashSet(); // non-action (eg. returned value) + + public void addDefaultMapping(String className, TypeConverter typeConverter) { + defaultMappings.put(className, typeConverter); + if (unknownMappings.contains(className)) { + unknownMappings.remove(className); + } + } + + public boolean containsDefaultMapping(String className) { + return defaultMappings.containsKey(className); + } + + public TypeConverter getDefaultMapping(String className) { + return defaultMappings.get(className); + } + + public Map getMapping(Class clazz) { + return mappings.get(clazz); + } + + public void addMapping(Class clazz, Map mapping) { + mappings.put(clazz, mapping); + } + + public boolean containsNoMapping(Class clazz) { + return noMapping.contains(clazz); + } + + public void addNoMapping(Class clazz) { + noMapping.add(clazz); + } + + public boolean containsUnknownMapping(String className) { + return unknownMappings.contains(className); + } + + public void addUnknownMapping(String className) { + unknownMappings.add(className); + } + +} diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/impl/XWorkConverter.java b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/impl/XWorkConverter.java index d80996432..67f0ba7e2 100644 --- a/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/impl/XWorkConverter.java +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/conversion/impl/XWorkConverter.java @@ -18,17 +18,16 @@ package com.opensymphony.xwork2.conversion.impl; import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.FileManager; import com.opensymphony.xwork2.FileManagerFactory; -import com.opensymphony.xwork2.ObjectFactory; import com.opensymphony.xwork2.XWorkConstants; -import com.opensymphony.xwork2.XWorkException; import com.opensymphony.xwork2.XWorkMessages; +import com.opensymphony.xwork2.conversion.ConversionAnnotationProcessor; +import com.opensymphony.xwork2.conversion.ConversionFileProcessor; +import com.opensymphony.xwork2.conversion.ConversionPropertiesProcessor; import com.opensymphony.xwork2.conversion.TypeConverter; +import com.opensymphony.xwork2.conversion.TypeConverterHolder; import com.opensymphony.xwork2.conversion.annotations.Conversion; -import com.opensymphony.xwork2.conversion.annotations.ConversionRule; -import com.opensymphony.xwork2.conversion.annotations.ConversionType; import com.opensymphony.xwork2.conversion.annotations.TypeConversion; import com.opensymphony.xwork2.inject.Inject; -import com.opensymphony.xwork2.ognl.XWorkTypeConverterWrapper; import com.opensymphony.xwork2.util.AnnotationUtils; import com.opensymphony.xwork2.util.ClassLoaderUtil; import com.opensymphony.xwork2.util.CompoundRoot; @@ -39,8 +38,6 @@ import com.opensymphony.xwork2.util.logging.LoggerFactory; import com.opensymphony.xwork2.util.reflection.ReflectionContextState; import org.apache.commons.lang3.StringUtils; -import java.io.IOException; -import java.io.InputStream; import java.lang.annotation.Annotation; import java.lang.reflect.Member; import java.lang.reflect.Method; @@ -48,11 +45,8 @@ import java.net.URL; import java.text.MessageFormat; import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -137,7 +131,8 @@ import java.util.regex.Pattern; */ public class XWorkConverter extends DefaultTypeConverter { - protected static final Logger LOG = LoggerFactory.getLogger(XWorkConverter.class); + private static final Logger LOG = LoggerFactory.getLogger(XWorkConverter.class); + public static final String REPORT_CONVERSION_ERRORS = "report.conversion.errors"; public static final String CONVERSION_PROPERTY_FULLNAME = "conversion.property.fullName"; public static final String CONVERSION_ERROR_PROPERTY_PREFIX = "invalid.fieldvalue."; @@ -148,68 +143,20 @@ public class XWorkConverter extends DefaultTypeConverter { public static final String MESSAGE_INDEX_PATTERN = "\\[\\d+\\]\\."; public static final String MESSAGE_INDEX_BRACKET_PATTERN = "[\\[\\]\\.]"; public static final String PERIOD = "."; - public static final Pattern messageIndexPattern = Pattern.compile(MESSAGE_INDEX_PATTERN); - - /** - * Target class conversion Mappings. - *
-     * Map>
-     *  - Class -> convert to class
-     *  - Map
-     *    - String -> property name
-     *                eg. Element_property, property etc.
-     *    - Object -> String to represent properties
-     *                eg. value part of
-     *                    KeyProperty_property=id
-     *             -> TypeConverter to represent an Ognl TypeConverter
-     *                eg. value part of
-     *                    property=foo.bar.MyConverter
-     *             -> Class to represent a class
-     *                eg. value part of
-     *                    Element_property=foo.bar.MyObject
-     * 
- */ - protected HashMap> mappings = new HashMap>(); // action - - /** - * Unavailable target class conversion mappings, serves as a simple cache. - */ - protected HashSet noMapping = new HashSet(); // action - - /** - * Record class and its type converter mapping. - *
-     * - String - classname as String
-     * - TypeConverter - instance of TypeConverter
-     * 
- */ - protected HashMap defaultMappings = new HashMap(); // non-action (eg. returned value) - - /** - * Record classes that doesn't have conversion mapping defined. - *
-     * - String -> classname as String
-     * 
- */ - protected HashSet unknownMappings = new HashSet(); // non-action (eg. returned value) + public static final Pattern messageIndexPattern = Pattern.compile(MESSAGE_INDEX_PATTERN); private TypeConverter defaultTypeConverter; - private ObjectFactory objectFactory; private FileManager fileManager; private boolean reloadingConfigs; + private ConversionFileProcessor fileProcessor; + private ConversionAnnotationProcessor annotationProcessor; + + private TypeConverterHolder converterHolder; + protected XWorkConverter() { } - @Inject - public void setObjectFactory(ObjectFactory factory) { - this.objectFactory = factory; - // note: this file is deprecated - loadConversionProperties("xwork-default-conversion.properties"); - - loadConversionProperties("xwork-conversion.properties"); - } - @Inject public void setDefaultTypeConverter(XWorkBasicConverter conv) { this.defaultTypeConverter = conv; @@ -225,6 +172,28 @@ public class XWorkConverter extends DefaultTypeConverter { this.reloadingConfigs = Boolean.parseBoolean(reloadingConfigs); } + @Inject + public void setConversionPropertiesProcessor(ConversionPropertiesProcessor propertiesProcessor) { + // note: this file is deprecated + propertiesProcessor.process("xwork-default-conversion.properties"); + propertiesProcessor.process("xwork-conversion.properties"); + } + + @Inject + public void setConversionFileProcessor(ConversionFileProcessor fileProcessor) { + this.fileProcessor = fileProcessor; + } + + @Inject + public void setConversionAnnotationProcessor(ConversionAnnotationProcessor annotationProcessor) { + this.annotationProcessor = annotationProcessor; + } + + @Inject + public void setTypeConverterHolder(TypeConverterHolder converterHolder) { + this.converterHolder = converterHolder; + } + public static String getConversionErrorMessage(String propertyName, ValueStack stack) { String defaultMessage = LocalizedTextUtil.findDefaultText(XWorkMessages.DEFAULT_INVALID_FIELDVALUE, ActionContext.getContext().getLocale(), @@ -234,7 +203,7 @@ public class XWorkConverter extends DefaultTypeConverter { List indexValues = getIndexValues(propertyName); - propertyName = removeAllIndexesInProperyName(propertyName); + propertyName = removeAllIndexesInProperytName(propertyName); String getTextExpression = "getText('" + CONVERSION_ERROR_PROPERTY_PREFIX + propertyName + "','" + defaultMessage + "')"; String message = (String) stack.findValue(getTextExpression); @@ -248,7 +217,7 @@ public class XWorkConverter extends DefaultTypeConverter { return message; } - private static String removeAllIndexesInProperyName(String propertyName) { + private static String removeAllIndexesInProperytName(String propertyName) { return propertyName.replaceAll(MESSAGE_INDEX_PATTERN, PERIOD); } @@ -386,11 +355,11 @@ public class XWorkConverter extends DefaultTypeConverter { * @return a TypeConverter to handle the specified class or null if none can be found */ public TypeConverter lookup(String className) { - if (unknownMappings.contains(className) && !defaultMappings.containsKey(className)) { + if (converterHolder.containsUnknownMapping(className) && !converterHolder.containsDefaultMapping(className)) { return null; } - TypeConverter result = defaultMappings.get(className); + TypeConverter result = converterHolder.getDefaultMapping(className); //Looks for super classes if (result == null) { @@ -430,13 +399,12 @@ public class XWorkConverter extends DefaultTypeConverter { protected Object getConverter(Class clazz, String property) { if (LOG.isDebugEnabled()) { - LOG.debug("Property: " + property); - LOG.debug("Class: " + clazz.getName()); + LOG.debug("Retrieving convert for class [#0] and property [#1]", clazz, property); } synchronized (clazz) { - if ((property != null) && !noMapping.contains(clazz)) { + if ((property != null) && !converterHolder.containsNoMapping(clazz)) { try { - Map mapping = mappings.get(clazz); + Map mapping = converterHolder.getMapping(clazz); if (mapping == null) { mapping = buildConverterMapping(clazz); @@ -446,7 +414,7 @@ public class XWorkConverter extends DefaultTypeConverter { Object converter = mapping.get(property); if (LOG.isDebugEnabled() && converter == null) { - LOG.debug("converter is null for property " + property + ". Mapping size: " + mapping.size()); + LOG.debug("Converter is null for property [#0]. Mapping size [#1]:", property, mapping.size()); for (String next : mapping.keySet()) { LOG.debug(next + ":" + mapping.get(next)); } @@ -454,13 +422,12 @@ public class XWorkConverter extends DefaultTypeConverter { return converter; } catch (Throwable t) { if (LOG.isDebugEnabled()) { - LOG.debug("Got exception trying to resolve convert for class [#0] and property [#1]", t, clazz.getName(), property); + LOG.debug("Got exception trying to resolve convert for class [#0] and property [#1]", t, clazz, property); } - noMapping.add(clazz); + converterHolder.addNoMapping(clazz); } } } - return null; } @@ -485,14 +452,11 @@ public class XWorkConverter extends DefaultTypeConverter { } public synchronized void registerConverter(String className, TypeConverter converter) { - defaultMappings.put(className, converter); - if (unknownMappings.contains(className)) { - unknownMappings.remove(className); - } + converterHolder.addDefaultMapping(className, converter); } public synchronized void registerConverterNotFound(String className) { - unknownMappings.add(className); + converterHolder.addUnknownMapping(className); } private Object[] getClassProperty(Map context) { @@ -509,79 +473,9 @@ public class XWorkConverter extends DefaultTypeConverter { * @param clazz class to look for converter mappings for */ protected void addConverterMapping(Map mapping, Class clazz) { - try { - String converterFilename = buildConverterFilename(clazz); - InputStream is = fileManager.loadFile(ClassLoaderUtil.getResource(converterFilename, clazz)); - - if (is != null) { - if (LOG.isDebugEnabled()) { - LOG.debug("processing conversion file [" + converterFilename + "] [class=" + clazz + "]"); - } - - Properties prop = new Properties(); - prop.load(is); - - for (Map.Entry entry : prop.entrySet()) { - String key = (String) entry.getKey(); - - if (mapping.containsKey(key)) { - break; - } - // for keyProperty of Set - if (key.startsWith(DefaultObjectTypeDeterminer.KEY_PROPERTY_PREFIX) - || key.startsWith(DefaultObjectTypeDeterminer.CREATE_IF_NULL_PREFIX)) { - if (LOG.isDebugEnabled()) { - LOG.debug("\t" + key + ":" + entry.getValue() + "[treated as String]"); - } - mapping.put(key, entry.getValue()); - } - //for properties of classes - else if (!(key.startsWith(DefaultObjectTypeDeterminer.ELEMENT_PREFIX) || - key.startsWith(DefaultObjectTypeDeterminer.KEY_PREFIX) || - key.startsWith(DefaultObjectTypeDeterminer.DEPRECATED_ELEMENT_PREFIX)) - ) { - TypeConverter _typeConverter = createTypeConverter((String) entry.getValue()); - if (LOG.isDebugEnabled()) { - LOG.debug("\t" + key + ":" + entry.getValue() + "[treated as TypeConverter " + _typeConverter + "]"); - } - mapping.put(key, _typeConverter); - } - //for keys of Maps - else if (key.startsWith(DefaultObjectTypeDeterminer.KEY_PREFIX)) { - - Class converterClass = Thread.currentThread().getContextClassLoader().loadClass((String) entry.getValue()); - - //check if the converter is a type converter if it is one - //then just put it in the map as is. Otherwise - //put a value in for the type converter of the class - if (converterClass.isAssignableFrom(TypeConverter.class)) { - TypeConverter _typeConverter = createTypeConverter((String) entry.getValue()); - if (LOG.isDebugEnabled()) { - LOG.debug("\t" + key + ":" + entry.getValue() + "[treated as TypeConverter " + _typeConverter + "]"); - } - mapping.put(key, _typeConverter); - } else { - if (LOG.isDebugEnabled()) { - LOG.debug("\t" + key + ":" + entry.getValue() + "[treated as Class " + converterClass + "]"); - } - mapping.put(key, converterClass); - } - } - //elements(values) of maps / lists - else { - Class _c = Thread.currentThread().getContextClassLoader().loadClass((String) entry.getValue()); - if (LOG.isDebugEnabled()) { - LOG.debug("\t" + key + ":" + entry.getValue() + "[treated as Class " + _c + "]"); - } - mapping.put(key, _c); - } - } - } - } catch (Exception ex) { - if (LOG.isErrorEnabled()) { - LOG.error("Problem loading properties for #0", ex, clazz.getName()); - } - } + // Process -conversion.properties file + String converterFilename = buildConverterFilename(clazz); + fileProcessor.process(mapping, clazz, converterFilename); // Process annotations Annotation[] annotations = clazz.getAnnotations(); @@ -589,156 +483,46 @@ public class XWorkConverter extends DefaultTypeConverter { for (Annotation annotation : annotations) { if (annotation instanceof Conversion) { Conversion conversion = (Conversion) annotation; - for (TypeConversion tc : conversion.conversions()) { - - String key = tc.key(); - - if (mapping.containsKey(key)) { + if (mapping.containsKey(tc.key())) { break; } if (LOG.isDebugEnabled()) { - if (StringUtils.isEmpty(key)) { + if (StringUtils.isEmpty(tc.key())) { LOG.debug("WARNING! key of @TypeConversion [#0] applied to [#1] is empty!", tc.converter(), clazz.getName()); } else { - LOG.debug("TypeConversion [#0] with key: [#1]", tc.converter(), key); - } - } - - if (key != null) { - try { - if (tc.type() == ConversionType.APPLICATION) { - defaultMappings.put(key, createTypeConverter(tc.converter())); - } else { - if (tc.rule().toString().equals(ConversionRule.KEY_PROPERTY) || tc.rule().toString().equals(ConversionRule.CREATE_IF_NULL)) { - mapping.put(key, tc.value()); - } - //for properties of classes - else if (!(tc.rule().toString().equals(ConversionRule.ELEMENT.toString())) || - tc.rule().toString().equals(ConversionRule.KEY.toString()) || - tc.rule().toString().equals(ConversionRule.COLLECTION.toString()) - ) { - mapping.put(key, createTypeConverter(tc.converter())); - - - } - //for keys of Maps - else if (tc.rule().toString().equals(ConversionRule.KEY.toString())) { - Class converterClass = Thread.currentThread().getContextClassLoader().loadClass(tc.converter()); - if (LOG.isDebugEnabled()) { - LOG.debug("Converter class: " + converterClass); - } - //check if the converter is a type converter if it is one - //then just put it in the map as is. Otherwise - //put a value in for the type converter of the class - if (converterClass.isAssignableFrom(TypeConverter.class)) { - mapping.put(key, createTypeConverter(tc.converter())); - } else { - mapping.put(key, converterClass); - if (LOG.isDebugEnabled()) { - LOG.debug("Object placed in mapping for key " - + key - + " is " - + mapping.get(key)); - } - - } - - } - //elements(values) of maps / lists - else { - mapping.put(key, Thread.currentThread().getContextClassLoader().loadClass(tc.converter())); - } - } - } catch (Exception e) { - if (LOG.isDebugEnabled()) { - LOG.debug("Got exception for #0", e, key); - } + LOG.debug("TypeConversion [#0] with key: [#1]", tc.converter(), tc.key()); } } + annotationProcessor.process(mapping, tc, tc.key()); } } } - Method[] methods = clazz.getMethods(); - - for (Method method : methods) { - + // Process annotated methods + for (Method method : clazz.getMethods()) { annotations = method.getAnnotations(); - for (Annotation annotation : annotations) { if (annotation instanceof TypeConversion) { TypeConversion tc = (TypeConversion) annotation; - - String key = tc.key(); - if (mapping.containsKey(key)) { + if (mapping.containsKey(tc.key())) { break; } + String key = tc.key(); // Default to the property name - if (key != null && key.length() == 0) { + if (StringUtils.isEmpty(key)) { key = AnnotationUtils.resolvePropertyName(method); - LOG.debug("key from method name... " + key + " - " + method.getName()); - } - - - if (LOG.isDebugEnabled()) { - LOG.debug(key + ":" + key); - } - - if (key != null) { - try { - if (tc.type() == ConversionType.APPLICATION) { - defaultMappings.put(key, createTypeConverter(tc.converter())); - } else { - if (tc.rule().toString().equals(ConversionRule.KEY_PROPERTY)) { - mapping.put(key, tc.value()); - } - //for properties of classes - else if (!(tc.rule().toString().equals(ConversionRule.ELEMENT.toString())) || - tc.rule().toString().equals(ConversionRule.KEY.toString()) || - tc.rule().toString().equals(ConversionRule.COLLECTION.toString()) - ) { - mapping.put(key, createTypeConverter(tc.converter())); - } - //for keys of Maps - else if (tc.rule().toString().equals(ConversionRule.KEY.toString())) { - Class converterClass = Thread.currentThread().getContextClassLoader().loadClass(tc.converter()); - if (LOG.isDebugEnabled()) { - LOG.debug("Converter class: " + converterClass); - } - //check if the converter is a type converter if it is one - //then just put it in the map as is. Otherwise - //put a value in for the type converter of the class - if (converterClass.isAssignableFrom(TypeConverter.class)) { - mapping.put(key, createTypeConverter(tc.converter())); - } else { - mapping.put(key, converterClass); - if (LOG.isDebugEnabled()) { - LOG.debug("Object placed in mapping for key " - + key - + " is " - + mapping.get(key)); - } - - } - - } - //elements(values) of maps / lists - else { - mapping.put(key, Thread.currentThread().getContextClassLoader().loadClass(tc.converter())); - } - } - } catch (Exception e) { - if (LOG.isDebugEnabled()) { - LOG.debug("Got exception for #0", e, key); - } + if (LOG.isDebugEnabled()) { + LOG.debug("Retrieved key [#0] from method name [#1]", key, method.getName()); } } + annotationProcessor.process(mapping, tc, key); } } } } + /** * Looks for converter mappings for the specified class, traversing up its class hierarchy and interfaces and adding * any additional mappings it may find. Mappings lower in the hierarchy have priority over those higher in the @@ -768,9 +552,9 @@ public class XWorkConverter extends DefaultTypeConverter { } if (mapping.size() > 0) { - mappings.put(clazz, mapping); + converterHolder.addMapping(clazz, mapping); } else { - noMapping.add(clazz); + converterHolder.addNoMapping(clazz); } return mapping; @@ -789,62 +573,6 @@ public class XWorkConverter extends DefaultTypeConverter { return mapping; } - TypeConverter createTypeConverter(String className) throws Exception { - // type converters are used across users - Object obj = objectFactory.buildBean(className, null); - if (obj instanceof TypeConverter) { - return (TypeConverter) obj; - - // For backwards compatibility - } else if (obj instanceof ognl.TypeConverter) { - return new XWorkTypeConverterWrapper((ognl.TypeConverter) obj); - } else { - throw new IllegalArgumentException("Type converter class " + obj.getClass() + " doesn't implement com.opensymphony.xwork2.conversion.TypeConverter"); - } - } - - public void loadConversionProperties(String propsName) { - loadConversionProperties(propsName, false); - } - - public void loadConversionProperties(String propsName, boolean require) { - try { - Iterator resources = ClassLoaderUtil.getResources(propsName, getClass(), true); - while (resources.hasNext()) { - URL url = resources.next(); - Properties props = new Properties(); - props.load(url.openStream()); - - if (LOG.isDebugEnabled()) { - LOG.debug("processing conversion file [" + propsName + "]"); - } - - for (Object o : props.entrySet()) { - Map.Entry entry = (Map.Entry) o; - String key = (String) entry.getKey(); - - try { - TypeConverter _typeConverter = createTypeConverter((String) entry.getValue()); - if (LOG.isDebugEnabled()) { - LOG.debug("\t" + key + ":" + entry.getValue() + " [treated as TypeConverter " + _typeConverter + "]"); - } - defaultMappings.put(key, _typeConverter); - } catch (Exception e) { - LOG.error("Conversion registration error", e); - } - } - } - } catch (IOException ex) { - if (require) { - throw new XWorkException("Cannot load conversion properties file: "+propsName, ex); - } else { - if (LOG.isDebugEnabled()) { - LOG.debug("Cannot load conversion properties file: #0", ex, propsName); - } - } - } - } - /** * Recurses through a class' interfaces and class hierarchy looking for a TypeConverter in the default mapping that * can handle the specified class. @@ -856,15 +584,15 @@ public class XWorkConverter extends DefaultTypeConverter { TypeConverter result = null; if (clazz != null) { - result = defaultMappings.get(clazz.getName()); + result = converterHolder.getDefaultMapping(clazz.getName()); if (result == null) { // Looks for direct interfaces (depth = 1 ) Class[] interfaces = clazz.getInterfaces(); for (Class anInterface : interfaces) { - if (defaultMappings.containsKey(anInterface.getName())) { - result = (TypeConverter) defaultMappings.get(anInterface.getName()); + if (converterHolder.containsDefaultMapping(anInterface.getName())) { + result = converterHolder.getDefaultMapping(anInterface.getName()); break; } } @@ -880,5 +608,4 @@ public class XWorkConverter extends DefaultTypeConverter { return result; } - }