diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/transformer/MapToObjectTransformer.java b/org.springframework.integration/src/main/java/org/springframework/integration/transformer/MapToObjectTransformer.java new file mode 100644 index 0000000000..97c5540051 --- /dev/null +++ b/org.springframework.integration/src/main/java/org/springframework/integration/transformer/MapToObjectTransformer.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2008 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.integration.transformer; + +import java.util.Map; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.validation.DataBinder; + +/** + * @author Oleg Zhurakousky + * @since 2.0 + */ +public class MapToObjectTransformer extends AbstractPayloadTransformer, Object> implements BeanFactoryAware{ + private Object target; + private String targetBeanName; + private ConfigurableBeanFactory beanFactory; + /** + * + * @param targetClass + */ + public MapToObjectTransformer(Class targetClass){ + try { + this.target = BeanUtils.instantiate(targetClass); + } catch (Exception e) { + throw new MessageTransformationException("Can not create instance of " + targetClass, e); + } + } + /** + * + * @param beanName + */ + public MapToObjectTransformer(String beanName){ + this.targetBeanName = beanName; + } + /* + * (non-Javadoc) + * @see org.springframework.integration.transformer.AbstractPayloadTransformer#transformPayload(java.lang.Object) + */ + @SuppressWarnings("unchecked") + protected Object transformPayload(Map payload) throws Exception { + if (StringUtils.hasText(targetBeanName)){ + Assert.isTrue(!beanFactory.isSingleton(targetBeanName), "bean " + targetBeanName + " must be 'prototype'"); + target = beanFactory.getBean(targetBeanName); + } + DataBinder binder = new DataBinder(target); + binder.setConversionService(beanFactory.getConversionService()); + MutablePropertyValues pv = new MutablePropertyValues((Map)payload); + binder.bind(pv); + return target; + } + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(org.springframework.beans.factory.BeanFactory) + */ + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; + } +} diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/transformer/ObjectToMapTransformer.java b/org.springframework.integration/src/main/java/org/springframework/integration/transformer/ObjectToMapTransformer.java new file mode 100644 index 0000000000..d4140bcabb --- /dev/null +++ b/org.springframework.integration/src/main/java/org/springframework/integration/transformer/ObjectToMapTransformer.java @@ -0,0 +1,37 @@ +/* + * Copyright 2002-2008 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.integration.transformer; + +import java.util.HashMap; +import java.util.Map; + + +/** + * @author Oleg Zhurakousky + * @since 2.0 + */ +public class ObjectToMapTransformer extends AbstractPayloadTransformer> { + /* + * (non-Javadoc) + * @see org.springframework.integration.transformer.AbstractPayloadTransformer#transformPayload(java.lang.Object) + */ + protected Map transformPayload(Object payload) throws Exception { + Map transformedMap = new HashMap(); + ObjectToSpelMapBuilder builder = new ObjectToSpelMapBuilder(); + transformedMap = builder.buildspelMap(payload); + return transformedMap; + } +} diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/transformer/ObjectToSpelMapBuilder.java b/org.springframework.integration/src/main/java/org/springframework/integration/transformer/ObjectToSpelMapBuilder.java new file mode 100644 index 0000000000..7e7bdcc74a --- /dev/null +++ b/org.springframework.integration/src/main/java/org/springframework/integration/transformer/ObjectToSpelMapBuilder.java @@ -0,0 +1,153 @@ +/* + * Copyright 2002-2008 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.integration.transformer; + +import java.beans.PropertyDescriptor; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.springframework.beans.BeanWrapperImpl; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.util.CollectionUtils; + +/** TODO: Map of Maps and List of Lists + * @author Oleg Zhurakousky + * @since 2.0 + */ +public class ObjectToSpelMapBuilder { + private final ExpressionParser parser = new SpelExpressionParser(); + private boolean serializeTypeName; + + /** + * + * @return + */ + public Map buildspelMap(Object rootObject){ + Map propertiesMap = new HashMap(); + EvaluationContext context = new StandardEvaluationContext(rootObject); + this.buildProperties(context, "", propertiesMap, rootObject); + return propertiesMap; + } + /* + * + */ + private void buildProperties(EvaluationContext context, String parentPropertyPath, Map propertiesMap, Object object){ + BeanWrapperImpl bw = new BeanWrapperImpl(object); + PropertyDescriptor[] descriptors = bw.getPropertyDescriptors(); + for (PropertyDescriptor propertyDescriptor : descriptors) { + Class propertyType = propertyDescriptor.getPropertyType(); + String propertyName = propertyDescriptor.getName(); + String propertyPath = parentPropertyPath + propertyName; + if (propertyType.isArray() || Collection.class.isAssignableFrom(propertyType)){ + this.processArray(context, propertyPath, propertyName, propertyType, propertiesMap); + } else if (propertyType.isAnonymousClass()){ + throw new IllegalArgumentException("anonymous class property transformation is not supported"); + } else if (propertyType.isAssignableFrom(Map.class)){ + Expression expression = parser.parseExpression(propertyPath); + Map map = (Map) expression.getValue(context); + if (map != null){ + this.processMap(context, propertiesMap, map, parentPropertyPath, propertyName); + } + } else { + Expression expression = parser.parseExpression(propertyPath); + Object propertyValue = expression.getValue(context); + if (propertyValue != null){ + this.processElementValue(context, propertyValue, propertyName, propertyPath, propertiesMap); + } + } + } + } + /* + * + */ + private void processMap(EvaluationContext context, Map mappedProperties, Map mapToTransform, String propertyPath, String propertyName){ + Iterator mapIter = mapToTransform.keySet().iterator(); + while (mapIter.hasNext()) { + Object keyElement = mapIter.next(); + Object elementValue = mapToTransform.get(keyElement); + String mapPropertyPath = propertyPath + propertyName + "['" + keyElement.toString() + "']"; + if (elementValue.getClass().isArray() || elementValue instanceof Collection){ + this.processArray(context, mapPropertyPath, "", elementValue.getClass(), mappedProperties); + } else if (elementValue instanceof Map) { + this.processMap(context, mappedProperties, (Map) elementValue, mapPropertyPath, ""); + } else { + this.processElementValue(context, elementValue, propertyName, mapPropertyPath, mappedProperties); + } + } + } + /* + * + */ + private void processArray(EvaluationContext context, String propertyPath, String propertyName, Class elementType, Map propertiesMap){ + Expression arrayExp = parser.parseExpression(propertyPath); + Object array = arrayExp.getValue(context); + List arrayElements = null; + if (elementType.isArray()){ + arrayElements = CollectionUtils.arrayToList(array); + } else { + arrayElements = (List) array; + } + + int i = 0; + for (Object arrayElement : arrayElements) { + String arrayPropertyPath = propertyPath + "[" + i++ + "]"; + if (arrayElement instanceof Map){ + // last argument is empty because it is not a named property, but a array element + this.processMap(context, propertiesMap, (Map) arrayElement, arrayPropertyPath, ""); + } else if (arrayElement.getClass().isArray()){ + this.processArray(context, arrayPropertyPath, propertyName, elementType, propertiesMap); + } else { + this.processElementValue(context, arrayElement, propertyName, arrayPropertyPath, propertiesMap); + } + } + } + /* + * + */ + private void processElementValue(EvaluationContext context, Object elementValue, String propertyName, String propertyPath, Map propertiesMap){ + // JDK packages considered shared, thus serializable + if (elementValue.getClass().getPackage().getName().contains("java")){ + if (propertyName.equals("class") && !serializeTypeName){ + return; + } else { + propertiesMap.put(propertyPath, elementValue); + } + } else { + this.buildProperties(context, propertyPath+".", propertiesMap, elementValue); + } + } + /** + * + * @return + */ + public boolean isSerializeTypeName() { + return serializeTypeName; + } + /** + * + * @param serializeTypeName + */ + public void setSerializeTypeName(boolean serializeTypeName) { + this.serializeTypeName = serializeTypeName; + } +} diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/transformer/MapToObjectTransformerTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/transformer/MapToObjectTransformerTests.java new file mode 100644 index 0000000000..ae32bb02ee --- /dev/null +++ b/org.springframework.integration/src/test/java/org/springframework/integration/transformer/MapToObjectTransformerTests.java @@ -0,0 +1,189 @@ +/* + * Copyright 2002-2008 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.integration.transformer; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.Test; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.context.support.StaticApplicationContext; +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.convert.support.ConversionServiceFactory; +import org.springframework.core.convert.support.GenericConversionService; +import org.springframework.integration.core.Message; +import org.springframework.integration.message.MessageBuilder; + +/** + * @author Oleg Zhurakousky + * @since 2.0 + */ +public class MapToObjectTransformerTests { + @SuppressWarnings("unchecked") + @Test + public void testMapToObjectTransformation(){ + Map map = new HashMap(); + map.put("fname", "Justin"); + map.put("lname", "Case"); + Address address = new Address(); + address.setStreet("1123 Main st"); + map.put("address", address); + + Message message = MessageBuilder.withPayload(map).build(); + + MapToObjectTransformer transformer = new MapToObjectTransformer(Person.class); + transformer.setBeanFactory(this.getBeanFactory()); + Message newMessage = transformer.transform(message); + Person person = (Person) newMessage.getPayload(); + assertNotNull(person); + assertEquals("Justin", person.getFname()); + assertEquals("Case", person.getLname()); + assertNull(person.getSsn()); + assertNotNull(person.getAddress()); + assertTrue(person.getAddress() instanceof Address); + assertEquals("1123 Main st", person.getAddress().getStreet()); + } + @SuppressWarnings("unchecked") + @Test(expected=MessageTransformationException.class) + public void testMapToObjectTransformationWithSingleton(){ + Map map = new HashMap(); + map.put("fname", "Justin"); + map.put("lname", "Case"); + Address address = new Address(); + address.setStreet("1123 Main st"); + map.put("address", address); + + Message message = MessageBuilder.withPayload(map).build(); + ConfigurableBeanFactory beanFactory = this.getBeanFactory(); + beanFactory.registerSingleton("person", new Person()); + MapToObjectTransformer transformer = new MapToObjectTransformer("person"); + transformer.setBeanFactory(beanFactory); + transformer.transform(message); + } + + @SuppressWarnings("unchecked") + @Test + public void testMapToObjectTransformationWithPrototype(){ + Map map = new HashMap(); + map.put("fname", "Justin"); + map.put("lname", "Case"); + Address address = new Address(); + address.setStreet("1123 Main st"); + map.put("address", address); + + Message message = MessageBuilder.withPayload(map).build(); + StaticApplicationContext ac = new StaticApplicationContext(); + ac.registerPrototype("person", Person.class); + MapToObjectTransformer transformer = new MapToObjectTransformer("person"); + transformer.setBeanFactory(ac.getBeanFactory()); + Message newMessage = transformer.transform(message); + Person person = (Person) newMessage.getPayload(); + assertNotNull(person); + assertEquals("Justin", person.getFname()); + assertEquals("Case", person.getLname()); + assertNull(person.getSsn()); + assertNotNull(person.getAddress()); + assertTrue(person.getAddress() instanceof Address); + assertEquals("1123 Main st", person.getAddress().getStreet()); + } + @SuppressWarnings("unchecked") + @Test + public void testMapToObjectTransformationWithConversionService(){ + Map map = new HashMap(); + map.put("fname", "Justin"); + map.put("lname", "Case"); + map.put("address", "1123 Main st"); + + Message message = MessageBuilder.withPayload(map).build(); + + MapToObjectTransformer transformer = new MapToObjectTransformer(Person.class); + ConfigurableBeanFactory beanFactory = this.getBeanFactory(); + ((GenericConversionService)beanFactory.getConversionService()).addConverter(new StringToAddressConverter()); + transformer.setBeanFactory(beanFactory); + + Message newMessage = transformer.transform(message); + Person person = (Person) newMessage.getPayload(); + assertNotNull(person); + assertEquals("Justin", person.getFname()); + assertEquals("Case", person.getLname()); + assertNotNull(person.getAddress()); + assertTrue(person.getAddress() instanceof Address); + assertEquals("1123 Main st", person.getAddress().getStreet()); + } + + private ConfigurableBeanFactory getBeanFactory(){ + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + GenericConversionService conversionService = ConversionServiceFactory.createDefaultConversionService(); + beanFactory.setConversionService(conversionService); + return beanFactory; + } + public static class Person{ + private String fname; + private String lname; + private String ssn; + private Address address; + public String getSsn() { + return ssn; + } + public void setSsn(String ssn) { + this.ssn = ssn; + } + public String getFname() { + return fname; + } + public void setFname(String fname) { + this.fname = fname; + } + public String getLname() { + return lname; + } + public void setLname(String lname) { + this.lname = lname; + } + public Address getAddress() { + return address; + } + public void setAddress(Address address) { + this.address = address; + } + } + + public static class Address { + private String street; + + public String getStreet() { + return street; + } + + public void setStreet(String street) { + this.street = street; + } + } + + public class StringToAddressConverter implements Converter{ + public Address convert(String source) { + Address address = new Address(); + address.setStreet(source); + return address; + } + } +} diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/transformer/ObjectToMapTransformerTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/transformer/ObjectToMapTransformerTests.java new file mode 100644 index 0000000000..961dd19a6a --- /dev/null +++ b/org.springframework.integration/src/test/java/org/springframework/integration/transformer/ObjectToMapTransformerTests.java @@ -0,0 +1,242 @@ +/* + * Copyright 2002-2008 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.integration.transformer; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.Test; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.integration.core.Message; +import org.springframework.integration.message.MessageBuilder; + +/** + * + * @author Oleg Zhurakousky + * @since 2.0 + */ +public class ObjectToMapTransformerTests { + @SuppressWarnings("unchecked") + @Test + public void testObjectToSpelMapTransformer(){ + Employee employee = this.buildEmployee(); + EvaluationContext context = new StandardEvaluationContext(employee); + ExpressionParser parser = new SpelExpressionParser(); + + ObjectToMapTransformer transformer = new ObjectToMapTransformer(); + Message message = MessageBuilder.withPayload(employee).build(); + Message transformedMessage = transformer.transform(message); + Map transformedMap = (Map) transformedMessage.getPayload(); + System.out.println(transformedMap); + + assertNotNull(transformedMap); + for (String key : transformedMap.keySet()) { + Expression expression = parser.parseExpression(key); + //System.out.println("Testing: " + key); + Object valueFromTheMap = transformedMap.get(key); + Object valueFromExpression = expression.getValue(context); + String packageNameOfValueType = valueFromTheMap.getClass().getPackage().getName(); + assertTrue(packageNameOfValueType.startsWith("java.lang")); + assertEquals(valueFromTheMap, valueFromExpression); + } + } + + @SuppressWarnings("unchecked") + public Employee buildEmployee(){ + Address companyAddress = new Address(); + companyAddress.setCity("Philadelphia"); + companyAddress.setStreet("1123 Main"); + companyAddress.setZip("12345"); + + Map coordinates = new HashMap(); + coordinates.put("latitude", new Long[]{(long)1, (long)5, (long)13}); + coordinates.put("longitude", new Long[]{(long)156}); + companyAddress.setCoordinates(coordinates); + + Employee employee = new Employee(); + employee.setCompanyName("ABC Inc."); + employee.setCompanyAddress(companyAddress); + ArrayList departments = new ArrayList(); + departments.add("HR"); + departments.add("IT"); + employee.setDepartments(departments); + + Person person = new Person(); + person.setFname("Justin"); + person.setLname("Case"); + person.setAkaNames("Hard", "Use", "Beer"); + Address personAddress = new Address(); + personAddress.setCity("Philly"); + personAddress.setStreet("123 Main"); + List listTestData = new ArrayList(); + listTestData.add("hello"); + listTestData.add("blah"); + Map> mapWithListTestData = new HashMap>(); + mapWithListTestData.put("mapWithListTestData", listTestData); + personAddress.setMapWithListData(mapWithListTestData); + person.setAddress(personAddress); + + Map remarksA = new HashMap(); + Map remarksB = new HashMap(); + remarksA.put("foo", "foo"); + remarksA.put("bar", "bar"); + remarksB.put("baz", "baz"); + List> remarks = new ArrayList>(); + remarks.add(remarksA); + remarks.add(remarksB); + person.setRemarks(remarks); + employee.setPerson(person); + + Map> testMapData = new HashMap>(); + + Map internalMapA = new HashMap(); + internalMapA.put("foo", "foo"); + internalMapA.put("bar", "bar"); + Map internalMapB = new HashMap(); + internalMapB.put("baz", "baz"); + + testMapData.put("internalMapA", internalMapA); + testMapData.put("internalMapB", internalMapB); + + employee.setTestMapInMapData(testMapData); + return employee; + } + + public static class Employee{ + private List departments; + private String companyName; + private Person person; + private Address companyAddress; + private Map> testMapInMapData; + public Map> getTestMapInMapData() { + return testMapInMapData; + } + public void setTestMapInMapData( + Map> testMapInMapData) { + this.testMapInMapData = testMapInMapData; + } + public String getCompanyName() { + return companyName; + } + public void setCompanyName(String companyName) { + this.companyName = companyName; + } + public Person getPerson() { + return person; + } + public void setPerson(Person person) { + this.person = person; + } + public Address getCompanyAddress() { + return companyAddress; + } + public void setCompanyAddress(Address companyAddress) { + this.companyAddress = companyAddress; + } + public List getDepartments() { + return departments; + } + public void setDepartments(List departments) { + this.departments = departments; + } + } + + public static class Person{ + private String fname; + private String lname; + private String[] akaNames; + private List> remarks; + public List> getRemarks() { + return remarks; + } + public void setRemarks(List> remarks) { + this.remarks = remarks; + } + private Address address; + public String[] getAkaNames() { + return akaNames; + } + public void setAkaNames(String... akaNames) { + this.akaNames = akaNames; + } + public String getFname() { + return fname; + } + public void setFname(String fname) { + this.fname = fname; + } + public String getLname() { + return lname; + } + public void setLname(String lname) { + this.lname = lname; + } + public Address getAddress() { + return address; + } + public void setAddress(Address address) { + this.address = address; + } + } + + public static class Address{ + private String street; + private String city; + private String zip; + private Map> mapWithListData; + private Map coordinates; + public Map> getMapWithListData() { + return mapWithListData; + } + public void setMapWithListData(Map> mapWithListData) { + this.mapWithListData = mapWithListData; + } + public String getStreet() { + return street; + } + public void setStreet(String street) { + this.street = street; + } + public String getCity() { + return city; + } + public void setCity(String city) { + this.city = city; + } + public String getZip() { + return zip; + } + public void setZip(String zip) { + this.zip = zip; + } + public Map getCoordinates() { + return coordinates; + } + public void setCoordinates(Map coordinates) { + this.coordinates = coordinates; + } + } +}