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);

View File

@@ -244,7 +244,7 @@
<programlisting language="xml"><![CDATA[ <int:channel id="threadScopedChannel" scope="thread">
<int:queue />
</int:channel>
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
@@ -254,12 +254,12 @@
</bean>
]]></programlisting>
<para>
The channel above also delegates to a queue internally, but the channel is bound
The channel above also delegates to a queue internally, but the channel is bound
to the current thread, so the contents of the queue are as well. That way the thread that
sends to the channel will later be able to receive those same Messages, but no other thread
would be able to access them. While thread-scoped channels are rarely needed, they can be
useful in situations where <classname>DirectChannels</classname> are being used to enforce a
single thread of operation but any reply Messages should be sent to a "terminal" channel.
sends to the channel will later be able to receive those same Messages, but no other thread
would be able to access them. While thread-scoped channels are rarely needed, they can be
useful in situations where <classname>DirectChannels</classname> are being used to enforce a
single thread of operation but any reply Messages should be sent to a "terminal" channel.
If that terminal channel is thread-scoped, the original sending thread can collect its replies from it.
</para>
<para>
@@ -415,13 +415,13 @@ public Message<?> receive(final PollableChannel<?> channel) { ... }]]></programl
]]></programlisting>
</para>
</section>
<section id="channel-datatype-channel">
<title>Datatype Channel Configuration</title>
<para>
There are times when a consumer can only process a particular type of payload and you need to therefore ensure the payload type of input Messages.
Of course the first thing that comes to mind is Message Filter. However all that Message Filter will do is filter out Messages that are not compliant with
the requirements of the consumer. Another way would be to use a Content Based Router and route Messages with non-compliant data-types to specific
There are times when a consumer can only process a particular type of payload and you need to therefore ensure the payload type of input Messages.
Of course the first thing that comes to mind is Message Filter. However all that Message Filter will do is filter out Messages that are not compliant with
the requirements of the consumer. Another way would be to use a Content Based Router and route Messages with non-compliant data-types to specific
Transformers to enforce transformation/conversion to the required data-type. This of course would work, but a simpler way of accomplishing the
same thing is to apply the <ulink url="http://www.eaipatterns.com/DatatypeChannel.html">Datatype Channel</ulink> pattern.
You can use separate Datatype Channels for each specific payload data-type.
@@ -430,10 +430,10 @@ public Message<?> receive(final PollableChannel<?> channel) { ... }]]></programl
To create a Datatype Channel that only
accepts messages containing a certain payload type, provide the fully-qualified class name in the
channel element's <literal>datatype</literal> attribute:
<programlisting language="xml"><![CDATA[<int:channel id="numberChannel" datatype="java.lang.Number"/>]]></programlisting>
</para>
<para>
<para>
Note that the type check passes for any type that is <emphasis>assignable</emphasis> to the channel's
datatype. In other words, the "numberChannel" above would accept messages whose payload is
<classname>java.lang.Integer</classname> or <classname>java.lang.Double</classname>. Multiple types can be
@@ -443,21 +443,21 @@ public Message<?> receive(final PollableChannel<?> channel) { ... }]]></programl
<para>
So the 'numberChannel' above will only accept Messages with a data-type of <classname>java.lang.Number</classname>. But what happens
if the payload of the Message is not of the required type? It depends on whether you have defined a bean named "integrationConversionService"
that is an instance of Spring's <ulink url="http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/validation.html#core-convert-ConversionService-API">Conversion Service</ulink>.
that is an instance of Spring's <ulink url="http://static.springsource.org/spring/docs/current/spring-framework-reference/html/validation.html#core-convert-ConversionService-API">Conversion Service</ulink>.
If not, then an Exception would be thrown immediately, but if you do have an "integrationConversionService" bean defined, it will be used
in an attempt to convert the Message's payload to the acceptable type.
</para>
<para>
You can even register custom converters. For example, let's say you are sending a Message with a String payload to the 'numberChannel' we configured above.
<programlisting language="java"><![CDATA[MessageChannel inChannel = context.getBean("numberChannel", MessageChannel.class);
inChannel.send(new GenericMessage<String>("5"));]]></programlisting>
inChannel.send(new GenericMessage<String>("5"));]]></programlisting>
</para>
<para>
<para>
Typically this would be a perfectly legal operation, however since we are using Datatype Channel the result of such operation would generate an exception:
<programlisting language="java"><![CDATA[Exception in thread "main" org.springframework.integration.MessageDeliveryException:
Channel 'numberChannel'
expected one of the following datataypes [class java.lang.Number],
<programlisting language="java"><![CDATA[Exception in thread "main" org.springframework.integration.MessageDeliveryException:
Channel 'numberChannel'
expected one of the following datataypes [class java.lang.Number],
but received [class java.lang.String]
…]]></programlisting>
</para>
@@ -467,22 +467,27 @@ All we need to do is implement a Converter.
<programlisting language="java"><![CDATA[public static class StringToIntegerConverter implements Converter<String, Integer> {
public Integer convert(String source) {
return Integer.parseInt(source);
}
}
}]]></programlisting>
</para>
<para>
Then, register it as a Converter with the Integration Conversion Service:
<programlisting language="java"><![CDATA[<int:converter ref="strToInt"/>
<bean id="strToInt" class="org.springframework.integration.util.Demo.StringToIntegerConverter"/>]]></programlisting>
</para>
<para>
When the 'converter' element is parsed, it will create the "integrationConversionService" bean on-demand if one is not already defined.
With that Converter in place, the send operation would now be successful since the Datatype Channel will use that Converter to convert the String
payload to an Integer.
</para>
</para>
<note>
<para>
For more information regarding Payload Type Conversion, please read <xref linkend="payload-type-conversion"/>.
</para>
</note>
</section>
<section id="channel-configuration-queuechannel">
<title>QueueChannel Configuration</title>
<para>
@@ -497,9 +502,9 @@ payload to an Integer.
recommended to set an explicit value for a bounded queue.
</note>
</para>
<para><emphasis>Persistent QueueChannel Configuration</emphasis></para>
<para>
Since a <classname>QueueChannel</classname> provides the capability to buffer Messages, but does so in-memory only
by default, it also introduces a possibility that Messages could be lost in the event of a system failure. To
@@ -513,35 +518,35 @@ payload to an Integer.
</para>
<para>
By default any <classname>QueueChannel</classname> only stores its Messages in an in-memory Queue
and can therefore lead to the lost message scenario mentioned above. However Spring Integration
and can therefore lead to the lost message scenario mentioned above. However Spring Integration
provides a <classname>JdbcMessageStore</classname> to allow a <classname>QueueChannel</classname> to be backed by an RDBMS.
</para>
<para>
You can configure a Message Store for any <classname>QueueChannel</classname> by adding the
<code>message-store</code> attribute as shown in the next example.
<programlisting language="xml"><![CDATA[<int:channel id="dbBackedChannel">
<int:queue message-store="messageStore">
<int:channel id="myChannel">
<int-jdbc:message-store id="messageStore" data-source="someDataSource"/>]]></programlisting>
The above example also shows that <classname>JdbcMessageStore</classname> can be configured with the namespace support
provided by the Spring Integration JDBC module. All you need to do is inject any <classname>javax.sql.DataSource</classname>
instance. The Spring Integration JDBC module also provides schema DDL for most popular databases. These schemas are located in
the <emphasis>org.springframework.integration.jdbc</emphasis> package of that module (spring-integration-jdbc).
<important>
One important feature is that with any transactional persistent store (e.g., JdbcMessageStore), as long as the poller has a transaction configured,
a Message removed from the store will only be permanently removed if the transaction completes
successfully, otherwise the transaction will roll back and the Message will not be lost.
successfully, otherwise the transaction will roll back and the Message will not be lost.
</important>
Many other implementations of the Message Store will be available as the growing number of Spring projects
related to "NoSQL" data stores provide the underlying support. Of course, you can always provide your own implementation
of the MessageGroupStore interface if you cannot find one that meets your particular needs.
</para>
</section>
<section id="channel-configuration-pubsubchannel">
<title>PublishSubscribeChannel Configuration</title>
<para>
@@ -648,16 +653,16 @@ payload to an Integer.
<section id="global-channel-configuration-interceptors">
<title>Global Channel Interceptor Configuration</title>
<titleabbrev id="global-channel-interceptor">Global Channel Interceptor</titleabbrev>
<para>
Channel Interceptors provide a clean and concise way of applying cross-cutting behavior per individual channel.
If the same behavior should be applied on multiple channels, configuring the same set of interceptors for
each channel <emphasis>would not be</emphasis> the most efficient way. To avoid repeated configuration while
also enabling interceptors to apply to multiple channels, Spring Integration provides
<emphasis>Global Interceptors</emphasis>.
Look at the example below:
<programlisting language="xml"><![CDATA[<int:channel-interceptor pattern="input*, bar*, foo" order="3">
<bean class="foo.barSampleInterceptor"/>
@@ -680,12 +685,12 @@ payload to an Integer.
</int:channel>]]></programlisting>
A reasonable question is how will a global interceptor be injected in relation to other interceptors
configured locally or through other global interceptor definitions? The current implementation provides
a very simple mechanism for defining the order of interceptor execution.
a very simple mechanism for defining the order of interceptor execution.
A positive number in the <code>order</code> attribute will ensure interceptor injection
after any existing interceptors and a negative number will ensure that the interceptor is injected before
existing interceptors.
This means that in the above example, the global interceptor will be injected <emphasis>AFTER</emphasis>
(since its order is greater than 0)
(since its order is greater than 0)
the 'wire-tap' interceptor configured locally. If there were another global interceptor with a matching
<code>pattern</code>, its order would be determined by comparing the values of the <code>order</code> attribute.
To inject a global interceptor <emphasis>BEFORE</emphasis> the existing interceptors, use a negative value for the <code>order</code> attribute.
@@ -700,7 +705,7 @@ payload to an Integer.
<title>Wire Tap</title>
<para>
As mentioned above, Spring Integration provides a simple <emphasis>Wire Tap</emphasis> interceptor out of
the box. You can configure a <emphasis>Wire Tap</emphasis> on any channel within an &lt;interceptors/&gt; element.
the box. You can configure a <emphasis>Wire Tap</emphasis> on any channel within an &lt;interceptors/&gt; element.
This is especially useful for debugging, and can be used in conjunction with Spring Integration's logging
Channel Adapter as follows: <programlisting language="xml"><![CDATA[ <int:channel id="in">
<int:interceptors>
@@ -710,23 +715,23 @@ payload to an Integer.
<int:logging-channel-adapter id="logger" level="DEBUG"/>]]></programlisting>
<tip>
The 'logging-channel-adapter' also accepts an 'expression' attribute so that you can evaluate
a SpEL expression against 'payload' and/or 'headers' variables. Alternatively, to simply log
The 'logging-channel-adapter' also accepts an 'expression' attribute so that you can evaluate
a SpEL expression against 'payload' and/or 'headers' variables. Alternatively, to simply log
the full Message toString() result, provide a value of "true" for the 'log-full-message' attribute.
That is <code>false</code> by default so that only the payload is logged. Setting that to
<code>true</code> enables logging of all headers in addition to the payload. The 'expression'
option does provide the most flexibility, however (e.g. expression="payload.user.name").
</tip>
</para>
<para>
<emphasis>A little more on Wire Tap</emphasis>
</para>
<para>
One of the common misconceptions about the wire tap and other similar components (<xref linkend="message-publishing-config"/>)
One of the common misconceptions about the wire tap and other similar components (<xref linkend="message-publishing-config"/>)
is that they are automatically asynchronous in nature. Wire-tap as a component is not
invoked asynchronously be default. Instead, Spring Integration focuses on a single unified
approach to configuring asynchronous behavior: the Message Channel.
approach to configuring asynchronous behavior: the Message Channel.
What makes certain parts of the message flow <emphasis>sync</emphasis> or <emphasis>async</emphasis>
is the type of <emphasis>Message Channel</emphasis> that has been configured within that flow. That
@@ -736,7 +741,7 @@ payload to an Integer.
just an internal, implicit realization of the EIP pattern, it is fully exposed as a configurable
component to the end user.
So, the Wire-tap component is ONLY responsible for performing the following 3 tasks:
So, the Wire-tap component is ONLY responsible for performing the following 3 tasks:
<itemizedlist>
<listitem>
<para>intercept a message flow by tapping into a channel (e.g., channelA)</para>
@@ -751,21 +756,21 @@ payload to an Integer.
It is essentially a variation of the Bridge, but it is encapsulated within a channel definition
(and hence easier to enable and disable without disrupting a flow). Also, unlike the bridge, it
basically forks another message flow. Is that flow <emphasis>synchronous</emphasis> or
basically forks another message flow. Is that flow <emphasis>synchronous</emphasis> or
<emphasis>asynchronous</emphasis>? The answer simply depends on the type of <emphasis>Message Channel</emphasis>
that 'channelB' is. And, now you know that we have: <emphasis>Direct Channel</emphasis>,
<emphasis>Pollable Channel</emphasis>, and <emphasis>Executor Channel</emphasis> as options.
The last two do break the thread boundary making communication via such channels
<emphasis>asynchronous</emphasis> simply because the dispatching of the message from that channel
to its subscribed handlers happens on a different thread than the one used to send the message to that
channel. That is what is going to make your wire-tap flow <emphasis>sync</emphasis> or <emphasis>async</emphasis>.
It is consistent with other components within the framework (e.g., Message Publisher) and actually
channel. That is what is going to make your wire-tap flow <emphasis>sync</emphasis> or <emphasis>async</emphasis>.
It is consistent with other components within the framework (e.g., Message Publisher) and actually
brings a level of consistency and simplicity by sparing you from worrying in advance (other than writing
thread safe code) whether a particular piece of code should be implemented as <emphasis>sync</emphasis> or
<emphasis>async</emphasis>. The actual wiring of two pieces of code (component A and component B) via
<emphasis>Message Channel</emphasis> is what makes their collaboration <emphasis>sync</emphasis> or
thread safe code) whether a particular piece of code should be implemented as <emphasis>sync</emphasis> or
<emphasis>async</emphasis>. The actual wiring of two pieces of code (component A and component B) via
<emphasis>Message Channel</emphasis> is what makes their collaboration <emphasis>sync</emphasis> or
<emphasis>async</emphasis>. You may even want to change from <emphasis>sync</emphasis> to
<emphasis>async</emphasis> in the future and <emphasis>Message Channel</emphasis> is what's going
<emphasis>async</emphasis> in the future and <emphasis>Message Channel</emphasis> is what's going
to allow you to do it swiftly without ever touching the code.</para>
<para>One final point regarding the Wire Tap is that, despite the rationale provided above for not
@@ -784,13 +789,13 @@ payload to an Integer.
<para>It is possible to configure a global wire tap as a special case of the <xref linkend="global-channel-configuration-interceptors" endterm="global-channel-interceptor"/>. Simply configure a top level <code>wire-tap</code> element. Now, in addition to the normal <code>wire-tap</code> namespace support, the <code>pattern</code> and <code>order</code> attributes are supported and work in exactly the same way as with the <code>channel-interceptor</code>
<programlisting language="xml"><![CDATA[<int:wire-tap pattern="input*, bar*, foo" order="3" channel="wiretapChannel"/>]]></programlisting>
</para>
<tip>A global wire tap provides a convenient way to configure a single channel wire tap externally without modifying the existing channel configuration. Simply set the <code>pattern</code> attribute to the target channel name. For example, This technique may be used to configure a test case to verify messages on a channel.
<tip>A global wire tap provides a convenient way to configure a single channel wire tap externally without modifying the existing channel configuration. Simply set the <code>pattern</code> attribute to the target channel name. For example, This technique may be used to configure a test case to verify messages on a channel.
</tip>
</section>
</section>
<section id="channel-special-channels">
<title>Special Channels</title>
@@ -798,7 +803,7 @@ payload to an Integer.
If namespace support is enabled, there are two special channels defined within the application context by default:
<code>errorChannel</code> and <code>nullChannel</code>. The 'nullChannel' acts like <code>/dev/null</code>,
simply logging any Message sent to it at DEBUG level and returning immediately. Any time you face channel
resolution errors for a reply that you don't care about, you can set the affected component's <code>output-channel</code> attribute
resolution errors for a reply that you don't care about, you can set the affected component's <code>output-channel</code> attribute
to 'nullChannel' (the name 'nullChannel' is reserved within the application context). The 'errorChannel' is
used internally for sending error messages and may be overridden with a custom configuration. This is
discussed in greater detail in <xref linkend="namespace-errorhandler"/>.

View File

@@ -586,7 +586,8 @@ any transaction configuration essentially allowing you to enhance the behavior o
be mapped to a Message payload or part of the payload or header (when using the Spring Expression Language). However there
are times when the type of input parameter of the endpoint method does not match the type of the payload or its part.
In this scenario we need to perform type conversion. Spring Integration provides a convenient way for registering type
converters (using the Spring 3.x ConversionService) within its own instance of a conversion service bean named <emphasis>integrationConversionService</emphasis>.
converters (using the Spring 3.x ConversionService) within its own instance of a conversion service bean named
<emphasis>integrationConversionService</emphasis>.
That bean is automatically created as soon as the first converter is defined using the Spring Integration namespace support.
To register a Converter all you need is to implement
@@ -594,13 +595,38 @@ any transaction configuration essentially allowing you to enhance the behavior o
convenient namespace support:
<programlisting language="xml"><![CDATA[ <int:converter ref="sampleConverter"/>
<bean id="sampleConverter" class="foo.bar.TestConverter"/>]]></programlisting>
<bean id="sampleConverter" class="foo.bar.TestConverter"/>]]></programlisting>
or as an inner bean:
<programlisting language="xml"><![CDATA[ <int:converter>
<bean class="org.springframework.integration.config.xml.ConverterParserTests$TestConverter3"/>
</int:converter>]]></programlisting>
<bean class="org.springframework.integration.config.xml.ConverterParserTests$TestConverter3"/>
</int:converter>]]></programlisting>
</para>
<important>
<para>
When configuring an <emphasis>Application Context</emphasis>, the
Spring Framework allows you to add a <emphasis>conversionService</emphasis> bean
(see <ulink url="http://static.springsource.org/spring/docs/current/spring-framework-reference/html/validation.html#core-convert-Spring-config">
Configuring a ConversionService</ulink> chapter). This service is used, when needed,
to perform appropriate conversions during bean creation and configuration.
</para>
<para>
In contrast, the <emphasis>integrationConversionService</emphasis> is used for runtime conversions.
These uses are quite different; converters that are intended for use when wiring bean
constructor-args and properties may produce unintended results if used at runtime
for Spring Integration expression evaluation against
Messages within Datatype Channels, Payload Type transformers etc.
</para>
<para>
However, if you do want to use the Spring <emphasis>conversionService</emphasis> as
the Spring Integration <emphasis>integrationConversionService</emphasis>,
you can configure an <emphasis>alias</emphasis> in the Application Context:
<programlisting language="xml"><![CDATA[<alias name="conversionService" alias="integrationConversionService"/>]]></programlisting>
In this case the <emphasis>conversionService</emphasis>'s Converters will be available for Spring Integration
runtime conversion.
</para>
</important>
</section>
<section id="async-polling">
@@ -620,7 +646,7 @@ any transaction configuration essentially allowing you to enhance the behavior o
forum (http://forum.springsource.org/showthread.php?t=94519):
<programlisting language="xml"><![CDATA[<int:service-activator input-channel="publishChannel" ref="myService">
<int:poller receive-timeout="5000" task-executor="taskExecutor" fixed-rate="50"/>
<int:poller receive-timeout="5000" task-executor="taskExecutor" fixed-rate="50"/>
</int:service-activator>
<task:executor id="taskExecutor" pool-size="20" queue-capacity="20"/>]]></programlisting>