/* * Copyright 2006-2007 the original author or authors. * * 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 org.springframework.batch.item.file.mapping; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.beans.PropertyEditor; import java.math.BigDecimal; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import org.junit.Test; import org.springframework.batch.item.file.transform.DefaultFieldSet; import org.springframework.batch.item.file.transform.FieldSet; import org.springframework.beans.BeanWrapperImpl; import org.springframework.beans.NotWritablePropertyException; import org.springframework.beans.PropertyEditorRegistry; import org.springframework.beans.propertyeditors.CustomNumberEditor; import org.springframework.beans.propertyeditors.PropertiesEditor; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.context.support.StaticApplicationContext; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.validation.BindException; import org.springframework.validation.DataBinder; public class BeanWrapperFieldSetMapperTests { @Test public void testNameAndTypeSpecified() throws Exception { BeanWrapperFieldSetMapper<TestObject> mapper = new BeanWrapperFieldSetMapper<TestObject>(); mapper.setTargetType(TestObject.class); mapper.setPrototypeBeanName("foo"); try { mapper.afterPropertiesSet(); } catch (IllegalStateException e) { // expected } } @Test public void testNameNorTypeSpecified() throws Exception { BeanWrapperFieldSetMapper<TestObject> mapper = new BeanWrapperFieldSetMapper<TestObject>(); try { mapper.afterPropertiesSet(); } catch (IllegalStateException e) { // expected } } @Test public void testVanillaBeanCreatedFromType() throws Exception { BeanWrapperFieldSetMapper<TestObject> mapper = new BeanWrapperFieldSetMapper<TestObject>(); mapper.setTargetType(TestObject.class); mapper.afterPropertiesSet(); FieldSet fieldSet = new DefaultFieldSet(new String[] { "This is some dummy string", "true", "C" }, new String[] { "varString", "varBoolean", "varChar" }); TestObject result = mapper.mapFieldSet(fieldSet); assertEquals("This is some dummy string", result.getVarString()); assertEquals(true, result.isVarBoolean()); assertEquals('C', result.getVarChar()); } @Test public void testNullPropertyAutoCreated() throws Exception { BeanWrapperFieldSetMapper<TestNestedA> mapper = new BeanWrapperFieldSetMapper<TestNestedA>(); mapper.setTargetType(TestNestedA.class); mapper.afterPropertiesSet(); FieldSet fieldSet = new DefaultFieldSet(new String[] { "Foo", "Bar" }, new String[] { "valueA", "testObjectB.valueA" }); TestNestedA result = mapper.mapFieldSet(fieldSet); assertEquals("Bar", result.getTestObjectB().getValueA()); } @Test public void testMapperWithSingleton() throws Exception { BeanWrapperFieldSetMapper<TestObject> mapper = new BeanWrapperFieldSetMapper<TestObject>(); @SuppressWarnings("resource") StaticApplicationContext context = new StaticApplicationContext(); mapper.setBeanFactory(context); context.getBeanFactory().registerSingleton("bean", new TestObject()); mapper.setPrototypeBeanName("bean"); FieldSet fieldSet = new DefaultFieldSet(new String[] { "This is some dummy string", "true", "C" }, new String[] { "varString", "varBoolean", "varChar" }); TestObject result = mapper.mapFieldSet(fieldSet); assertEquals("This is some dummy string", result.getVarString()); assertEquals(true, result.isVarBoolean()); assertEquals('C', result.getVarChar()); } @Test public void testPropertyNameMatching() throws Exception { BeanWrapperFieldSetMapper<TestObject> mapper = new BeanWrapperFieldSetMapper<TestObject>(); @SuppressWarnings("resource") StaticApplicationContext context = new StaticApplicationContext(); mapper.setBeanFactory(context); mapper.setDistanceLimit(2); context.getBeanFactory().registerSingleton("bean", new TestObject()); mapper.setPrototypeBeanName("bean"); FieldSet fieldSet = new DefaultFieldSet(new String[] { "This is some dummy string", "true", "C" }, new String[] { "VarString", "VAR_BOOLEAN", "VAR_CHAR" }); TestObject result = mapper.mapFieldSet(fieldSet); assertEquals("This is some dummy string", result.getVarString()); assertEquals(true, result.isVarBoolean()); assertEquals('C', result.getVarChar()); } @Test @SuppressWarnings({"unchecked", "resource"}) public void testMapperWithPrototype() throws Exception { ApplicationContext context = new ClassPathXmlApplicationContext("bean-wrapper.xml", getClass()); BeanWrapperFieldSetMapper<TestObject> mapper = (BeanWrapperFieldSetMapper<TestObject>) context .getBean("fieldSetMapper"); FieldSet fieldSet = new DefaultFieldSet(new String[] { "This is some dummy string", "true", "C" }, new String[] { "varString", "varBoolean", "varChar" }); TestObject result = mapper.mapFieldSet(fieldSet); assertEquals("This is some dummy string", result.getVarString()); assertEquals(true, result.isVarBoolean()); assertEquals('C', result.getVarChar()); } @Test public void testMapperWithNestedBeanPaths() throws Exception { TestNestedA testNestedA = new TestNestedA(); TestNestedB testNestedB = new TestNestedB(); testNestedA.setTestObjectB(testNestedB); testNestedB.setTestObjectC(new TestNestedC()); BeanWrapperFieldSetMapper<TestNestedA> mapper = new BeanWrapperFieldSetMapper<TestNestedA>(); @SuppressWarnings("resource") StaticApplicationContext context = new StaticApplicationContext(); mapper.setBeanFactory(context); context.getBeanFactory().registerSingleton("bean", testNestedA); mapper.setPrototypeBeanName("bean"); FieldSet fieldSet = new DefaultFieldSet( new String[] { "This is some dummy string", "1", "Another dummy", "2" }, new String[] { "valueA", "valueB", "testObjectB.valueA", "testObjectB.testObjectC.value" }); TestNestedA result = mapper.mapFieldSet(fieldSet); assertEquals("This is some dummy string", result.getValueA()); assertEquals(1, result.getValueB()); assertEquals("Another dummy", result.getTestObjectB().getValueA()); assertEquals(2, result.getTestObjectB().getTestObjectC().getValue()); } @Test public void testMapperWithSimilarNamePropertyMatches() throws Exception { TestNestedA testNestedA = new TestNestedA(); BeanWrapperFieldSetMapper<TestNestedA> mapper = new BeanWrapperFieldSetMapper<TestNestedA>(); @SuppressWarnings("resource") StaticApplicationContext context = new StaticApplicationContext(); mapper.setBeanFactory(context); mapper.setDistanceLimit(2); context.getBeanFactory().registerSingleton("bean", testNestedA); mapper.setPrototypeBeanName("bean"); FieldSet fieldSet = new DefaultFieldSet(new String[] { "This is some dummy string", "1" }, new String[] { "VALUE_A", "VALUE_B" }); TestNestedA result = mapper.mapFieldSet(fieldSet); assertEquals("This is some dummy string", result.getValueA()); assertEquals(1, result.getValueB()); } @Test public void testMapperWithNotVerySimilarNamePropertyMatches() throws Exception { TestNestedC testNestedC = new TestNestedC(); BeanWrapperFieldSetMapper<TestNestedC> mapper = new BeanWrapperFieldSetMapper<TestNestedC>(); @SuppressWarnings("resource") StaticApplicationContext context = new StaticApplicationContext(); mapper.setBeanFactory(context); context.getBeanFactory().registerSingleton("bean", testNestedC); mapper.setPrototypeBeanName("bean"); FieldSet fieldSet = new DefaultFieldSet(new String[] { "1" }, new String[] { "foo" }); TestNestedC result = mapper.mapFieldSet(fieldSet); // "foo" is similar enough to "value" that it matches - but only because // nothing else does... assertEquals(1, result.getValue()); } @Test public void testMapperWithNestedBeanPathsAndPropertyMatches() throws Exception { TestNestedA testNestedA = new TestNestedA(); TestNestedB testNestedB = new TestNestedB(); testNestedA.setTestObjectB(testNestedB); testNestedB.setTestObjectC(new TestNestedC()); BeanWrapperFieldSetMapper<TestNestedA> mapper = new BeanWrapperFieldSetMapper<TestNestedA>(); @SuppressWarnings("resource") StaticApplicationContext context = new StaticApplicationContext(); mapper.setBeanFactory(context); context.getBeanFactory().registerSingleton("bean", testNestedA); mapper.setDistanceLimit(2); mapper.setPrototypeBeanName("bean"); FieldSet fieldSet = new DefaultFieldSet(new String[] { "Another dummy", "2" }, new String[] { "TestObjectB.ValueA", "TestObjectB.TestObjectC.Value" }); TestNestedA result = mapper.mapFieldSet(fieldSet); assertEquals("Another dummy", result.getTestObjectB().getValueA()); assertEquals(2, result.getTestObjectB().getTestObjectC().getValue()); } @Test public void testMapperWithNestedBeanPathsAndPropertyMisMatches() throws Exception { TestNestedA testNestedA = new TestNestedA(); TestNestedB testNestedB = new TestNestedB(); testNestedA.setTestObjectB(testNestedB); BeanWrapperFieldSetMapper<?> mapper = new BeanWrapperFieldSetMapper<Object>(); @SuppressWarnings("resource") StaticApplicationContext context = new StaticApplicationContext(); mapper.setBeanFactory(context); context.getBeanFactory().registerSingleton("bean", testNestedA); mapper.setPrototypeBeanName("bean"); FieldSet fieldSet = new DefaultFieldSet(new String[] { "Another dummy" }, new String[] { "TestObjectB.foo" }); try { mapper.mapFieldSet(fieldSet); fail("Expected NotWritablePropertyException"); } catch (NotWritablePropertyException e) { // expected } } @Test public void testMapperWithNestedBeanPathsAndPropertyPrefixMisMatches() throws Exception { TestNestedA testNestedA = new TestNestedA(); TestNestedB testNestedB = new TestNestedB(); testNestedA.setTestObjectB(testNestedB); BeanWrapperFieldSetMapper<?> mapper = new BeanWrapperFieldSetMapper<Object>(); @SuppressWarnings("resource") StaticApplicationContext context = new StaticApplicationContext(); mapper.setBeanFactory(context); context.getBeanFactory().registerSingleton("bean", testNestedA); mapper.setPrototypeBeanName("bean"); FieldSet fieldSet = new DefaultFieldSet(new String[] { "2" }, new String[] { "TestObjectA.garbage" }); try { mapper.mapFieldSet(fieldSet); fail("Expected NotWritablePropertyException"); } catch (NotWritablePropertyException e) { // expected } } @Test public void testPlainBeanWrapper() throws Exception { TestObject result = new TestObject(); BeanWrapperImpl wrapper = new BeanWrapperImpl(result); PropertiesEditor editor = new PropertiesEditor(); editor.setAsText("varString=This is some dummy string\nvarBoolean=true\nvarChar=C"); Properties props = (Properties) editor.getValue(); wrapper.setPropertyValues(props); assertEquals("This is some dummy string", result.getVarString()); assertEquals(true, result.isVarBoolean()); assertEquals('C', result.getVarChar()); } @Test public void testNestedList() throws Exception { TestNestedList nestedList = new TestNestedList(); List<TestNestedC> nestedC = new ArrayList<TestNestedC>(); nestedC.add(new TestNestedC()); nestedC.add(new TestNestedC()); nestedC.add(new TestNestedC()); nestedList.setNestedC(nestedC); BeanWrapperFieldSetMapper<?> mapper = new BeanWrapperFieldSetMapper<Object>(); @SuppressWarnings("resource") StaticApplicationContext context = new StaticApplicationContext(); mapper.setBeanFactory(context); context.getBeanFactory().registerSingleton("bean", nestedList); mapper.setPrototypeBeanName("bean"); FieldSet fieldSet = new DefaultFieldSet(new String[] { "1", "2", "3" }, new String[] { "NestedC[0].Value", "NestedC[1].Value", "NestedC[2].Value" }); mapper.mapFieldSet(fieldSet); assertEquals(1, nestedList.getNestedC().get(0).getValue()); assertEquals(2, nestedList.getNestedC().get(1).getValue()); assertEquals(3, nestedList.getNestedC().get(2).getValue()); } @Test public void testAutoPopulateNestedList() throws Exception { TestNestedList nestedList = new TestNestedList(); BeanWrapperFieldSetMapper<?> mapper = new BeanWrapperFieldSetMapper<Object>() { @Override protected void initBinder(DataBinder binder) { // Use reflection so it compiles (and fails) with Spring 2.5 ReflectionTestUtils.setField(binder, "autoGrowNestedPaths", true); } }; @SuppressWarnings("resource") StaticApplicationContext context = new StaticApplicationContext(); mapper.setBeanFactory(context); context.getBeanFactory().registerSingleton("bean", nestedList); mapper.setPrototypeBeanName("bean"); FieldSet fieldSet = new DefaultFieldSet(new String[] { "1", "2", "3" }, new String[] { "NestedC[0].Value", "NestedC[1].Value", "NestedC[2].Value" }); mapper.mapFieldSet(fieldSet); assertEquals(1, nestedList.getNestedC().get(0).getValue()); assertEquals(2, nestedList.getNestedC().get(1).getValue()); assertEquals(3, nestedList.getNestedC().get(2).getValue()); } @Test public void testPaddedLongWithNoEditor() throws Exception { BeanWrapperFieldSetMapper<TestObject> mapper = new BeanWrapperFieldSetMapper<TestObject>(); mapper.setTargetType(TestObject.class); FieldSet fieldSet = new DefaultFieldSet(new String[] { "00009" }, new String[] { "varLong" }); TestObject bean = mapper.mapFieldSet(fieldSet); // since Spring 2.5.5 this is OK (before that BATCH-261) assertEquals(9, bean.getVarLong()); } @Test public void testPaddedLongWithEditor() throws Exception { BeanWrapperFieldSetMapper<TestObject> mapper = new BeanWrapperFieldSetMapper<TestObject>(); mapper.setTargetType(TestObject.class); FieldSet fieldSet = new DefaultFieldSet(new String[] { "00009" }, new String[] { "varLong" }); mapper.setCustomEditors(Collections.singletonMap(Long.TYPE, new CustomNumberEditor(Long.class, NumberFormat .getNumberInstance(), true))); TestObject bean = mapper.mapFieldSet(fieldSet); assertEquals(9, bean.getVarLong()); } @Test public void testPaddedLongWithDefaultAndCustomEditor() throws Exception { BeanWrapperFieldSetMapper<TestObject> mapper = new BeanWrapperFieldSetMapper<TestObject>(); mapper.setTargetType(TestObject.class); FieldSet fieldSet = new DefaultFieldSet(new String[] { "00009", "78" }, new String[] { "varLong", "varInt" }); mapper.setCustomEditors(Collections.singletonMap(Long.TYPE, new CustomNumberEditor(Long.class, NumberFormat .getNumberInstance(), true))); TestObject bean = mapper.mapFieldSet(fieldSet); assertEquals(9, bean.getVarLong()); assertEquals(78, bean.getVarInt()); } @Test public void testNumberFormatWithDefaultAndCustomEditor() throws Exception { BeanWrapperFieldSetMapper<TestObject> mapper = new BeanWrapperFieldSetMapper<TestObject>(); mapper.setTargetType(TestObject.class); FieldSet fieldSet = new DefaultFieldSet(new String[] { "9.876,1", "7,890.1" }, new String[] { "varDouble", "varFloat" }); Map<Class<?>, PropertyEditor> editors = new HashMap<Class<?>, PropertyEditor>(); editors.put(Double.TYPE, new CustomNumberEditor(Double.class, NumberFormat.getInstance(Locale.GERMAN), true)); editors.put(Float.TYPE, new CustomNumberEditor(Float.class, NumberFormat.getInstance(Locale.UK), true)); mapper.setCustomEditors(editors); TestObject bean = mapper.mapFieldSet(fieldSet); assertEquals(9876.1, bean.getVarDouble(), 0.01); assertEquals(7890.1, bean.getVarFloat(), 0.01); } @Test public void testBinderWithErrors() throws Exception { BeanWrapperFieldSetMapper<TestObject> mapper = new BeanWrapperFieldSetMapper<TestObject>(); mapper.setTargetType(TestObject.class); FieldSet fieldSet = new DefaultFieldSet(new String[] { "foo", "7890.1" }, new String[] { "varDouble", "varFloat" }); try { mapper.mapFieldSet(fieldSet); fail("Expected BindException"); } catch (BindException e) { assertEquals(1, e.getErrorCount()); assertEquals("typeMismatch", e.getFieldError("varDouble").getCode()); } } @Test public void testFieldSpecificCustomEditor() throws Exception { BeanWrapperFieldSetMapper<TestTwoDoubles> mapper = new BeanWrapperFieldSetMapper<TestTwoDoubles>() { @Override protected void initBinder(DataBinder binder) { binder.registerCustomEditor(Double.TYPE, "value", new CustomNumberEditor(Double.class, NumberFormat .getNumberInstance(Locale.GERMAN), true)); } }; mapper.setTargetType(TestTwoDoubles.class); FieldSet fieldSet = new DefaultFieldSet(new String[] { "9.876,1", "7890.1" }, new String[] { "value", "other" }); TestTwoDoubles bean = mapper.mapFieldSet(fieldSet); assertEquals(9876.1, bean.getValue(), 0.01); assertEquals(7890.1, bean.getOther(), 0.01); } @Test public void testFieldSpecificCustomEditorWithRegistry() throws Exception { BeanWrapperFieldSetMapper<TestTwoDoubles> mapper = new BeanWrapperFieldSetMapper<TestTwoDoubles>() { @Override public void registerCustomEditors(PropertyEditorRegistry registry) { super.registerCustomEditors(registry); registry.registerCustomEditor(Double.TYPE, "value", new CustomNumberEditor(Double.class, NumberFormat .getNumberInstance(Locale.GERMAN), true)); } }; mapper.setTargetType(TestTwoDoubles.class); FieldSet fieldSet = new DefaultFieldSet(new String[] { "9.876,1", "7890.1" }, new String[] { "value", "other" }); TestTwoDoubles bean = mapper.mapFieldSet(fieldSet); assertEquals(9876.1, bean.getValue(), 0.01); assertEquals(7890.1, bean.getOther(), 0.01); } @Test public void testStrict() throws Exception { BeanWrapperFieldSetMapper<TestObject> mapper = new BeanWrapperFieldSetMapper<TestObject>(); mapper.setStrict(true); mapper.setTargetType(TestObject.class); mapper.afterPropertiesSet(); FieldSet fieldSet = new DefaultFieldSet(new String[] { "This is some dummy string", "This won't be mapped", "true", "C" }, new String[] { "varString", "illegalPropertyName", "varBoolean", "varChar" }); try { mapper.mapFieldSet(fieldSet); fail("expected error"); } catch (NotWritablePropertyException e) { assertTrue(e.getMessage().contains("'illegalPropertyName'")); } } @Test public void testNotStrict() throws Exception { BeanWrapperFieldSetMapper<TestObject> mapper = new BeanWrapperFieldSetMapper<TestObject>(); mapper.setStrict(false); mapper.setTargetType(TestObject.class); mapper.afterPropertiesSet(); FieldSet fieldSet = new DefaultFieldSet(new String[] { "This is some dummy string", "This won't be mapped", "true", "C" }, new String[] { "varString", "illegalPropertyName", "varBoolean", "varChar" }); TestObject result = mapper.mapFieldSet(fieldSet); assertEquals("This is some dummy string", result.getVarString()); assertEquals(true, result.isVarBoolean()); assertEquals('C', result.getVarChar()); } private static class TestNestedList { List<TestNestedC> nestedC = new ArrayList<TestNestedC>(); public List<TestNestedC> getNestedC() { return nestedC; } public void setNestedC(List<TestNestedC> nestedC) { this.nestedC = nestedC; } } public static class TestNestedA { private String valueA; private int valueB; TestNestedB testObjectB; public TestNestedB getTestObjectB() { return testObjectB; } public void setTestObjectB(TestNestedB testObjectB) { this.testObjectB = testObjectB; } public String getValueA() { return valueA; } public void setValueA(String valueA) { this.valueA = valueA; } public int getValueB() { return valueB; } public void setValueB(int valueB) { this.valueB = valueB; } } public static class TestNestedB { private String valueA; private TestNestedC testObjectC; public TestNestedC getTestObjectC() { return testObjectC; } public void setTestObjectC(TestNestedC testObjectC) { this.testObjectC = testObjectC; } public String getValueA() { return valueA; } public void setValueA(String valueA) { this.valueA = valueA; } } public static class TestNestedC { private int value; public int getValue() { return value; } public void setValue(int value) { this.value = value; } } public static class TestTwoDoubles { private double value; private double other; public double getValue() { return value; } public void setValue(double value) { this.value = value; } public double getOther() { return other; } public void setOther(double other) { this.other = other; } } public static class TestObject { String varString; boolean varBoolean; char varChar; byte varByte; short varShort; int varInt; long varLong; float varFloat; double varDouble; BigDecimal varBigDecimal; Date varDate; public Date getVarDate() { return (Date) varDate.clone(); } public void setVarDate(Date varDate) { this.varDate = varDate == null ? null : (Date) varDate.clone(); } public TestObject() { } public BigDecimal getVarBigDecimal() { return varBigDecimal; } public void setVarBigDecimal(BigDecimal varBigDecimal) { this.varBigDecimal = varBigDecimal; } public boolean isVarBoolean() { return varBoolean; } public void setVarBoolean(boolean varBoolean) { this.varBoolean = varBoolean; } public byte getVarByte() { return varByte; } public void setVarByte(byte varByte) { this.varByte = varByte; } public char getVarChar() { return varChar; } public void setVarChar(char varChar) { this.varChar = varChar; } public double getVarDouble() { return varDouble; } public void setVarDouble(double varDouble) { this.varDouble = varDouble; } public float getVarFloat() { return varFloat; } public void setVarFloat(float varFloat) { this.varFloat = varFloat; } public long getVarLong() { return varLong; } public void setVarLong(long varLong) { this.varLong = varLong; } public short getVarShort() { return varShort; } public void setVarShort(short varShort) { this.varShort = varShort; } public String getVarString() { return varString; } public void setVarString(String varString) { this.varString = varString; } public int getVarInt() { return varInt; } public void setVarInt(int varInt) { this.varInt = varInt; } } }