Merge remote-tracking branch 'upstream/master' into 4.0.0-WIP

Conflicts:
	spring-integration-core/src/main/java/org/springframework/integration/channel/registry/ChannelRegistry.java
	spring-integration-core/src/main/java/org/springframework/integration/channel/registry/LocalChannelRegistry.java
	spring-integration-core/src/main/java/org/springframework/integration/mapping/AbstractHeaderMapper.java
	spring-integration-core/src/main/java/org/springframework/integration/support/json/AbstractJacksonJsonMessageParser.java
	spring-integration-core/src/test/java/org/springframework/integration/channel/registry/LocalChannelRegistryTests.java
	spring-integration-core/src/test/java/org/springframework/integration/handler/ServiceActivatorDefaultFrameworkMethodTests.java
	spring-integration-jmx/src/test/java/org/springframework/integration/jmx/ServiceActivatorDefaultFrameworkMethodTests.java
	spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/OperationInvokingOutboundGatewayTests.java

Resolved.
This commit is contained in:
Gary Russell
2013-11-01 18:19:02 -04:00
65 changed files with 2126 additions and 909 deletions

View File

@@ -26,6 +26,7 @@ import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.integration.EiMessageHeaderAccessor;
import org.springframework.integration.amqp.AmqpHeaders;
import org.springframework.integration.json.JsonHeaders;
import org.springframework.integration.mapping.AbstractHeaderMapper;
import org.springframework.util.StringUtils;
@@ -45,6 +46,8 @@ import org.springframework.util.StringUtils;
*
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Gary Russell
* @author Artem Bilan
* @since 2.1
*/
public class DefaultAmqpHeaderMapper extends AbstractHeaderMapper<MessageProperties> implements AmqpHeaderMapper {
@@ -70,6 +73,9 @@ public class DefaultAmqpHeaderMapper extends AbstractHeaderMapper<MessagePropert
STANDARD_HEADER_NAMES.add(AmqpHeaders.TIMESTAMP);
STANDARD_HEADER_NAMES.add(AmqpHeaders.TYPE);
STANDARD_HEADER_NAMES.add(AmqpHeaders.USER_ID);
STANDARD_HEADER_NAMES.add(JsonHeaders.TYPE_ID);
STANDARD_HEADER_NAMES.add(JsonHeaders.CONTENT_TYPE_ID);
STANDARD_HEADER_NAMES.add(JsonHeaders.KEY_TYPE_ID);
STANDARD_HEADER_NAMES.add(AmqpHeaders.SPRING_REPLY_CORRELATION);
STANDARD_HEADER_NAMES.add(AmqpHeaders.SPRING_REPLY_TO_STACK);
}
@@ -157,6 +163,14 @@ public class DefaultAmqpHeaderMapper extends AbstractHeaderMapper<MessagePropert
if (StringUtils.hasText(userId)) {
headers.put(AmqpHeaders.USER_ID, userId);
}
for (String jsonHeader : JsonHeaders.HEADERS) {
Object value = amqpMessageProperties.getHeaders().get(jsonHeader.replaceFirst(JsonHeaders.PREFIX, ""));
if (value instanceof String && StringUtils.hasText((String) value)) {
headers.put(jsonHeader, value);
}
}
Object replyCorrelation = amqpMessageProperties.getHeaders().get(AmqpHeaders.STACKED_CORRELATION_HEADER);
if (replyCorrelation instanceof String) {
if (StringUtils.hasText((String) replyCorrelation)) {
@@ -186,6 +200,7 @@ public class DefaultAmqpHeaderMapper extends AbstractHeaderMapper<MessagePropert
Map<String, Object> headers = amqpMessageProperties.getHeaders();
headers.remove(AmqpHeaders.STACKED_CORRELATION_HEADER);
headers.remove(AmqpHeaders.STACKED_REPLY_TO_HEADER);
return headers;
}
@@ -272,6 +287,18 @@ public class DefaultAmqpHeaderMapper extends AbstractHeaderMapper<MessagePropert
if (StringUtils.hasText(userId)) {
amqpMessageProperties.setUserId(userId);
}
for (String jsonHeader : JsonHeaders.HEADERS) {
Object value = getHeaderIfAvailable(headers, jsonHeader, Object.class);
if (value != null) {
headers.remove(jsonHeader);
if (value instanceof Class<?>) {
value = ((Class<?>) value).getName();
}
amqpMessageProperties.setHeader(jsonHeader.replaceFirst(JsonHeaders.PREFIX, ""), value.toString());
}
}
String replyCorrelation = getHeaderIfAvailable(headers, AmqpHeaders.SPRING_REPLY_CORRELATION, String.class);
if (StringUtils.hasLength(replyCorrelation)) {
amqpMessageProperties.setHeader("spring_reply_correlation", replyCorrelation);

View File

@@ -0,0 +1,168 @@
/*
* Copyright 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.
* 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.amqp.inbound;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.amqp.core.MessageListener;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.connection.Connection;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.support.converter.JsonMessageConverter;
import org.springframework.amqp.support.converter.SimpleMessageConverter;
import org.springframework.integration.Message;
import org.springframework.integration.amqp.support.DefaultAmqpHeaderMapper;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.core.PollableChannel;
import org.springframework.integration.json.JsonToObjectTransformer;
import org.springframework.integration.json.ObjectToJsonTransformer;
import org.springframework.integration.message.GenericMessage;
import org.springframework.integration.transformer.Transformer;
import com.rabbitmq.client.Channel;
/**
* @author Artem Bilan
* @since 3.0
*/
public class InboundEndpointTests {
@Test
public void testInt2809JavaTypePropertiesToAmqp() {
Connection connection = mock(Connection.class);
doAnswer(new Answer<Channel>() {
public Channel answer(InvocationOnMock invocation) throws Throwable {
return mock(Channel.class);
}
}).when(connection).createChannel(anyBoolean());
ConnectionFactory connectionFactory = mock(ConnectionFactory.class);
when(connectionFactory.createConnection()).thenReturn(connection);
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
AmqpInboundChannelAdapter adapter = new AmqpInboundChannelAdapter(container);
adapter.setMessageConverter(new JsonMessageConverter());
PollableChannel channel = new QueueChannel();
adapter.setOutputChannel(channel);
adapter.afterPropertiesSet();
Object payload = new Foo("bar1");
Transformer objectToJsonTransformer = new ObjectToJsonTransformer();
Message<?> jsonMessage = objectToJsonTransformer.transform(new GenericMessage<Object>(payload));
MessageProperties amqpMessageProperties = new MessageProperties();
org.springframework.amqp.core.Message amqpMessage =
new SimpleMessageConverter().toMessage(jsonMessage.getPayload(), amqpMessageProperties);
new DefaultAmqpHeaderMapper().fromHeadersToRequest(jsonMessage.getHeaders(), amqpMessageProperties);
MessageListener listener = (MessageListener) container.getMessageListener();
listener.onMessage(amqpMessage);
Message<?> result = channel.receive(1000);
assertEquals(payload, result.getPayload());
}
@Test
public void testInt2809JavaTypePropertiesFromAmqp() {
Connection connection = mock(Connection.class);
doAnswer(new Answer<Channel>() {
public Channel answer(InvocationOnMock invocation) throws Throwable {
return mock(Channel.class);
}
}).when(connection).createChannel(anyBoolean());
ConnectionFactory connectionFactory = mock(ConnectionFactory.class);
when(connectionFactory.createConnection()).thenReturn(connection);
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
AmqpInboundChannelAdapter adapter = new AmqpInboundChannelAdapter(container);
PollableChannel channel = new QueueChannel();
adapter.setOutputChannel(channel);
adapter.afterPropertiesSet();
Object payload = new Foo("bar1");
MessageProperties amqpMessageProperties = new MessageProperties();
org.springframework.amqp.core.Message amqpMessage = new JsonMessageConverter().toMessage(payload, amqpMessageProperties);
MessageListener listener = (MessageListener) container.getMessageListener();
listener.onMessage(amqpMessage);
Message<?> receive = channel.receive(1000);
Message<?> result = new JsonToObjectTransformer().transform(receive);
assertEquals(payload, result.getPayload());
}
public static class Foo {
private String bar;
public Foo() {
}
public Foo(String bar) {
this.bar = bar;
}
public String getBar() {
return bar;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Foo foo = (Foo) o;
if (bar != null ? !bar.equals(foo.bar) : foo.bar != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
return bar != null ? bar.hashCode() : 0;
}
}
}

View File

@@ -1,50 +0,0 @@
/*
* 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. 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.channel.registry;
import org.springframework.messaging.MessageChannel;
/**
* A strategy interface used to bind a {@link MessageChannel} to a logical name. The name
* is intended to identify a logical consumer or producer of messages. This may be a
* queue, a channel adapter, another message channel, a Spring bean, etc.
*
* @author Mark Fisher
* @author David Turanski
* @since 3.0
*/
public interface ChannelRegistry {
/**
* Register a message consumer
* @param name the logical identity of the message source
* @param channel the channel bound as a consumer
*/
void inbound(String name, MessageChannel channel);
/**
* Register a message producer
* @param name the logical identity of the message target
* @param channel the channel bound as a producer
*/
void outbound(String name, MessageChannel channel);
/**
* Create a tap on an already registered inbound channel
* @param name the registered name
* @param channel the channel that will receive messages from the tap
*/
void tap(String name, MessageChannel channel);
}

View File

@@ -1,168 +0,0 @@
/*
* 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. 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.channel.registry;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.messaging.MessageChannel;
import org.springframework.integration.channel.AbstractMessageChannel;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.PublishSubscribeChannel;
import org.springframework.integration.channel.interceptor.WireTap;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.integration.handler.BridgeHandler;
import org.springframework.util.Assert;
/**
* A simple implementation of {@link ChannelRegistry} for in-process use. For inbound and
* outbound, creates a {@link DirectChannel} and bridges the passed
* {@link MessageChannel} to the channel which is registered in the given application
* context. If that channel does not yet exist, it will be created. For tap, it adds a
* {@link WireTap} for an inbound channel whose name matches the one provided. If no such
* inbound channel exists at the time of the method invocation, it will throw an
* Exception. Otherwise the provided channel instance will receive messages from the wire
* tap on that inbound channel.
*
* @author David Turanski
* @author Mark Fisher
* @since 3.0
*/
public class LocalChannelRegistry implements ChannelRegistry, ApplicationContextAware, InitializingBean {
private volatile AbstractApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
this.applicationContext = (AbstractApplicationContext) applicationContext;
}
@Override
public void afterPropertiesSet() throws Exception {
Assert.notNull(applicationContext, "The 'applicationContext' property cannot be null");
}
/**
* Looks up or creates a DirectChannel with the given name and creates a bridge from
* that channel to the provided channel instance. Also registers a wire tap if the
* channel for the given name had been created. The target of the wire tap is a
* publish-subscribe channel.
*/
@Override
public void inbound(String name, MessageChannel channel) {
Assert.hasText(name, "a valid name is required to register an inbound channel");
Assert.notNull(channel, "channel must not be null");
DirectChannel registeredChannel = lookupOrCreateSharedChannel(name, DirectChannel.class);
bridge(registeredChannel, channel);
createSharedTapChannelIfNecessary(registeredChannel);
}
/**
* Looks up or creates a DirectChannel with the given name and creates a bridge to
* that channel from the provided channel instance.
*/
@Override
public void outbound(String name, MessageChannel channel) {
Assert.hasText(name, "a valid name is required to register an outbound channel");
Assert.notNull(channel, "channel must not be null");
Assert.isTrue(channel instanceof SubscribableChannel,
"channel must be of type " + SubscribableChannel.class.getName());
DirectChannel registeredChannel = lookupOrCreateSharedChannel(name, DirectChannel.class);
bridge((SubscribableChannel) channel, registeredChannel);
}
/**
* Looks up a wiretap for the inbound channel with the given name and creates a
* bridge from that wiretap's output channel to the provided channel instance.
* Will throw an Exception if no such wiretap exists.
*/
@Override
public void tap(String name, MessageChannel channel) {
Assert.hasText(name, "a valid name is required to register a tap channel");
Assert.notNull(channel, "channel must not be null");
SubscribableChannel tapChannel = null;
String tapName = name + ".tap";
try {
tapChannel = applicationContext.getBean(tapName, SubscribableChannel.class);
}
catch (Exception e) {
throw new IllegalArgumentException("No tap channel exists for '" + name
+ "'. A tap is only valid for a registered inbound channel.");
}
bridge(tapChannel, channel);
}
protected synchronized <T extends AbstractMessageChannel> T lookupOrCreateSharedChannel(String name, Class<T> requiredType) {
T channel = null;
if (applicationContext.containsBean(name)) {
try {
channel = applicationContext.getBean(name, requiredType);
}
catch (Exception e) {
throw new IllegalArgumentException("bean '" + name
+ "' is already registered but does not match the required type");
}
}
else {
channel = createSharedChannel(name, requiredType);
}
return channel;
}
protected <T extends AbstractMessageChannel> T createSharedChannel(String name, Class<T> requiredType) {
try {
T channel = requiredType.newInstance();
channel.setComponentName(name);
channel.setBeanFactory(applicationContext);
channel.setBeanName(name);
channel.afterPropertiesSet();
applicationContext.getBeanFactory().registerSingleton(name, channel);
return channel;
}
catch (Exception e) {
throw new IllegalArgumentException("failed to create channel: " + name, e);
}
}
private synchronized void createSharedTapChannelIfNecessary(AbstractMessageChannel channel) {
String tapName = channel.getComponentName() + ".tap";
PublishSubscribeChannel tapChannel = null;
if (!applicationContext.containsBean(tapName)) {
tapChannel = createSharedChannel(tapName, PublishSubscribeChannel.class);
WireTap wireTap = new WireTap(tapChannel);
channel.addInterceptor(wireTap);
}
else {
try {
tapChannel = applicationContext.getBean(tapName, PublishSubscribeChannel.class);
}
catch (Exception e) {
throw new IllegalArgumentException("bean '" + tapName
+ "' is already registered but does not match the required type");
}
}
}
protected BridgeHandler bridge(SubscribableChannel from, MessageChannel to) {
BridgeHandler handler = new BridgeHandler();
handler.setOutputChannel(to);
handler.afterPropertiesSet();
from.subscribe(handler);
return handler;
}
}

View File

@@ -1,4 +0,0 @@
/**
* Provides classes representing channel registries.
*/
package org.springframework.integration.channel.registry;

View File

@@ -73,7 +73,7 @@ public class ServiceActivatorFactoryBean extends AbstractStandardMessageHandlerF
}
/*
* Return a reply-producing message handler so that we still get 'produced no reply' messages
* and the super class will inject the advice chain to advise the handler if needed.
* and the super class will inject the advice chain to advise the handler method if needed.
*/
handler = new AbstractReplyProducingMessageHandler() {

View File

@@ -16,12 +16,12 @@
package org.springframework.integration.config.xml;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.json.JsonToObjectTransformer;
import org.springframework.util.StringUtils;
/**
* @author Mark Fisher
@@ -39,7 +39,9 @@ public class JsonToObjectTransformerParser extends AbstractTransformerParser {
protected void parseTransformer(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
String type = element.getAttribute("type");
String objectMapper = element.getAttribute("object-mapper");
builder.addConstructorArgValue(type);
if (StringUtils.hasText(type)) {
builder.addConstructorArgValue(type);
}
if (StringUtils.hasText(objectMapper)) {
builder.addConstructorArgReference(objectMapper);
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 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.
* 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.json;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
/**
* Pre-defined names and prefixes to be used for setting and/or retrieving JSON
* entries from/to Message Headers and other adapter, e.g. AMQP.
*
* @author Artem Bilan
* @since 3.0
*/
public class JsonHeaders {
public static final String PREFIX = "json";
public static final String TYPE_ID = PREFIX + "__TypeId__";
public static final String CONTENT_TYPE_ID = PREFIX + "__ContentTypeId__";
public static final String KEY_TYPE_ID = PREFIX + "__KeyTypeId__";
public static final Collection<String> HEADERS =
Collections.unmodifiableList(Arrays.asList(TYPE_ID, CONTENT_TYPE_ID, KEY_TYPE_ID));
}

View File

@@ -16,10 +16,13 @@
package org.springframework.integration.json;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.integration.Message;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.support.json.JacksonJsonObjectMapper;
import org.springframework.integration.support.json.JacksonJsonObjectMapperProvider;
import org.springframework.integration.support.json.JsonObjectMapper;
import org.springframework.integration.transformer.AbstractPayloadTransformer;
import org.springframework.integration.transformer.AbstractTransformer;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -36,13 +39,17 @@ import org.springframework.util.ClassUtils;
* @see JacksonJsonObjectMapperProvider
* @since 2.0
*/
public class JsonToObjectTransformer<T> extends AbstractPayloadTransformer<String, T> {
public class JsonToObjectTransformer extends AbstractTransformer implements BeanClassLoaderAware {
private final Class<T> targetClass;
private final Class<?> targetClass;
private final JsonObjectMapper<?> jsonObjectMapper;
public JsonToObjectTransformer(Class<T> targetClass) {
public JsonToObjectTransformer() {
this((Class<?>) null);
}
public JsonToObjectTransformer(Class<?> targetClass) {
this(targetClass, null);
}
@@ -52,8 +59,7 @@ public class JsonToObjectTransformer<T> extends AbstractPayloadTransformer<Strin
* @deprecated in favor of {@link #JsonToObjectTransformer(Class, JsonObjectMapper)}
*/
@Deprecated
public JsonToObjectTransformer(Class<T> targetClass, Object objectMapper) throws ClassNotFoundException {
Assert.notNull(targetClass, "targetClass must not be null");
public JsonToObjectTransformer(Class<?> targetClass, Object objectMapper) throws ClassNotFoundException {
this.targetClass = targetClass;
if (objectMapper != null) {
try {
@@ -70,15 +76,34 @@ public class JsonToObjectTransformer<T> extends AbstractPayloadTransformer<Strin
}
}
public JsonToObjectTransformer(Class<T> targetClass, JsonObjectMapper<?> jsonObjectMapper) {
Assert.notNull(targetClass, "targetClass must not be null");
public JsonToObjectTransformer(JsonObjectMapper<?> jsonObjectMapper) {
this(null, jsonObjectMapper);
}
public JsonToObjectTransformer(Class<?> targetClass, JsonObjectMapper<?> jsonObjectMapper) {
this.targetClass = targetClass;
this.jsonObjectMapper = (jsonObjectMapper != null) ? jsonObjectMapper : JacksonJsonObjectMapperProvider.newInstance();
}
@Override
protected T transformPayload(String payload) throws Exception {
return this.jsonObjectMapper.fromJson(payload, this.targetClass);
public void setBeanClassLoader(ClassLoader classLoader) {
if (this.jsonObjectMapper instanceof BeanClassLoaderAware) {
((BeanClassLoaderAware) this.jsonObjectMapper).setBeanClassLoader(classLoader);
}
}
@Override
protected Object doTransform(Message<?> message) throws Exception {
if (this.targetClass != null) {
return this.jsonObjectMapper.fromJson(message.getPayload(), this.targetClass);
}
else {
Object result = this.jsonObjectMapper.fromJson(message.getPayload(), message.getHeaders());
MessageBuilder<Object> messageBuilder = MessageBuilder.withPayload(result)
.copyHeaders(message.getHeaders())
.removeHeaders(JsonHeaders.HEADERS.toArray(new String[3]));
return messageBuilder.build();
}
}
}

View File

@@ -109,6 +109,9 @@ public class ObjectToJsonTransformer extends AbstractTransformer {
else if (StringUtils.hasLength(this.contentType)) {
headers.put(MessageHeaders.CONTENT_TYPE, this.contentType);
}
this.jsonObjectMapper.populateJavaTypes(headers, message.getPayload().getClass());
messageBuilder.copyHeaders(headers);
return messageBuilder.build();
}

View File

@@ -26,6 +26,8 @@ import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.integration.json.JsonHeaders;
import org.springframework.messaging.MessageHeaders;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
@@ -248,7 +250,7 @@ public abstract class AbstractHeaderMapper<T> implements RequestReplyHeaderMappe
if (!type.isAssignableFrom(value.getClass())) {
if (logger.isWarnEnabled()) {
logger.warn("skipping header '" + name + "' since it is not of expected type [" + type + "], it is [" +
value.getClass() + "]");
value.getClass() + "]");
}
return null;
}
@@ -271,9 +273,14 @@ public abstract class AbstractHeaderMapper<T> implements RequestReplyHeaderMappe
*/
private String addPrefixIfNecessary(String prefix, String propertyName) {
String headerName = propertyName;
if (StringUtils.hasText(prefix) && !headerName.startsWith(prefix) && !headerName.equals(MessageHeaders.CONTENT_TYPE)) {
if (StringUtils.hasText(prefix) && !headerName.startsWith(prefix) &&
!headerName.equals(MessageHeaders.CONTENT_TYPE) &&
(!JsonHeaders.HEADERS.contains(headerName) || !JsonHeaders.HEADERS.contains(JsonHeaders.PREFIX + headerName))) {
headerName = prefix + propertyName;
}
if (JsonHeaders.HEADERS.contains(JsonHeaders.PREFIX + headerName)) {
headerName = JsonHeaders.PREFIX + headerName;
}
return headerName;
}

View File

@@ -16,10 +16,14 @@
package org.springframework.integration.store.metadata;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Properties;
import org.apache.commons.logging.Log;
@@ -37,6 +41,7 @@ import org.springframework.util.DefaultPropertiesPersister;
*
* @author Oleg Zhurakousky
* @author Mark Fisher
* @author Gary Russell
* @since 2.0
*/
public class PropertiesPersistingMetadataStore implements MetadataStore, InitializingBean, DisposableBean {
@@ -86,9 +91,9 @@ public class PropertiesPersistingMetadataStore implements MetadataStore, Initial
}
private void saveMetadata() {
FileOutputStream outputStream = null;
OutputStream outputStream = null;
try {
outputStream = new FileOutputStream(this.file);
outputStream = new BufferedOutputStream(new FileOutputStream(this.file));
this.persister.store(this.metadata, outputStream, "Last feed entry");
}
catch (IOException e) {
@@ -104,15 +109,15 @@ public class PropertiesPersistingMetadataStore implements MetadataStore, Initial
}
catch (IOException e) {
// not fatal for the functionality of the component
logger.warn("Failed to close FileOutputStream to " + this.file.getAbsolutePath(), e);
logger.warn("Failed to close OutputStream to " + this.file.getAbsolutePath(), e);
}
}
}
private void loadMetadata() {
FileInputStream inputStream = null;
InputStream inputStream = null;
try {
inputStream = new FileInputStream(this.file);
inputStream = new BufferedInputStream(new FileInputStream(this.file));
this.persister.load(this.metadata, inputStream);
}
catch (Exception e) {
@@ -128,7 +133,7 @@ public class PropertiesPersistingMetadataStore implements MetadataStore, Initial
}
catch (Exception e2) {
// non fatal
logger.warn("Failed to close FileInputStream for: " + this.file.getAbsolutePath());
logger.warn("Failed to close InputStream for: " + this.file.getAbsolutePath());
}
}
}

View File

@@ -16,8 +16,10 @@
package org.springframework.integration.support.json;
import org.springframework.messaging.Message;
import java.lang.reflect.Type;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
/**
* Base {@link JsonInboundMessageMapper.JsonMessageParser} implementation for Jackson processors.
@@ -68,7 +70,7 @@ abstract class AbstractJacksonJsonMessageParser<P> implements JsonInboundMessage
Class<?> headerType = this.messageMapper.getHeaderTypes().containsKey(headerName) ?
this.messageMapper.getHeaderTypes().get(headerName) : Object.class;
try {
return this.objectMapper.fromJson(parser, headerType);
return this.objectMapper.fromJson(parser, (Type) headerType);
}
catch (Exception e) {
throw new IllegalArgumentException("Mapping header '" + headerName + "' of JSON message '" +

View File

@@ -0,0 +1,84 @@
/*
* Copyright 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.
* 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.support.json;
import java.io.File;
import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.Type;
import java.net.URL;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.util.ClassUtils;
/**
* Base class for Jackson {@link JsonObjectMapper} implementations.
*
* @author Artem Bilan
* @since 3.0
*/
public abstract class AbstractJacksonJsonObjectMapper<P, J> implements JsonObjectMapper<P>, BeanClassLoaderAware {
protected static final Collection<Class<?>> supportedJsonTypes =
Arrays.<Class<?>> asList(String.class, byte[].class, File.class, URL.class, InputStream.class, Reader.class);
private volatile ClassLoader classLoader = ClassUtils.getDefaultClassLoader();
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Override
public <T> T fromJson(Object json, Class<T> valueType) throws Exception {
return this.fromJson(json, this.constructType(valueType));
}
@Override
public <T> T fromJson(Object json, Map<String, Object> javaTypes) throws Exception {
J javaType = this.extractJavaType(javaTypes);
return this.fromJson(json, javaType);
}
protected J createJavaType(Map<String, Object> javaTypes, String javaTypeKey) throws Exception {
Object classValue = javaTypes.get(javaTypeKey);
if (classValue == null) {
throw new IllegalArgumentException("Could not resolve '" + javaTypeKey + "' in 'javaTypes'.");
}
else {
Class<?> aClass = null;
if (classValue instanceof Class<?>) {
aClass = (Class<?>) classValue;
}
else {
aClass = ClassUtils.forName(classValue.toString(), this.classLoader);
}
return this.constructType(aClass);
}
}
protected abstract <T> T fromJson(Object json, J type) throws Exception;
protected abstract J extractJavaType(Map<String, Object> javaTypes) throws Exception;
protected abstract J constructType(Type type);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 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.
@@ -16,15 +16,22 @@
package org.springframework.integration.support.json;
import java.io.File;
import java.io.InputStream;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Type;
import java.net.URL;
import java.util.Collection;
import java.util.Map;
import org.springframework.integration.json.JsonHeaders;
import org.springframework.util.Assert;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.util.Assert;
/**
* Jackson 2 JSON-processor (@link https://github.com/FasterXML) {@linkplain JsonObjectMapper} implementation.
* Delegates <code>toJson</code> and <code>fromJson</code>
@@ -33,7 +40,7 @@ import org.springframework.util.Assert;
* @author Artem Bilan
* @since 3.0
*/
public class Jackson2JsonObjectMapper implements JsonObjectMapper<JsonParser> {
public class Jackson2JsonObjectMapper extends AbstractJacksonJsonObjectMapper<JsonParser, JavaType> {
private final ObjectMapper objectMapper;
@@ -46,6 +53,7 @@ public class Jackson2JsonObjectMapper implements JsonObjectMapper<JsonParser> {
this.objectMapper = objectMapper;
}
@Override
public String toJson(Object value) throws Exception {
return this.objectMapper.writeValueAsString(value);
}
@@ -55,18 +63,72 @@ public class Jackson2JsonObjectMapper implements JsonObjectMapper<JsonParser> {
this.objectMapper.writeValue(writer, value);
}
public <T> T fromJson(String json, Class<T> valueType) throws Exception {
return this.objectMapper.readValue(json, valueType);
}
@Override
public <T> T fromJson(Reader json, Class<T> valueType) throws Exception {
return this.objectMapper.readValue(json, valueType);
protected <T> T fromJson(Object json, JavaType type) throws Exception {
if (json instanceof String) {
return this.objectMapper.readValue((String) json, type);
}
else if(json instanceof byte[]) {
return this.objectMapper.readValue((byte[]) json, type);
}
else if (json instanceof File) {
return this.objectMapper.readValue((File) json, type);
}
else if (json instanceof URL) {
return this.objectMapper.readValue((URL) json, type);
}
else if (json instanceof InputStream) {
return this.objectMapper.readValue((InputStream) json, type);
}
else if (json instanceof Reader) {
return this.objectMapper.readValue((Reader) json, type);
}
else {
throw new IllegalArgumentException("'json' argument must be an instance of: " + supportedJsonTypes);
}
}
@Override
public <T> T fromJson(JsonParser parser, Type valueType) throws Exception {
return this.objectMapper.readValue(parser, this.objectMapper.constructType(valueType));
return this.objectMapper.readValue(parser, this.constructType(valueType));
}
@Override
public void populateJavaTypes(Map<String, Object> map, Class<?> sourceClass) {
JavaType javaType = this.objectMapper.constructType(sourceClass);
map.put(JsonHeaders.TYPE_ID, javaType.getRawClass());
if (javaType.isContainerType() && !javaType.isArrayType()) {
map.put(JsonHeaders.CONTENT_TYPE_ID, javaType.getContentType().getRawClass());
}
if (javaType.getKeyType() != null) {
map.put(JsonHeaders.KEY_TYPE_ID, javaType.getKeyType().getRawClass());
}
}
@Override
@SuppressWarnings({ "unchecked" })
protected JavaType extractJavaType(Map<String, Object> javaTypes) throws Exception {
JavaType classType = this.createJavaType(javaTypes, JsonHeaders.TYPE_ID);
if (!classType.isContainerType() || classType.isArrayType()) {
return classType;
}
JavaType contentClassType = this.createJavaType(javaTypes, JsonHeaders.CONTENT_TYPE_ID);
if (classType.getKeyType() == null) {
return this.objectMapper.getTypeFactory()
.constructCollectionType((Class<? extends Collection<?>>) classType.getRawClass(), contentClassType);
}
JavaType keyClassType = this.createJavaType(javaTypes, JsonHeaders.KEY_TYPE_ID);
return this.objectMapper.getTypeFactory()
.constructMapType((Class<? extends Map<?, ?>>) classType.getRawClass(), keyClassType, contentClassType);
}
@Override
protected JavaType constructType(Type type) {
return this.objectMapper.constructType(type);
}
}

View File

@@ -16,13 +16,20 @@
package org.springframework.integration.support.json;
import java.io.File;
import java.io.InputStream;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Type;
import java.net.URL;
import java.util.Collection;
import java.util.Map;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.JavaType;
import org.springframework.integration.json.JsonHeaders;
import org.springframework.util.Assert;
/**
@@ -33,7 +40,7 @@ import org.springframework.util.Assert;
* @author Artem Bilan
* @since 3.0
*/
public class JacksonJsonObjectMapper implements JsonObjectMapper<JsonParser> {
public class JacksonJsonObjectMapper extends AbstractJacksonJsonObjectMapper<JsonParser, JavaType> {
private final ObjectMapper objectMapper;
@@ -46,6 +53,7 @@ public class JacksonJsonObjectMapper implements JsonObjectMapper<JsonParser> {
this.objectMapper = objectMapper;
}
@Override
public String toJson(Object value) throws Exception {
return this.objectMapper.writeValueAsString(value);
}
@@ -55,18 +63,72 @@ public class JacksonJsonObjectMapper implements JsonObjectMapper<JsonParser> {
this.objectMapper.writeValue(writer, value);
}
public <T> T fromJson(String json, Class<T> valueType) throws Exception {
return this.objectMapper.readValue(json, valueType);
}
@Override
public <T> T fromJson(Reader json, Class<T> valueType) throws Exception {
return this.objectMapper.readValue(json, valueType);
}
@Override
public <T> T fromJson(JsonParser parser, Type valueType) throws Exception {
return this.objectMapper.readValue(parser, this.objectMapper.constructType(valueType));
return this.objectMapper.readValue(parser, this.constructType(valueType));
}
@Override
protected <T> T fromJson(Object json, JavaType type) throws Exception {
if (json instanceof String) {
return this.objectMapper.readValue((String) json, type);
}
else if(json instanceof byte[]) {
return this.objectMapper.readValue((byte[]) json, type);
}
else if (json instanceof File) {
return this.objectMapper.readValue((File) json, type);
}
else if (json instanceof URL) {
return this.objectMapper.readValue((URL) json, type);
}
else if (json instanceof InputStream) {
return this.objectMapper.readValue((InputStream) json, type);
}
else if (json instanceof Reader) {
return this.objectMapper.readValue((Reader) json, type);
}
else {
throw new IllegalArgumentException("'json' argument must be an instance of: " + supportedJsonTypes);
}
}
@Override
public void populateJavaTypes(Map<String, Object> map, Class<?> sourceClass) {
JavaType javaType = this.constructType(sourceClass);
map.put(JsonHeaders.TYPE_ID, javaType.getRawClass());
if (javaType.isContainerType() && !javaType.isArrayType()) {
map.put(JsonHeaders.CONTENT_TYPE_ID, javaType.getContentType().getRawClass());
}
if (javaType.getKeyType() != null) {
map.put(JsonHeaders.KEY_TYPE_ID, javaType.getKeyType().getRawClass());
}
}
@Override
protected JavaType constructType(Type type) {
return this.objectMapper.constructType(type);
}
@Override
@SuppressWarnings({ "unchecked" })
protected JavaType extractJavaType(Map<String, Object> javaTypes) throws Exception {
JavaType classType = this.createJavaType(javaTypes, JsonHeaders.TYPE_ID);
if (!classType.isContainerType() || classType.isArrayType()) {
return classType;
}
JavaType contentClassType = this.createJavaType(javaTypes, JsonHeaders.CONTENT_TYPE_ID);
if (classType.getKeyType() == null) {
return this.objectMapper.getTypeFactory()
.constructCollectionType((Class<? extends Collection<?>>) classType.getRawClass(), contentClassType);
}
JavaType keyClassType = this.createJavaType(javaTypes, JsonHeaders.KEY_TYPE_ID);
return this.objectMapper.getTypeFactory()
.constructMapType((Class<? extends Map<?, ?>>) classType.getRawClass(), keyClassType, contentClassType);
}
}

View File

@@ -16,9 +16,9 @@
package org.springframework.integration.support.json;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Type;
import java.util.Map;
/**
* Strategy interface to convert an Object to/from the JSON representation.
@@ -33,10 +33,11 @@ public interface JsonObjectMapper<P> {
void toJson(Object value, Writer writer) throws Exception;
<T> T fromJson(String json, Class<T> valueType) throws Exception;
<T> T fromJson(Object json, Class<T> valueType) throws Exception;
<T> T fromJson(Reader json, Class<T> valueType) throws Exception;
<T> T fromJson(Object json, Map<String, Object> javaTypes) throws Exception;
<T> T fromJson(P parser, Type valueType) throws Exception;
void populateJavaTypes(Map<String, Object> map, Class<?> sourceClass);
}

View File

@@ -16,9 +16,9 @@
package org.springframework.integration.support.json;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Type;
import java.util.Map;
/**
* Simple {@linkplain JsonObjectMapper} adapter implementation, if there is no need
@@ -39,12 +39,7 @@ public abstract class JsonObjectMapperAdapter<P> implements JsonObjectMapper<P>
}
@Override
public <T> T fromJson(String json, Class<T> valueType) throws Exception {
return null;
}
@Override
public <T> T fromJson(Reader json, Class<T> valueType) throws Exception {
public <T> T fromJson(Object json, Class<T> valueType) throws Exception {
return null;
}
@@ -53,4 +48,13 @@ public abstract class JsonObjectMapperAdapter<P> implements JsonObjectMapper<P>
return null;
}
@Override
public <T> T fromJson(Object json, Map<String, Object> javaTypes) throws Exception {
return null;
}
@Override
public void populateJavaTypes(Map<String, Object> map, Class<?> sourceClass) {
}
}

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.
@@ -16,6 +16,8 @@
package org.springframework.integration.util;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
@@ -24,6 +26,24 @@ import java.util.Set;
*/
public abstract class ClassUtils {
/**
* Map with primitive wrapper type as key and corresponding primitive
* type as value, for example: Integer.class -> int.class.
*/
private static final Map<Class<?>, Class<?>> primitiveWrapperTypeMap = new HashMap<Class<?>, Class<?>>(8);
static {
primitiveWrapperTypeMap.put(Boolean.class, boolean.class);
primitiveWrapperTypeMap.put(Byte.class, byte.class);
primitiveWrapperTypeMap.put(Character.class, char.class);
primitiveWrapperTypeMap.put(Double.class, double.class);
primitiveWrapperTypeMap.put(Float.class, float.class);
primitiveWrapperTypeMap.put(Integer.class, int.class);
primitiveWrapperTypeMap.put(Long.class, long.class);
primitiveWrapperTypeMap.put(Short.class, short.class);
}
public static Class<?> findClosestMatch(Class<?> type, Set<Class<?>> candidates, boolean failOnTie) {
int minTypeDiffWeight = Integer.MAX_VALUE;
Class<?> closestMatch = null;
@@ -35,7 +55,7 @@ public abstract class ClassUtils {
}
else if (failOnTie && typeDiffWeight < Integer.MAX_VALUE && (typeDiffWeight == minTypeDiffWeight)) {
throw new IllegalStateException("Unresolvable ambiguity while attempting to find closest match for [" +
type.getName() + "]. Candidate types [" + closestMatch.getName() + "] and [" + candidate.getName() +
type.getName() + "]. Candidate types [" + closestMatch.getName() + "] and [" + candidate.getName() +
"] have equal weight.");
}
}
@@ -67,4 +87,14 @@ public abstract class ClassUtils {
return result;
}
/**
* Resolve the given class if it is a primitive wrapper class,
* returning the corresponding primitive type instead.
* @param clazz the wrapper class to check
* @return the corresponding primitive if the clazz is a wrapper, otherwise null
*/
public static Class<?> resolvePrimitiveType(Class<?> clazz) {
return primitiveWrapperTypeMap.get(clazz);
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 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.
* 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.util;
/**
* Utility methods for analyzing stack traces.
*
* @author Gary Russell
* @since 3.0
*
*/
public class StackTraceUtils {
private StackTraceUtils() {}
/**
* Traverses the stack trace element array looking for instances that contain the first or second
* Strings in the className property.
* @param firstClass The first class to look for.
* @param secondClass The second class to look for.
* @param stackTrace The stack trace.
* @return true if the first class appears first, false if the second appears first
* @throws IllegalArgumentException if neither class is found.
*/
public static boolean isFrameContainingXBeforeFrameContainingY(String firstClass, String secondClass, StackTraceElement[] stackTrace) {
for (StackTraceElement element : stackTrace) {
if (element.getClassName().contains(firstClass)) {
return true;
}
else if (element.getClassName().contains(secondClass)) {
return false;
}
}
throw new IllegalArgumentException("Neither " + firstClass + " nor " + secondClass + " class found");
}
}

View File

@@ -1,171 +0,0 @@
/*
* 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. 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.channel.registry;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessagingException;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.messaging.support.GenericMessage;
/**
* @author David Turanski
* @author Mark Fisher
* @since 3.0
*/
public class LocalChannelRegistryTests {
private LocalChannelRegistry registry = new LocalChannelRegistry();
private GenericApplicationContext context = new GenericApplicationContext();
@Before
public void setUp() {
registry.setApplicationContext(context);
context.refresh();
}
@Test
public void testInbound() {
DirectChannel channel = new DirectChannel();
registry.inbound("inbound", channel);
assertTrue(context.containsBean("inbound"));
final AtomicBoolean messageReceived = new AtomicBoolean();
channel.subscribe(new MessageHandler() {
@Override
public void handleMessage(Message<?> message) throws MessagingException {
messageReceived.set(true);
assertEquals("hello", message.getPayload());
}
});
SubscribableChannel registeredChannel = context.getBean("inbound", SubscribableChannel.class);
registeredChannel.send(new GenericMessage<String>("hello"));
assertTrue(messageReceived.get());
}
@Test
public void testOutbound() {
DirectChannel channel = new DirectChannel();
registry.outbound("outbound", channel);
assertTrue(context.containsBean("outbound"));
final AtomicBoolean messageReceived = new AtomicBoolean();
SubscribableChannel registeredChannel = context.getBean("outbound", SubscribableChannel.class);
registeredChannel.subscribe(new MessageHandler() {
@Override
public void handleMessage(Message<?> message) throws MessagingException {
messageReceived.set(true);
assertEquals("hello", message.getPayload());
}
});
channel.send(new GenericMessage<String>("hello"));
assertTrue(messageReceived.get());
}
@Test(expected = IllegalArgumentException.class)
public void testOutboundTapShouldFail() {
DirectChannel channel = new DirectChannel();
registry.outbound("outbound", channel);
DirectChannel tapChannel = new DirectChannel();
registry.tap("outbound", tapChannel);
}
@Test
public void testInboundTap() {
DirectChannel channel = new DirectChannel();
registry.inbound("inbound", channel);
DirectChannel tapChannel = new DirectChannel();
registry.tap("inbound", tapChannel);
final AtomicBoolean originalMessageReceived = new AtomicBoolean();
final AtomicBoolean tapMessageReceived = new AtomicBoolean();
tapChannel.subscribe(new MessageHandler() {
@Override
public void handleMessage(Message<?> message) throws MessagingException {
tapMessageReceived.set(true);
assertEquals("hello", message.getPayload());
}
});
channel.subscribe(new MessageHandler() {
@Override
public void handleMessage(Message<?> message) throws MessagingException {
originalMessageReceived.set(true);
assertEquals("hello", message.getPayload());
}
});
MessageChannel registeredChannel = context.getBean("inbound", MessageChannel.class);
registeredChannel.send(new GenericMessage<String>("hello"));
assertTrue(originalMessageReceived.get());
assertTrue(tapMessageReceived.get());
}
@Test
public void testFlowThroughRegisteredChannelFromOutboundToInbound() {
DirectChannel outbound = new DirectChannel();
DirectChannel inbound = new DirectChannel();
registry.outbound("foo", outbound);
registry.inbound("foo", inbound);
final AtomicBoolean messageReceived = new AtomicBoolean();
inbound.subscribe(new MessageHandler() {
@Override
public void handleMessage(Message<?> message) throws MessagingException {
messageReceived.set(true);
assertEquals("hello", message.getPayload());
}
});
outbound.send(new GenericMessage<String>("hello"));
assertTrue(messageReceived.get());
}
@Test
public void testFlowThroughRegisteredChannelFromOutboundToInboundWithTap() {
DirectChannel outbound = new DirectChannel();
DirectChannel inbound = new DirectChannel();
DirectChannel tap = new DirectChannel();
registry.outbound("foo", outbound);
registry.inbound("foo", inbound);
registry.tap("foo", tap);
final AtomicBoolean originalMessageReceived = new AtomicBoolean();
final AtomicBoolean tapMessageReceived = new AtomicBoolean();
inbound.subscribe(new MessageHandler() {
@Override
public void handleMessage(Message<?> message) throws MessagingException {
originalMessageReceived.set(true);
assertEquals("hello", message.getPayload());
}
});
tap.subscribe(new MessageHandler() {
@Override
public void handleMessage(Message<?> message) throws MessagingException {
tapMessageReceived.set(true);
assertEquals("hello", message.getPayload());
}
});
outbound.send(new GenericMessage<String>("hello"));
assertTrue(originalMessageReceived.get());
assertTrue(tapMessageReceived.get());
}
}

View File

@@ -22,19 +22,19 @@
<service-activator id="replyingHandlerWithStandardMethodTestService"
input-channel="replyingHandlerWithStandardMethodTestInputChannel"
method="handleMessage">
<beans:bean
<beans:bean id="innerReplyingHandler"
class="org.springframework.integration.handler.ServiceActivatorDefaultFrameworkMethodTests$TestReplyingMessageHandler"/>
</service-activator>
<service-activator id="replyingHandlerWithOtherMethodTestService"
input-channel="replyingHandlerWithOtherMethodTestInputChannel"
method="foo">
<beans:bean
<beans:bean id="innerReplyingHandlerFoo"
class="org.springframework.integration.handler.ServiceActivatorDefaultFrameworkMethodTests$TestReplyingMessageHandler"/>
</service-activator>
<service-activator id="handlerTestService" input-channel="handlerTestInputChannel">
<beans:bean
<beans:bean id="innerHandler"
class="org.springframework.integration.handler.ServiceActivatorDefaultFrameworkMethodTests$TestMessageHandler"/>
</service-activator>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/integration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration.xsd">
<service-activator id="optimizedRefReplyingHandlerTestService1"
input-channel="optimizedRefReplyingHandlerTestInputChannel" ref="testReplyingMessageHandler"/>
<service-activator id="optimizedRefReplyingHandlerTestService2"
input-channel="optimizedRefReplyingHandlerTestInputChannel" ref="testReplyingMessageHandler"/>
<beans:bean id="testReplyingMessageHandler"
class="org.springframework.integration.handler.ServiceActivatorDefaultFrameworkMethodTests$TestReplyingMessageHandler"/>
</beans:beans>

View File

@@ -17,16 +17,24 @@
package org.springframework.integration.handler;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.integration.util.StackTraceUtils;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
@@ -90,7 +98,7 @@ public class ServiceActivatorDefaultFrameworkMethodTests {
assertEquals("TEST", reply.getPayload());
assertEquals("replyingHandlerTestInputChannel,replyingHandlerTestService", reply.getHeaders().get("history").toString());
StackTraceElement[] st = (StackTraceElement[]) reply.getHeaders().get("callStack");
assertEquals("doDispatch", st[3].getMethodName()); // close to the metal
assertTrue(StackTraceUtils.isFrameContainingXBeforeFrameContainingY("Dispatcher", "MethodInvokerHelper", st)); // close to the metal
}
@Test
@@ -103,7 +111,7 @@ public class ServiceActivatorDefaultFrameworkMethodTests {
assertEquals("optimizedRefReplyingHandlerTestInputChannel,optimizedRefReplyingHandlerTestService",
reply.getHeaders().get("history").toString());
StackTraceElement[] st = (StackTraceElement[]) reply.getHeaders().get("callStack");
assertEquals("doDispatch", st[3].getMethodName());
assertTrue(StackTraceUtils.isFrameContainingXBeforeFrameContainingY("Dispatcher", "MethodInvokerHelper", st)); // close to the metal
}
@Test
@@ -115,7 +123,7 @@ public class ServiceActivatorDefaultFrameworkMethodTests {
assertEquals("TEST", reply.getPayload());
assertEquals("replyingHandlerWithStandardMethodTestInputChannel,replyingHandlerWithStandardMethodTestService", reply.getHeaders().get("history").toString());
StackTraceElement[] st = (StackTraceElement[]) reply.getHeaders().get("callStack");
assertEquals("doDispatch", st[3].getMethodName()); // close to the metal
assertTrue(StackTraceUtils.isFrameContainingXBeforeFrameContainingY("Dispatcher", "MethodInvokerHelper", st)); // close to the metal
}
@Test
@@ -149,6 +157,22 @@ public class ServiceActivatorDefaultFrameworkMethodTests {
assertEquals("processorTestInputChannel,processorTestService", reply.getHeaders().get("history").toString());
}
@Test
public void testFailOnDoubleReference() {
try {
new ClassPathXmlApplicationContext(this.getClass().getSimpleName() + "-fail-context.xml",
this.getClass());
fail("Expected exception due to 2 endpoints referencing the same bean");
}
catch (Exception e) {
assertThat(e, Matchers.instanceOf(BeanCreationException.class));
assertThat(e.getCause(), Matchers.instanceOf(BeanCreationException.class));
assertThat(e.getCause().getCause(), Matchers.instanceOf(IllegalArgumentException.class));
assertThat(e.getCause().getCause().getMessage(),
Matchers.containsString("An AbstractReplyProducingMessageHandler may only be referenced once"));
}
}
@SuppressWarnings("unused")
private static class TestReplyingMessageHandler extends AbstractReplyProducingMessageHandler {
@@ -162,6 +186,10 @@ public class ServiceActivatorDefaultFrameworkMethodTests {
}
public String foo(String in) {
Exception e = new RuntimeException();
StackTraceElement[] st = e.getStackTrace();
// use this to test that StackTraceUtils works as expected and returns false
assertFalse(StackTraceUtils.isFrameContainingXBeforeFrameContainingY("Dispatcher", "MethodInvokerHelper", st));
return "bar";
}
@@ -174,7 +202,7 @@ public class ServiceActivatorDefaultFrameworkMethodTests {
public void handleMessage(Message<?> requestMessage) {
Exception e = new RuntimeException();
StackTraceElement[] st = e.getStackTrace();
assertEquals("doDispatch", st[4].getMethodName());
assertTrue(StackTraceUtils.isFrameContainingXBeforeFrameContainingY("Dispatcher", "MethodInvokerHelper", st)); // close to the metal
}
}

View File

@@ -8,16 +8,16 @@
http://www.springframework.org/schema/integration/spring-integration.xsd">
<json-to-object-transformer id="defaultJacksonMapperTransformer" input-channel="defaultObjectMapperInput"
type="org.springframework.integration.json.JsonToObjectTransformerParserTests$TestPerson"/>
type="org.springframework.integration.json.TestPerson"/>
<json-to-object-transformer id="customJacksonMapperTransformer" input-channel="customObjectMapperInput"
type="org.springframework.integration.json.JsonToObjectTransformerParserTests$TestPerson"
type="org.springframework.integration.json.TestPerson"
object-mapper="customObjectMapper"/>
<beans:bean id="customObjectMapper" class="org.springframework.integration.json.JsonToObjectTransformerParserTests$CustomObjectMapper"/>
<json-to-object-transformer id="customJsonMapperTransformer" input-channel="customJsonObjectMapperInput"
type="org.springframework.integration.json.JsonToObjectTransformerParserTests$TestPerson"
type="org.springframework.integration.json.TestPerson"
object-mapper="customJsonObjectMapper"/>
<beans:bean id="customJsonObjectMapper" class="org.springframework.integration.json.JsonToObjectTransformerParserTests$CustomJsonObjectMapper"/>

View File

@@ -134,79 +134,6 @@ public class JsonToObjectTransformerParserTests {
}
static class TestPerson {
private String firstName;
private String lastName;
private int age;
private TestAddress address;
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getLastName() {
return this.lastName;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return this.age;
}
public void setAddress(TestAddress address) {
this.address = address;
}
public TestAddress getAddress() {
return this.address;
}
@Override
public String toString() {
return "name=" + this.firstName + " " + this.lastName
+ ", age=" + this.age + ", address=" + this.address;
}
}
static class TestAddress {
private int number;
private String street;
public void setNumber(int number) {
this.number = number;
}
public void setStreet(String street) {
this.street = street;
}
@Override
public String toString() {
return this.number + " " + this.street;
}
}
static class CustomObjectMapper extends ObjectMapper {
public CustomObjectMapper() {
@@ -219,8 +146,8 @@ public class JsonToObjectTransformerParserTests {
static class CustomJsonObjectMapper extends JsonObjectMapperAdapter {
@Override
public Object fromJson(String json, Class valueType) throws Exception {
return new TestJsonContainer(json);
public Object fromJson(Object json, Class valueType) throws Exception {
return new TestJsonContainer((String) json);
}
}

View File

@@ -22,6 +22,8 @@ import org.codehaus.jackson.JsonParser.Feature;
import org.codehaus.jackson.map.ObjectMapper;
import org.junit.Test;
import org.springframework.integration.Message;
import org.springframework.integration.message.GenericMessage;
import org.springframework.integration.support.json.JacksonJsonObjectMapper;
/**
@@ -33,9 +35,11 @@ public class JsonToObjectTransformerTests {
@Test
public void objectPayload() throws Exception {
JsonToObjectTransformer<TestPerson> transformer = new JsonToObjectTransformer<TestPerson>(TestPerson.class);
JsonToObjectTransformer transformer = new JsonToObjectTransformer(TestPerson.class);
String jsonString = "{\"firstName\":\"John\",\"lastName\":\"Doe\",\"age\":42,\"address\":{\"number\":123,\"street\":\"Main Street\"}}";
TestPerson person = transformer.transformPayload(jsonString);
Message<?> message = transformer.transform(new GenericMessage<String>(jsonString));
@SuppressWarnings("unchecked")
TestPerson person = (TestPerson) message.getPayload();
assertEquals("John", person.getFirstName());
assertEquals("Doe", person.getLastName());
assertEquals(42, person.getAge());
@@ -47,10 +51,12 @@ public class JsonToObjectTransformerTests {
ObjectMapper customMapper = new ObjectMapper();
customMapper.configure(Feature.ALLOW_UNQUOTED_FIELD_NAMES, Boolean.TRUE);
customMapper.configure(Feature.ALLOW_SINGLE_QUOTES, Boolean.TRUE);
JsonToObjectTransformer<TestPerson> transformer =
new JsonToObjectTransformer<TestPerson>(TestPerson.class, new JacksonJsonObjectMapper(customMapper));
JsonToObjectTransformer transformer =
new JsonToObjectTransformer(TestPerson.class, new JacksonJsonObjectMapper(customMapper));
String jsonString = "{firstName:'John', lastName:'Doe', age:42, address:{number:123, street:'Main Street'}}";
TestPerson person = transformer.transformPayload(jsonString);
Message<?> message = transformer.transform(new GenericMessage<String>(jsonString));
@SuppressWarnings("unchecked")
TestPerson person = (TestPerson) message.getPayload();
assertEquals("John", person.getFirstName());
assertEquals("Doe", person.getLastName());
assertEquals(42, person.getAge());
@@ -60,82 +66,8 @@ public class JsonToObjectTransformerTests {
@SuppressWarnings("deprecation")
@Test(expected = IllegalArgumentException.class)
public void testInt2831IllegalArgument() throws Exception {
new JsonToObjectTransformer<String>(String.class, new Object());
new JsonToObjectTransformer(String.class, new Object());
}
@SuppressWarnings("unused")
private static class TestPerson {
private String firstName;
private String lastName;
private int age;
private TestAddress address;
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getLastName() {
return this.lastName;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return this.age;
}
public void setAddress(TestAddress address) {
this.address = address;
}
public TestAddress getAddress() {
return this.address;
}
@Override
public String toString() {
return "name=" + this.firstName + " " + this.lastName
+ ", age=" + this.age + ", address=" + this.address;
}
}
@SuppressWarnings("unused")
private static class TestAddress {
private int number;
private String street;
public void setNumber(int number) {
this.number = number;
}
public void setStreet(String street) {
this.street = street;
}
@Override
public String toString() {
return this.number + " " + this.street;
}
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 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.
* 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.json;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.springframework.integration.Message;
import org.springframework.integration.message.GenericMessage;
/**
* @author Artem Bilan
* @since 3.0
*/
public class JsonTransformersSymmetricalTests {
@Test
public void testInt2809ObjectToJson_JsonToObject() {
TestPerson person = new TestPerson("John", "Doe", 42);
person.setAddress(new TestAddress(123, "Main Street"));
ObjectToJsonTransformer objectToJsonTransformer = new ObjectToJsonTransformer();
Message<?> jsonMessage = objectToJsonTransformer.transform(new GenericMessage<Object>(person));
JsonToObjectTransformer jsonToObjectTransformer = new JsonToObjectTransformer();
Message<?> result = jsonToObjectTransformer.transform(jsonMessage);
assertEquals(person, result.getPayload());
}
}

View File

@@ -174,87 +174,6 @@ public class ObjectToJsonTransformerParserTests {
}
static class TestPerson {
private String firstName;
private String lastName;
private int age;
private TestAddress address;
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getLastName() {
return this.lastName;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return this.age;
}
public void setAddress(TestAddress address) {
this.address = address;
}
public TestAddress getAddress() {
return this.address;
}
@Override
public String toString() {
return "\"name\":\"" + this.firstName + " " + this.lastName
+ "\", \"age\":" + this.age + ", \"address\":\"" + this.address + "\"";
}
}
static class TestAddress {
private int number;
private String street;
public int getNumber() {
return this.number;
}
public void setNumber(int number) {
this.number = number;
}
public String getStreet() {
return this.street;
}
public void setStreet(String street) {
this.street = street;
}
@Override
public String toString() {
return this.number + " " + this.street;
}
}
static class CustomObjectMapper extends ObjectMapper {
public CustomObjectMapper() {

View File

@@ -147,66 +147,4 @@ public class ObjectToJsonTransformerTests {
new ObjectToJsonTransformer(new Object());
}
@SuppressWarnings("unused")
private static class TestPerson {
private final String firstName;
private final String lastName;
private final int age;
private TestAddress address;
public TestPerson(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public int getAge() {
return age;
}
public TestAddress getAddress() {
return address;
}
public void setAddress(TestAddress address) {
this.address = address;
}
}
@SuppressWarnings("unused")
private static class TestAddress {
private final int number;
private final String street;
public TestAddress(int number, String street) {
this.number = number;
this.street = street;
}
public int getNumber() {
return number;
}
public String getStreet() {
return street;
}
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright 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.
* 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.json;
/**
* @author Mark Fisher
* @since 2.0
*/
@SuppressWarnings("unused")
class TestAddress {
private volatile int number;
private volatile String street;
TestAddress() {
}
public TestAddress(int number, String street) {
this.number = number;
this.street = street;
}
public int getNumber() {
return this.number;
}
public void setNumber(int number) {
this.number = number;
}
public String getStreet() {
return this.street;
}
public void setStreet(String street) {
this.street = street;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TestAddress that = (TestAddress) o;
if (number != that.number) return false;
if (street != null ? !street.equals(that.street) : that.street != null) return false;
return true;
}
@Override
public int hashCode() {
int result = number;
result = 31 * result + (street != null ? street.hashCode() : 0);
return result;
}
@Override
public String toString() {
return this.number + " " + this.street;
}
}

View File

@@ -0,0 +1,105 @@
/*
* Copyright 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.
* 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.json;
/**
* @author Mark Fisher
* @since 2.0
*/
@SuppressWarnings("unused")
class TestPerson {
private volatile String firstName;
private volatile String lastName;
private volatile int age;
private volatile TestAddress address;
TestPerson() {
}
public TestPerson(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getLastName() {
return this.lastName;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return this.age;
}
public void setAddress(TestAddress address) {
this.address = address;
}
public TestAddress getAddress() {
return this.address;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TestPerson that = (TestPerson) o;
if (age != that.age) return false;
if (address != null ? !address.equals(that.address) : that.address != null) return false;
if (firstName != null ? !firstName.equals(that.firstName) : that.firstName != null) return false;
if (lastName != null ? !lastName.equals(that.lastName) : that.lastName != null) return false;
return true;
}
@Override
public int hashCode() {
int result = firstName != null ? firstName.hashCode() : 0;
result = 31 * result + (lastName != null ? lastName.hashCode() : 0);
result = 31 * result + age;
result = 31 * result + (address != null ? address.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "name=" + this.firstName + " " + this.lastName
+ ", age=" + this.age + ", address=" + this.address;
}
}

View File

@@ -16,11 +16,15 @@
package org.springframework.integration.file;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.Charset;
import org.apache.commons.logging.Log;
@@ -329,12 +333,12 @@ public class FileWritingMessageHandler extends AbstractReplyProducingMessageHand
private File handleFileMessage(final File sourceFile, File tempFile, final File resultFile) throws IOException {
if (FileExistsMode.APPEND.equals(this.fileExistsMode)){
File fileToWriteTo = this.determineFileToWrite(resultFile, tempFile);
final FileOutputStream fos = new FileOutputStream(fileToWriteTo, true);
final FileInputStream fis = new FileInputStream(sourceFile);
final BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(fileToWriteTo, true));
final BufferedInputStream bis = new BufferedInputStream(new FileInputStream(sourceFile));
WhileLockedProcessor whileLockedProcessor = new WhileLockedProcessor(this.lockRegistry, fileToWriteTo.getAbsolutePath()){
@Override
protected void whileLocked() throws IOException {
FileCopyUtils.copy(fis, fos);
FileCopyUtils.copy(bis, bos);
}
};
whileLockedProcessor.doWhileLocked();
@@ -362,11 +366,11 @@ public class FileWritingMessageHandler extends AbstractReplyProducingMessageHand
final boolean append = FileExistsMode.APPEND.equals(this.fileExistsMode);
final FileOutputStream fos = new FileOutputStream(fileToWriteTo, append);
final BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(fileToWriteTo, append));
WhileLockedProcessor whileLockedProcessor = new WhileLockedProcessor(this.lockRegistry, fileToWriteTo.getAbsolutePath()){
@Override
protected void whileLocked() throws IOException {
FileCopyUtils.copy(bytes, fos);
FileCopyUtils.copy(bytes, bos);
}
};
@@ -380,7 +384,7 @@ public class FileWritingMessageHandler extends AbstractReplyProducingMessageHand
final boolean append = FileExistsMode.APPEND.equals(this.fileExistsMode);
final OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(fileToWriteTo, append), this.charset);
final Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fileToWriteTo, append), this.charset));
WhileLockedProcessor whileLockedProcessor = new WhileLockedProcessor(this.lockRegistry, fileToWriteTo.getAbsolutePath()){
@Override
protected void whileLocked() throws IOException {

View File

@@ -16,6 +16,7 @@
package org.springframework.integration.file.remote.gateway;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
@@ -521,9 +522,9 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
if (!localFile.exists()) {
String tempFileName = localFile.getAbsolutePath() + this.temporaryFileSuffix;
File tempFile = new File(tempFileName);
FileOutputStream fileOutputStream = new FileOutputStream(tempFile);
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(tempFile));
try {
session.read(remoteFilePath, fileOutputStream);
session.read(remoteFilePath, outputStream);
}
catch (Exception e) {
if (e instanceof RuntimeException){
@@ -535,7 +536,7 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
}
finally {
try {
fileOutputStream.close();
outputStream.close();
}
catch (Exception ignored2) {
//Ignore it

View File

@@ -16,9 +16,11 @@
package org.springframework.integration.file.remote.synchronizer;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
@@ -202,9 +204,9 @@ public abstract class AbstractInboundFileSynchronizer<F> implements InboundFileS
if (!localFile.exists()) {
String tempFileName = localFile.getAbsolutePath() + this.temporaryFileSuffix;
File tempFile = new File(tempFileName);
FileOutputStream fileOutputStream = new FileOutputStream(tempFile);
OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(tempFile));
try {
session.read(remoteFilePath, fileOutputStream);
session.read(remoteFilePath, outputStream);
}
catch (Exception e) {
if (e instanceof RuntimeException){
@@ -216,7 +218,7 @@ public abstract class AbstractInboundFileSynchronizer<F> implements InboundFileS
}
finally {
try {
fileOutputStream.close();
outputStream.close();
}
catch (Exception ignored2) {
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 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.
@@ -16,9 +16,11 @@
package org.springframework.integration.file.transformer;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.Charset;
import org.springframework.util.Assert;
@@ -26,8 +28,9 @@ import org.springframework.util.FileCopyUtils;
/**
* A payload transformer that copies a File's contents to a String.
*
*
* @author Mark Fisher
* @author Gary Russell
*/
public class FileToStringTransformer extends AbstractFilePayloadTransformer<String> {
@@ -45,7 +48,7 @@ public class FileToStringTransformer extends AbstractFilePayloadTransformer<Stri
@Override
protected final String transformFile(File file) throws Exception {
InputStreamReader reader = new InputStreamReader(new FileInputStream(file), this.charset);
Reader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), this.charset));
return FileCopyUtils.copyToString(reader);
}

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.
@@ -16,6 +16,7 @@
package org.springframework.integration.http.multipart;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
@@ -29,8 +30,9 @@ import org.springframework.web.multipart.MultipartFile;
/**
* A {@link MultipartFile} implementation that represents an uploaded File.
* The actual file content either exists in memory (in a byte array) or in a File.
*
*
* @author Mark Fisher
* @author Gary Russell
* @since 2.0
*/
public class UploadedMultipartFile implements MultipartFile {
@@ -94,7 +96,7 @@ public class UploadedMultipartFile implements MultipartFile {
if (this.bytes != null) {
return new ByteArrayInputStream(this.bytes);
}
return new FileInputStream(this.file);
return new BufferedInputStream(new FileInputStream(this.file));
}
public String getOriginalFilename() {

View File

@@ -34,6 +34,7 @@ import javax.management.ObjectName;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.integration.MessageHandlingException;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
import org.springframework.integration.util.ClassUtils;
import org.springframework.jmx.support.ObjectNameManager;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessagingException;
@@ -68,7 +69,6 @@ public class OperationInvokingMessageHandler extends AbstractReplyProducingMessa
private volatile String operationName;
/**
* Provide a reference to the MBeanServer within which the MBean
* target for operation invocation has been registered.
@@ -134,7 +134,7 @@ public class OperationInvokingMessageHandler extends AbstractReplyProducingMessa
*/
value = paramsFromMessage.get("p" + (index + 1));
}
if (value != null && value.getClass().getName().equals(paramInfo.getType())) {
if (value != null && valueTypeMatchesParameterType(value, paramInfo)) {
values[index] = value;
signature[index] = paramInfo.getType();
index++;
@@ -151,18 +151,29 @@ public class OperationInvokingMessageHandler extends AbstractReplyProducingMessa
}
throw new MessagingException(requestMessage, "failed to find JMX operation '"
+ operationName + "' on MBean [" + objectName + "] of type [" + mbeanInfo.getClassName()
+ "] with " + paramsFromMessage.size() + " parameters: " + paramsFromMessage.keySet());
+ "] with " + paramsFromMessage.size() + " parameters: " + paramsFromMessage);
}
catch (JMException e) {
throw new MessageHandlingException(requestMessage, "failed to invoke JMX operation '" +
operationName + "' on MBean [" + objectName + "]" + " with " +
paramsFromMessage.size() + " parameters: " + paramsFromMessage.keySet(), e);
paramsFromMessage.size() + " parameters: " + paramsFromMessage, e);
}
catch (IOException e) {
throw new MessageHandlingException(requestMessage, "IOException on MBeanServerConnection", e);
}
}
private boolean valueTypeMatchesParameterType(Object value, MBeanParameterInfo paramInfo) {
Class<? extends Object> valueClass = value.getClass();
if (valueClass.getName().equals(paramInfo.getType())) {
return true;
}
else {
Class<?> primitiveType = ClassUtils.resolvePrimitiveType(valueClass);
return primitiveType != null && primitiveType.getName().equals(paramInfo.getType());
}
}
/**
* First checks if defaultObjectName is set, otherwise falls back on {@link JmxHeaders#OBJECT_NAME} header.
*/
@@ -229,4 +240,4 @@ public class OperationInvokingMessageHandler extends AbstractReplyProducingMessa
return map;
}
}
}

View File

@@ -1,11 +1,11 @@
/*
* 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. 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.
@@ -33,7 +33,7 @@ import org.springframework.util.StringUtils;
/**
* Parser for the 'mbean-export' element of the integration JMX namespace.
*
*
* @author Mark Fisher
* @author Gary Russell
* @since 2.0
@@ -61,7 +61,8 @@ public class MBeanExporterParser extends AbstractSingleBeanDefinitionParser {
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "object-name-static-properties");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "managed-components", "componentNamePatterns");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "shutdown-executor");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "object-naming-strategy", "namingStrategy");
builder.addPropertyValue("server", mbeanServer);
this.registerMBeanExporterHelper(parserContext.getRegistry());
}
@@ -70,7 +71,7 @@ public class MBeanExporterParser extends AbstractSingleBeanDefinitionParser {
BeanDefinitionBuilder mBeanExporterHelperBuilder = BeanDefinitionBuilder.rootBeanDefinition(MBeanExporterHelper.class);
BeanDefinitionReaderUtils.registerWithGeneratedName(mBeanExporterHelperBuilder.getBeanDefinition(), registry);
}
private Object getMBeanServer(Element element, ParserContext parserContext) {
String mbeanServer = element.getAttribute("server");
if (StringUtils.hasText(mbeanServer)) {

View File

@@ -27,6 +27,7 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;
import javax.management.DynamicMBean;
@@ -80,6 +81,8 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.util.Assert;
import org.springframework.util.PatternMatchUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.FieldCallback;
import org.springframework.util.ReflectionUtils.FieldFilter;
/**
* <p>
@@ -106,6 +109,7 @@ import org.springframework.util.ReflectionUtils;
* @author Helena Edelson
* @author Oleg Zhurakousky
* @author Gary Russell
* @author Artem Bilan
*/
@ManagedResource
public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostProcessor, BeanFactoryAware,
@@ -163,7 +167,7 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP
private final MetadataMBeanInfoAssembler assembler = new MetadataMBeanInfoAssembler(attributeSource);
private final MetadataNamingStrategy namingStrategy = new MetadataNamingStrategy(attributeSource);
private final MetadataNamingStrategy defaultNamingStrategy = new MetadataNamingStrategy(attributeSource);
private String[] componentNamePatterns = { "*" };
@@ -179,7 +183,7 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP
super();
// Shouldn't be necessary, but to be on the safe side...
setAutodetect(false);
setNamingStrategy(namingStrategy);
setNamingStrategy(defaultNamingStrategy);
setAssembler(assembler);
}
@@ -206,7 +210,7 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP
*/
public void setDefaultDomain(String domain) {
this.domain = domain;
this.namingStrategy.setDefaultDomain(domain);
this.defaultNamingStrategy.setDefaultDomain(domain);
}
public void setComponentNamePatterns(String[] componentNamePatterns) {
@@ -217,7 +221,7 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
super.setBeanFactory(beanFactory);
Assert.isTrue(beanFactory instanceof ListableBeanFactory, "A ListableBeanFactory is required.");
Assert.isInstanceOf(ListableBeanFactory.class, beanFactory, "A ListableBeanFactory is required.");
this.beanFactory = (ListableBeanFactory) beanFactory;
}
@@ -246,6 +250,12 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP
}
if (bean instanceof MessageHandler) {
if (this.handlerInAnonymousWrapper(bean) != null) {
if (logger.isDebugEnabled()) {
logger.debug("Skipping " + beanName + " because it wraps another handler");
}
return bean;
}
SimpleMessageHandlerMetrics monitor = new SimpleMessageHandlerMetrics((MessageHandler) bean);
Object advised = applyHandlerInterceptor(bean, monitor, beanClassLoader);
handlers.add(monitor);
@@ -282,6 +292,33 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP
}
private MessageHandler handlerInAnonymousWrapper(final Object bean) {
if (bean != null && bean.getClass().isAnonymousClass()) {
final AtomicReference<MessageHandler> wrapped = new AtomicReference<MessageHandler>();
ReflectionUtils.doWithFields(bean.getClass(), new FieldCallback() {
@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
field.setAccessible(true);
Object handler = field.get(bean);
if (handler instanceof MessageHandler) {
wrapped.set((MessageHandler) handler);
}
}
}, new FieldFilter() {
@Override
public boolean matches(Field field) {
return wrapped.get() == null && field.getName().startsWith("val$");
}
});
return wrapped.get();
}
else {
return null;
}
}
/**
* Copy of private method in super class. Needed so we can avoid using the bean factory to extract the bean again,
* and risk it being a proxy (which it almost certainly is by now).
@@ -987,20 +1024,22 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP
String source = "endpoint";
Object endpoint = null;
MessageHandler messageHandler = monitor.getMessageHandler();
for (String beanName : names) {
endpoint = beanFactory.getBean(beanName);
Object field = null;
try {
field = extractTarget(getField(endpoint, "handler"));
Object field = extractTarget(getField(endpoint, "handler"));
if (field == messageHandler ||
this.extractTarget(this.handlerInAnonymousWrapper(field)) == messageHandler) {
name = beanName;
endpointName = beanName;
break;
}
}
catch (Exception e) {
logger.trace("Could not get handler from bean = " + beanName);
}
if (field == monitor.getMessageHandler()) {
name = beanName;
endpointName = beanName;
break;
}
}
if (name != null && endpoint != null && name.startsWith("_org.springframework.integration")) {
name = getInternalComponentName(name);
@@ -1044,11 +1083,11 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP
}
if (name == null) {
if (monitor.getMessageHandler() instanceof NamedComponent) {
name = ((NamedComponent) monitor.getMessageHandler()).getComponentName();
if (messageHandler instanceof NamedComponent) {
name = ((NamedComponent) messageHandler).getComponentName();
}
if (name == null) {
name = monitor.getMessageHandler().toString();
name = messageHandler.toString();
}
source = "handler";
}

View File

@@ -204,6 +204,19 @@
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="object-naming-strategy" use="optional">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.jmx.export.naming.ObjectNamingStrategy" />
</tool:annotation>
</xsd:appinfo>
<xsd:documentation>
An ObjectNamingStrategy to generate the MBean's ObjectName. See the reference documentation
for details of the default ObjectName. Also see 'object-name-static-properties'.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>

View File

@@ -6,6 +6,6 @@ log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %t %c{2}:%L - %m
log4j.category.org.springframework=WARN
log4j.category.org.springframework.integration=DEBUG
log4j.category.org.springframework.beans.factory=DEBUG
#log4j.category.org.springframework.integration=DEBUG
#log4j.category.org.springframework.beans.factory=DEBUG
#log4j.category.org.springframework.integration.monitor=TRACE

View File

@@ -27,19 +27,19 @@
<service-activator id="replyingHandlerWithStandardMethodTestService"
input-channel="replyingHandlerWithStandardMethodTestInputChannel"
method="handleMessage">
<beans:bean
<beans:bean id="innerReplyingHandler"
class="org.springframework.integration.jmx.ServiceActivatorDefaultFrameworkMethodTests$TestReplyingMessageHandler"/>
</service-activator>
<service-activator id="replyingHandlerWithOtherMethodTestService"
input-channel="replyingHandlerWithOtherMethodTestInputChannel"
method="foo">
<beans:bean
<beans:bean id="innerReplyingHandlerFoo"
class="org.springframework.integration.jmx.ServiceActivatorDefaultFrameworkMethodTests$TestReplyingMessageHandler"/>
</service-activator>
<service-activator id="handlerTestService" input-channel="handlerTestInputChannel">
<beans:bean
<beans:bean id="innerHandler"
class="org.springframework.integration.jmx.ServiceActivatorDefaultFrameworkMethodTests$TestMessageHandler"/>
</service-activator>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/integration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:int-jmx="http://www.springframework.org/schema/integration/jmx"
xsi:schemaLocation="http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/jmx http://www.springframework.org/schema/integration/jmx/spring-integration-jmx.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:mbean-server/>
<int-jmx:mbean-export default-domain="test2"/>
<service-activator id="optimizedRefReplyingHandlerTestService1"
input-channel="optimizedRefReplyingHandlerTestInputChannel" ref="testReplyingMessageHandler"/>
<service-activator id="optimizedRefReplyingHandlerTestService2"
input-channel="optimizedRefReplyingHandlerTestInputChannel" ref="testReplyingMessageHandler"/>
<beans:bean id="testReplyingMessageHandler"
class="org.springframework.integration.jmx.ServiceActivatorDefaultFrameworkMethodTests$TestReplyingMessageHandler"/>
</beans:beans>

View File

@@ -17,18 +17,26 @@
package org.springframework.integration.jmx;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
import org.springframework.integration.handler.MessageProcessor;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.integration.util.StackTraceUtils;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
@@ -92,7 +100,7 @@ public class ServiceActivatorDefaultFrameworkMethodTests {
assertEquals("TEST", reply.getPayload());
assertEquals("replyingHandlerTestInputChannel,replyingHandlerTestService", reply.getHeaders().get("history").toString());
StackTraceElement[] st = (StackTraceElement[]) reply.getHeaders().get("callStack");
assertEquals("doDispatch", st[15].getMethodName()); // close to the metal
assertTrue(StackTraceUtils.isFrameContainingXBeforeFrameContainingY("Dispatcher", "MethodInvokerHelper", st));
}
@Test
@@ -105,7 +113,7 @@ public class ServiceActivatorDefaultFrameworkMethodTests {
assertEquals("optimizedRefReplyingHandlerTestInputChannel,optimizedRefReplyingHandlerTestService",
reply.getHeaders().get("history").toString());
StackTraceElement[] st = (StackTraceElement[]) reply.getHeaders().get("callStack");
assertEquals("doDispatch", st[15].getMethodName());
assertTrue(StackTraceUtils.isFrameContainingXBeforeFrameContainingY("Dispatcher", "MethodInvokerHelper", st));
}
@Test
@@ -117,7 +125,7 @@ public class ServiceActivatorDefaultFrameworkMethodTests {
assertEquals("TEST", reply.getPayload());
assertEquals("replyingHandlerWithStandardMethodTestInputChannel,replyingHandlerWithStandardMethodTestService", reply.getHeaders().get("history").toString());
StackTraceElement[] st = (StackTraceElement[]) reply.getHeaders().get("callStack");
assertEquals("doDispatch", st[15].getMethodName()); // close to the metal
assertTrue(StackTraceUtils.isFrameContainingXBeforeFrameContainingY("Dispatcher", "MethodInvokerHelper", st));
}
@Test
@@ -151,6 +159,23 @@ public class ServiceActivatorDefaultFrameworkMethodTests {
assertEquals("processorTestInputChannel,processorTestService", reply.getHeaders().get("history").toString());
}
@Test
public void testFailOnDoubleReference() {
try {
new ClassPathXmlApplicationContext(this.getClass().getSimpleName() + "-fail-context.xml",
this.getClass());
fail("Expected exception due to 2 endpoints referencing the same bean");
}
catch (Exception e) {
assertThat(e, Matchers.instanceOf(BeanCreationException.class));
assertThat(e.getCause(), Matchers.instanceOf(BeanCreationException.class));
assertThat(e.getCause().getCause(), Matchers.instanceOf(IllegalArgumentException.class));
assertThat(e.getCause().getCause().getMessage(),
Matchers.containsString("An AbstractReplyProducingMessageHandler may only be referenced once"));
}
}
private interface Foo {
public String foo(String in);
@@ -169,6 +194,10 @@ public class ServiceActivatorDefaultFrameworkMethodTests {
}
public String foo(String in) {
Exception e = new RuntimeException();
StackTraceElement[] st = e.getStackTrace();
// use this to test that StackTraceUtils works as expected and returns false
assertFalse(StackTraceUtils.isFrameContainingXBeforeFrameContainingY("Dispatcher", "MethodInvokerHelper", st));
return "bar";
}
@@ -181,7 +210,7 @@ public class ServiceActivatorDefaultFrameworkMethodTests {
public void handleMessage(Message<?> requestMessage) {
Exception e = new RuntimeException();
StackTraceElement[] st = e.getStackTrace();
assertEquals("doDispatch", st[28].getMethodName());
assertTrue(StackTraceUtils.isFrameContainingXBeforeFrameContainingY("Dispatcher", "MethodInvokerHelper", st));
}
}

View File

@@ -18,11 +18,14 @@
<jmx:mbean-export id="integratioMbeanExporter"
server="mbs"
default-domain="tests.MBeanExpoerterParser"
object-name-static-properties="appProperties"/>
object-name-static-properties="appProperties"
object-naming-strategy="keyNamer"/>
<util:properties id="appProperties">
<prop key="foo">foo</prop>
<prop key="bar">bar</prop>
</util:properties>
<bean id="keyNamer" class="org.springframework.jmx.export.naming.KeyNamingStrategy" />
</beans>

View File

@@ -18,6 +18,7 @@ package org.springframework.integration.jmx.config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.util.Properties;
@@ -57,6 +58,7 @@ public class MBeanExporterParserTests {
assertTrue(properties.containsKey("foo"));
assertTrue(properties.containsKey("bar"));
assertEquals(server, exporter.getServer());
assertSame(context.getBean("keyNamer"), TestUtils.getPropertyValue(exporter, "namingStrategy"));
exporter.destroy();
}

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:jmx="http://www.springframework.org/schema/integration/jmx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/jmx
http://www.springframework.org/schema/integration/jmx/spring-integration-jmx.xsd">
<jmx:mbean-export id="integrationMbeanExporter" server="mbs" default-domain="test.MBeanRegistration"
object-naming-strategy="namer" />
<bean id="namer" class="org.springframework.integration.jmx.config.MBeanRegistrationCustomNamingTests$Namer" />
<context:mbean-server id="mbs" />
<int:channel id="testChannel" />
<int:service-activator id="service" input-channel="testChannel" method="get">
<bean class="org.springframework.integration.jmx.config.MBeanRegistrationCustomNamingTests$Source"/>
</int:service-activator>
<int:logging-channel-adapter id="logger" channel="testChannel"/>
<int:chain id="chain" input-channel="chainin">
<int:transformer id="t1" expression="'foo'"/>
<int:filter id="f1" expression="true"/>
</int:chain>
</beans>

View File

@@ -0,0 +1,72 @@
/*
* Copyright 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. 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.jmx.config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.Set;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jmx.export.naming.KeyNamingStrategy;
import org.springframework.jmx.export.naming.ObjectNamingStrategy;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Dave Syer
* @author Gary Russell
* @since 3.0
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class MBeanRegistrationCustomNamingTests {
@Autowired
private MBeanServer server;
@Test
public void testHandlerMBeanRegistration() throws Exception {
Set<ObjectName> names = server.queryNames(new ObjectName("test.MBeanRegistration:type=Integration,componentType=MessageHandler,*"), null);
assertEquals(6, names.size());
assertTrue(names.contains(new ObjectName("test.MBeanRegistration:type=Integration,componentType=MessageHandler,name=chain,bean=endpoint")));
assertTrue(names.contains(new ObjectName("test.MBeanRegistration:type=Integration,componentType=MessageHandler,name=chain$child.t1,bean=handler")));
assertTrue(names.contains(new ObjectName("test.MBeanRegistration:type=Integration,componentType=MessageHandler,name=chain$child.f1,bean=handler")));
}
public static class Source {
public String get() {
return "foo";
}
}
public static class Namer implements ObjectNamingStrategy {
private final ObjectNamingStrategy realNamer = new KeyNamingStrategy();
@Override
public ObjectName getObjectName(Object managedBean, String beanKey) throws MalformedObjectNameException {
String actualBeanKey = beanKey.replace("type=", "type=Integration,componentType=");
return realNamer.getObjectName(managedBean, actualBeanKey);
}
}
}

View File

@@ -33,6 +33,14 @@
</jmx:request-handler-advice-chain>
</jmx:operation-invoking-outbound-gateway>
<si:channel id="primitiveChannel"/>
<jmx:operation-invoking-outbound-gateway request-channel="primitiveChannel"
reply-channel="withReplyChannelOutput"
object-name="org.springframework.integration.jmx.config:type=TestBean,name=testBeanGateway"
operation-name="testPrimitiveArgs"
requires-reply="false" />
<si:chain id="operationInvokingWithinChain" input-channel="jmxOutboundGatewayInsideChain" output-channel="withReplyChannelOutput">
<jmx:operation-invoking-outbound-gateway operation-name="testWithReturn" requires-reply="true"
object-name="org.springframework.integration.jmx.config:type=TestBean,name=testBeanGateway"/>

View File

@@ -17,31 +17,40 @@
package org.springframework.integration.jmx.config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice;
import org.springframework.integration.jmx.OperationInvokingMessageHandler;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.PollableChannel;
import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice;
import org.springframework.integration.jmx.OperationInvokingMessageHandler;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Oleg Zhurakousky
* @author Artem Bilan
* @author Gary Russell
*
*/
@ContextConfiguration
@@ -49,15 +58,15 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
public class OperationInvokingOutboundGatewayTests {
@Autowired
@Qualifier("withReplyChannel")
private MessageChannel withReplyChannel;
@Autowired
@Qualifier("withReplyChannelOutput")
private MessageChannel primitiveChannel;
@Autowired
private PollableChannel withReplyChannelOutput;
@Autowired
@Qualifier("withNoReplyChannel")
private MessageChannel withNoReplyChannel;
@Autowired
@@ -90,6 +99,42 @@ public class OperationInvokingOutboundGatewayTests {
assertEquals(3, adviceCalled);
}
@Test
public void gatewayWithPrimitiveArgs() throws Exception {
primitiveChannel.send(new GenericMessage<Object[]>(new Object[] { true, 0L, 1 }));
assertEquals(1, testBean.messages.size());
List<Object> argList = new ArrayList<Object>();
argList.add(false);
argList.add(123L);
argList.add(42);
primitiveChannel.send(new GenericMessage<List<Object>>(argList));
assertEquals(2, testBean.messages.size());
Map<String, Object> argMap = new HashMap<String, Object>();
argMap.put("p1", true);
argMap.put("p2", 0L);
argMap.put("p3", 42);
primitiveChannel.send(new GenericMessage<Map<String, Object>>(argMap));
assertEquals(3, testBean.messages.size());
argMap.put("p2", true);
argMap.put("p1", 0L);
argMap.put("p3", 42);
try {
primitiveChannel.send(new GenericMessage<Map<String, Object>>(argMap));
fail("Expected Exception");
}
catch (Exception e) {
assertThat(e, Matchers.instanceOf(MessagingException.class));
assertThat(e.getMessage(), Matchers.containsString("failed to find JMX operation"));
}
// TODO: Uncomment when Spring Framework minimum is 3.2.3
// argMap = new HashMap<String, Object>();
// argMap.put("bool", true);
// argMap.put("time", 0L);
// argMap.put("foo", 42);
// primitiveChannel.send(new GenericMessage<Map<String, Object>>(argMap));
// assertEquals(4, testBean.messages.size());
}
@Test
public void gatewayWithNoReplyChannel() throws Exception {
withNoReplyChannel.send(new GenericMessage<String>("1"));
@@ -102,7 +147,7 @@ public class OperationInvokingOutboundGatewayTests {
@Test //INT-1029, INT-2822
public void testOutboundGatewayInsideChain() throws Exception {
List handlers = TestUtils.getPropertyValue(this.operationInvokingWithinChain, "handlers", List.class);
List<?> handlers = TestUtils.getPropertyValue(this.operationInvokingWithinChain, "handlers", List.class);
assertEquals(1, handlers.size());
Object handler = handlers.get(0);
assertTrue(handler instanceof OperationInvokingMessageHandler);

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.
@@ -26,6 +26,7 @@ import org.springframework.jmx.export.annotation.ManagedResource;
/**
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Gary Russell
* @since 2.0
*/
@ManagedResource
@@ -42,11 +43,16 @@ public class TestBean {
public void test(String text) {
this.messages.add(text);
}
@ManagedOperation
public List<String> testWithReturn(String text) {
this.messages.add(text);
return messages;
}
@ManagedOperation
public void testPrimitiveArgs(boolean bool, long time, int foo) {
this.messages.add(bool + " " + time + " " + foo);
}
}

View File

@@ -0,0 +1,245 @@
/*
* Copyright 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.
* 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.redis.inbound;
import java.util.concurrent.TimeUnit;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.BoundListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.integration.Message;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.MessagingException;
import org.springframework.integration.channel.MessagePublishingErrorHandler;
import org.springframework.integration.endpoint.MessageProducerSupport;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.util.ErrorHandlingTaskExecutor;
import org.springframework.jmx.export.annotation.ManagedMetric;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.util.Assert;
/**
* @author Mark Fisher
* @author Gunnar Hillert
* @author Artem Bilan
* @since 3.0
*/
@ManagedResource
public class RedisQueueMessageDrivenEndpoint extends MessageProducerSupport {
public static final long DEFAULT_RECEIVE_TIMEOUT = 1000;
private final BoundListOperations<String, byte[]> boundListOperations;
private MessageChannel errorChannel;
private volatile TaskExecutor taskExecutor;
private volatile RedisSerializer<?> serializer = new JdkSerializationRedisSerializer();
private volatile boolean expectMessage = false;
private volatile long receiveTimeout = DEFAULT_RECEIVE_TIMEOUT;
private volatile boolean active;
private volatile boolean listening;
/**
* @param queueName Must not be an empty String
* @param connectionFactory Must not be null
*/
public RedisQueueMessageDrivenEndpoint(String queueName, RedisConnectionFactory connectionFactory) {
Assert.hasText(queueName, "'queueName' is required");
Assert.notNull(connectionFactory, "'connectionFactory' must not be null");
RedisTemplate<String, byte[]> template = new RedisTemplate<String, byte[]>();
template.setConnectionFactory(connectionFactory);
template.setEnableDefaultSerializer(false);
template.setKeySerializer(new StringRedisSerializer());
template.afterPropertiesSet();
this.boundListOperations = template.boundListOps(queueName);
}
public void setSerializer(RedisSerializer<?> serializer) {
this.serializer = serializer;
}
/**
* When data is retrieved from the Redis queue, does the returned data represent
* just the payload for a Message, or does the data represent a serialized
* {@link Message}?. {@code expectMessage} defaults to false. This means
* the retrieved data will be used as the payload for a new Spring Integration
* Message. Otherwise, the data is deserialized as Spring Integration
* Message.
*
* @param expectMessage Defaults to false
*/
public void setExpectMessage(boolean expectMessage) {
this.expectMessage = expectMessage;
}
/**
* This timeout (milliseconds) is used when retrieving elements from the queue
* specified by {@link #boundListOperations}.
* <p/>
* If the queue does contain elements, the data is retrieved immediately. However,
* if the queue is empty, the Redis connection is blocked until either an element
* can be retrieved from the queue or until the specified timeout passes.
* <p/>
* A timeout of zero can be used to block indefinitely. If not set explicitly
* the timeout value will default to {@code 1000}
* <p/>
* See also: http://redis.io/commands/brpop
*
* @param receiveTimeout Must be non-negative. Specified in milliseconds.
*/
public void setReceiveTimeout(long receiveTimeout) {
Assert.isTrue(receiveTimeout > 0, "'receiveTimeout' must be > 0.");
this.receiveTimeout = receiveTimeout;
}
public void setTaskExecutor(TaskExecutor taskExecutor) {
this.taskExecutor = taskExecutor;
}
@Override
public void setErrorChannel(MessageChannel errorChannel) {
super.setErrorChannel(errorChannel);
this.errorChannel = errorChannel;
}
@Override
protected void onInit() {
super.onInit();
if (this.expectMessage) {
Assert.notNull(this.serializer, "'serializer' has to be provided where 'expectMessage == true'.");
}
if (this.taskExecutor == null) {
String beanName = this.getComponentName();
this.taskExecutor = new SimpleAsyncTaskExecutor((beanName == null ? "" : beanName + "-") + this.getComponentType());
}
if (!(this.taskExecutor instanceof ErrorHandlingTaskExecutor)) {
MessagePublishingErrorHandler errorHandler = new MessagePublishingErrorHandler();
errorHandler.setDefaultErrorChannel(this.errorChannel);
this.taskExecutor = new ErrorHandlingTaskExecutor(this.taskExecutor, errorHandler);
}
}
@Override
public String getComponentType() {
return "int-redis:message-driven-channel-adapter";
}
@SuppressWarnings("unchecked")
private void popMessageAndSend() {
Message<Object> message = null;
byte[] value = this.boundListOperations.rightPop(this.receiveTimeout, TimeUnit.MILLISECONDS);
if (value != null) {
if (this.expectMessage) {
try {
message = (Message<Object>) this.serializer.deserialize(value);
}
catch (Exception e) {
throw new MessagingException("Deserialization of Message failed.", e);
}
}
else {
Object payload = value;
if (this.serializer != null) {
payload = this.serializer.deserialize(value);
}
message = MessageBuilder.withPayload(payload).build();
}
}
if (message != null) {
this.sendMessage(message);
}
}
@Override
protected void doStart() {
if (!this.active) {
this.active = true;
this.restart();
}
}
private void restart() {
this.taskExecutor.execute(new ListenerTask());
}
@Override
protected void doStop() {
this.active = false;
}
public boolean isListening() {
return listening;
}
/**
* Returns the size of the Queue specified by {@link #boundListOperations}. The queue is
* represented by a Redis list. If the queue does not exist <code>0</code>
* is returned. See also http://redis.io/commands/llen
*
* @return Size of the queue. Never negative.
*/
@ManagedMetric
public long getQueueSize() {
return this.boundListOperations.size();
}
/**
* Clear the Redis Queue specified by {@link #boundListOperations}.
*/
@ManagedOperation
public void clearQueue() {
this.boundListOperations.getOperations().delete(this.boundListOperations.getKey());
}
private class ListenerTask implements Runnable {
@Override
public void run() {
RedisQueueMessageDrivenEndpoint.this.listening = true;
try {
while (RedisQueueMessageDrivenEndpoint.this.active) {
RedisQueueMessageDrivenEndpoint.this.popMessageAndSend();
}
}
finally {
if (RedisQueueMessageDrivenEndpoint.this.active) {
RedisQueueMessageDrivenEndpoint.this.restart();
}
else {
RedisQueueMessageDrivenEndpoint.this.listening = false;
}
}
}
}
}

View File

@@ -0,0 +1,112 @@
/*
* Copyright 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.
* 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.redis.outbound;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.integration.Message;
import org.springframework.integration.expression.IntegrationEvaluationContextAware;
import org.springframework.integration.handler.AbstractMessageHandler;
import org.springframework.util.Assert;
/**
* @author Mark Fisher
* @author Gunnar Hillert
* @author Artem Bilan
* @since 3.0
*/
public class RedisQueueOutboundChannelAdapter extends AbstractMessageHandler implements IntegrationEvaluationContextAware {
private final RedisSerializer<String> stringSerializer = new StringRedisSerializer();
private final RedisTemplate<String, Object> template;
private final Expression queueNameExpression;
private EvaluationContext evaluationContext;
private volatile boolean extractPayload = true;
private volatile RedisSerializer<?> serializer = new JdkSerializationRedisSerializer();
private volatile boolean serializerExplicitlySet;
public RedisQueueOutboundChannelAdapter(String queueName, RedisConnectionFactory connectionFactory) {
this(new LiteralExpression(queueName), connectionFactory);
}
public RedisQueueOutboundChannelAdapter(Expression queueNameExpression, RedisConnectionFactory connectionFactory) {
Assert.notNull(queueNameExpression, "'queueNameExpression' is required");
Assert.hasText(queueNameExpression.getExpressionString(), "'queueNameExpression.getExpressionString()' is required");
Assert.notNull(connectionFactory, "'connectionFactory' must not be null");
this.queueNameExpression = queueNameExpression;
this.template = new RedisTemplate<String, Object>();
this.template.setConnectionFactory(connectionFactory);
this.template.setEnableDefaultSerializer(false);
this.template.setKeySerializer(new StringRedisSerializer());
this.template.afterPropertiesSet();
}
@Override
public void setIntegrationEvaluationContext(EvaluationContext evaluationContext) {
this.evaluationContext = evaluationContext;
}
public void setExtractPayload(boolean extractPayload) {
this.extractPayload = extractPayload;
}
public void setSerializer(RedisSerializer<?> serializer) {
Assert.notNull(serializer, "'serializer' must not be null");
this.serializer = serializer;
this.serializerExplicitlySet = true;
}
@Override
public String getComponentType() {
return "int-redis:outbound-channel-adapter";
}
@Override
@SuppressWarnings("unchecked")
protected void handleMessageInternal(Message<?> message) throws Exception {
Object value = message;
if (this.extractPayload) {
value = message.getPayload();
}
if (!(value instanceof byte[])) {
if (value instanceof String && !this.serializerExplicitlySet) {
value = this.stringSerializer.serialize((String) value);
}
else {
value = ((RedisSerializer<Object>) this.serializer).serialize(value);
}
}
String queueName = this.queueNameExpression.getValue(this.evaluationContext, message, String.class);
this.template.boundListOps(queueName).leftPush(value);
}
}

View File

@@ -0,0 +1,157 @@
/*
* Copyright 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.
* 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.redis.inbound;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import java.util.Date;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.integration.Message;
import org.springframework.integration.MessagingException;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.core.PollableChannel;
import org.springframework.integration.message.ErrorMessage;
import org.springframework.integration.redis.rules.RedisAvailable;
import org.springframework.integration.redis.rules.RedisAvailableTests;
import org.springframework.integration.support.MessageBuilder;
/**
* @author Gunnar Hillert
* @author Artem Bilan
* @since 3.0
*/
public class RedisQueueMessageDrivenEndpointTests extends RedisAvailableTests {
@Test
@RedisAvailable
@SuppressWarnings("unchecked")
public void testInt3014Default() throws Exception {
String queueName = "si.test.redisQueueInboundChannelAdapterTests";
RedisConnectionFactory connectionFactory = this.getConnectionFactoryForTest();
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
redisTemplate.setConnectionFactory(connectionFactory);
redisTemplate.setEnableDefaultSerializer(false);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
redisTemplate.afterPropertiesSet();
String payload = "testing";
redisTemplate.boundListOps(queueName).leftPush(payload);
Date payload2 = new Date();
redisTemplate.boundListOps(queueName).leftPush(payload2);
PollableChannel channel = new QueueChannel();
RedisQueueMessageDrivenEndpoint endpoint = new RedisQueueMessageDrivenEndpoint(queueName, connectionFactory);
endpoint.setOutputChannel(channel);
endpoint.setReceiveTimeout(1000);
endpoint.afterPropertiesSet();
endpoint.start();
Message<Object> receive = (Message<Object>) channel.receive(2000);
assertNotNull(receive);
assertEquals(payload, receive.getPayload());
receive = (Message<Object>) channel.receive(2000);
assertNotNull(receive);
assertEquals(payload2, receive.getPayload());
endpoint.stop();
this.waitUntilListening(endpoint);
}
@Test
@RedisAvailable
@SuppressWarnings("unchecked")
public void testInt3014ExpectMessageTrue() throws Exception {
final String queueName = "si.test.redisQueueInboundChannelAdapterTests2";
RedisConnectionFactory connectionFactory = this.getConnectionFactoryForTest();
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
redisTemplate.setConnectionFactory(connectionFactory);
redisTemplate.setEnableDefaultSerializer(false);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
redisTemplate.afterPropertiesSet();
Message<?> message = MessageBuilder.withPayload("testing").build();
redisTemplate.boundListOps(queueName).leftPush(message);
redisTemplate.boundListOps(queueName).leftPush("test");
PollableChannel channel = new QueueChannel();
PollableChannel errorChannel = new QueueChannel();
RedisQueueMessageDrivenEndpoint endpoint = new RedisQueueMessageDrivenEndpoint(queueName, connectionFactory);
endpoint.setExpectMessage(true);
endpoint.setOutputChannel(channel);
endpoint.setErrorChannel(errorChannel);
endpoint.setReceiveTimeout(1000);
endpoint.afterPropertiesSet();
endpoint.start();
Message<Object> receive = (Message<Object>) channel.receive(2000);
assertNotNull(receive);
assertEquals(message, receive);
receive = (Message<Object>) errorChannel.receive(2000);
assertNotNull(receive);
assertThat(receive, Matchers.instanceOf(ErrorMessage.class));
assertThat(receive.getPayload(), Matchers.instanceOf(MessagingException.class));
assertThat(((Exception) receive.getPayload()).getMessage(), Matchers.containsString("Deserialization of Message failed."));
assertThat(((Exception) receive.getPayload()).getCause(), Matchers.instanceOf(ClassCastException.class));
assertThat(((Exception) receive.getPayload()).getCause().getMessage(),
Matchers.containsString("java.lang.String cannot be cast to org.springframework.integration.Message"));
endpoint.stop();
this.waitUntilListening(endpoint);
}
public void waitUntilListening(RedisQueueMessageDrivenEndpoint endpoint) throws Exception {
int n = 0;
while (endpoint.isListening()) {
Thread.sleep(100);
if (n++ > 100) {
throw new Exception("RedisQueueMessageDrivenEndpoint failed to stop.");
}
}
}
}

View File

@@ -0,0 +1,143 @@
/*
* Copyright 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.
* 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.redis.outbound;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.util.Arrays;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.JacksonJsonRedisSerializer;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.integration.Message;
import org.springframework.integration.message.GenericMessage;
import org.springframework.integration.redis.rules.RedisAvailable;
import org.springframework.integration.redis.rules.RedisAvailableTests;
import org.springframework.integration.support.MessageBuilder;
/**
* @author Gunnar Hillert
* @author Artem Bilan
* @since 3.0
*/
public class RedisQueueOutboundChannelAdapterTests extends RedisAvailableTests {
@Test
@RedisAvailable
public void testInt3015Default() throws Exception {
final String queueName = "si.test.testRedisQueueOutboundChannelAdapter";
RedisConnectionFactory connectionFactory = this.getConnectionFactoryForTest();
final RedisQueueOutboundChannelAdapter handler = new RedisQueueOutboundChannelAdapter(queueName, connectionFactory);
String payload = "testing";
handler.handleMessage(MessageBuilder.withPayload(payload).build());
RedisTemplate<String, ?> redisTemplate = new StringRedisTemplate();
redisTemplate.setConnectionFactory(connectionFactory);
redisTemplate.afterPropertiesSet();
Object result = redisTemplate.boundListOps(queueName).rightPop(5000, TimeUnit.MILLISECONDS);
assertNotNull(result);
assertEquals(payload, result);
Date payload2 = new Date();
handler.handleMessage(MessageBuilder.withPayload(payload2).build());
RedisTemplate<String, ?> redisTemplate2 = new RedisTemplate<String, Object>();
redisTemplate2.setConnectionFactory(connectionFactory);
redisTemplate2.setEnableDefaultSerializer(false);
redisTemplate2.setKeySerializer(new StringRedisSerializer());
redisTemplate2.setValueSerializer(new JdkSerializationRedisSerializer());
redisTemplate2.afterPropertiesSet();
Object result2 = redisTemplate2.boundListOps(queueName).rightPop(5000, TimeUnit.MILLISECONDS);
assertNotNull(result2);
assertEquals(payload2, result2);
}
@Test
@RedisAvailable
public void testInt3015ExtractPayloadFalse() throws Exception {
final String queueName = "si.test.testRedisQueueOutboundChannelAdapter2";
RedisConnectionFactory connectionFactory = this.getConnectionFactoryForTest();
final RedisQueueOutboundChannelAdapter handler = new RedisQueueOutboundChannelAdapter(queueName, connectionFactory);
handler.setExtractPayload(false);
Message<String> message = MessageBuilder.withPayload("testing").build();
handler.handleMessage(message);
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
redisTemplate.setConnectionFactory(connectionFactory);
redisTemplate.setEnableDefaultSerializer(false);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
redisTemplate.afterPropertiesSet();
Object result = redisTemplate.boundListOps(queueName).rightPop(5000, TimeUnit.MILLISECONDS);
assertNotNull(result);
assertEquals(message, result);
}
@Test
@RedisAvailable
public void testInt3015ExplicitSerializer() throws Exception {
final String queueName = "si.test.testRedisQueueOutboundChannelAdapter2";
RedisConnectionFactory connectionFactory = this.getConnectionFactoryForTest();
final RedisQueueOutboundChannelAdapter handler = new RedisQueueOutboundChannelAdapter(queueName, connectionFactory);
handler.setSerializer(new JacksonJsonRedisSerializer<Object>(Object.class));
RedisTemplate<String, ?> redisTemplate = new StringRedisTemplate();
redisTemplate.setConnectionFactory(connectionFactory);
redisTemplate.afterPropertiesSet();
handler.handleMessage(new GenericMessage<Object>(Arrays.asList("foo", "bar", "baz")));
Object result = redisTemplate.boundListOps(queueName).rightPop(5000, TimeUnit.MILLISECONDS);
assertNotNull(result);
assertEquals("[\"foo\",\"bar\",\"baz\"]", result);
handler.handleMessage(new GenericMessage<Object>("test"));
result = redisTemplate.boundListOps(queueName).rightPop(5000, TimeUnit.MILLISECONDS);
assertNotNull(result);
assertEquals("\"test\"", result);
}
}

View File

@@ -0,0 +1,8 @@
log4j.rootCategory=WARN, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n
log4j.category.org.springframework.integration=WARN
log4j.category.org.springframework.integration.redis=DEBUG

View File

@@ -108,8 +108,9 @@ xsi:schemaLocation="http://www.springframework.org/schema/integration/ftp
</para>
<para>
<classname>DefaultFtpSessionFactory</classname> provides an abstraction over the underlying client API which, in the current release of
Spring Integration, is <ulink url="http://commons.apache.org/net/">Apache Commons Net</ulink>. This spares you from the low level configuration details
<classname>DefaultFtpSessionFactory</classname> provides an abstraction over the underlying client API which,
since <emphasis>Spring Integration 2.0</emphasis>, is <ulink url="http://commons.apache.org/net/">Apache Commons Net</ulink>.
This spares you from the low level configuration details
of the <classname>org.apache.commons.net.ftp.FTPClient</classname>. However there are times when access to lower level <classname>FTPClient</classname> details is
necessary to achieve more advanced configuration (e.g., setting data timeout, default timeout etc.). For that purpose, <classname>AbstractFtpSessionFactory</classname>
(the base class for all FTP Session Factories) exposes hooks, in the form of the two post-processing methods below.

View File

@@ -66,7 +66,7 @@
custom <interfacename>HttpMessageConverter</interfacename> to add the default converters after the custom converters.
By default this flag is set to false, meaning that the custom converters replace the default list.
</para>
<para>Starting with this release MultiPart File support was implemented. If the request has been wrapped as a
<para>Starting with <emphasis>Spring Integration 2.0</emphasis>, MultiPart File support is implemented. If the request has been wrapped as a
<emphasis>MultipartHttpServletRequest</emphasis>, when using the default converters, that request will be converted
to a Message payload that is a MultiValueMap containing values that may be byte arrays, Strings, or instances of
Spring's <interfacename>MultipartFile</interfacename> depending on the content type of the individual parts.
@@ -619,7 +619,7 @@ By default the HTTP request will be generated using an instance of <classname>Si
</para>
<para>
<classname><ulink url="http://static.springsource.org/spring/docs/current/javadoc-api/org/springframework/http/client/HttpComponentsClientHttpRequestFactory.html">HttpComponentsClientHttpRequestFactory</ulink></classname>
- Uses <ulink url="http://hc.apache.org/httpcomponents-client-ga/httpclient/">Apache HttpComponents HttpClient</ulink> (Since Spring 3.1)
- Uses <ulink url="http://hc.apache.org/httpcomponents-client-ga/">Apache HttpComponents HttpClient</ulink> (Since Spring 3.1)
</para>
<para>
<classname><ulink url="http://static.springsource.org/spring/docs/current/javadoc-api/org/springframework/http/client/CommonsClientHttpRequestFactory.html">ClientHttpRequestFactory</ulink></classname>
@@ -652,6 +652,13 @@ By default the HTTP request will be generated using an instance of <classname>Si
</para>
</note>
<important>
When using the <emphasis>Apache HttpComponents HttpClient</emphasis> with a Pooling Connection Manager, be aware that, by
default, the connection manager will create no more than 2 concurrent connections per given route and no more than 20 connections
in total. For many real-world applications these limits may prove too constraining. Refer to the Apache documentation
(link above) for information about configuring this important component.
</important>
<para>
Here is an example of how to configure an <emphasis>HTTP Outbound Gateway</emphasis>
using a <classname>SimpleClientHttpRequestFactory</classname>, configured

View File

@@ -81,7 +81,7 @@
relatively simple. It only requires a JMX ObjectName in its
configuration as shown below.
</para>
<programlisting language="xml"><![CDATA[<context:mbean:export/>
<programlisting language="xml"><![CDATA[<context:mbean-export/>
<int-jmx:notification-publishing-channel-adapter id="adapter"
channel="channel"
@@ -106,7 +106,7 @@
key. On the other hand, you can rely on a fallback <emphasis>default-notification-type</emphasis>
attribute provided in the configuration.
</para>
<programlisting language="xml"><![CDATA[<context:mbean:export/>
<programlisting language="xml"><![CDATA[<context:mbean-export/>
<int-jmx:notification-publishing-channel-adapter id="adapter"
channel="channel"
@@ -242,7 +242,8 @@
and a domain name (if desired). The domain can be left out, in which
case the default domain is <emphasis>org.springframework.integration</emphasis>.
</para>
<programlisting language="xml"><![CDATA[<int-jmx:mbean-export default-domain="my.company.domain" server="mbeanServer"/>
<programlisting language="xml"><![CDATA[<int-jmx:mbean-export id="integrationMBeanExporter"
default-domain="my.company.domain" server="mbeanServer"/>
<bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean">
<property name="locateExistingServerIfPossible" value="true"/>
@@ -250,7 +251,7 @@
<para>
Once the exporter is defined, start up your application with:
</para>
<screen>-Dcom.sun.management.jmxremote
<screen> -Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=6969
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false</screen>
@@ -262,16 +263,20 @@
sophisticated features than JConsole.)
</para>
<para>
<important>
<para>
The MBean exporter is orthogonal to the one provided in Spring core
- it registers message channels and message handlers, but not itself. You
can expose the exporter itself, and certain other components in Spring
Integration, using the standard <literal>&lt;context:mbean-export/&gt;</literal>
tag. The exporter has a couple of useful metrics attached to it, for
tag. The exporter has a some metrics attached to it, for
instance a count of the number of active handlers and the number of
queued messages (these would both be important if you wanted to
shutdown the context without losing any messages).
</para>
queued messages.
</para>
<para>
It also has a useful operation, as discussed in <xref linkend="jmx-mbean-shutdown"/>.
</para>
</important>
<section id="jmx-mbean-features">
<title>MBean ObjectNames</title>
@@ -361,6 +366,46 @@
</tbody>
</tgroup>
</table>
<para>
Custom elements can be appended to the object name by providing a reference to a
<classname>Properties</classname> object in the <code>object-name-static-properties</code> attribute.
</para>
<para>
Also, since <emphasis>Spring Integration 3.0</emphasis>, you can use a custom
<ulink url="http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/jmx/export/naming/ObjectNamingStrategy.html"
>ObjectNamingStrategy</ulink>
using the <code>object-naming-strategy</code> attribute. This permits greater control over the naming of the
MBeans. For example, to group all Integration MBeans under
an 'Integration' type. A simple custom naming strategy implementation might be:
</para>
<programlisting language="java"><![CDATA[public class Namer implements ObjectNamingStrategy {
private final ObjectNamingStrategy realNamer = new KeyNamingStrategy();
@Override
public ObjectName getObjectName(Object managedBean, String beanKey) throws MalformedObjectNameException {
String actualBeanKey = beanKey.replace("type=", "type=Integration,componentType=");
return realNamer.getObjectName(managedBean, actualBeanKey);
}
}]]></programlisting>
<para>
The <code>beanKey</code> argument is a String containing the standard object name beginning with
the <code>default-domain</code> and including any additional static properties.
This example simply moves the standard <code>type</code> part to <code>componentType</code> and
sets the <code>type</code> to 'Integration',
enabling selection of all Integration MBeans in one query:
<code>"my.domain:type=Integration,*</code>. This also groups the beans under one tree entry under the
domain in tools like VisualVM.
</para>
<note>
The default naming strategy is a
<ulink url="http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/jmx/export/naming/MetadataNamingStrategy.html"
>MetadataNamingStrategy</ulink>. The exporter propagates the <code>default-domain</code> to that object to allow it
to generate a fallback object name if parsing of the bean key fails. If your custom naming strategy is a
<classname>MetadataNamingStrategy</classname> (or subclass), the exporter will <emphasis role="bold">not</emphasis>
propagate the <code>default-domain</code>; you will need to configure it on your strategy bean.
</note>
</section>
<section id="jmx-channel-features">

View File

@@ -58,4 +58,22 @@
If no time is left when we get to step 6, it probably means some thread is hung; in which case, the
operation attempts a forced shutdown on all schedulers and executors before exiting.
</note>
<para>
As discussed in <xref linkend="jmx-mbean-shutdown"/> this operation can be invoked using JMX. If you
wish to programmatically invoke the method, you will need to inject, or otherwise get a reference to,
the <classname>IntegrationMBeanExporter</classname>. If no <code>id</code> attribute is provided on
the <code>&lt;int-jmx:mbean-export/></code> definition, the bean will have a generated name. This
name contains a random component to avoid <classname>ObjectName</classname> collisions if multiple
Spring Integration contexts exist in the same JVM (MBeanServer).
</para>
<para>
For this reason, if you wish to invoke the method programmatically, it is recommended that you
provide the exporter with an <code>id</code> attribute so it can easily be accessed in the
application context.
</para>
<para>
Finally, the operation can be invoked using the <code>&lt;control-bus&gt;</code>; see the
<ulink url="https://github.com/spring-projects/spring-integration-samples/tree/master/intermediate/monitoring"
>monitoring Spring Integration sample application</ulink> for details.
</para>
</section>

View File

@@ -205,7 +205,7 @@ public interface TransactionSynchronizationProcessor {
}]]></programlisting>
The factory is responsible for creating a
<ulink url="http://static.springsource.org/spring-framework/docs/3.1.2.RELEASE/javadoc-api/org/springframework/transaction/support/TransactionSynchronization.html">TransactionSynchronization</ulink>
<ulink url="http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/support/TransactionSynchronization.html">TransactionSynchronization</ulink>
object. You can implement your own, or use the one provided by the framework:
<classname>DefaultTransactionSynchronizationFactory</classname>. This implementation returns a
<classname>TransactionSynchronization</classname> that delegates to a default implementation of

View File

@@ -206,7 +206,7 @@ public class Kid {
a BeanCreationException will be thrown. 
</note>
<para>
<emphasis>JSON Transformers</emphasis>
<emphasis role="bold">JSON Transformers</emphasis>
</para>
<para>
<emphasis>Object to JSON</emphasis> and <emphasis>JSON to Object</emphasis> transformers are provided.
@@ -279,7 +279,7 @@ public class Foo {
</para>
<important>
<para>
Beginning with version 2.2, the <code>object-to-json-transformer</code> sets the <emphasis>content-type</emphasis>
Beginning with <emphasis>version 2.2</emphasis>, the <code>object-to-json-transformer</code> sets the <emphasis>content-type</emphasis>
header to <code>application/json</code>, by default, if the input message does not already have that header
present.
</para>
@@ -290,66 +290,50 @@ public class Foo {
attribute to an empty string (<code>""</code>). This will result in a message with no <code>content-type</code>
header, unless such a header was present on the input message.
</para>
<para>
The behavior of adding the default header has a side affect - causing applications with the following
sequence to fail:
</para>
<para>
<code>->object-to-json-transformer->amqp-outbound-adapter----></code>
</para>
<para>
<code>---->amqp-inbound-adapter->json-to-object-transformer-></code>
</para>
<para>
This is because the default <classname>SimpleMessageConverter</classname> used by the inbound adapter doesn't
recognize this content type and the adapter emits a message with a <code>byte[]</code> payload instead of
<code>String</code>, which was the case with earlier versions.
</para>
<para>
If you are using this pattern, there are a number of ways to configure the environment so that JSON
conversion will be performed correctly.
</para>
<para>
One solution is to set the content type to a text type, so the inbound converter will convert the JSON to
String. This solution requires a change to just the outbound application.
</para>
<para>
<programlisting language="xml"><![CDATA[<object-to-json-transformer ... content-type="text/x-json"/>]]></programlisting>
</para>
<para>
The second solution is to eliminate the json transformers altogether and use an
<classname>org.springframework.amqp.support.converter.JsonMessageConverter</classname> on both the
outbound and inbound adapters. This configures the adapters to perform the JSON conversion and
the transformers are not necessary. The converter on the outbound adapter adds
type information to the message properties; the inbound converter uses this type information for the conversion.
The converter is provided to the adapters using the <emphasis>message-converter</emphasis> attribute.
This solution requires a change to both the inbound and outbound applications.
</para>
<para>
The third solution is to eliminate the <emphasis>json-to-object-transformer</emphasis> in just
the inbound application and use an
<classname>org.springframework.amqp.support.converter.JsonMessageConverter</classname> on the
inbound adapter. The converter
is provided to the adapter using the <emphasis>message-converter</emphasis> attribute.
However, because there will be no type information in the message properties,
this also requires adding the <emphasis>defaultType</emphasis> to the converter, using the
same type as currently configured on the <emphasis>json-to-object-transformer</emphasis>.
This solution requires a change to just the inbound application. The configuration below shows
how to configure the message converter; it requires <code>spring-amqp</code> 1.1.3 or
above.
</para>
<programlisting language="xml"><![CDATA[<bean id="jsonConverterWithPOType"
class="org.springframework.amqp.support.converter.JsonMessageConverter">
<property name="classMapper">
<bean class="org.springframework.amqp.support.converter.DefaultClassMapper">
<property name="defaultType"
value="foo.PurchaseOrder" />
</bean>
</property>
</bean>
<int-amqp:inbound-channel-adapter ... message-converter="jsonConverterWithPOType" ... />]]></programlisting>
</important>
<para>
Beginning with <emphasis>version 3.0</emphasis>, the <classname>ObjectToJsonTransformer</classname> adds headers,
reflecting the source type, to the message. Similarly, the <classname>JsonToObjectTransformer</classname> can
use those type headers when converting the JSON to an object. These headers are mapped in the AMQP adapters so that
they are entirely compatible with the Spring-AMQP
<ulink url="http://docs.spring.io/spring-amqp/api/">JsonMessageConverter</ulink>.
</para>
<para>
This enables the following flows to work without any special configuration...
</para>
<para>
<code>...->amqp-outbound-adapter----></code>
</para>
<para>
<code>---->amqp-inbound-adapter->json-to-object-transformer->...</code>
</para>
<para>
Where the outbound adapter is configured with a <classname>JsonMessageConverter</classname> and the
inbound adapter uses the default <classname>SimpleMessageConverter</classname>.
</para>
<para>
<code>...->object-to-json-transformer->amqp-outbound-adapter----></code>
</para>
<para>
<code>---->amqp-inbound-adapter->...</code>
</para>
<para>
Where the outbound adapter is configured with a <classname>SimpleMessageConverter</classname> and the
inbound adapter uses the default <classname>JsonMessageConverter</classname>.
</para>
<para>
<code>...->object-to-json-transformer->amqp-outbound-adapter----></code>
</para>
<para>
<code>---->amqp-inbound-adapter->json-to-object-transformer-></code>
</para>
<para>
Where both adapters are configured with a <classname>SimpleMessageConverter</classname>.
</para>
<note>
When using the headers to determine the type, you should <emphasis role="bold">not</emphasis> provide
a <code>class</code> attribute, because it takes precedence over the headers.
</note>
<para>
In addition to JSON Transformers, Spring Integration provides a built-in <emphasis>#jsonPath</emphasis>
SpEL function for use in expressions. For more information see <xref linkend="spel"/>.

View File

@@ -124,7 +124,7 @@ twitter.oauth.accessTokenSecret=AbRxUAvyNCtqQtxFK8w5ZMtMj20KFhB6o]]></programlis
<link linkend="http://support.twitter.com/groups/31-twitter-basics/topics/109-tweets-messages/articles/119138-types-of-tweets-and-where-they-appear">twitter messages, or tweets</link>
</para>
<para>
The current release of Spring Integration provides support for receiving tweets as <emphasis>Timeline Updates</emphasis>,
<emphasis>Spring Integration version 2.0 and above</emphasis> provides support for receiving tweets as <emphasis>Timeline Updates</emphasis>,
<emphasis>Direct Messages</emphasis>, <emphasis>Mention Messages</emphasis> as well as Search Results.
</para>
<para>
@@ -241,7 +241,7 @@ received.
Twitter outbound channel adapters allow you to send Twitter Messages, or tweets.
</para>
<para>
The current release of Spring Integration supports sending <emphasis>Status Update Messages</emphasis> and <emphasis>Direct Messages</emphasis>.
<emphasis>Spring Integration version 2.0 and above</emphasis> supports sending <emphasis>Status Update Messages</emphasis> and <emphasis>Direct Messages</emphasis>.
Twitter outbound channel adapters will take the Message payload and send it as a Twitter message. Currently the only supported payload type is
<classname>String</classname>, so consider adding a <emphasis>transformer</emphasis> if the payload of the incoming message is not a String.
</para>

View File

@@ -63,12 +63,23 @@
<section id="3.0-jmx">
<title>JMX Support</title>
<para>
A new <code>&lt;int-jmx:tree-polling-channel-adapter/&gt;</code> is provided; this
adapter queries the JMX MBean tree and sends a message with a payload that is the
graph of objects that matches the query. By default the MBeans are mapped to
primitives and simple Objects like Map, List and arrays - permitting simple
transformation, for example, to JSON
<xref linkend="jmx"/>.
<itemizedlist>
<listitem>
A new <code>&lt;int-jmx:tree-polling-channel-adapter/&gt;</code> is provided; this
adapter queries the JMX MBean tree and sends a message with a payload that is the
graph of objects that matches the query. By default the MBeans are mapped to
primitives and simple Objects like Map, List and arrays - permitting simple
transformation, for example, to JSON.
</listitem>
<listitem>
The <classname>IntegrationMBeanExporter</classname> now allows the configuration of
a custom <classname>ObjectNamingStrategy</classname> using the <code>naming-strategy</code>
attribute.
</listitem>
</itemizedlist>
</para>
<para>
For more information, see <xref linkend="jmx"/>.
</para>
</section>
<section id="3.0-tail">
@@ -314,10 +325,20 @@
<section id="3.0-json-transformers">
<title>Jackson Support (JSON)</title>
<para>
A new abstraction for JSON conversion has been introduced. Implementations for Jackson 1.x
and Jackson 2 are currently provided, with the version being determined by presence on
the classpath. Previously, only Jackson 1.x was supported. For more information,
see 'JSON Transformers' in <xref linkend="transformer"/>.
<itemizedlist>
<listitem>
A new abstraction for JSON conversion has been introduced. Implementations for Jackson 1.x
and Jackson 2 are currently provided, with the version being determined by presence on
the classpath. Previously, only Jackson 1.x was supported.
</listitem>
<listitem>
The <classname>ObjectToJsonTransformer</classname> and <classname>JsonToObjectTransformer</classname>
now emit/consume headers containing type information.
</listitem>
</itemizedlist>
</para>
<para>
For more information, see 'JSON Transformers' in <xref linkend="transformer"/>.
</para>
</section>
<section id="3.0-http-endpointss">