From 282a73123223b9ab0b2e1f50f42631faa1e7ea0e Mon Sep 17 00:00:00 2001 From: dsyer Date: Thu, 23 Aug 2007 13:22:55 +0000 Subject: [PATCH] RSOLVED - issue BATCH-109: Possibly include BeanWrapperFieldSetMapper.setClassName() to not have to create a prototype in an app.ctx http://opensource.atlassian.com/projects/spring/browse/BATCH-109 --- .../mapping/BeanWrapperFieldSetMapper.java | 99 +++++++++++++++---- .../BeanWrapperFieldSetMapperTests.java | 33 +++++++ 2 files changed, 111 insertions(+), 21 deletions(-) diff --git a/infrastructure/src/main/java/org/springframework/batch/io/file/support/mapping/BeanWrapperFieldSetMapper.java b/infrastructure/src/main/java/org/springframework/batch/io/file/support/mapping/BeanWrapperFieldSetMapper.java index 88cbaf658..9a2d1d70d 100644 --- a/infrastructure/src/main/java/org/springframework/batch/io/file/support/mapping/BeanWrapperFieldSetMapper.java +++ b/infrastructure/src/main/java/org/springframework/batch/io/file/support/mapping/BeanWrapperFieldSetMapper.java @@ -34,13 +34,15 @@ import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.InitializingBean; import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; /** * {@link FieldSetMapper} implementation based on bean property paths. The * {@link FieldSet} to be mapped should have field name meta data corresponding * to bean property paths in a prototype instance of the desired type. The - * prototype instance is initialized by referring to to object by bean name in - * the enclosing BeanFactory.
+ * prototype instance is initialized either by referring to to object by bean + * name in the enclosing BeanFactory, or by providing a class to instantiate + * reflectively.
* * Nested property paths, including indexed properties in maps and collections, * can be referenced by the {@link FieldSet} names. They will be converted to @@ -70,10 +72,13 @@ import org.springframework.util.Assert; * @author Dave Syer * */ -public class BeanWrapperFieldSetMapper implements FieldSetMapper, BeanFactoryAware, InitializingBean { +public class BeanWrapperFieldSetMapper implements FieldSetMapper, + BeanFactoryAware, InitializingBean { private String name; + private Class type; + private BeanFactory beanFactory; private static Map propertiesMatched = new HashMap(); @@ -82,6 +87,7 @@ public class BeanWrapperFieldSetMapper implements FieldSetMapper, BeanFactoryAwa /* * (non-Javadoc) + * * @see org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(org.springframework.beans.factory.BeanFactory) */ public void setBeanFactory(BeanFactory beanFactory) { @@ -94,32 +100,80 @@ public class BeanWrapperFieldSetMapper implements FieldSetMapper, BeanFactoryAwa * prototype scoped bean so that a new instance is returned for each field * set mapped. * + * Either this property or the type property must be specified, but not + * both. + * * @param name + * the name of a prototype bean in the enclosing BeanFactory */ public void setPrototypeBeanName(String name) { this.name = name; } + /** + * Public setter for the type of bean to create instead of using a prototype + * bean. An object of this type will be created from its default constructor + * for every call to {@link #mapLine(FieldSet)}.
+ * + * Either this property or the prototype bean name must be specified, but + * not both. + * + * @param type + * the type to set + */ + public void setTargetType(Class type) { + this.type = type; + } + + /** + * Check that precisely one of type or prototype bean name is specified. + * + * @throws IllegalStateException + * if neither is set or both properties are set. + * + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + */ public void afterPropertiesSet() throws Exception { - Assert.notNull(name); + Assert.state(name != null || type != null, + "Either name or type must be provided."); + Assert.state(name == null || type == null, + "Both name and type cannot be specified together."); } /** * Map the {@link FieldSet} to an object retrieved from the enclosing Spring * context. * - * @throws NotWritablePropertyException if the {@link FieldSet} contains a - * field that cannot be mapped to a bean property. + * @throws NotWritablePropertyException + * if the {@link FieldSet} contains a field that cannot be + * mapped to a bean property. * * @see org.springframework.batch.io.file.FieldSetMapper#mapLine(org.springframework.batch.io.file.FieldSet) */ public Object mapLine(FieldSet fs) { - Object copy = beanFactory.getBean(name); + Object copy = getBean(); BeanWrapper wrapper = new BeanWrapperImpl(copy); wrapper.setPropertyValues(getBeanProperties(copy, fs.getProperties())); return copy; } + private Object getBean() { + if (name != null) { + return beanFactory.getBean(name); + } + try { + return type.newInstance(); + } catch (InstantiationException e) { + ReflectionUtils.handleReflectionException(e); + } catch (IllegalAccessException e) { + ReflectionUtils.handleReflectionException(e); + } + throw new IllegalStateException( + "Internal error: could not create bean instance for mapping."); // should + // not + // happen + } + /** * @param bean * @param properties @@ -128,7 +182,7 @@ public class BeanWrapperFieldSetMapper implements FieldSetMapper, BeanFactoryAwa private Properties getBeanProperties(Object bean, Properties properties) { Class cls = bean.getClass(); - + // Map from field names to property names Map matches = (Map) propertiesMatched.get(cls); if (matches == null) { @@ -159,8 +213,9 @@ public class BeanWrapperFieldSetMapper implements FieldSetMapper, BeanFactoryAwa private String findPropertyName(Object bean, String key) { Class cls = bean.getClass(); - - int index = PropertyAccessorUtils.getFirstNestedPropertySeparatorIndex(key); + + int index = PropertyAccessorUtils + .getFirstNestedPropertySeparatorIndex(key); String prefix; String suffix; @@ -174,32 +229,33 @@ public class BeanWrapperFieldSetMapper implements FieldSetMapper, BeanFactoryAwa return null; } - Object nestedValue = new BeanWrapperImpl(bean).getPropertyValue(nestedName); + Object nestedValue = new BeanWrapperImpl(bean) + .getPropertyValue(nestedName); return nestedName + "." + findPropertyName(nestedValue, suffix); } - + String name = null; int distance = 0; index = key.indexOf(PropertyAccessor.PROPERTY_KEY_PREFIX_CHAR); - + if (index > 0) { prefix = key.substring(0, index); suffix = key.substring(index); - } - else { + } else { prefix = key; suffix = ""; } - + while (name == null && distance <= distanceLimit) { - String[] candidates = PropertyMatches.forProperty(prefix, cls, distance).getPossibleMatches(); + String[] candidates = PropertyMatches.forProperty(prefix, cls, + distance).getPossibleMatches(); // If we find precisely one match, then use that one... if (candidates.length == 1) { String candidate = candidates[0]; - if (candidate.equals(prefix)) { // if it's the same don't replace it... + if (candidate.equals(prefix)) { // if it's the same don't + // replace it... name = key; - } - else { + } else { name = candidate + suffix; } } @@ -208,7 +264,8 @@ public class BeanWrapperFieldSetMapper implements FieldSetMapper, BeanFactoryAwa return name; } - private void switchPropertyNames(Properties properties, String oldName, String newName) { + private void switchPropertyNames(Properties properties, String oldName, + String newName) { String value = properties.getProperty(oldName); properties.remove(oldName); properties.setProperty(newName, value); diff --git a/infrastructure/src/test/java/org/springframework/batch/io/file/support/mapping/BeanWrapperFieldSetMapperTests.java b/infrastructure/src/test/java/org/springframework/batch/io/file/support/mapping/BeanWrapperFieldSetMapperTests.java index 8d78a0dea..99c155a9e 100644 --- a/infrastructure/src/test/java/org/springframework/batch/io/file/support/mapping/BeanWrapperFieldSetMapperTests.java +++ b/infrastructure/src/test/java/org/springframework/batch/io/file/support/mapping/BeanWrapperFieldSetMapperTests.java @@ -34,6 +34,39 @@ import org.springframework.context.support.StaticApplicationContext; public class BeanWrapperFieldSetMapperTests extends TestCase { + public void testNameAndTypeSpecified() throws Exception { + BeanWrapperFieldSetMapper mapper = new BeanWrapperFieldSetMapper(); + mapper.setTargetType(TestObject.class); + mapper.setPrototypeBeanName("foo"); + try { + mapper.afterPropertiesSet(); + } catch (IllegalStateException e) { + // expected + } + } + + public void testNameNorTypeSpecified() throws Exception { + BeanWrapperFieldSetMapper mapper = new BeanWrapperFieldSetMapper(); + try { + mapper.afterPropertiesSet(); + } catch (IllegalStateException e) { + // expected + } + } + + public void testVanillaBeanCreatedFromType() throws Exception { + BeanWrapperFieldSetMapper mapper = new BeanWrapperFieldSetMapper(); + mapper.setTargetType(TestObject.class); + mapper.afterPropertiesSet(); + + FieldSet fieldSet = new FieldSet(new String[] { "This is some dummy string", "true", "C" }, new String[] { + "varString", "varBoolean", "varChar" }); + TestObject result = (TestObject) mapper.mapLine(fieldSet); + assertEquals("This is some dummy string", result.getVarString()); + assertEquals(true, result.isVarBoolean()); + assertEquals('C', result.getVarChar()); + } + public void testMapperWithSingleton() throws Exception { BeanWrapperFieldSetMapper mapper = new BeanWrapperFieldSetMapper(); StaticApplicationContext context = new StaticApplicationContext();