INT-2888 Fix MapToObjectTransformerParser CL Issue

In the parent-child environment configuration that relies on class names may produce
a `ClassNotFoundException`.

* remove usage of `ClassLoader` in the `MapToObjectTransformerParser` and allow to use the `ConversionService` from application context
* remove deprecation from `MapToObjectTransformer`
* refactor `MapToObjectTransformer` to use `IntegrationObjectSupport#getConversionService()`
* remove fallback to the `beanFactory#getConversionService()` in the `ExpressionUtils`

JIRA: https://jira.springsource.org/browse/INT-2888

INT-2928: Do Not Fallback to BF's ConversionService

* Polishing according PR's comments
* Important note about `conversionService` & `integrationConversionService` beans
* Link a JIRA about elimination of `BeanFactory`'s `ConversionService` usage
* Add a note to the 2.2-3.0 Migration Guide

JIRA: https://jira.springsource.org/browse/INT-2928

INT-2888 Doc Polishing
This commit is contained in:
Artem Bilan
2013-01-18 11:37:09 +02:00
committed by Gary Russell
parent e994c4dab3
commit 77fae8844a
7 changed files with 194 additions and 130 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2013 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.
@@ -18,13 +18,12 @@ package org.springframework.integration.config.xml;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.transformer.MapToObjectTransformer;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
/**
* @author Oleg Zhurakousky
* @author Artem Bilan
* @since 2.0
*/
public class MapToObjectTransformerParser extends AbstractTransformerParser {
@@ -38,17 +37,17 @@ public class MapToObjectTransformerParser extends AbstractTransformerParser {
protected void parseTransformer(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
String ref = element.getAttribute("ref");
String type = element.getAttribute("type");
Assert.isTrue(!(StringUtils.hasText(ref) && StringUtils.hasText(type)),
"'type' and 'ref' attributes are mutually-exclusive, but both have valid values; type: " + type + "; ref:");
if (StringUtils.hasText(ref)){
if (StringUtils.hasText(ref) && StringUtils.hasText(type)) {
parserContext.getReaderContext().error("'type' and 'ref' attributes are mutually-exclusive, " +
"but both have valid values; type: " + type + "; ref: " + ref,
IntegrationNamespaceUtils.createElementDescription(element));
}
if (StringUtils.hasText(ref)) {
builder.getBeanDefinition().getConstructorArgumentValues().addGenericArgumentValue(ref, "java.lang.String");
} else if (StringUtils.hasText(type)){
ClassLoader classLoader = parserContext.getReaderContext().getBeanClassLoader();
if (classLoader == null) {
classLoader = this.getClass().getClassLoader();
}
Class<?> clazz = ClassUtils.resolveClassName(type, classLoader);
builder.getBeanDefinition().getConstructorArgumentValues().addGenericArgumentValue(clazz, "java.lang.Class");
}
else if (StringUtils.hasText(type)) {
builder.getBeanDefinition().getConstructorArgumentValues().addGenericArgumentValue(type, "java.lang.Class");
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 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.
@@ -17,7 +17,6 @@
package org.springframework.integration.expression;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.context.expression.MapAccessor;
import org.springframework.core.convert.ConversionService;
@@ -32,6 +31,7 @@ import org.springframework.integration.context.IntegrationContextUtils;
*
* @author Gary Russell
* @author Oleg Zhurakousky
* @author Artem Bilan
* @since 2.2
*/
public abstract class ExpressionUtils {
@@ -97,10 +97,7 @@ public abstract class ExpressionUtils {
* @return the evaluation context.
*/
public static StandardEvaluationContext createStandardEvaluationContext(BeanFactory beanFactory) {
ConversionService conversionService = IntegrationContextUtils.getConversionService(beanFactory);
if (conversionService == null && beanFactory instanceof ConfigurableListableBeanFactory){
conversionService = ((ConfigurableListableBeanFactory)beanFactory).getConversionService();
}
return createStandardEvaluationContext(new BeanFactoryResolver(beanFactory), conversionService);
return createStandardEvaluationContext(new BeanFactoryResolver(beanFactory),
IntegrationContextUtils.getConversionService(beanFactory));
}
}

View File

@@ -20,7 +20,6 @@ import java.util.Map;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.util.Assert;
@@ -36,14 +35,15 @@ import org.springframework.validation.DataBinder;
* to types that represent the properties of the Object.
*
* @author Oleg Zhurakousky
* @author Gunnar Hillert
* @author Artem Bilan
* @since 2.0
*/
public class MapToObjectTransformer extends AbstractPayloadTransformer<Map<?,?>, Object>{
public class MapToObjectTransformer extends AbstractPayloadTransformer<Map<?, ?>, Object> {
private final Class<?> targetClass;
private final String targetBeanName;
/**
* @param targetClass
*/
@@ -52,6 +52,7 @@ public class MapToObjectTransformer extends AbstractPayloadTransformer<Map<?,?>,
this.targetClass = targetClass;
this.targetBeanName = null;
}
/**
* @param beanName
*/
@@ -60,31 +61,30 @@ public class MapToObjectTransformer extends AbstractPayloadTransformer<Map<?,?>,
this.targetBeanName = beanName;
this.targetClass = null;
}
/*
* (non-Javadoc)
* @see org.springframework.integration.transformer.AbstractPayloadTransformer#transformPayload(java.lang.Object)
*/
protected Object transformPayload(Map<?,?> payload) throws Exception {
@Override
protected Object transformPayload(Map<?, ?> payload) throws Exception {
Object target = (this.targetClass != null)
? BeanUtils.instantiate(this.targetClass)
: this.getBeanFactory().getBean(this.targetBeanName);
DataBinder binder = new DataBinder(target);
ConversionService conversionService = null;
if (this.getBeanFactory() instanceof ConfigurableBeanFactory){
conversionService = ((ConfigurableBeanFactory)this.getBeanFactory()).getConversionService();
}
if (conversionService == null){
ConversionService conversionService = this.getConversionService();
if (conversionService == null) {
conversionService = new DefaultConversionService();
}
binder.setConversionService(conversionService);
binder.bind(new MutablePropertyValues(payload));
return target;
}
protected void onInit(){
@Override
protected void onInit() {
if (StringUtils.hasText(this.targetBeanName)) {
Assert.isTrue(this.getBeanFactory().isPrototype(this.targetBeanName),
"target bean [" + targetBeanName + "] must have 'prototype' scope");
}
}
}

View File

@@ -9,27 +9,27 @@
<int:channel id="output">
<int:queue/>
</int:channel>
<int:map-to-object-transformer input-channel="input"
output-channel="output"
<int:map-to-object-transformer input-channel="input"
output-channel="output"
type="org.springframework.integration.config.xml.MapToObjectTransformerParserTests$Person"/>
<int:channel id="inputA"/>
<int:channel id="outputA">
<int:queue/>
</int:channel>
<int:map-to-object-transformer input-channel="inputA"
output-channel="outputA"
<int:map-to-object-transformer input-channel="inputA"
output-channel="outputA"
ref="person"/>
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<bean id="conversionService" name="integrationConversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<list>
<bean class="org.springframework.integration.config.xml.MapToObjectTransformerParserTests$StringToAddressConverter" />
</list>
</property>
</bean>
<bean id="person" class="org.springframework.integration.config.xml.MapToObjectTransformerParserTests$Person" scope="prototype"/>
</beans>

View File

@@ -20,43 +20,49 @@ import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import java.lang.reflect.Constructor;
import java.security.AccessController;
import java.security.PrivilegedExceptionAction;
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.beans.BeanUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.core.convert.converter.ConverterRegistry;
import org.springframework.integration.Message;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.util.ClassUtils;
/**
* @author Oleg Zhurakousky
* @author Gunnar Hillert
*
* @author Artem Bilan
* @since 2.0
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public class MapToObjectTransformerTests {
@Test
public void testMapToObjectTransformation(){
Map map = new HashMap();
public void testMapToObjectTransformation() {
Map<String, Object> map = new HashMap<String, Object>();
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();
Message<?> message = MessageBuilder.withPayload(map).build();
MapToObjectTransformer transformer = new MapToObjectTransformer(Person.class);
transformer.setBeanFactory(this.getBeanFactory());
Message newMessage = transformer.transform(message);
Message<?> newMessage = transformer.transform(message);
Person person = (Person) newMessage.getPayload();
assertNotNull(person);
assertEquals("Justin", person.getFname());
@@ -67,20 +73,20 @@ public class MapToObjectTransformerTests {
}
@Test
public void testMapToObjectTransformationWithPrototype(){
Map map = new HashMap();
public void testMapToObjectTransformationWithPrototype() {
Map<String, Object> map = new HashMap<String, Object>();
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();
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);
Message<?> newMessage = transformer.transform(message);
Person person = (Person) newMessage.getPayload();
assertNotNull(person);
assertEquals("Justin", person.getFname());
@@ -91,20 +97,22 @@ public class MapToObjectTransformerTests {
}
@Test
public void testMapToObjectTransformationWithConversionService(){
Map map = new HashMap();
public void testMapToObjectTransformationWithConversionService() {
Map<String, Object> map = new HashMap<String, Object>();
map.put("fname", "Justin");
map.put("lname", "Case");
map.put("address", "1123 Main st");
Message message = MessageBuilder.withPayload(map).build();
Message<?> message = MessageBuilder.withPayload(map).build();
MapToObjectTransformer transformer = new MapToObjectTransformer(Person.class);
ConfigurableBeanFactory beanFactory = this.getBeanFactory();
((GenericConversionService)beanFactory.getConversionService()).addConverter(new StringToAddressConverter());
BeanFactory beanFactory = this.getBeanFactory();
ConverterRegistry conversionService =
beanFactory.getBean(IntegrationContextUtils.INTEGRATION_CONVERSION_SERVICE_BEAN_NAME, ConverterRegistry.class);
conversionService.addConverter(new StringToAddressConverter());
transformer.setBeanFactory(beanFactory);
Message newMessage = transformer.transform(message);
Message<?> newMessage = transformer.transform(message);
Person person = (Person) newMessage.getPayload();
assertNotNull(person);
assertEquals("Justin", person.getFname());
@@ -113,45 +121,73 @@ public class MapToObjectTransformerTests {
assertEquals("1123 Main st", person.getAddress().getStreet());
}
private ConfigurableBeanFactory getBeanFactory(){
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
GenericConversionService conversionService = new DefaultConversionService();
beanFactory.setConversionService(conversionService);
return beanFactory;
private BeanFactory getBeanFactory() {
GenericApplicationContext ctx = TestUtils.createTestApplicationContext();
Constructor<?> constructorToUse = null;
try {
// Add the integrationConversionService (reflection needed because of package protection)
final Class<?> conversionServiceCreatorClass = ClassUtils.forName("org.springframework.integration.context.ConversionServiceCreator",
ClassUtils.getDefaultClassLoader());
constructorToUse = AccessController.doPrivileged(new PrivilegedExceptionAction<Constructor<?>>() {
public Constructor<?> run() throws Exception {
return conversionServiceCreatorClass.getDeclaredConstructor((Class[]) null);
}
});
}
catch (Exception e) {
throw new RuntimeException("Unexpected Privilege Exception: ", e);
}
ctx.addBeanFactoryPostProcessor((BeanFactoryPostProcessor) BeanUtils.instantiateClass(constructorToUse));
ctx.refresh();
return ctx;
}
public static class Person{
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() {
@@ -163,7 +199,8 @@ public class MapToObjectTransformerTests {
}
}
public class StringToAddressConverter implements Converter<String, Address>{
public class StringToAddressConverter implements Converter<String, Address> {
public Address convert(String source) {
Address address = new Address();
address.setStreet(source);