renamed modules org.springframework.integration.* -> spring-integration-*

@Ignore'd SimpleTcpNetOutboundGatewayTests#testOutboundClose() to avoid failure; this failure is correlated to the module name change, but hard to understand how it would be caused by it
This commit is contained in:
Chris Beams
2010-05-25 13:21:25 +00:00
parent b97b2fb090
commit c08a7a657e
1484 changed files with 18 additions and 23 deletions

View File

@@ -0,0 +1,88 @@
/*
* Copyright 2002-2010 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.aggregator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.integration.channel.MessageChannelTemplate;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.core.MessageHeaders;
import org.springframework.integration.message.MessageBuilder;
import org.springframework.integration.store.MessageGroup;
import org.springframework.util.Assert;
import java.util.*;
/**
* Base class for MessageGroupProcessor implementations that aggregate the group of Messages into a single Message.
*
* @author Iwein Fuld
* @author Alexander Peters
* @author Mark Fisher
* @since 2.0
*/
public abstract class AbstractAggregatingMessageGroupProcessor implements MessageGroupProcessor {
private final Log logger = LogFactory.getLog(this.getClass());
@SuppressWarnings("unchecked")
public final void processAndSend(MessageGroup group, MessageChannelTemplate channelTemplate,
MessageChannel outputChannel) {
Assert.notNull(group, "MessageGroup must not be null");
Assert.notNull(outputChannel, "'outputChannel' must not be null");
Object payload = this.aggregatePayloads(group);
Map<String, Object> headers = this.aggregateHeaders(group);
MessageBuilder<?> builder = (payload instanceof Message) ? MessageBuilder.fromMessage((Message<?>) payload)
: MessageBuilder.withPayload(payload);
Message<?> message = builder.copyHeadersIfAbsent(headers).build();
channelTemplate.send(message, outputChannel);
}
/**
* This default implementation simply returns all headers that have no conflicts among the group. An absent header
* on one or more Messages within the group is not considered a conflict. Subclasses may override this method with
* more advanced conflict-resolution strategies if necessary.
*/
protected Map<String, Object> aggregateHeaders(MessageGroup group) {
Map<String, Object> aggregatedHeaders = new HashMap<String, Object>();
Set<String> conflictKeys = new HashSet<String>();
for (Message<?> message : group.getUnmarked()) {
MessageHeaders currentHeaders = message.getHeaders();
for (String key : currentHeaders.keySet()) {
if (MessageHeaders.ID.equals(key) || MessageHeaders.TIMESTAMP.equals(key)
|| MessageHeaders.SEQUENCE_SIZE.equals(key)) {
continue;
}
Object value = currentHeaders.get(key);
if (!aggregatedHeaders.containsKey(key)) {
aggregatedHeaders.put(key, value);
} else if (!value.equals(aggregatedHeaders.get(key))) {
conflictKeys.add(key);
}
}
}
for (String keyToRemove : conflictKeys) {
if (logger.isDebugEnabled()) {
logger.debug("Excluding header '" + keyToRemove + "' upon aggregation due to conflict(s) "
+ "in MessageGroup with correlation key: " + group.getCorrelationKey());
}
aggregatedHeaders.remove(keyToRemove);
}
return aggregatedHeaders;
}
protected abstract Object aggregatePayloads(MessageGroup group);
}

View File

@@ -0,0 +1,288 @@
/*
* Copyright 2002-2010 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.aggregator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.integration.channel.ChannelResolver;
import org.springframework.integration.channel.MessageChannelTemplate;
import org.springframework.integration.channel.NullChannel;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.core.MessageHeaders;
import org.springframework.integration.core.MessageProducer;
import org.springframework.integration.handler.AbstractMessageHandler;
import org.springframework.integration.store.MessageGroup;
import org.springframework.integration.store.MessageGroupCallback;
import org.springframework.integration.store.MessageGroupStore;
import org.springframework.integration.store.MessageStore;
import org.springframework.integration.store.SimpleMessageStore;
import org.springframework.util.Assert;
/**
* Message handler that holds a buffer of correlated messages in a
* {@link MessageStore}. This class takes care of correlated groups of messages
* that can be completed in batches. It is useful for aggregating, resequencing,
* or custom implementations requiring correlation.
* <p/>
* To customize this handler inject {@link CorrelationStrategy},
* {@link ReleaseStrategy}, and {@link MessageGroupProcessor} implementations as
* you require.
* <p/>
* By default the CorrelationStrategy will be a
* HeaderAttributeCorrelationStrategy and the ReleaseStrategy will be a
* SequenceSizeReleaseStrategy.
*
* @author Iwein Fuld
* @author Dave Syer
* @since 2.0
*/
public class CorrelatingMessageHandler extends AbstractMessageHandler implements
MessageProducer {
private static final Log logger = LogFactory
.getLog(CorrelatingMessageHandler.class);
public static final long DEFAULT_SEND_TIMEOUT = 1000L;
public static final long DEFAULT_REAPER_INTERVAL = 1000L;
public static final long DEFAULT_TIMEOUT = 60000L;
private MessageGroupStore messageStore;
private final MessageGroupProcessor outputProcessor;
private volatile CorrelationStrategy correlationStrategy;
private volatile ReleaseStrategy releaseStrategy;
private MessageChannel outputChannel;
private final MessageChannelTemplate channelTemplate = new MessageChannelTemplate();
private volatile MessageChannel discardChannel = new NullChannel();
private boolean sendPartialResultOnExpiry = false;
private final ConcurrentMap<Object, Object> locks = new ConcurrentHashMap<Object, Object>();
public CorrelatingMessageHandler(MessageGroupProcessor processor,
MessageGroupStore store, CorrelationStrategy correlationStrategy,
ReleaseStrategy releaseStrategy) {
Assert.notNull(store);
Assert.notNull(processor);
setMessageStore(store);
this.outputProcessor = processor;
this.correlationStrategy = correlationStrategy == null ? new HeaderAttributeCorrelationStrategy(
MessageHeaders.CORRELATION_ID)
: correlationStrategy;
this.releaseStrategy = releaseStrategy == null ? new SequenceSizeReleaseStrategy()
: releaseStrategy;
this.channelTemplate.setSendTimeout(DEFAULT_SEND_TIMEOUT);
}
public CorrelatingMessageHandler(MessageGroupProcessor processor,
MessageGroupStore store) {
this(processor, store, null, null);
}
public CorrelatingMessageHandler(MessageGroupProcessor processor) {
this(processor, new SimpleMessageStore(0), null, null);
}
public void setMessageStore(MessageGroupStore store) {
this.messageStore = store;
store.registerMessageGroupExpiryCallback(new MessageGroupCallback() {
public void execute(MessageGroup group) {
forceComplete(group);
}
});
}
public void setCorrelationStrategy(CorrelationStrategy correlationStrategy) {
Assert.notNull(correlationStrategy);
this.correlationStrategy = correlationStrategy;
}
public void setReleaseStrategy(ReleaseStrategy releaseStrategy) {
Assert.notNull(releaseStrategy);
this.releaseStrategy = releaseStrategy;
}
public void setOutputChannel(MessageChannel outputChannel) {
Assert.notNull(outputChannel, "'outputChannel' must not be null");
this.outputChannel = outputChannel;
}
public void setChannelResolver(ChannelResolver channelResolver) {
super.setChannelResolver(channelResolver);
}
public void setDiscardChannel(MessageChannel discardChannel) {
this.discardChannel = discardChannel;
}
public void setSendTimeout(long sendTimeout) {
this.channelTemplate.setSendTimeout(sendTimeout);
}
public void setSendPartialResultOnExpiry(boolean sendPartialResultOnExpiry) {
this.sendPartialResultOnExpiry = sendPartialResultOnExpiry;
}
@Override
public String getComponentType() {
return "aggregator";
}
@Override
protected void handleMessageInternal(Message<?> message) throws Exception {
Object correlationKey = correlationStrategy.getCorrelationKey(message);
if (logger.isDebugEnabled()) {
logger.debug("Handling message with correlationKey ["
+ correlationKey + "]: " + message);
}
// TODO: INT-1117 - make the lock global?
Object lock = getLock(correlationKey);
synchronized (lock) {
MessageGroup group = messageStore.getMessageGroup(correlationKey);
if (group.add(message)) {
store(correlationKey, message);
if (releaseStrategy.canRelease(group)) {
if (logger.isDebugEnabled()) {
logger.debug("Completing group with correlationKey ["
+ correlationKey + "]");
}
try {
outputProcessor.processAndSend(group, channelTemplate,
this.resolveReplyChannel(message,
this.outputChannel));
} finally {
// Always clean up even if there was an exception
// processing messages
if (group.isComplete() || group.getSequenceSize() == 0) {
// The group is complete or else there is no
// sequence so there is no more state to track
remove(group);
} else {
// Mark these messages as processed, but do not
// remove the group from store
mark(group);
}
}
} else if (group.isComplete()) {
try {
// If not releasing any messages the group might still
// be complete
for (Message<?> discard : group.getUnmarked()) {
discardChannel.send(discard);
}
} finally {
remove(group);
}
}
} else {
discardChannel.send(message);
}
}
}
private final boolean forceComplete(MessageGroup group) {
Object correlationKey = group.getCorrelationKey();
Object lock = getLock(correlationKey);
synchronized (lock) {
if (group.size() > 0) {
// last chance for normal completion
try {
if (releaseStrategy.canRelease(group)) {
outputProcessor.processAndSend(group, channelTemplate,
resolveReplyChannel(group.getOne(),
this.outputChannel));
} else {
if (sendPartialResultOnExpiry) {
if (logger.isInfoEnabled()) {
logger
.info("Processing partially complete messages for key ["
+ correlationKey
+ "] to: "
+ outputChannel);
}
outputProcessor.processAndSend(group,
channelTemplate, resolveReplyChannel(group
.getOne(), this.outputChannel));
} else {
if (logger.isInfoEnabled()) {
logger
.info("Discarding partially complete messages for key ["
+ correlationKey
+ "] to: "
+ discardChannel);
}
for (Message<?> message : group.getUnmarked()) {
discardChannel.send(message);
}
}
}
} finally {
remove(group);
}
return true;
}
return false;
}
}
private Object getLock(Object correlationKey) {
locks.putIfAbsent(correlationKey, correlationKey);
return locks.get(correlationKey);
}
private void mark(MessageGroup group) {
messageStore.markMessageGroup(group);
}
private void remove(MessageGroup group) {
Object correlationKey = group.getCorrelationKey();
messageStore.removeMessageGroup(correlationKey);
locks.remove(correlationKey);
}
private void store(Object correlationKey, Message<?> message) {
messageStore.addMessageToGroup(correlationKey, message);
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2002-2010 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.aggregator;
import org.springframework.integration.core.Message;
/**
* Strategy for determining how messages shall be correlated. Implementations
* shall return the correlation key value associated with a particular message.
*
* @author Marius Bogoevici
*/
public interface CorrelationStrategy {
Object getCorrelationKey(Message<?> message);
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2002-2009 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.aggregator;
import java.lang.reflect.Method;
import org.springframework.integration.core.Message;
import org.springframework.integration.handler.MethodInvokingMessageProcessor;
import org.springframework.util.Assert;
/**
* {@link CorrelationStrategy} implementation that works as an adapter to another bean.
*
* @author Marius Bogoevici
*/
public class CorrelationStrategyAdapter implements CorrelationStrategy {
private final MethodInvokingMessageProcessor processor;
public CorrelationStrategyAdapter(Object object, String methodName) {
this.processor = new MethodInvokingMessageProcessor(object, methodName, true);
}
public CorrelationStrategyAdapter(Object object, Method method) {
Assert.notNull(object, "'object' must not be null");
Assert.notNull(method, "'method' must not be null");
Assert.isTrue(method.getParameterTypes().length == 1, "Method must accept exactly one parameter");
Assert.isTrue(!Void.TYPE.equals(method.getReturnType()), "Method return type must not be void");
this.processor = new MethodInvokingMessageProcessor(object, method);
}
public Object getCorrelationKey(Message<?> message) {
return processor.processMessage(message);
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2002-2010 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.aggregator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.springframework.integration.core.Message;
import org.springframework.integration.store.MessageGroup;
import org.springframework.util.Assert;
/**
* This implementation of MessageGroupProcessor will take the messages from the
* MessageGroup and pass them on in a single message with a Collection as a payload.
*
* @author Iwein Fuld
* @author Alexander Peters
* @author Mark Fisher
* @since 2.0
*/
public class DefaultAggregatingMessageGroupProcessor extends AbstractAggregatingMessageGroupProcessor {
@Override
protected final Object aggregatePayloads(MessageGroup group) {
Collection<Message<?>> messages = group.getUnmarked();
Assert.notEmpty(messages, this.getClass().getSimpleName() + " cannot process empty message groups");
List<Object> payloads = new ArrayList<Object>(messages.size());
for (Message<?> message : messages) {
payloads.add(message.getPayload());
}
return payloads;
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2002-2010 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.aggregator;
import org.springframework.integration.core.Message;
/**
* Default implementation of {@link CorrelationStrategy}. Uses a header
* attribute to determine the correlation key value.
*
* @author Marius Bogoevici
*/
public class HeaderAttributeCorrelationStrategy implements CorrelationStrategy {
private String attributeName;
public HeaderAttributeCorrelationStrategy(String attributeName) {
this.attributeName = attributeName;
}
public Object getCorrelationKey(Message<?> message) {
return message.getHeaders().get(this.attributeName);
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2002-2010 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.aggregator;
import org.springframework.integration.store.MessageGroup;
/**
* A {@link ReleaseStrategy} that releases only the first <code>n</code> messages, where <code>n</code> is a threshold.
*
* @author Dave Syer
*
*/
public class MessageCountReleaseStrategy implements ReleaseStrategy {
private final int threshold;
/**
* @param threshold the number of messages to accept before releasing
*/
public MessageCountReleaseStrategy(int threshold) {
super();
this.threshold = threshold;
}
/**
* Convenient constructor is only one message is required (threshold=1).
*/
public MessageCountReleaseStrategy() {
this(1);
}
/**
* Release the group if it has more messages than the threshold and has not previously been released. Previous
* releases leave an imprint on the group in the form of marked messages. It is possible that more messages than the
* threshold could be released, but only if multiple consumers receive messages from the same group concurrently.
*/
public boolean canRelease(MessageGroup group) {
return group.size() >= threshold && group.getMarked().size() == 0;
}
}

View File

@@ -0,0 +1,22 @@
package org.springframework.integration.aggregator;
import org.springframework.integration.channel.MessageChannelTemplate;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.store.MessageGroup;
/**
* A processor for <i>correlated</i> groups of messages.
*
* @author Iwein Fuld
* @see org.springframework.integration.aggregator.CorrelatingMessageHandler
*/
public interface MessageGroupProcessor {
/**
* Process the given group and send the resulting message(s) to the output channel using the channel template.
* Implementations are free to send as little or as many messages based on the invocation as needed. For example an
* aggregating processor will send only a single message representing the group, where a resequencing strategy will
* send all messages in the group individually.
*/
void processAndSend(MessageGroup group, MessageChannelTemplate channelTemplate, MessageChannel outputChannel);
}

View File

@@ -0,0 +1,117 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.aggregator;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessagingException;
import org.springframework.integration.util.DefaultMethodInvoker;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* Base class for implementing adapters for methods which take as an argument a
* list of {@link Message Message} instances or payloads.
*
* @author Marius Bogoevici
*/
public class MessageListMethodAdapter {
private final DefaultMethodInvoker invoker;
protected final Method method;
public MessageListMethodAdapter(Object object, String methodName) {
Assert.notNull(object, "'object' must not be null");
Assert.notNull(methodName, "'methodName' must not be null");
this.method = ReflectionUtils.findMethod(object.getClass(), methodName, new Class<?>[]{List.class});
Assert.notNull(this.method, "Method '" + methodName +
"(List<?> args)' not found on '" + object.getClass().getName() + "'.");
this.invoker = new DefaultMethodInvoker(object, this.method);
}
public MessageListMethodAdapter(Object object, Method method) {
Assert.notNull(object, "'object' must not be null");
Assert.notNull(method, "'method' must not be null");
Assert.isTrue(method.getParameterTypes().length == 1
&& method.getParameterTypes()[0].equals(List.class),
"Method " + method + " does not accept exactly one parameter, of type List.");
this.method = method;
this.invoker = new DefaultMethodInvoker(object, this.method);
}
public Method getMethod() {
return method;
}
private static boolean isActualTypeParameterizedMessage(Method method) {
return (getCollectionActualType(method) instanceof ParameterizedType)
&& Message.class.isAssignableFrom((Class<?>) ((ParameterizedType) getCollectionActualType(method)).getRawType());
}
protected final Object executeMethod(Collection<? extends Message<?>> messages) {
try {
if (isMethodParameterParameterized(this.method) && isHavingActualTypeArguments(this.method)
&& (isActualTypeRawMessage(this.method) || isActualTypeParameterizedMessage(this.method))) {
return this.invoker.invokeMethod(messages);
}
return this.invoker.invokeMethod(extractPayloadsFromMessages(messages));
}
catch (InvocationTargetException e) {
throw new MessagingException(
"Method '" + this.method + "' threw an Exception.", e.getTargetException());
}
catch (Exception e) {
throw new MessagingException("Failed to invoke method '" + this.method + "'.");
}
}
private List<?> extractPayloadsFromMessages(Collection<? extends Message<?>> messages) {
List<Object> payloadList = new ArrayList<Object>();
for (Message<?> message : messages) {
payloadList.add(message.getPayload());
}
return payloadList;
}
private static boolean isActualTypeRawMessage(Method method) {
return getCollectionActualType(method).equals(Message.class);
}
private static Type getCollectionActualType(Method method) {
return ((ParameterizedType) method.getGenericParameterTypes()[0]).getActualTypeArguments()[0];
}
private static boolean isHavingActualTypeArguments(Method method) {
return ((ParameterizedType) method.getGenericParameterTypes()[0]).getActualTypeArguments().length == 1;
}
private static boolean isMethodParameterParameterized(Method method) {
return method.getGenericParameterTypes().length == 1
&& method.getGenericParameterTypes()[0] instanceof ParameterizedType;
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.aggregator;
import java.util.Comparator;
import org.springframework.integration.core.Message;
/**
* A {@link Comparator} implementation based on the 'sequence number'
* property of a {@link Message Message's} header.
*
* @author Mark Fisher
*/
public class MessageSequenceComparator implements Comparator<Message<?>> {
public int compare(Message<?> message1, Message<?> message2) {
Integer s1 = message1.getHeaders().getSequenceNumber();
Integer s2 = message2.getHeaders().getSequenceNumber();
if (s1 == null) {
s1 = 0;
}
if (s2 == null) {
s2 = 0;
}
return s1.compareTo(s2);
}
}

View File

@@ -0,0 +1,187 @@
/*
* Copyright 2002-2010 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.aggregator;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import org.springframework.aop.support.AopUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.integration.annotation.Aggregator;
import org.springframework.integration.annotation.Header;
import org.springframework.integration.core.Message;
import org.springframework.integration.store.MessageGroup;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
/**
* MessageGroupProcessor that serves as an adapter for the invocation of a POJO method.
*
* @author Iwein Fuld
* @author Mark Fisher
* @since 2.0
*/
public class MethodInvokingMessageGroupProcessor extends AbstractAggregatingMessageGroupProcessor {
private final MessageListMethodAdapter adapter;
/**
* Creates a wrapper around the target passed in. This constructor will choose the best fitting method and throw an
* exception when methods are ambiguous or no fitting methods can be found.
*
* @param target the object to wrap
* @throws IllegalStateException when no single method can be found unambiguously
*/
public MethodInvokingMessageGroupProcessor(Object target) {
this.adapter = new MessageListMethodAdapter(target, this.findAggregatorMethod(target));
}
/**
* Creates a wrapper around the object passed in. This constructor will look for a named method specifically and
* fail when it cannot find a method with the given name.
*
* @param target the object to wrap
* @param method the name of the method to look for
*/
public MethodInvokingMessageGroupProcessor(Object target, String method) {
this.adapter = new MessageListMethodAdapter(target, method);
}
@Override
protected final Object aggregatePayloads(MessageGroup group) {
final Collection<Message<?>> messagesUpForProcessing = group.getUnmarked();
Object result = this.adapter.executeMethod(messagesUpForProcessing);
return result;
}
private Method findAggregatorMethod(Object candidate) {
Class<?> targetClass = AopUtils.getTargetClass(candidate);
if (targetClass == null) {
targetClass = candidate.getClass();
}
Method method = this.findAnnotatedMethod(targetClass);
if (method == null) {
method = this.findSinglePublicMethod(targetClass);
}
return method;
}
private Method findAnnotatedMethod(final Class<?> targetClass) {
final AtomicReference<Method> annotatedMethod = new AtomicReference<Method>();
ReflectionUtils.doWithMethods(targetClass, new ReflectionUtils.MethodCallback() {
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
Annotation annotation = AnnotationUtils.findAnnotation(method, Aggregator.class);
if (annotation != null) {
Assert.isNull(annotatedMethod.get(), "found more than one method on target class [" + targetClass
+ "] with the annotation type [" + Aggregator.class.getName() + "]");
annotatedMethod.set(method);
}
}
});
return annotatedMethod.get();
}
private Method findSinglePublicMethod(Class<?> targetClass) {
Set<Method> methods = new HashSet<Method>();
for (Method method : targetClass.getMethods()) {
if (!method.getDeclaringClass().equals(Object.class)) {
methods.add(method);
}
}
removeListIncompatibleMethodsFrom(methods);
removeVoidMethodsFrom(methods);
removeUnfittingFrom(methods);
if (methods.size() > 1) {
throw new IllegalArgumentException("Class [" + targetClass + "] contains more than one public Method.");
}
return methods.isEmpty() ? null : methods.iterator().next();
}
private void removeListIncompatibleMethodsFrom(Set<Method> candidates) {
removeMethodsMatchingSelector(candidates, new MethodSelector() {
public boolean select(Method method) {
int found = 0;
for (Class<?> parameterClass : method.getParameterTypes()) {
if (Collection.class.isAssignableFrom(parameterClass)) {
found++;
}
}
return found != 1;
}
});
}
private void removeVoidMethodsFrom(Set<Method> candidates) {
removeMethodsMatchingSelector(candidates, new MethodSelector() {
public boolean select(Method method) {
return method.getReturnType().getName().equals("void");
}
});
}
private Set<Method> removeUnfittingFrom(Set<Method> candidates) {
return removeMethodsMatchingSelector(candidates, new MethodSelector() {
public boolean select(Method method) {
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
Class<?>[] parameterTypes = method.getParameterTypes();
return (!isFittinglyAnnotated(parameterTypes, parameterAnnotations));
}
});
}
private boolean isFittinglyAnnotated(Class<?>[] parameterTypes, Annotation[][] parameterAnnotations) {
int candidateParametersFound = 0;
for (int i = 0; i < parameterTypes.length; i++) {
Class<?> parameterType = parameterTypes[i];
if (Collection.class.isAssignableFrom(parameterType)) {
boolean headerAnnotationFound = false;
for (Annotation annotation : parameterAnnotations[i]) {
if (annotation instanceof Header) {
headerAnnotationFound = true;
}
}
if (!headerAnnotationFound) {
candidateParametersFound++;
}
}
}
return candidateParametersFound == 1;
}
private Set<Method> removeMethodsMatchingSelector(Set<Method> candidates, MethodSelector selector) {
Set<Method> removed = new HashSet<Method>();
Iterator<Method> iterator = candidates.iterator();
while (iterator.hasNext()) {
Method method = iterator.next();
if (selector.select(method)) {
iterator.remove();
removed.add(method);
}
}
return removed;
}
private interface MethodSelector {
boolean select(Method method);
}
}

View File

@@ -0,0 +1,24 @@
package org.springframework.integration.aggregator;
import org.springframework.integration.channel.MessageChannelTemplate;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.store.MessageGroup;
/**
* This implementation of MessageGroupProcessor will forward all messages inside the group to the given output channel.
* This is useful if there is no requirement to process the messages, but they should just be blocked as a group until
* their ReleaseStrategy lets them pass through.
*
* @author Iwein Fuld
* @since 2.0.0
*/
public class PassThroughMessageGroupProcessor implements MessageGroupProcessor {
public void processAndSend(MessageGroup group, MessageChannelTemplate channelTemplate, MessageChannel outputChannel) {
for (Message<?> message : group.getUnmarked()) {
channelTemplate.send(message, outputChannel);
}
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.aggregator;
import org.springframework.integration.store.MessageGroup;
/**
* Strategy for determining when a group of messages reaches a state of
* completion (i.e. can trip a barrier).
*
* @author Mark Fisher
* @author Dave Syer
*/
public interface ReleaseStrategy {
boolean canRelease(MessageGroup group);
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.aggregator;
import java.lang.reflect.Method;
import org.springframework.integration.store.MessageGroup;
import org.springframework.util.Assert;
/**
* Adapter for methods annotated with
* {@link org.springframework.integration.annotation.ReleaseStrategy @ReleaseStrategy}
* and for '<code>release-strategy</code>' elements that include a '<code>method</code>'
* attribute (e.g. &lt;release-strategy ref="beanReference" method="methodName"/&gt;).
*
* @author Marius Bogoevici
*/
public class ReleaseStrategyAdapter extends MessageListMethodAdapter implements ReleaseStrategy {
public ReleaseStrategyAdapter(Object object, Method method) {
super(object, method);
this.assertMethodReturnsBoolean();
}
public ReleaseStrategyAdapter(Object object, String methodName) {
super(object, methodName);
this.assertMethodReturnsBoolean();
}
public boolean canRelease(MessageGroup messages) {
return ((Boolean) executeMethod(messages.getUnmarked())).booleanValue() && messages.getMarked().isEmpty();
}
private void assertMethodReturnsBoolean() {
Assert.isTrue(Boolean.class.equals(this.getMethod().getReturnType())
|| boolean.class.equals(this.getMethod().getReturnType()),
"Method '" + getMethod().getName() + "' does not return a boolean value");
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2002-2010 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.aggregator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.springframework.integration.channel.MessageChannelTemplate;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.store.MessageGroup;
/**
* This class implements all the strategy interfaces needed for a default resequencer.
*
* @author Iwein Fuld
* @author Dave Syer
*
* @since 2.0
*/
public class ResequencingMessageGroupProcessor implements MessageGroupProcessor {
private volatile Comparator<Message<?>> comparator = new SequenceNumberComparator();
/**
* A comparator to use to order messages before processing. The default is to order by sequence number.
*
* @param comparator the comparator to use to order messages
*/
public void setComparator(Comparator<Message<?>> comparator) {
this.comparator = comparator;
}
public void processAndSend(MessageGroup group, MessageChannelTemplate channelTemplate, MessageChannel outputChannel) {
Collection<Message<?>> messages = group.getUnmarked();
if (messages.size() > 0) {
List<Message<?>> sorted = new ArrayList<Message<?>>(messages);
Collections.sort(sorted, comparator);
for (Message<?> message : sorted) {
channelTemplate.send(message, outputChannel);
}
}
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2002-2010 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.aggregator;
import java.util.Comparator;
import org.springframework.integration.core.Message;
/**
* @author Dave Syer
*
* @since 2.0
*
*/
public class SequenceNumberComparator implements Comparator<Message<?>> {
/**
* If both messages have a sequence number then compare that, otherwise if one has a sequence number and the other
* doesn't then the numbered message comes first, or finally of neither has a sequence number then they are equal in
* rank.
*/
public int compare(Message<?> o1, Message<?> o2) {
Integer sequenceNumber1 = o1.getHeaders().getSequenceNumber();
Integer sequenceNumber2 = o2.getHeaders().getSequenceNumber();
if (sequenceNumber1 == sequenceNumber2) {
return 0;
}
if (sequenceNumber1 == null) {
return -sequenceNumber2;
}
if (sequenceNumber2 == null) {
return sequenceNumber1;
}
return sequenceNumber1.compareTo(sequenceNumber2);
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.aggregator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.springframework.integration.core.Message;
import org.springframework.integration.store.MessageGroup;
/**
* An implementation of {@link ReleaseStrategy} that simply compares the
* current size of the message list to the expected 'sequenceSize'.
*
* @author Mark Fisher
* @author Marius Bogoevici
* @author Dave Syer
*/
public class SequenceSizeReleaseStrategy implements ReleaseStrategy {
private volatile Comparator<Message<?>> comparator = new SequenceNumberComparator();
private volatile boolean releasePartialSequences;
public SequenceSizeReleaseStrategy() {
this(false);
}
public SequenceSizeReleaseStrategy(boolean releasePartialSequences) {
this.releasePartialSequences = releasePartialSequences;
}
/**
* Flag that determines if partial sequences are allowed. If true then as soon as enough messages arrive that can be
* ordered they will be released, provided they all have sequence numbers greater than those already released.
*
* @param releasePartialSequences
*/
public void setReleasePartialSequences(boolean releasePartialSequences) {
this.releasePartialSequences = releasePartialSequences;
}
public boolean canRelease(MessageGroup messages) {
if (releasePartialSequences) {
List<Message<?>> sorted = new ArrayList<Message<?>>(messages.getUnmarked());
Collections.sort(sorted, comparator);
int head = sorted.get(sorted.size() - 1).getHeaders().getSequenceNumber();
int tail = sorted.get(0).getHeaders().getSequenceNumber() - 1;
return tail == messages.getMarked().size() && head - tail == sorted.size();
}
return messages.isComplete();
}
}

View File

@@ -0,0 +1,81 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.springframework.integration.aggregator;
import org.springframework.integration.core.Message;
import org.springframework.integration.store.MessageGroup;
/**
* A {@link ReleaseStrategy} that releases all messages if any of the following is true:
*
* <ul>
* <li>The sequence is complete (if there is one).</li>
* <li>There are more messages than a threshold set by the user.</li>
* <li>The time elapsed since the earliest message, according to their timestamps, exceeds a timeout set by the user.</li>
* </ul>
*
* @author Dave Syer
*
* @since 2.0
*/
public class TimeoutCountSequenceSizeReleaseStrategy implements ReleaseStrategy {
/**
* Default timeout is one minute.
*/
public static final long DEFAULT_TIMEOUT = 60 * 1000;
/**
* Default threshold is effectively infinite.
*/
public static final int DEFAULT_THRESHOLD = Integer.MAX_VALUE;
private final int threshold;
private final long timeout;
public TimeoutCountSequenceSizeReleaseStrategy() {
this(DEFAULT_THRESHOLD, DEFAULT_TIMEOUT);
}
/**
* @param threshold the number of messages to accept before releasing
* @param timeout the timeout for the release in milliseconds
*/
public TimeoutCountSequenceSizeReleaseStrategy(int threshold, long timeout) {
this.threshold = threshold;
this.timeout = timeout;
}
public boolean canRelease(MessageGroup messages) {
long elapsedTime = System.currentTimeMillis() - findEarliestTimestamp(messages);
return messages.isComplete() || messages.getUnmarked().size() >= threshold || elapsedTime > timeout;
}
/**
* @param messages the message group
* @return the earliest timestamp or Long.MAX_VALUE
*/
private long findEarliestTimestamp(MessageGroup messages) {
long result = Long.MAX_VALUE;
for (Message<?> message : messages.getUnmarked()) {
long timestamp = message.getHeaders().getTimestamp();
if (timestamp < result) {
result = timestamp;
}
}
return result;
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.integration.aggregator.CorrelatingMessageHandler;
/**
* Indicates that a method is capable of aggregating messages.
* <p>
* A method annotated with @Aggregator may accept a collection
* of Messages or Message payloads and should return a single
* Message or a single Object to be used as a Message payload.
*
* @author Marius Bogoevici
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Aggregator {
/**
* channel name for receiving messages to be aggregated
*/
String inputChannel() default "";
/**
* channel name for sending aggregated result messages
*/
String outputChannel() default "";
/**
* channel name for sending discarded messages (due to a timeout)
*/
String discardChannel() default "";
/**
* timeout for sending results to the reply target (in milliseconds)
*/
long sendTimeout() default CorrelatingMessageHandler.DEFAULT_SEND_TIMEOUT;
/**
* indicates whether to send an incomplete aggregate on expiry of the message group
*/
boolean sendPartialResultsOnExpiry() default false;
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2002-2010 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.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Indicates that a given method is capable of determining the correlation key
* of a message sent as parameter.
*
* @author Marius Bogoevici
*/
@Retention (RetentionPolicy.RUNTIME)
@Target (ElementType.METHOD)
@Documented
public @interface CorrelationStrategy {
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2002-2010 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.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Indicates that a method is capable of playing the role of a Message Filter.
* <p>
* A method annotated with @Filter may accept a parameter of type
* {@link org.springframework.integration.core.Message} or of the expected
* Message payload's type. Any type conversion supported by default or any
* Converters registered with the "integrationConversionService" bean will be
* applied to the Message payload if necessary. Header values can also be passed
* as Message parameters by using the {@link Header @Header} parameter annotation.
* <p>
* The return type of the annotated method must be a boolean (or Boolean).
*
* @author Mark Fisher
* @since 2.0
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Filter {
String inputChannel() default "";
String outputChannel() default "";
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Indicates that a method is capable of mapping its parameters to a message
* or message payload. These method-level annotations are detected by the
* {@link org.springframework.integration.gateway.GatewayProxyFactoryBean}
* where the annotation attributes can override the default channel settings.
*
* <p>A method annotated with @Gateway may accept a single non-annotated
* parameter of type {@link org.springframework.integration.core.Message}
* or of the intended Message payload type. Method parameters may be mapped
* to individual Message header values by using the {@link Header @Header}
* parameter annotation. Alternatively, to pass the entire Message headers
* map, a Map-typed parameter may be annotated with {@link Headers}.
*
* <p>Return values from the annotated method may be of any type. If the
* declared return value is not a Message, the reply Message's payload will be
* returned and any type conversion as supported by Spring's
* {@link org.springframework.beans.SimpleTypeConverter} will be applied to
* the return value if necessary.
*
* @author Mark Fisher
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Gateway {
String requestChannel() default "";
String replyChannel() default "";
long requestTimeout() default -1;
long replyTimeout() default -1;
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation indicating that a method parameter's value should be
* retrieved from the message headers. The value of the annotation
* provides the header name, and the optional 'required' property
* specifies whether the attribute value must be available within
* the header. The default value for 'required' is <code>true</code>.
*
* @author Mark Fisher
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Header {
String value() default "";
boolean required() default true;
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation indicating that a method parameter's value should be mapped to or
* from the message headers. The annotated parameter must be assignable to
* {@link java.util.Map}, and all of the Map's keys must be Strings.
*
* @author Mark Fisher
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Headers {
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.stereotype.Component;
/**
* Stereotype annotation indicating that a class is capable of serving as a
* Message Endpoint.
*
* @author Mark Fisher
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Component
public @interface MessageEndpoint {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
*
* @return the suggested component name, if any
*/
String value() default "";
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2002-2009 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.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This annotation allows you to specify a SpEL expression indicating that a method
* parameter's value should be mapped from the payload of a Message. The expression
* will be evaluated against the payload object as the root context. The annotated
* parameter type must match or be convertible from the evaluation result.
* <p>
* Example: void foo(@Payload("city.name") String cityName) - will map the value of
* the 'name' property of the 'city' property of the payload object.
*
* @author Oleg Zhurakousky
* @since 2.0
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Payload {
/**
* Expression for matching against nested properties of the payload.
*/
String value() default "";
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Indicates that a method is capable of asserting if a list of messages or
* payload objects is complete.
*
* @author Marius Bogoevici
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface ReleaseStrategy {
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Indicates that a method is capable of resolving to a channel or channel name
* based on a message, message header(s), or both.
* <p>
* A method annotated with @Router may accept a parameter of type
* {@link org.springframework.integration.core.Message} or of the expected
* Message payload's type. Any type conversion supported by
* {@link org.springframework.beans.SimpleTypeConverter} will be applied to
* the Message payload if necessary. Header values can also be passed as
* Message parameters by using the {@link Header @Header} parameter annotation.
* <p>
* Return values from the annotated method may be either a Collection or Array
* whose elements are either
* {@link org.springframework.integration.core.MessageChannel channels} or
* Strings. In the latter case, the endpoint hosting this router will attempt
* to resolve each channel name with the Channel Registry.
*
* @author Mark Fisher
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Router {
String inputChannel() default "";
String defaultOutputChannel() default "";
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Indicates that a method is capable of handling a message or message payload.
* <p>
* A method annotated with @ServiceActivator may accept a parameter of type
* {@link org.springframework.integration.core.Message} or of the expected
* Message payload's type. Any type conversion supported by
* {@link org.springframework.beans.SimpleTypeConverter} will be applied to
* the Message payload if necessary. Header values can also be passed as
* Message parameters by using the {@link Header @Header} parameter annotation.
* <p>
* Return values from the annotated method may be of any type. If the return
* value is not a Message, a reply Message will be created with that object
* as its payload.
*
* @author Mark Fisher
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface ServiceActivator {
String inputChannel() default "";
String outputChannel() default "";
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Indicates that a method is capable of splitting a single message or message
* payload to produce multiple messages or payloads.
* <p>
* A method annotated with @Splitter may accept a parameter of type
* {@link org.springframework.integration.core.Message} or of the expected
* Message payload's type. Any type conversion supported by
* {@link org.springframework.beans.SimpleTypeConverter} will be applied to
* the Message payload if necessary. Header values can also be passed as
* Message parameters by using the {@link Header @Header} parameter annotation.
* <p>
* Return values from the annotated method may be either a Collection or Array
* with elements of any type. If the type is not a Message, each will be used
* as the payload for creating a new Message.
*
* @author Mark Fisher
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Splitter {
String inputChannel() default "";
String outputChannel() default "";
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Indicates that a method is capable of transforming a message, message header,
* or message payload.
*
* @author Mark Fisher
*/
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transformer {
String inputChannel() default "";
String outputChannel() default "";
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright 2002-2010 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.aop;
import java.lang.reflect.Method;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
/**
* Base class for {@link ExpressionSource} implementations.
*
* @author Mark Fisher
* @since 2.0
*/
public abstract class AbstractExpressionSource implements ExpressionSource {
private volatile String methodNameVariableName = ExpressionSource.DEFAULT_METHOD_NAME_VARIABLE_NAME;
private volatile String argumentMapVariableName = ExpressionSource.DEFAULT_ARGUMENT_MAP_VARIABLE_NAME;
private volatile String returnValueVariableName = ExpressionSource.DEFAULT_RETURN_VALUE_VARIABLE_NAME;
private volatile String exceptionVariableName = ExpressionSource.DEFAULT_EXCEPTION_VARIABLE_NAME;
private final ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
public void setMethodNameVariableName(String methodNameVariableName) {
this.methodNameVariableName = methodNameVariableName;
}
public String getMethodNameVariableName(Method method) {
return this.methodNameVariableName;
}
public void setArgumentMapVariableName(String argumentMapVariableName) {
this.argumentMapVariableName = argumentMapVariableName;
}
public String getArgumentMapVariableName(Method method) {
return this.argumentMapVariableName;
}
public void setExceptionVariableName(String exceptionVariableName) {
this.exceptionVariableName = exceptionVariableName;
}
public String getExceptionVariableName(Method method) {
return this.exceptionVariableName;
}
public void setReturnValueVariableName(String returnValueVariableName) {
this.returnValueVariableName = returnValueVariableName;
}
public String getReturnValueVariableName(Method method) {
return this.returnValueVariableName;
}
protected String[] discoverMethodParameterNames(Method method) {
return this.parameterNameDiscoverer.getParameterNames(method);
}
public abstract String getPayloadExpression(Method method);
public abstract String[] getArgumentVariableNames(Method method);
public abstract String[] getHeaderExpressions(Method method);
public abstract String getChannelName(Method method);
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2002-2009 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.aop;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation that provides the variable names to use when constructing the
* evaluation context for a MessagePublishingInterceptor.
*
* @author Mark Fisher
* @since 2.0
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExpressionBinding {
/**
* Name of the variable in the context that refers to the method name.
* <p>The default is "method".
*/
String methodNameVariableName() default ExpressionSource.DEFAULT_METHOD_NAME_VARIABLE_NAME;
/**
* Names of the arguments as a comma-separated list. If not provided, the
* names will be discovered automatically if enabled by the compiler settings.
* These names will be used as the keys in the argument Map.
*/
String argumentVariableNames() default "";
/**
* Name of the variable in the context that refers to the Map of arguments.
* <p>The default is "args".
*/
String argumentMapVariableName() default ExpressionSource.DEFAULT_ARGUMENT_MAP_VARIABLE_NAME;
/**
* Name of the variable in the context that refers to the return value, if any.
* <p>The default is "return".
*/
String returnValueVariableName() default ExpressionSource.DEFAULT_RETURN_VALUE_VARIABLE_NAME;
/**
* Name of the variable in the context that refers to any exception thrown
* by the method invocation that is being intercepted.
* <p>The default is "exception".
*/
String exceptionVariableName() default ExpressionSource.DEFAULT_EXCEPTION_VARIABLE_NAME;
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright 2002-2009 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.aop;
import java.lang.reflect.Method;
/**
* Strategy for determining the expression string and evaluation context
* variable names from a Method.
*
* @author Mark Fisher
* @since 2.0
*/
interface ExpressionSource {
static final String DEFAULT_METHOD_NAME_VARIABLE_NAME = "method";
static final String DEFAULT_ARGUMENT_MAP_VARIABLE_NAME = "args";
static final String DEFAULT_RETURN_VALUE_VARIABLE_NAME = "return";
static final String DEFAULT_EXCEPTION_VARIABLE_NAME = "exception";
/**
* Returns the expression string to be evaluated for creating the Message
* payload.
*/
String getPayloadExpression(Method method);
/**
* Returns the array of expression strings to be evaluated for any headers
* that should be set on the published Message.
*/
String[] getHeaderExpressions(Method method);
/**
* Returns the variable name to be associated with the intercepted
* method's name.
*/
String getMethodNameVariableName(Method method);
/**
* Returns the variable names to be associated with the intercepted method
* invocation's argument array.
*/
String[] getArgumentVariableNames(Method method);
/**
* Returns the variable name to use in the evaluation context for the Map
* of arguments. The keys in this map will be determined by the result of
* the {@link #getArgumentVariableNames(Method)} method.
*/
String getArgumentMapVariableName(Method method);
/**
* Returns the variable name to use in the evaluation context for any
* return value resulting from the method invocation.
*/
String getReturnValueVariableName(Method method);
/**
* Returns the variable name to use in the evaluation context for any
* exception thrown from the method invocation.
*/
String getExceptionVariableName(Method method);
/**
* Returns the channel name to which Messages should be published
* for this particular method invocation.
*/
String getChannelName(Method method);
}

View File

@@ -0,0 +1,166 @@
/*
* Copyright 2002-2010 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.aop;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.support.AopUtils;
import org.springframework.context.expression.MapAccessor;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParseException;
import org.springframework.expression.spel.SpelParserConfiguration;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.integration.channel.ChannelResolver;
import org.springframework.integration.channel.MessageChannelTemplate;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.message.MessageBuilder;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* A {@link MethodInterceptor} that publishes Messages to a channel. The
* payload of the published Message can be derived from arguments or any return
* value or exception resulting from the method invocation. That mapping is the
* responsibility of the EL expression provided by the ExpressionSource.
*
* @author Mark Fisher
* @since 2.0
*/
public class MessagePublishingInterceptor implements MethodInterceptor {
private final MessageChannelTemplate channelTemplate = new MessageChannelTemplate();
private volatile ExpressionSource expressionSource;
private final ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true));
private volatile ChannelResolver channelResolver;
public MessagePublishingInterceptor(ExpressionSource expressionSource) {
Assert.notNull(expressionSource, "expressionSource must not be null");
this.expressionSource = expressionSource;
}
public void setExpressionSource(ExpressionSource expressionSource) {
Assert.notNull(expressionSource, "expressionSource must not be null");
this.expressionSource = expressionSource;
}
public void setDefaultChannel(MessageChannel defaultChannel) {
this.channelTemplate.setDefaultChannel(defaultChannel);
}
public void setChannelResolver(ChannelResolver channelResolver) {
this.channelResolver = channelResolver;
}
public final Object invoke(final MethodInvocation invocation) throws Throwable {
Assert.notNull(this.expressionSource, "ExpressionSource is required.");
final StandardEvaluationContext context = new StandardEvaluationContext();
context.addPropertyAccessor(new MapAccessor());
Class<?> targetClass = AopUtils.getTargetClass(invocation.getThis());
Method method = AopUtils.getMostSpecificMethod(invocation.getMethod(), targetClass);
String[] argumentNames = this.expressionSource.getArgumentVariableNames(method);
context.setVariable(this.expressionSource.getMethodNameVariableName(method), method.getName());
if (invocation.getArguments().length > 0 && argumentNames != null) {
int index = 0;
Map<String, Object> argumentMap = new HashMap<String, Object>();
for (String argumentName : argumentNames) {
if (invocation.getArguments().length <= index) {
break;
}
argumentMap.put(argumentName, invocation.getArguments()[index++]);
}
context.setVariable(this.expressionSource.getArgumentMapVariableName(method), argumentMap);
}
try {
Object returnValue = invocation.proceed();
context.setVariable(this.expressionSource.getReturnValueVariableName(method), returnValue);
return returnValue;
}
catch (Throwable t) {
context.setVariable(this.expressionSource.getExceptionVariableName(method), t);
throw t;
}
finally {
publishMessage(method, context);
}
}
private void publishMessage(Method method, StandardEvaluationContext context) throws Exception {
String payloadExpressionString = this.expressionSource.getPayloadExpression(method);
if (!StringUtils.hasText(payloadExpressionString)) {
payloadExpressionString = "#" + this.expressionSource.getReturnValueVariableName(method);
}
Expression expression = this.parser.parseExpression(payloadExpressionString);
Object result = expression.getValue(context);
if (result != null) {
MessageBuilder<?> builder = (result instanceof Message<?>)
? MessageBuilder.fromMessage((Message<?>) result)
: MessageBuilder.withPayload(result);
Map<String, Object> headers = this.evaluateHeaders(method, context);
if (headers != null) {
builder.copyHeaders(headers);
}
Message<?> message = builder.build();
String channelName = this.expressionSource.getChannelName(method);
MessageChannel channel = null;
if (channelName != null) {
Assert.state(this.channelResolver != null, "ChannelResolver is required to resolve channel names.");
channel = this.channelResolver.resolveChannelName(channelName);
}
if (channel != null) {
this.channelTemplate.send(message, channel);
}
else {
this.channelTemplate.send(message);
}
}
}
private Map<String, Object> evaluateHeaders(Method method, StandardEvaluationContext context)
throws ParseException, EvaluationException {
String[] headerExpressionStrings = this.expressionSource.getHeaderExpressions(method);
if (headerExpressionStrings != null) {
Map<String, Object> headers = new HashMap<String, Object>();
context.setRootObject(headers);
for (String headerExpression : headerExpressionStrings) {
if (StringUtils.hasText(headerExpression)) {
Expression expression = this.parser.parseExpression(headerExpression);
expression.getValue(context);
}
}
if (headers.size() > 0) {
return headers;
}
}
return null;
}
}

View File

@@ -0,0 +1,144 @@
/*
* Copyright 2002-2009 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.aop;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Set;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* An {@link ExpressionSource} implementation that retrieves the expression
* string and evaluation context variable names from an annotation.
*
* @author Mark Fisher
* @since 2.0
*/
public class MethodAnnotationExpressionSource implements ExpressionSource {
private final Set<Class<? extends Annotation>> annotationTypes;
private volatile String channelAttributeName = "channel";
private final ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
public MethodAnnotationExpressionSource() {
this(Collections.<Class<? extends Annotation>>singleton(Publisher.class));
}
public MethodAnnotationExpressionSource(Set<Class<? extends Annotation>> annotationTypes) {
Assert.notEmpty(annotationTypes, "annotationTypes must not be empty");
this.annotationTypes = annotationTypes;
}
public void setChannelAttributeName(String channelAttributeName) {
Assert.hasText(channelAttributeName, "channelAttributeName must not be empty");
this.channelAttributeName = channelAttributeName;
}
public String getPayloadExpression(Method method) {
return this.getAnnotationValue(method, null, String.class);
}
public String[] getHeaderExpressions(Method method) {
return this.getAnnotationValue(method, "headers", String[].class);
}
public String getMethodNameVariableName(Method method) {
ExpressionBinding annotation = AnnotationUtils.findAnnotation(method, ExpressionBinding.class);
if (annotation != null) {
return annotation.methodNameVariableName();
}
return ExpressionSource.DEFAULT_METHOD_NAME_VARIABLE_NAME;
}
public String[] getArgumentVariableNames(Method method) {
ExpressionBinding annotation = AnnotationUtils.findAnnotation(method, ExpressionBinding.class);
if (annotation != null) {
String argNameList = annotation.argumentVariableNames();
if (StringUtils.hasText(argNameList)) {
return StringUtils.tokenizeToStringArray(argNameList, ",");
}
}
return this.parameterNameDiscoverer.getParameterNames(method);
}
public String getArgumentMapVariableName(Method method) {
ExpressionBinding annotation = AnnotationUtils.findAnnotation(method, ExpressionBinding.class);
if (annotation != null) {
return annotation.argumentMapVariableName();
}
return ExpressionSource.DEFAULT_ARGUMENT_MAP_VARIABLE_NAME;
}
public String getReturnValueVariableName(Method method) {
ExpressionBinding annotation = AnnotationUtils.findAnnotation(method, ExpressionBinding.class);
if (annotation != null) {
return annotation.returnValueVariableName();
}
return ExpressionSource.DEFAULT_RETURN_VALUE_VARIABLE_NAME;
}
public String getExceptionVariableName(Method method) {
ExpressionBinding annotation = AnnotationUtils.findAnnotation(method, ExpressionBinding.class);
if (annotation != null) {
return annotation.exceptionVariableName();
}
return ExpressionSource.DEFAULT_EXCEPTION_VARIABLE_NAME;
}
public String getChannelName(Method method) {
String channelName = this.getAnnotationValue(method, this.channelAttributeName, String.class);
return (StringUtils.hasText(channelName) ? channelName : null);
}
@SuppressWarnings("unchecked")
private <T> T getAnnotationValue(Method method, String attributeName, Class<T> expectedType) {
T value = null;
for (Class<? extends Annotation> annotationType : this.annotationTypes) {
Annotation annotation = AnnotationUtils.findAnnotation(method, annotationType);
if (annotation != null) {
if (value != null) {
throw new IllegalStateException(
"method [" + method + "] contains more than one publisher annotation");
}
Object valueAsObject = (attributeName == null) ? AnnotationUtils.getValue(annotation)
: AnnotationUtils.getValue(annotation, attributeName);
if (valueAsObject != null) {
if (expectedType.isAssignableFrom(valueAsObject.getClass())) {
value = (T) valueAsObject;
}
else {
throw new IllegalArgumentException("expected type [" + expectedType.getName() +
"] for attribute '" + attributeName + "' on publisher annotation [" +
annotationType + "], but actual type was [" + valueAsObject.getClass() + "]");
}
}
}
}
return value;
}
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2002-2010 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.aop;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Map;
import org.springframework.util.Assert;
import org.springframework.util.PatternMatchUtils;
/**
* @author Mark Fisher
* @since 2.0
*/
public class MethodNameMappingExpressionSource extends AbstractExpressionSource {
private final Map<String, String> payloadExpressionMap;
private volatile Map<String, String[]> headerExpressionMap = Collections.emptyMap();
private volatile Map<String, String> channelMap = Collections.emptyMap();
private volatile Map<String, String[]> argumentVariableNameMap;
public MethodNameMappingExpressionSource(Map<String, String> payloadExpressionMap) {
Assert.notEmpty(payloadExpressionMap, "payloadExpressionMap must not be empty");
this.payloadExpressionMap = payloadExpressionMap;
}
public void setArgumentVariableNameMap(Map<String, String[]> argumentVariableNameMap) {
this.argumentVariableNameMap = argumentVariableNameMap;
}
public void setHeaderExpressionMap(Map<String, String[]> headerExpressionMap) {
this.headerExpressionMap = headerExpressionMap;
}
public void setChannelMap(Map<String, String> channelMap) {
this.channelMap = channelMap;
}
public String[] getArgumentVariableNames(Method method) {
if (this.argumentVariableNameMap != null) {
for (Map.Entry<String, String[]> entry : this.argumentVariableNameMap.entrySet()) {
if (PatternMatchUtils.simpleMatch(entry.getKey(), method.getName())) {
return entry.getValue();
}
}
}
return this.discoverMethodParameterNames(method);
}
public String getPayloadExpression(Method method) {
for (Map.Entry<String, String> entry : this.payloadExpressionMap.entrySet()) {
if (PatternMatchUtils.simpleMatch(entry.getKey(), method.getName())) {
return entry.getValue();
}
}
return null;
}
public String[] getHeaderExpressions(Method method) {
for (Map.Entry<String, String[]> entry : this.headerExpressionMap.entrySet()) {
if (PatternMatchUtils.simpleMatch(entry.getKey(), method.getName())) {
return entry.getValue();
}
}
return null;
}
public String getChannelName(Method method) {
for (Map.Entry<String, String> entry : this.channelMap.entrySet()) {
if (PatternMatchUtils.simpleMatch(entry.getKey(), method.getName())) {
return entry.getValue();
}
}
return null;
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2002-2009 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.aop;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation to indicate that a method, or all public methods if applied at
* class-level, should publish Messages whose payloads will be determined by
* the provided EL expression.
*
* @author Mark Fisher
* @since 2.0
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Publisher {
/**
* String representation of a Spel Expression to evaluate when creating the
* Message payload. The default will be empty, thereby causing the return
* value to be used as the payload.
*/
String value() default "";
/**
* String representations of Spel Expressions to evaluate for adding any
* headers to the Message. Optional.
*/
String[] headers() default "";
/**
* Name of the Message Channel to which Messages will be published.
*/
String channel() default "";
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright 2002-2009 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.aop;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.aopalliance.aop.Advice;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.AbstractPointcutAdvisor;
import org.springframework.aop.support.ComposablePointcut;
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.integration.channel.BeanFactoryChannelResolver;
import org.springframework.integration.core.MessageChannel;
/**
* An advisor that will apply the {@link MessagePublishingInterceptor} to any
* methods containing the provided annotations. If no annotations are provided,
* the default will be {@link Publisher @Publisher}.
*
* @author Mark Fisher
* @since 2.0
*/
@SuppressWarnings("serial")
public class PublisherAnnotationAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {
private final Set<Class<? extends Annotation>> publisherAnnotationTypes;
private final MessagePublishingInterceptor interceptor;
public PublisherAnnotationAdvisor(Class<? extends Annotation> ... publisherAnnotationTypes) {
this.publisherAnnotationTypes = new HashSet<Class<? extends Annotation>>(Arrays.asList(publisherAnnotationTypes));
ExpressionSource source = new MethodAnnotationExpressionSource(this.publisherAnnotationTypes);
this.interceptor = new MessagePublishingInterceptor(source);
}
@SuppressWarnings("unchecked")
public PublisherAnnotationAdvisor() {
this(Publisher.class);
}
public void setDefaultChannel(MessageChannel defaultChannel) {
this.interceptor.setDefaultChannel(defaultChannel);
}
public void setBeanFactory(BeanFactory beanFactory) {
this.interceptor.setChannelResolver(new BeanFactoryChannelResolver(beanFactory));
}
public Advice getAdvice() {
return this.interceptor;
}
public Pointcut getPointcut() {
return this.buildPointcut();
}
private Pointcut buildPointcut() {
ComposablePointcut result = null;
for (Class<? extends Annotation> publisherAnnotationType : this.publisherAnnotationTypes) {
Pointcut cpc = new AnnotationMatchingPointcut(publisherAnnotationType, true);
Pointcut mpc = new AnnotationMatchingPointcut(null, publisherAnnotationType);
if (result == null) {
result = new ComposablePointcut(cpc).union(mpc);
}
else {
result.union(cpc).union(mpc);
}
}
return result;
}
}

View File

@@ -0,0 +1,113 @@
/*
* Copyright 2002-2009 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.aop;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.ProxyConfig;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.integration.core.MessageChannel;
import org.springframework.util.ClassUtils;
/**
* Post-processes beans that contain the method-level @{@link Publisher} annotation.
*
* @author Oleg Zhurakousky
* @author Mark Fisher
* @since 2.0
*/
public class PublisherAnnotationBeanPostProcessor extends ProxyConfig
implements BeanPostProcessor, BeanClassLoaderAware, BeanFactoryAware, InitializingBean, Ordered {
private volatile MessageChannel defaultChannel;
private volatile PublisherAnnotationAdvisor advisor;
private volatile int order = Ordered.LOWEST_PRECEDENCE;
private volatile BeanFactory beanFactory;
private volatile ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
/**
* Set the default channel where Messages should be sent if the annotation
* itself does not provide a channel.
*/
public void setDefaultChannel(MessageChannel defaultChannel){
this.defaultChannel = defaultChannel;
}
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
public void setBeanClassLoader(ClassLoader classLoader) {
this.beanClassLoader = classLoader;
}
public void setOrder(int order) {
this.order = order;
}
public int getOrder() {
return this.order;
}
public void afterPropertiesSet(){
advisor = new PublisherAnnotationAdvisor();
advisor.setBeanFactory(beanFactory);
advisor.setDefaultChannel(defaultChannel);
}
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
Class<?> targetClass = AopUtils.getTargetClass(bean);
if (targetClass == null) {
return bean;
}
if (AopUtils.canApply(this.advisor, targetClass)) {
if (bean instanceof Advised) {
((Advised) bean).addAdvisor(this.advisor);
return bean;
}
else {
ProxyFactory proxyFactory = new ProxyFactory(bean);
// Copy our properties (proxyTargetClass etc) inherited from ProxyConfig.
proxyFactory.copyFrom(this);
proxyFactory.addAdvisor(this.advisor);
return proxyFactory.getProxy(this.beanClassLoader);
}
}
else {
// cannot apply advisor
return bean;
}
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2002-2010 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.aop;
import java.lang.reflect.Method;
/**
* Simple implementation of {@link ExpressionSource} that allows for
* configuration of a single channel name, payload expression, and
* array of header key=value expressions.
*
* @author Mark Fisher
* @since 2.0
*/
public class SimpleExpressionSource extends AbstractExpressionSource {
private volatile String channelName;
private volatile String payloadExpression;
private volatile String[] headerExpressions;
public void setChannelName(String channelName) {
this.channelName = channelName;
}
@Override
public String getChannelName(Method method) {
return this.channelName;
}
public void setPayloadExpression(String payloadExpression) {
this.payloadExpression = payloadExpression;
}
@Override
public String getPayloadExpression(Method method) {
return this.payloadExpression;
}
public void setHeaderExpressions(String[] headerExpressions) {
this.headerExpressions = headerExpressions;
}
@Override
public String[] getHeaderExpressions(Method method) {
return this.headerExpressions;
}
@Override
public String[] getArgumentVariableNames(Method method) {
return this.discoverMethodParameterNames(method);
}
}

View File

@@ -0,0 +1,291 @@
/*
* Copyright 2002-2010 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;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.OrderComparator;
import org.springframework.core.convert.ConversionService;
import org.springframework.integration.context.IntegrationObjectSupport;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.core.MessagingException;
import org.springframework.integration.message.MessageBuilder;
import org.springframework.integration.message.MessageDeliveryException;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Base class for {@link MessageChannel} implementations providing common
* properties such as the channel name. Also provides the common functionality
* for sending and receiving {@link Message Messages} including the invocation
* of any {@link ChannelInterceptor ChannelInterceptors}.
*
* @author Mark Fisher
*/
public abstract class AbstractMessageChannel extends IntegrationObjectSupport implements MessageChannel {
private final Log logger = LogFactory.getLog(this.getClass());
private final AtomicLong sendSuccessCount = new AtomicLong();
private final AtomicLong sendErrorCount = new AtomicLong();
private volatile Class<?>[] datatypes = new Class<?>[] { Object.class };
private final ChannelInterceptorList interceptors = new ChannelInterceptorList();
@Override
public String getComponentType() {
return "channel";
}
/**
* Return the current count of Messages that have been sent
* to this channel successfully.
*/
public long getSendSuccessCount() {
return this.sendSuccessCount.get();
}
/**
* Return the current count of errors that have occurred while
* attempting to send a Message to this channel. This value is
* incremented whenever an Exception is thrown from one of the
* send() methods.
*/
public long getSendErrorCount() {
return this.sendErrorCount.get();
}
/**
* Specify the Message payload datatype(s) supported by this channel. If a
* payload type does not match directly, but the 'conversionService' is
* available, then type conversion will be attempted in the order of the
* elements provided in this array.
* <p>
* If this property is not set explicitly, any Message payload type will be
* accepted.
* @see #setConversionService(ConversionService)
*/
public void setDatatypes(Class<?>... datatypes) {
this.datatypes = (datatypes != null && datatypes.length > 0)
? datatypes : new Class<?>[] { Object.class };
}
/**
* Set the list of channel interceptors. This will clear any existing
* interceptors.
*/
public void setInterceptors(List<ChannelInterceptor> interceptors) {
Collections.sort(interceptors, new OrderComparator());
this.interceptors.set(interceptors);
}
/**
* Add a channel interceptor to the end of the list.
*/
public void addInterceptor(ChannelInterceptor interceptor) {
this.interceptors.add(interceptor);
}
/**
* Specify the {@link ConversionService} to use when trying to convert to
* one of this channel's supported datatypes for a Message whose payload
* does not already match. If this property is not set explicitly but
* the channel is managed within a context, it will attempt to locate a
* bean named "integrationConversionService" defined within that context.
* Finally, if that bean is not available, it will fallback to the
* "conversionService" bean, if available.
*/
public void setConversionService(ConversionService conversionService) {
super.setConversionService(conversionService);
}
/**
* Exposes the interceptor list for subclasses.
*/
protected ChannelInterceptorList getInterceptors() {
return this.interceptors;
}
/**
* Send a message on this channel. If the channel is at capacity, this
* method will block until either space becomes available or the sending
* thread is interrupted.
*
* @param message the Message to send
*
* @return <code>true</code> if the message is sent successfully or
* <code>false</code> if the sending thread is interrupted.
*/
public final boolean send(Message<?> message) {
return this.send(message, -1);
}
/**
* Send a message on this channel. If the channel is at capacity, this
* method will block until either the timeout occurs or the sending thread
* is interrupted. If the specified timeout is 0, the method will return
* immediately. If less than zero, it will block indefinitely (see
* {@link #send(Message)}).
*
* @param message the Message to send
* @param timeout the timeout in milliseconds
*
* @return <code>true</code> if the message is sent successfully,
* <code>false</code> if the message cannot be sent within the allotted
* time or the sending thread is interrupted.
*/
public final boolean send(Message<?> message, long timeout) {
Assert.notNull(message, "message must not be null");
Assert.notNull(message.getPayload(), "message payload must not be null");
message = this.convertPayloadIfNecessary(message);
message.getHeaders().getHistory().addEvent(this);
message = this.interceptors.preSend(message, this);
if (message == null) {
return false;
}
try {
boolean sent = this.doSend(message, timeout);
if (sent) {
this.sendSuccessCount.incrementAndGet();
}
this.interceptors.postSend(message, this, sent);
return sent;
}
catch (Exception e) {
this.sendErrorCount.incrementAndGet();
if (e instanceof MessagingException) {
throw (MessagingException) e;
}
throw new MessageDeliveryException(message,
"failed to send Message to channel '" + this.getComponentName() + "'", e);
}
}
private Message<?> convertPayloadIfNecessary(Message<?> message) {
// first pass checks if the payload type already matches any of the datatypes
for (Class<?> datatype : this.datatypes) {
if (datatype.isAssignableFrom(message.getPayload().getClass())) {
return message;
}
}
// second pass applies conversion if possible, attempting datatypes in order
ConversionService conversionService = this.getConversionService();
if (conversionService != null) {
for (Class<?> datatype : this.datatypes) {
if (conversionService.canConvert(message.getPayload().getClass(), datatype)) {
Object convertedPayload = conversionService.convert(message.getPayload(), datatype);
return MessageBuilder.withPayload(convertedPayload).copyHeaders(message.getHeaders()).build();
}
}
}
throw new MessageDeliveryException(message, "Channel '" + this.getComponentName() +
"' expected one of the following datataypes [" +
StringUtils.arrayToCommaDelimitedString(this.datatypes) +
"], but received [" + message.getPayload().getClass() + "]");
}
/**
* Subclasses must implement this method. A non-negative timeout indicates
* how long to wait if the channel is at capacity (if the value is 0, it
* must return immediately with or without success). A negative timeout
* value indicates that the method should block until either the message is
* accepted or the blocking thread is interrupted.
*/
protected abstract boolean doSend(Message<?> message, long timeout);
/**
* A convenience wrapper class for the list of ChannelInterceptors.
*/
protected class ChannelInterceptorList {
private final List<ChannelInterceptor> interceptors = new CopyOnWriteArrayList<ChannelInterceptor>();
public boolean set(List<ChannelInterceptor> interceptors) {
synchronized (this.interceptors) {
this.interceptors.clear();
return this.interceptors.addAll(interceptors);
}
}
public boolean add(ChannelInterceptor interceptor) {
return this.interceptors.add(interceptor);
}
public Message<?> preSend(Message<?> message, MessageChannel channel) {
if (logger.isDebugEnabled()) {
logger.debug("preSend on channel '" + channel + "', message: " + message);
}
for (ChannelInterceptor interceptor : interceptors) {
message = interceptor.preSend(message, channel);
if (message == null) {
return null;
}
}
return message;
}
public void postSend(Message<?> message, MessageChannel channel, boolean sent) {
if (logger.isDebugEnabled()) {
logger.debug("postSend (sent=" + sent + ") on channel '" + channel + "', message: " + message);
}
for (ChannelInterceptor interceptor : interceptors) {
interceptor.postSend(message, channel, sent);
}
}
public boolean preReceive(MessageChannel channel) {
if (logger.isTraceEnabled()) {
logger.trace("preReceive on channel '" + channel + "'");
}
for (ChannelInterceptor interceptor : interceptors) {
if (!interceptor.preReceive(channel)) {
return false;
}
}
return true;
}
public Message<?> postReceive(Message<?> message, MessageChannel channel) {
if (message != null && logger.isDebugEnabled()) {
logger.debug("postReceive on channel '" + channel + "', message: " + message);
}
else if (logger.isTraceEnabled()) {
logger.trace("postReceive on channel '" + channel + "', message is null");
}
for (ChannelInterceptor interceptor : interceptors) {
message = interceptor.postReceive(message, channel);
if (message == null) {
return null;
}
}
return message;
}
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2002-2010 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;
import org.springframework.integration.core.Message;
/**
* Base class for all pollable channels.
*
* @author Mark Fisher
*/
public abstract class AbstractPollableChannel extends AbstractMessageChannel implements PollableChannel {
/**
* Receive the first available message from this channel. If the channel
* contains no messages, this method will block.
*
* @return the first available message or <code>null</code> if the
* receiving thread is interrupted.
*/
public final Message<?> receive() {
return this.receive(-1);
}
/**
* Receive the first available message from this channel. If the channel
* contains no messages, this method will block until the allotted timeout
* elapses. If the specified timeout is 0, the method will return
* immediately. If less than zero, it will block indefinitely (see
* {@link #receive()}).
*
* @param timeout the timeout in milliseconds
*
* @return the first available message or <code>null</code> if no message
* is available within the allotted time or the receiving thread is
* interrupted.
*/
public final Message<?> receive(long timeout) {
if (!this.getInterceptors().preReceive(this)) {
return null;
}
Message<?> message = this.doReceive(timeout);
message = this.getInterceptors().postReceive(message, this);
return message;
}
/**
* Subclasses must implement this method. A non-negative timeout indicates
* how long to wait if the channel is empty (if the value is 0, it must
* return immediately with or without success). A negative timeout value
* indicates that the method should block until either a message is
* available or the blocking thread is interrupted.
*/
protected abstract Message<?> doReceive(long timeout);
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2002-2009 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;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.dispatcher.MessageDispatcher;
import org.springframework.integration.message.MessageHandler;
import org.springframework.util.Assert;
/**
* Base implementation of {@link MessageChannel} that invokes the subscribed
* {@link MessageHandler handler(s)} by delegating to a {@link MessageDispatcher}.
*
* @author Mark Fisher
*/
public abstract class AbstractSubscribableChannel extends AbstractMessageChannel implements SubscribableChannel {
public boolean subscribe(MessageHandler handler) {
return this.getRequiredDispatcher().addHandler(handler);
}
public boolean unsubscribe(MessageHandler handle) {
return this.getRequiredDispatcher().removeHandler(handle);
}
@Override
protected boolean doSend(Message<?> message, long timeout) {
return this.getRequiredDispatcher().dispatch(message);
}
private MessageDispatcher getRequiredDispatcher() {
MessageDispatcher dispatcher = this.getDispatcher();
Assert.state(dispatcher != null, "'dispatcher' must not be null");
return dispatcher;
}
protected abstract MessageDispatcher getDispatcher();
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.channel;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.integration.core.MessageChannel;
import org.springframework.util.Assert;
/**
* {@link ChannelResolver} implementation based on a Spring {@link BeanFactory}.
*
* <p>Will lookup Spring managed beans identified by bean name,
* expecting them to be of type {@link MessageChannel}.
*
* @author Mark Fisher
* @see org.springframework.beans.factory.BeanFactory
*/
public class BeanFactoryChannelResolver implements ChannelResolver, BeanFactoryAware {
private volatile BeanFactory beanFactory;
/**
* Create a new instance of the {@link BeanFactoryChannelResolver} class.
* <p>The BeanFactory to access must be set via <code>setBeanFactory</code>.
* This will happen automatically if this resolver is defined within an
* ApplicationContext thereby receiving the callback upon initialization.
* @see #setBeanFactory
*/
public BeanFactoryChannelResolver() {
}
/**
* Create a new instance of the {@link BeanFactoryChannelResolver} class.
* <p>Use of this constructor is redundant if this object is being created
* by a Spring IoC container as the supplied {@link BeanFactory} will be
* replaced by the {@link BeanFactory} that creates it (c.f. the
* {@link BeanFactoryAware} contract). So only use this constructor if you
* are instantiating this object explicitly rather than defining a bean.
*
* @param beanFactory the bean factory to be used to lookup {@link MessageChannel}s.
*/
public BeanFactoryChannelResolver(BeanFactory beanFactory) {
Assert.notNull(beanFactory, "BeanFactory must not be null");
this.beanFactory = beanFactory;
}
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
public MessageChannel resolveChannelName(String name) {
Assert.state(this.beanFactory != null, "BeanFactory is required");
try {
return (MessageChannel) this.beanFactory.getBean(name, MessageChannel.class);
}
catch (BeansException e) {
throw new ChannelResolutionException(
"failed to look up MessageChannel bean with name '" + name + "'", e);
}
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2002-2010 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;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageChannel;
/**
* Interface for interceptors that are able to view and/or modify the
* {@link Message Messages} being sent-to and/or received-from a
* {@link MessageChannel}.
*
* @author Mark Fisher
*/
public interface ChannelInterceptor {
/**
* Invoked before the Message is actually sent to the channel.
* This allows for modification of the Message if necessary.
* If this method returns <code>null</code>, then the actual
* send invocation will not occur.
*/
Message<?> preSend(Message<?> message, MessageChannel channel);
/**
* Invoked immediately after the send invocation. The boolean
* value argument represents the return value of that invocation.
*/
void postSend(Message<?> message, MessageChannel channel, boolean sent);
/**
* Invoked as soon as receive is called and before a Message is
* actually retrieved. If the return value is 'false', then no
* Message will be retrieved. This only applies to PollableChannels.
*/
boolean preReceive(MessageChannel channel);
/**
* Invoked immediately after a Message has been retrieved but before
* it is returned to the caller. The Message may be modified if
* necessary. This only applies to PollableChannels.
*/
Message<?> postReceive(Message<?> message, MessageChannel channel);
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2002-2010 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;
import java.util.ArrayList;
import java.util.List;
import org.springframework.integration.core.Message;
import org.springframework.integration.selector.MessageSelector;
import org.springframework.util.Assert;
/**
* A utility class for purging {@link Message Messages} from one or more
* {@link QueueChannel QueueChannels}. Any message that does <em>not</em>
* match the provided {@link MessageSelector} will be removed from the channel.
* If no {@link MessageSelector} is provided, then <em>all</em> messages will be
* cleared from the channel.
* <p>
* Note that the {@link #purge()} method operates on a snapshot of the messages
* within a channel at the time that the method is invoked. It is therefore
* possible that new messages will arrive on the channel during the purge
* operation and thus will <em>not</em> be removed. Likewise, messages to be
* purged may have been removed from the channel while the operation is taking
* place. Such messages will not be included in the returned list.
*
* @author Mark Fisher
*/
public class ChannelPurger {
private final QueueChannel[] channels;
private final MessageSelector selector;
public ChannelPurger(QueueChannel ... channels) {
this(null, channels);
}
public ChannelPurger(MessageSelector selector, QueueChannel ... channels) {
Assert.notEmpty(channels, "at least one channel is required");
if (channels.length == 1) {
Assert.notNull(channels[0], "channel must not be null");
}
this.selector = selector;
this.channels = channels;
}
public final List<Message<?>> purge() {
List<Message<?>> purgedMessages = new ArrayList<Message<?>>();
for (QueueChannel channel : this.channels) {
List<Message<?>> results = (this.selector == null) ?
channel.clear() : channel.purge(this.selector);
if (results != null) {
purgedMessages.addAll(results);
}
}
return purgedMessages;
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.channel;
import org.springframework.integration.core.MessagingException;
/**
* Thrown by a ChannelResolver when it cannot resolve a channel name.
*
* @author Mark Fisher
* @see ChannelResolver
*/
public class ChannelResolutionException extends MessagingException {
/**
* Create a new ChannelResolutionException.
* @param description the description
*/
public ChannelResolutionException(String description) {
super(description);
}
/**
* Create a new ChannelResolutionException.
* @param description the description
* @param cause the root cause (if any)
*/
public ChannelResolutionException(String description, Throwable cause) {
super(description, cause);
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.channel;
import org.springframework.integration.core.MessageChannel;
/**
* Strategy for resolving a name to a {@link MessageChannel}.
*
* @author Mark Fisher
*/
public interface ChannelResolver {
/**
* Return the MessageChannel for the given name.
*/
MessageChannel resolveChannelName(String channelName);
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2002-2009 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;
import org.springframework.integration.dispatcher.LoadBalancingStrategy;
import org.springframework.integration.dispatcher.UnicastingDispatcher;
/**
* A channel that invokes a single subscriber for each sent Message.
* The invocation will occur in the sender's thread.
*
* @author Dave Syer
* @author Mark Fisher
* @author Iwein Fuld
*/
public class DirectChannel extends AbstractSubscribableChannel {
private final UnicastingDispatcher dispatcher = new UnicastingDispatcher();
/**
* Create a channel with no {@link LoadBalancingStrategy}.
* The dispatcher for such a channel will invoke its
* MessageHandlers in a fixed-order.
*/
public DirectChannel() {
}
/**
* Create a DirectChannel with a {@link LoadBalancingStrategy}. The
* strategy <emphasis>must not</emphasis> be null.
*/
public DirectChannel(LoadBalancingStrategy loadBalancingStrategy) {
this.dispatcher.setLoadBalancingStrategy(loadBalancingStrategy);
}
/**
* Specify whether the channel's dispatcher should have failover enabled.
* By default, it will. Set this value to 'false' to disable it.
*/
public void setFailover(boolean failover) {
this.dispatcher.setFailover(failover);
}
@Override
protected UnicastingDispatcher getDispatcher() {
return this.dispatcher;
}
}

View File

@@ -0,0 +1,109 @@
/*
* Copyright 2002-2010 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;
import java.util.concurrent.Executor;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.dispatcher.LoadBalancingStrategy;
import org.springframework.integration.dispatcher.UnicastingDispatcher;
import org.springframework.integration.util.ErrorHandlingTaskExecutor;
import org.springframework.util.Assert;
import org.springframework.util.ErrorHandler;
/**
* An implementation of {@link MessageChannel} that delegates to an instance of
* {@link UnicastingDispatcher} which in turn delegates all dispatching
* invocations to an {@link Executor}.
* <p>
* <emphasis>NOTE: unlike DirectChannel, the ExecutorChannel does not support a
* shared transactional context between sender and handler, because the
* {@link Executor} typically does not block the sender's Thread since it
* uses another Thread for the dispatch.</emphasis> (SyncTaskExecutor is an
* exception but would provide no value for this channel. If synchronous
* dispatching is required, a DirectChannel should be used instead).
*
* @author Mark Fisher
* @since 1.0.3
*/
public class ExecutorChannel extends AbstractSubscribableChannel {
private volatile UnicastingDispatcher dispatcher;
private volatile Executor executor;
private volatile boolean failover = true;
private volatile LoadBalancingStrategy loadBalancingStrategy;
/**
* Create an ExecutorChannel that delegates to the provided
* {@link Executor} when dispatching Messages.
* <p>
* The Executor must not be null.
*/
public ExecutorChannel(Executor executor) {
this(executor, null);
}
/**
* Create an ExecutorChannel with a {@link LoadBalancingStrategy} that
* delegates to the provided {@link Executor} when dispatching Messages.
* <p>
* The Executor must not be null.
*/
public ExecutorChannel(Executor executor, LoadBalancingStrategy loadBalancingStrategy) {
Assert.notNull(executor, "executor must not be null");
this.executor = executor;
this.dispatcher = new UnicastingDispatcher(executor);
if (loadBalancingStrategy != null) {
this.loadBalancingStrategy = loadBalancingStrategy;
this.dispatcher.setLoadBalancingStrategy(loadBalancingStrategy);
}
}
/**
* Specify whether the channel's dispatcher should have failover enabled.
* By default, it will. Set this value to 'false' to disable it.
*/
public void setFailover(boolean failover) {
this.failover = failover;
this.dispatcher.setFailover(failover);
}
@Override
protected UnicastingDispatcher getDispatcher() {
return this.dispatcher;
}
@Override
public final void onInit() {
if (!(this.executor instanceof ErrorHandlingTaskExecutor)) {
ErrorHandler errorHandler = new MessagePublishingErrorHandler(
new BeanFactoryChannelResolver(this.getBeanFactory()));
this.executor = new ErrorHandlingTaskExecutor(this.executor, errorHandler);
}
this.dispatcher = new UnicastingDispatcher(this.executor);
this.dispatcher.setFailover(this.failover);
if (this.loadBalancingStrategy != null) {
this.dispatcher.setLoadBalancingStrategy(this.loadBalancingStrategy);
}
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2002-2009 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;
import java.util.HashMap;
import java.util.Map;
import org.springframework.integration.core.MessageChannel;
import org.springframework.util.Assert;
/**
* {@link ChannelResolver} implementation that resolves {@link MessageChannel}
* instances by matching the channel name against keys within a Map.
*
* @author Mark Fisher
*/
public class MapBasedChannelResolver implements ChannelResolver {
private volatile Map<String, ? extends MessageChannel> channelMap = new HashMap<String, MessageChannel>();
/**
* Empty constructor for use when providing the channel map via
* {@link #setChannelMap(Map)}.
*/
public MapBasedChannelResolver() {
}
/**
* Create a {@link ChannelResolver} that uses the provided Map.
* Each String key will resolve to the associated channel value.
*/
public MapBasedChannelResolver(Map<String, ? extends MessageChannel> channelMap) {
this.setChannelMap(channelMap);
}
/**
* Provide a map of channels to be used by this resolver.
* Each String key will resolve to the associated channel value.
*/
public void setChannelMap(Map<String, ? extends MessageChannel> channelMap) {
Assert.notNull(channelMap, "channelMap must not be null");
this.channelMap = channelMap;
}
public MessageChannel resolveChannelName(String channelName) {
return this.channelMap.get(channelName);
}
}

View File

@@ -0,0 +1,329 @@
/*
* Copyright 2002-2009 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;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.core.MessageHeaders;
import org.springframework.integration.message.MessageBuilder;
import org.springframework.integration.message.MessageDeliveryException;
import org.springframework.integration.selector.MessageSelector;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.Assert;
/**
* This is the central class for invoking message exchange operations across
* {@link MessageChannel}s. It supports one-way send and receive calls as well
* as request/reply.
* <p>
* To enable transactions, configure the 'transactionManager' property with a
* reference to an instance of Spring's {@link PlatformTransactionManager}
* strategy and optionally provide the other transactional attributes
* (e.g. 'propagationBehaviorName').
*
* @author Mark Fisher
*/
public class MessageChannelTemplate implements InitializingBean {
protected final Log logger = LogFactory.getLog(this.getClass());
private volatile MessageChannel defaultChannel;
private volatile long sendTimeout = -1;
private volatile long receiveTimeout = -1;
private volatile PlatformTransactionManager transactionManager;
private volatile TransactionTemplate transactionTemplate;
private volatile String propagationBehaviorName = "PROPAGATION_REQUIRED";
private volatile String isolationLevelName = "ISOLATION_DEFAULT";
private volatile int transactionTimeout = -1;
private volatile boolean readOnly = false;
private volatile boolean initialized;
private final Object initializationMonitor = new Object();
/**
* Create a MessageChannelTemplate with no default channel. Note, that one
* may be provided by invoking {@link #setDefaultChannel(MessageChannel)}.
*/
public MessageChannelTemplate() {
}
/**
* Create a MessageChannelTemplate with the given default channel.
*/
public MessageChannelTemplate(MessageChannel defaultChannel) {
this.defaultChannel = defaultChannel;
}
/**
* Specify the default MessageChannel to use when invoking the send and/or
* receive methods that do not expect a channel parameter.
*/
public void setDefaultChannel(MessageChannel defaultChannel) {
this.defaultChannel = defaultChannel;
}
/**
* Specify the timeout value to use for send operations.
*
* @param sendTimeout the send timeout in milliseconds
*/
public void setSendTimeout(long sendTimeout) {
this.sendTimeout = sendTimeout;
}
/**
* Specify the timeout value to use for receive operations.
*
* @param receiveTimeout the receive timeout in milliseconds
*/
public void setReceiveTimeout(long receiveTimeout) {
this.receiveTimeout = receiveTimeout;
}
/**
* Specify a transaction manager to use for all exchange operations.
* If none is provided, then the operations will occur without any
* transactional behavior (i.e. there is no default transaction manager).
*/
public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
public void setPropagationBehaviorName(String propagationBehaviorName) {
this.propagationBehaviorName = propagationBehaviorName;
}
public void setIsolationLevelName(String isolationLevelName) {
this.isolationLevelName = isolationLevelName;
}
public void setTransactionTimeout(int transactionTimeout) {
this.transactionTimeout = transactionTimeout;
}
public void setTransactionReadOnly(boolean readOnly) {
this.readOnly = readOnly;
}
private TransactionTemplate getTransactionTemplate() {
if (!this.initialized) {
this.afterPropertiesSet();
}
return this.transactionTemplate;
}
public void afterPropertiesSet() {
synchronized (this.initializationMonitor) {
if (this.initialized) {
return;
}
if (this.transactionManager != null) {
TransactionTemplate template = new TransactionTemplate(this.transactionManager);
template.setPropagationBehaviorName(this.propagationBehaviorName);
template.setIsolationLevelName(this.isolationLevelName);
template.setTimeout(this.transactionTimeout);
template.setReadOnly(this.readOnly);
this.transactionTemplate = template;
}
this.initialized = true;
}
}
public boolean send(final Message<?> message) {
return this.send(message, this.getRequiredDefaultChannel());
}
public boolean send(final Message<?> message, final MessageChannel channel) {
TransactionTemplate txTemplate = this.getTransactionTemplate();
if (txTemplate != null) {
return txTemplate.execute(new TransactionCallback<Boolean>() {
public Boolean doInTransaction(TransactionStatus status) {
return doSend(message, channel);
}
});
}
return this.doSend(message, channel);
}
public Message<?> receive() {
MessageChannel channel = this.getRequiredDefaultChannel();
Assert.state(channel instanceof PollableChannel,
"The 'defaultChannel' must be a PollableChannel for receive operations.");
return this.receive((PollableChannel) channel);
}
public Message<?> receive(final PollableChannel channel) {
TransactionTemplate txTemplate = this.getTransactionTemplate();
if (txTemplate != null) {
return txTemplate.execute(new TransactionCallback<Message<?>>() {
public Message<?> doInTransaction(TransactionStatus status) {
return doReceive(channel);
}
});
}
return this.doReceive(channel);
}
public Message<?> sendAndReceive(final Message<?> request) {
return this.sendAndReceive(request, this.getRequiredDefaultChannel());
}
public Message<?> sendAndReceive(final Message<?> request, final MessageChannel channel) {
TransactionTemplate txTemplate = this.getTransactionTemplate();
if (txTemplate != null) {
return txTemplate.execute(new TransactionCallback<Message<?>>() {
public Message<?> doInTransaction(TransactionStatus status) {
return doSendAndReceive(request, channel);
}
});
}
return this.doSendAndReceive(request, channel);
}
private boolean doSend(Message<?> message, MessageChannel channel) {
Assert.notNull(channel, "channel must not be null");
long timeout = this.sendTimeout;
boolean sent = (timeout >= 0)
? channel.send(message, timeout)
: channel.send(message);
if (!sent && this.logger.isTraceEnabled()) {
this.logger.trace("failed to send message to channel '" + channel + "' within timeout: " + timeout);
}
return sent;
}
private Message<?> doReceive(PollableChannel channel) {
Assert.notNull(channel, "channel must not be null");
long timeout = this.receiveTimeout;
Message<?> message = (timeout >= 0)
? channel.receive(timeout)
: channel.receive();
if (message == null && this.logger.isTraceEnabled()) {
this.logger.trace("failed to receive message from channel '" + channel + "' within timeout: " + timeout);
}
return message;
}
private Message<?> doSendAndReceive(Message<?> request, MessageChannel channel) {
Object originalReplyChannelHeader = request.getHeaders().getReplyChannel();
Object originalErrorChannelHeader = request.getHeaders().getErrorChannel();
TemporaryReplyChannel replyChannel = new TemporaryReplyChannel(this.receiveTimeout);
request = MessageBuilder.fromMessage(request)
.setReplyChannel(replyChannel)
.setErrorChannel(replyChannel)
.build();
if (!this.doSend(request, channel)) {
throw new MessageDeliveryException(request, "failed to send message to channel");
}
Message<?> reply = this.doReceive(replyChannel);
if (reply != null) {
reply = MessageBuilder.fromMessage(reply)
.setHeader(MessageHeaders.REPLY_CHANNEL, originalReplyChannelHeader)
.setHeader(MessageHeaders.ERROR_CHANNEL, originalErrorChannelHeader)
.build();
}
return reply;
}
private MessageChannel getRequiredDefaultChannel() {
Assert.state(this.defaultChannel != null,
"No 'defaultChannel' specified for MessageChannelTemplate. "
+ "Unable to invoke methods without a channel argument.");
return this.defaultChannel;
}
@SuppressWarnings("unchecked")
private static class TemporaryReplyChannel implements PollableChannel {
private volatile Message<?> message;
private final long receiveTimeout;
private final CountDownLatch latch = new CountDownLatch(1);
public TemporaryReplyChannel(long receiveTimeout) {
this.receiveTimeout = receiveTimeout;
}
public String getName() {
return "temporaryReplyChannel";
}
public Message receive() {
return this.receive(-1);
}
public Message receive(long timeout) {
try {
if (this.receiveTimeout < 0) {
this.latch.await();
}
else {
this.latch.await(this.receiveTimeout, TimeUnit.MILLISECONDS);
}
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return this.message;
}
public boolean send(Message<?> message) {
return this.send(message, -1);
}
public boolean send(Message<?> message, long timeout) {
this.message = message;
this.latch.countDown();
return true;
}
public List<Message<?>> clear() {
return null;
}
public List<Message<?>> purge(MessageSelector selector) {
return null;
}
}
}

View File

@@ -0,0 +1,121 @@
/*
* Copyright 2002-2009 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;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.core.MessagingException;
import org.springframework.integration.message.ErrorMessage;
import org.springframework.util.Assert;
import org.springframework.util.ErrorHandler;
/**
* {@link ErrorHandler} implementation that sends an {@link ErrorMessage} to a
* {@link MessageChannel}.
*
* @author Mark Fisher
* @author Iwein Fuld
*/
public class MessagePublishingErrorHandler implements ErrorHandler, BeanFactoryAware {
private final Log logger = LogFactory.getLog(this.getClass());
private volatile ChannelResolver channelResolver;
private volatile MessageChannel defaultErrorChannel;
private volatile long sendTimeout = 1000;
public MessagePublishingErrorHandler() {
}
public MessagePublishingErrorHandler(ChannelResolver channelResolver) {
Assert.notNull(channelResolver, "channelResolver must not be null");
this.channelResolver = channelResolver;
}
public void setDefaultErrorChannel(MessageChannel defaultErrorChannel) {
this.defaultErrorChannel = defaultErrorChannel;
}
public void setSendTimeout(long sendTimeout) {
this.sendTimeout = sendTimeout;
}
public void setBeanFactory(BeanFactory beanFactory) {
Assert.notNull(beanFactory, "beanFactory must not be null");
if (this.channelResolver == null) {
this.channelResolver = new BeanFactoryChannelResolver(beanFactory);
}
}
public final void handleError(Throwable t) {
Message<?> failedMessage = (t instanceof MessagingException) ?
((MessagingException) t).getFailedMessage() : null;
MessageChannel errorChannel = this.resolveErrorChannel(failedMessage);
boolean sent = false;
if (errorChannel != null) {
try {
if (this.sendTimeout >= 0) {
sent = errorChannel.send(new ErrorMessage(t), this.sendTimeout);
}
else {
sent = errorChannel.send(new ErrorMessage(t));
}
}
catch (Throwable errorDeliveryError) { // message will be logged only
if (logger.isWarnEnabled()) {
logger.warn("Error message was not delivered.", errorDeliveryError);
}
}
}
if (!sent && logger.isErrorEnabled()) {
if (failedMessage != null) {
logger.error("failure occurred in messaging task with message: " + failedMessage, t);
}
else {
logger.error("failure occurred in messaging task", t);
}
}
}
private MessageChannel resolveErrorChannel(Message<?> failedMessage) {
if (this.defaultErrorChannel == null && this.channelResolver != null) {
this.defaultErrorChannel = this.channelResolver.resolveChannelName(
IntegrationContextUtils.ERROR_CHANNEL_BEAN_NAME);
}
if (failedMessage == null || failedMessage.getHeaders().getErrorChannel() == null) {
return this.defaultErrorChannel;
}
Object errorChannelHeader = failedMessage.getHeaders().getErrorChannel();
if (errorChannelHeader instanceof MessageChannel) {
return (MessageChannel) errorChannelHeader;
}
Assert.isInstanceOf(String.class, errorChannelHeader,
"Unsupported error channel header type. Expected MessageChannel or String, but actual type is [" +
errorChannelHeader.getClass() + "]");
return this.channelResolver.resolveChannelName((String) errorChannelHeader);
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2002-2010 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;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.integration.core.Message;
/**
* A channel implementation that essentially behaves like "/dev/null".
* All receive() calls will return <em>null</em>, and all send() calls
* will return <em>true</em> although no action is performed.
* Note however that the invocations are logged at debug-level.
*
* @author Mark Fisher
*/
public class NullChannel implements PollableChannel {
private final Log logger = LogFactory.getLog(this.getClass());
public boolean send(Message<?> message) {
if (logger.isDebugEnabled()) {
logger.debug("message sent to null channel: " + message);
}
return true;
}
public boolean send(Message<?> message, long timeout) {
return this.send(message);
}
public Message<?> receive() {
if (logger.isDebugEnabled()) {
logger.debug("receive called on null channel");
}
return null;
}
public Message<?> receive(long timeout) {
return this.receive();
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2002-2010 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;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageChannel;
/**
* Interface for Message Channels from which Messages may be actively received through polling.
*
* @author Mark Fisher
*/
public interface PollableChannel extends MessageChannel {
/**
* Receive a message from this channel, blocking indefinitely if necessary.
*
* @return the next available {@link Message} or <code>null</code> if interrupted
*/
Message<?> receive();
/**
* Receive a message from this channel, blocking until either a message is
* available or the specified timeout period elapses.
*
* @param timeout the timeout in milliseconds
*
* @return the next available {@link Message} or <code>null</code> if the
* specified timeout period elapses or the message reception is interrupted
*/
Message<?> receive(long timeout);
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright 2002-2010 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;
import java.util.Comparator;
import java.util.concurrent.PriorityBlockingQueue;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageHeaders;
import org.springframework.integration.core.MessagePriority;
import org.springframework.integration.util.UpperBound;
/**
* A message channel that prioritizes messages based on a {@link Comparator}.
* The default comparator is based upon the message header's 'priority'.
*
* @author Mark Fisher
*/
public class PriorityChannel extends QueueChannel {
private final UpperBound upperBound;
/**
* Create a channel with the specified queue capacity. If the capacity
* is a non-positive value, the queue will be unbounded. Message priority
* will be determined by the provided {@link Comparator}. If the comparator
* is <code>null</code>, the priority will be based upon the value of
* {@link MessageHeaders#getPriority()}.
*/
public PriorityChannel(int capacity, Comparator<Message<?>> comparator) {
super(new PriorityBlockingQueue<Message<?>>(11,
(comparator != null) ? comparator : new MessagePriorityComparator()));
this.upperBound = new UpperBound(capacity);
}
/**
* Create a channel with the specified queue capacity. Message priority
* will be based upon the value of {@link MessageHeaders#getPriority()}.
*/
public PriorityChannel(int capacity) {
this(capacity, null);
}
/**
* Create a channel with an unbounded queue. Message priority will be
* determined by the provided {@link Comparator}. If the comparator
* is <code>null</code>, the priority will be based upon the value of
* {@link MessageHeaders#getPriority()}.
*/
public PriorityChannel(Comparator<Message<?>> comparator) {
this(0, comparator);
}
/**
* Create a channel with an unbounded queue. Message priority will be
* based on the value of {@link MessageHeaders#getPriority()}.
*/
public PriorityChannel() {
this(0, null);
}
@Override
protected boolean doSend(Message<?> message, long timeout) {
if (!upperBound.tryAcquire(timeout)) {
return false;
}
return super.doSend(message, 0);
}
@Override
protected Message<?> doReceive(long timeout) {
Message<?> message = super.doReceive(timeout);
if (message != null) {
upperBound.release();
return message;
}
return null;
}
private static class MessagePriorityComparator implements Comparator<Message<?>> {
public int compare(Message<?> message1, Message<?> message2) {
MessagePriority priority1 = message1.getHeaders().getPriority();
MessagePriority priority2 = message2.getHeaders().getPriority();
priority1 = priority1 != null ? priority1 : MessagePriority.NORMAL;
priority2 = priority2 != null ? priority2 : MessagePriority.NORMAL;
return priority1.compareTo(priority2);
}
}
}

View File

@@ -0,0 +1,126 @@
/*
* Copyright 2002-2010 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;
import java.util.concurrent.Executor;
import org.springframework.integration.dispatcher.BroadcastingDispatcher;
import org.springframework.integration.util.ErrorHandlingTaskExecutor;
import org.springframework.util.ErrorHandler;
/**
* A channel that sends Messages to each of its subscribers.
*
* @author Mark Fisher
*/
public class PublishSubscribeChannel extends AbstractSubscribableChannel {
private volatile BroadcastingDispatcher dispatcher;
private volatile Executor executor;
private volatile ErrorHandler errorHandler;
private volatile boolean ignoreFailures;
private volatile boolean applySequence;
/**
* Create a PublishSubscribeChannel that will use an {@link Executor}
* to invoke the handlers. If this is null, each invocation will occur in
* the message sender's thread.
*/
public PublishSubscribeChannel(Executor executor) {
this.executor = executor;
this.dispatcher = new BroadcastingDispatcher(executor);
}
/**
* Create a PublishSubscribeChannel that will invoke the handlers in the
* message sender's thread.
*/
public PublishSubscribeChannel() {
this(null);
}
/**
* Provide an {@link ErrorHandler} strategy for handling Exceptions that
* occur downstream from this channel. This will <i>only</i> be applied if
* an Executor has been configured to dispatch the Messages for this
* channel. Otherwise, Exceptions will be thrown directly within the
* sending Thread. If no ErrorHandler is provided, and this channel does
* delegate its dispatching to an Executor, the default strategy is
* a {@link MessagePublishingErrorHandler} that sends error messages to
* the failed request Message's error channel header if available or to
* the default 'errorChannel' otherwise.
* @see #PublishSubscribeChannel(Executor)
*/
public void setErrorHandler(ErrorHandler errorHandler) {
this.errorHandler = errorHandler;
}
/**
* Specify whether failures for one or more of the handlers should be
* ignored. By default this is <code>false</code> meaning that an Exception
* will be thrown whenever a handler fails. To override this and suppress
* Exceptions, set the value to <code>true</code>.
*/
public void setIgnoreFailures(boolean ignoreFailures) {
this.ignoreFailures = ignoreFailures;
this.getDispatcher().setIgnoreFailures(ignoreFailures);
}
/**
* Specify whether to apply the sequence number and size headers to the
* messages prior to invoking the subscribed handlers. By default, this
* value is <code>false</code> meaning that sequence headers will
* <em>not</em> be applied. If planning to use an Aggregator downstream
* with the default correlation and completion strategies, you should set
* this flag to <code>true</code>.
*/
public void setApplySequence(boolean applySequence) {
this.applySequence = applySequence;
this.getDispatcher().setApplySequence(applySequence);
}
/**
* Callback method for initialization.
*/
@Override
public final void onInit() {
if (this.executor != null) {
if (!(this.executor instanceof ErrorHandlingTaskExecutor)) {
if (this.errorHandler == null) {
this.errorHandler = new MessagePublishingErrorHandler(
new BeanFactoryChannelResolver(this.getBeanFactory()));
}
this.executor = new ErrorHandlingTaskExecutor(this.executor, this.errorHandler);
}
this.dispatcher = new BroadcastingDispatcher(this.executor);
this.dispatcher.setIgnoreFailures(this.ignoreFailures);
this.dispatcher.setApplySequence(this.applySequence);
}
}
@Override
protected BroadcastingDispatcher getDispatcher() {
return this.dispatcher;
}
}

View File

@@ -0,0 +1,139 @@
/*
* Copyright 2002-2010 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;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import org.springframework.integration.core.Message;
import org.springframework.integration.selector.MessageSelector;
import org.springframework.util.Assert;
/**
* Simple implementation of a message channel. Each {@link Message} is placed in
* a {@link BlockingQueue} whose capacity may be specified upon construction.
* The capacity must be a positive integer value. For a zero-capacity version
* based upon a {@link java.util.concurrent.SynchronousQueue}, consider the
* {@link RendezvousChannel}.
*
* @author Mark Fisher
*/
public class QueueChannel extends AbstractPollableChannel {
private final BlockingQueue<Message<?>> queue;
/**
* Create a channel with the specified queue.
*/
public QueueChannel(BlockingQueue<Message<?>> queue) {
Assert.notNull(queue, "'queue' must not be null");
this.queue = queue;
}
/**
* Create a channel with the specified queue capacity.
*/
public QueueChannel(int capacity) {
Assert.isTrue(capacity > 0, "The capacity must be a positive integer. " +
"For a zero-capacity alternative, consider using a 'RendezvousChannel'.");
this.queue = new LinkedBlockingQueue<Message<?>>(capacity);
}
/**
* Create a channel with "unbounded" queue capacity. The actual capacity value is
* {@link Integer#MAX_VALUE}. Note that a bounded queue is recommended, since an
* unbounded queue may lead to OutOfMemoryErrors.
*/
public QueueChannel() {
this(new LinkedBlockingQueue<Message<?>>());
}
protected boolean doSend(Message<?> message, long timeout) {
Assert.notNull(message, "'message' must not be null");
try {
if (timeout > 0) {
return this.queue.offer(message, timeout, TimeUnit.MILLISECONDS);
}
if (timeout == 0) {
return this.queue.offer(message);
}
queue.put(message);
return true;
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
protected Message<?> doReceive(long timeout) {
try {
if (timeout > 0) {
return queue.poll(timeout, TimeUnit.MILLISECONDS);
}
if (timeout == 0) {
return queue.poll();
}
return queue.take();
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
}
}
/**
* Remove all {@link Message Messages} from this channel.
*/
public List<Message<?>> clear() {
List<Message<?>> clearedMessages = new ArrayList<Message<?>>();
this.queue.drainTo(clearedMessages);
return clearedMessages;
}
/**
* Remove any {@link Message Messages} that are not accepted by the provided selector.
*/
public List<Message<?>> purge(MessageSelector selector) {
if (selector == null) {
return this.clear();
}
List<Message<?>> purgedMessages = new ArrayList<Message<?>>();
Object[] array = this.queue.toArray();
for (Object o : array) {
Message<?> message = (Message<?>) o;
if (!selector.accept(message) && this.queue.remove(message)) {
purgedMessages.add(message);
}
}
return purgedMessages;
}
public int getQueueSize() {
return this.queue.size();
}
public int getRemainingCapacity() {
return this.queue.remainingCapacity();
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.channel;
import java.util.concurrent.SynchronousQueue;
import org.springframework.integration.core.Message;
/**
* A zero-capacity version of {@link QueueChannel} that delegates to a
* {@link SynchronousQueue} internally. This accommodates "handoff" scenarios
* (i.e. blocking while waiting for another party to send or receive).
*
* @author Mark Fisher
*/
public class RendezvousChannel extends QueueChannel {
public RendezvousChannel() {
super(new SynchronousQueue<Message<?>>());
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2002-2010 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;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.message.MessageHandler;
/**
* Interface for any MessageChannel implementation that accepts subscribers.
* The subscribers must implement the {@link MessageHandler} interface and
* will be invoked when a Message is available.
*
* @author Mark Fisher
*/
public interface SubscribableChannel extends MessageChannel {
/**
* Register a {@link MessageHandler} as a subscriber to this channel.
*/
boolean subscribe(MessageHandler handler);
/**
* Remove a {@link MessageHandler} from the subscribers of this channel.
*/
boolean unsubscribe(MessageHandler handler);
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2002-2010 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;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
import org.springframework.integration.core.Message;
/**
* A channel implementation that stores messages in a thread-bound queue. In
* other words, send() will put a message at the tail of the queue for the
* current thread, and receive() will retrieve a message from the head of the
* queue. Since, by definition, only one thread will interact with the queue
* at a time, the timeout values on send and receive have no effect. If there
* are no Messages in the queue, the receive operations will return a
* <code>null</code> value immediately, regardless of any timeout value.
*
* @author Dave Syer
* @author Mark Fisher
*/
public class ThreadLocalChannel extends AbstractPollableChannel {
private final ThreadLocalMessageHolder messageHolder = new ThreadLocalMessageHolder();
@Override
protected boolean doSend(Message<?> message, long timeout) {
if (message == null) {
return false;
}
return messageHolder.get().add(message);
}
@Override
protected Message<?> doReceive(long timeout) {
return messageHolder.get().poll();
}
/**
* The thread-bound Queue.
*/
private static class ThreadLocalMessageHolder extends ThreadLocal<Queue<Message<?>>> {
@Override
protected Queue<Message<?>> initialValue() {
return new LinkedBlockingQueue<Message<?>>();
}
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.channel.interceptor;
import org.springframework.integration.channel.ChannelInterceptor;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageChannel;
/**
* A {@link ChannelInterceptor} with no-op method implementations so that
* subclasses do not have to implement all of the interface's methods.
*
* @author Mark Fisher
*/
public class ChannelInterceptorAdapter implements ChannelInterceptor {
public Message<?> preSend(Message<?> message, MessageChannel channel) {
return message;
}
public void postSend(Message<?> message, MessageChannel channel, boolean sent) {
}
public boolean preReceive(MessageChannel channel) {
return true;
}
public Message<?> postReceive(Message<?> message, MessageChannel channel) {
return message;
}
}

View File

@@ -0,0 +1,212 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.channel.interceptor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.OrderComparator;
import org.springframework.integration.channel.AbstractMessageChannel;
import org.springframework.integration.channel.ChannelInterceptor;
import org.springframework.util.CollectionUtils;
/**
* Will apply global interceptors to channels (<channel-interceptor-chain>). Since global interceptors
* could be Ordered or un-Ordered they will be sorted before merged with other interceptors in the channel.
* Sorting will only be done within the given interceptor chain which itself defines 'order' attribute
* essentially creating a group of ordered interceptors which are ordered internally and then these chain
* groups are also ordered. For example:
* <pre>
* channel-interceptor-chain channel-name-pattern="foo" order="5" - positive order value means AFTER local channel interceptors
* Ordered-global interceptor (4)
* Ordered-global interceptor (1)
* channel-interceptor-chain
* channel-interceptor-chain channel-name-pattern="foo" order="-1" - negative order value means AFTER local channel interceptors
* Ordered-global interceptor (3)
* Ordered-global interceptor (10)
* channel-interceptor-chain
*
* channel id="foo"
* Ordered-in-channel interceptor (1)
* channel
*
* will result in channel with the following interceptors
* Channel "foo"
* Ordered-global interceptor (3)
* Ordered-global interceptor (10)
* Ordered-in-channel interceptor (1)
* Ordered-global interceptor (1)
* Ordered-global interceptor (4)
* </pre>
*
* @author Oleg Zhurakousky
* @since 2.0
*/
final class GlobalChannelInterceptorBeanPostProcessor implements BeanPostProcessor, InitializingBean{
private final static Log logger = LogFactory.getLog(GlobalChannelInterceptorBeanPostProcessor.class);
private final OrderComparator comparator = new OrderComparator();
private List<String> allAvailablePatters;
private List<GlobalChannelInterceptorChain> globalInterceptors;
private final Map<String, Pattern> compiledPatterns = new HashMap<String, Pattern>();
private List<GlobalChannelInterceptorChain> positiveOrderChains = new ArrayList<GlobalChannelInterceptorChain>();
private List<GlobalChannelInterceptorChain> negativeOrderChains = new ArrayList<GlobalChannelInterceptorChain>();
/**
*
* @param globalInterceptors
*/
GlobalChannelInterceptorBeanPostProcessor(List<GlobalChannelInterceptorChain> globalInterceptors){
this.globalInterceptors = globalInterceptors;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization(java.lang.Object, java.lang.String)
*/
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization(java.lang.Object, java.lang.String)
*/
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if (channelPatternMatches(beanName)){
if (bean instanceof AbstractMessageChannel){
logger.debug("Applying global interceptors on channel '" + beanName + "'");
this.mergeInterceptorsToChannel((AbstractMessageChannel) bean, beanName);
} else {
logger.warn("Attempt to add channel interceptors is unsuccessfull. Global channel interceptors " +
"can only be added to AbstractMessageChannel. Current implementation is: " + bean.getClass() +
" This might happen becouse you specified a single wild-card '*' in 'channel-name-pattern'");
}
}
return bean;
}
/**
*
* @param channel
* @param channelName
*/
private void mergeInterceptorsToChannel(AbstractMessageChannel channel, String channelName){
List<ChannelInterceptor> tInt = null;
List<ChannelInterceptor> interceptors = this.getExistingInterceptors(channel);
// POSITIVE
List<GlobalChannelInterceptorChain> tempPositiveInterceptorChains = new ArrayList<GlobalChannelInterceptorChain>();
for (GlobalChannelInterceptorChain positiveOrderChain : positiveOrderChains) {
if (channelPatternMatches(channelName, positiveOrderChain.getPatterns())){
tempPositiveInterceptorChains.add(positiveOrderChain);
}
}
// sort chain
Collections.sort(tempPositiveInterceptorChains, comparator);
for (GlobalChannelInterceptorChain globalChannelInterceptorChain : tempPositiveInterceptorChains) {
tInt = globalChannelInterceptorChain.getInterceptors();
// sort within the chain
Collections.sort(tInt, comparator);
interceptors.addAll(tInt);
}
// NEGATIVE
List<GlobalChannelInterceptorChain> tempNegativeInterceptorChains = new ArrayList<GlobalChannelInterceptorChain>();
for (GlobalChannelInterceptorChain negativeOrderChain : negativeOrderChains) {
if (channelPatternMatches(channelName, negativeOrderChain.getPatterns())){
tempNegativeInterceptorChains.add(negativeOrderChain);
}
}
// sort chains
Collections.sort(tempNegativeInterceptorChains, comparator);
for (GlobalChannelInterceptorChain globalChannelInterceptorChain : tempNegativeInterceptorChains) {
tInt = globalChannelInterceptorChain.getInterceptors();
// sort within the chain
Collections.sort(tInt, comparator);
interceptors.addAll(0, tInt);
}
}
/*
*
*/
private void filterPositiveNegativeOrderChains(){
for (GlobalChannelInterceptorChain globalInterceptorChain : globalInterceptors) {
if (globalInterceptorChain.getOrder() < 0){
negativeOrderChains.add(globalInterceptorChain);
} else {
positiveOrderChains.add(globalInterceptorChain);
}
}
}
/*
*
*/
@SuppressWarnings("unchecked")
private List<ChannelInterceptor> getExistingInterceptors(AbstractMessageChannel channel){
DirectFieldAccessor channelAccessor = new DirectFieldAccessor(channel);
Object iWrapper = channelAccessor.getPropertyValue("interceptors");
DirectFieldAccessor iWrapperAccessor = new DirectFieldAccessor(iWrapper);
List<ChannelInterceptor> interceptors = (List<ChannelInterceptor>) iWrapperAccessor.getPropertyValue("interceptors");
return interceptors;
}
/*
*
*/
private boolean channelPatternMatches(String beanName, String... patternsToMatch){
String[] patterns = null;
if (patternsToMatch.length > 0){
patterns = patternsToMatch;
} else {
patterns = allAvailablePatters.toArray(new String[]{});
}
for (String channelPattern : patterns) {
channelPattern = channelPattern.trim();
if (channelPattern.trim().equals("*")){
return true;
}
Pattern p = compiledPatterns.get(channelPattern);
if (p == null){
p = Pattern.compile(channelPattern);
compiledPatterns.put(channelPattern, p);
}
Matcher m = p.matcher(beanName);
if (m.find()){
return true;
}
}
return false;
}
@SuppressWarnings("unchecked")
public void afterPropertiesSet() throws Exception {
allAvailablePatters = new ArrayList<String>();
for (GlobalChannelInterceptorChain globalInterceptorchain : globalInterceptors) {
allAvailablePatters.addAll(CollectionUtils.arrayToList(globalInterceptorchain.getPatterns()));
}
this.filterPositiveNegativeOrderChains();
logger.info("Initialized: '" + this.getClass().getSimpleName() + "' to apply global channel interceptors");
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.channel.interceptor;
import java.util.List;
import org.springframework.core.Ordered;
import org.springframework.integration.channel.ChannelInterceptor;
/**
* @author Oleg Zhurakousky
* @since 2.0
*/
final class GlobalChannelInterceptorChain implements Ordered{
private List<ChannelInterceptor> interceptors;
private String[] patterns;
private int order;
public GlobalChannelInterceptorChain(List<ChannelInterceptor> interceptors, String[] patterns, int order){
this.interceptors = interceptors;
this.patterns = patterns;
this.order = order;
}
List<ChannelInterceptor> getInterceptors(){
return interceptors;
}
String[] getPatterns() {
return patterns;
}
public String toString(){
return interceptors.toString();
}
public int getOrder() {
return order;
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.channel.interceptor;
import java.util.Arrays;
import java.util.List;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.message.MessageDeliveryException;
import org.springframework.integration.selector.MessageSelector;
/**
* A {@link org.springframework.integration.channel.ChannelInterceptor} that
* delegates to a list of {@link MessageSelector MessageSelectors} to decide
* whether a {@link Message} should be accepted on the {@link MessageChannel}.
*
* @author Mark Fisher
*/
public class MessageSelectingInterceptor extends ChannelInterceptorAdapter {
private final List<MessageSelector> selectors;
public MessageSelectingInterceptor(MessageSelector... selectors) {
this.selectors = Arrays.asList(selectors);
}
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
for (MessageSelector selector : this.selectors) {
if (!selector.accept(message)) {
throw new MessageDeliveryException(message,
"selector '" + selector + "' did not accept message");
}
}
return message;
}
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright 2002-2010 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.interceptor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.Lifecycle;
import org.springframework.integration.channel.ChannelInterceptor;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.selector.MessageSelector;
import org.springframework.util.Assert;
/**
* A {@link ChannelInterceptor} that publishes a copy of the intercepted message
* to a secondary target while still sending the original message to the main channel.
*
* @author Mark Fisher
*/
public class WireTap extends ChannelInterceptorAdapter implements Lifecycle {
private static final Log logger = LogFactory.getLog(WireTap.class);
private final MessageChannel channel;
private volatile long timeout = 0;
private final MessageSelector selector;
private volatile boolean running = true;
/**
* Create a new wire tap with <em>no</em> {@link MessageSelector}.
*
* @param channel the MessageChannel to which intercepted messages will be sent
*/
public WireTap(MessageChannel channel) {
this(channel, null);
}
/**
* Create a new wire tap with the provided {@link MessageSelector}.
*
* @param channel the channel to which intercepted messages will be sent
* @param selector the selector that must accept a message for it to be
* sent to the intercepting channel
*/
public WireTap(MessageChannel channel, MessageSelector selector) {
Assert.notNull(channel, "channel must not be null");
this.channel = channel;
this.selector = selector;
}
/**
* Specify the timeout value for sending to the intercepting target.
*
* @param timeout the timeout in milliseconds
*/
public void setTimeout(long timeout) {
this.timeout = timeout;
}
/**
* Check whether the wire tap is currently running.
*/
public boolean isRunning() {
return this.running;
}
/**
* Restart the wire tap if it has been stopped. It is running by default.
*/
public void start() {
this.running = true;
}
/**
* Stop the wire tap. To restart, invoke {@link #start()}.
*/
public void stop() {
this.running = false;
}
/**
* Intercept the Message and, <em>if accepted</em> by the {@link MessageSelector},
* send it to the secondary target. If this wire tap's {@link MessageSelector} is
* <code>null</code>, it will accept all messages.
*/
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
if (this.running && (this.selector == null || this.selector.accept(message))) {
boolean sent = (this.timeout >= 0)
? this.channel.send(message, this.timeout)
: this.channel.send(message);
if (!sent && logger.isWarnEnabled()) {
logger.warn("failed to send message to WireTap channel '" + this.channel + "'");
}
}
return message;
}
}

View File

@@ -0,0 +1,160 @@
/*
* Copyright 2002-2010 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.config;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.handler.AbstractMessageHandler;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
import org.springframework.integration.message.MessageHandler;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Base class for FactoryBeans that create MessageHandler instances.
*
* @author Mark Fisher
* @author Alexander Peters
*/
abstract class AbstractMessageHandlerFactoryBean implements FactoryBean<MessageHandler>, BeanFactoryAware {
private volatile MessageHandler handler;
private volatile Object targetObject;
private volatile String targetMethodName;
private volatile String expression;
private volatile MessageChannel outputChannel;
private volatile Integer order;
private volatile boolean initialized;
private final Object initializationMonitor = new Object();
private BeanFactory beanFactory;
public void setTargetObject(Object targetObject) {
this.targetObject = targetObject;
}
public void setTargetMethodName(String targetMethodName) {
this.targetMethodName = targetMethodName;
}
public void setExpression(String expression) {
this.expression = expression;
}
public void setOutputChannel(MessageChannel outputChannel) {
this.outputChannel = outputChannel;
}
public void setOrder(Integer order) {
this.order = order;
}
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
public MessageHandler getObject() throws Exception {
if (this.handler == null) {
this.initializeHandler();
Assert.notNull(this.handler, "failed to create MessageHandler");
if (this.handler instanceof AbstractReplyProducingMessageHandler && this.outputChannel != null) {
((AbstractReplyProducingMessageHandler) this.handler).setOutputChannel(this.outputChannel);
}
if (this.handler instanceof BeanFactoryAware) {
((BeanFactoryAware) this.handler).setBeanFactory(beanFactory);
}
if (this.handler instanceof AbstractMessageHandler && this.order != null) {
((AbstractMessageHandler) this.handler).setOrder(this.order.intValue());
}
}
return this.handler;
}
public Class<? extends MessageHandler> getObjectType() {
if (this.handler != null) {
return this.handler.getClass();
}
return MessageHandler.class;
}
public boolean isSingleton() {
return true;
}
private void initializeHandler() {
synchronized (this.initializationMonitor) {
if (this.initialized) {
return;
}
if (this.targetObject == null) {
Assert.isTrue(!StringUtils.hasText(this.targetMethodName),
"The target method is only allowed when a target object (ref or inner bean) is also provided.");
}
if (this.targetObject != null) {
Assert.state(this.expression == null,
"The 'targetObject' and 'expression' properties are mutually exclusive.");
this.handler = this.createMethodInvokingHandler(this.targetObject, this.targetMethodName);
}
else if (this.expression != null) {
this.handler = this.createExpressionEvaluatingHandler(this.expression);
}
else {
this.handler = this.createDefaultHandler();
}
if (this.handler instanceof BeanFactoryAware) {
((BeanFactoryAware) this.handler).setBeanFactory(beanFactory);
}
this.initialized = true;
}
if (this.handler instanceof InitializingBean) {
try {
((InitializingBean) this.handler).afterPropertiesSet();
}
catch (Exception e) {
throw new BeanInitializationException("failed to initialize MessageHandler", e);
}
}
}
/**
* Subclasses must implement this method to create the MessageHandler.
*/
abstract MessageHandler createMethodInvokingHandler(Object targetObject, String targetMethodName);
MessageHandler createExpressionEvaluatingHandler(String expression) {
throw new UnsupportedOperationException(this.getClass().getName() + " does not support expressions.");
}
MessageHandler createDefaultHandler() {
throw new IllegalArgumentException(
"Exactly one of the 'targetObject' or 'expression' property is required.");
}
}

View File

@@ -0,0 +1,195 @@
/*
* Copyright 2002-2009 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.config;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.SmartLifecycle;
import org.springframework.integration.channel.PollableChannel;
import org.springframework.integration.channel.SubscribableChannel;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.endpoint.AbstractEndpoint;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.endpoint.PollingConsumer;
import org.springframework.integration.message.MessageHandler;
import org.springframework.integration.scheduling.PollerMetadata;
import org.springframework.util.Assert;
/**
* @author Mark Fisher
*/
public class ConsumerEndpointFactoryBean
implements FactoryBean, BeanFactoryAware, BeanNameAware, InitializingBean, SmartLifecycle {
private volatile MessageHandler handler;
private volatile String beanName;
private volatile String inputChannelName;
private volatile PollerMetadata pollerMetadata;
private volatile boolean autoStartup = true;
private volatile ConfigurableBeanFactory beanFactory;
private volatile AbstractEndpoint endpoint;
private volatile boolean initialized;
private final Object initializationMonitor = new Object();
private final Object handlerMonitor = new Object();
public void setHandler(MessageHandler handler) {
Assert.notNull(handler, "handler must not be null");
synchronized (this.handlerMonitor) {
Assert.isNull(this.handler, "handler cannot be overridden");
this.handler = handler;
}
}
public void setInputChannelName(String inputChannelName) {
this.inputChannelName = inputChannelName;
}
public void setPollerMetadata(PollerMetadata pollerMetadata) {
this.pollerMetadata = pollerMetadata;
}
public void setAutoStartup(boolean autoStartup) {
this.autoStartup = autoStartup;
}
public void setBeanName(String beanName) {
this.beanName = beanName;
}
public void setBeanFactory(BeanFactory beanFactory) {
Assert.isInstanceOf(ConfigurableBeanFactory.class, beanFactory,
"a ConfigurableBeanFactory is required");
this.beanFactory = (ConfigurableBeanFactory) beanFactory;
}
public void afterPropertiesSet() throws Exception {
this.initializeEndpoint();
}
public boolean isSingleton() {
return true;
}
public Object getObject() throws Exception {
if (!this.initialized) {
this.initializeEndpoint();
}
return this.endpoint;
}
public Class<?> getObjectType() {
if (this.endpoint == null) {
return AbstractEndpoint.class;
}
return this.endpoint.getClass();
}
private void initializeEndpoint() throws Exception {
synchronized (this.initializationMonitor) {
if (this.initialized) {
return;
}
Assert.hasText(this.inputChannelName, "inputChannelName is required");
Assert.isTrue(this.beanFactory.containsBean(this.inputChannelName),
"no such input channel '" + this.inputChannelName + "' for endpoint '" + this.beanName + "'");
MessageChannel channel = (MessageChannel)
this.beanFactory.getBean(this.inputChannelName, MessageChannel.class);
if (channel instanceof SubscribableChannel) {
Assert.isNull(this.pollerMetadata, "A poller should not be specified for endpoint '" + this.beanName
+ "', since '" + this.inputChannelName + "' is a SubscribableChannel (not pollable).");
this.endpoint = new EventDrivenConsumer((SubscribableChannel) channel, this.handler);
}
else if (channel instanceof PollableChannel) {
PollingConsumer pollingConsumer = new PollingConsumer(
(PollableChannel) channel, this.handler);
if (this.pollerMetadata == null) {
this.pollerMetadata = IntegrationContextUtils.getDefaultPollerMetadata(this.beanFactory);
Assert.notNull(this.pollerMetadata, "No poller has been defined for endpoint '"
+ this.beanName + "', and no default poller is available within the context.");
}
pollingConsumer.setTrigger(this.pollerMetadata.getTrigger());
pollingConsumer.setMaxMessagesPerPoll(this.pollerMetadata.getMaxMessagesPerPoll());
pollingConsumer.setReceiveTimeout(this.pollerMetadata.getReceiveTimeout());
pollingConsumer.setTaskExecutor(this.pollerMetadata.getTaskExecutor());
pollingConsumer.setTransactionManager(this.pollerMetadata.getTransactionManager());
pollingConsumer.setTransactionDefinition(this.pollerMetadata.getTransactionDefinition());
pollingConsumer.setAdviceChain(this.pollerMetadata.getAdviceChain());
this.endpoint = pollingConsumer;
}
else {
throw new IllegalArgumentException(
"unsupported channel type: [" + channel.getClass() + "]");
}
this.endpoint.setBeanName(this.beanName);
this.endpoint.setBeanFactory(this.beanFactory);
this.endpoint.setAutoStartup(this.autoStartup);
this.endpoint.afterPropertiesSet();
this.initialized = true;
}
}
/*
* SmartLifecycle implementation (delegates to the created endpoint)
*/
public boolean isAutoStartup() {
return (this.endpoint != null) ? this.endpoint.isAutoStartup() : true;
}
public int getPhase() {
return (this.endpoint != null) ? this.endpoint.getPhase() : 0;
}
public boolean isRunning() {
return (this.endpoint != null) ? this.endpoint.isRunning() : false;
}
public void start() {
if (this.endpoint != null) {
this.endpoint.start();
}
}
public void stop() {
if (this.endpoint != null) {
this.endpoint.stop();
}
}
public void stop(Runnable callback) {
if (this.endpoint != null) {
this.endpoint.stop(callback);
}
}
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright 2002-2010 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.config;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.filter.ExpressionEvaluatingSelector;
import org.springframework.integration.filter.MessageFilter;
import org.springframework.integration.filter.MethodInvokingSelector;
import org.springframework.integration.message.MessageHandler;
import org.springframework.integration.selector.MessageSelector;
import org.springframework.util.StringUtils;
/**
* Factory bean for creating a Message Filter.
*
* @author Mark Fisher
* @since 2.0
*/
public class FilterFactoryBean extends AbstractMessageHandlerFactoryBean {
private volatile MessageChannel discardChannel;
private volatile Boolean throwExceptionOnRejection;
private volatile Long sendTimeout;
public void setDiscardChannel(MessageChannel discardChannel) {
this.discardChannel = discardChannel;
}
public void setThrowExceptionOnRejection(Boolean throwExceptionOnRejection) {
this.throwExceptionOnRejection = throwExceptionOnRejection;
}
public void setSendTimeout(Long sendTimeout) {
this.sendTimeout = sendTimeout;
}
@Override
MessageHandler createMethodInvokingHandler(Object targetObject, String targetMethodName) {
MessageSelector selector = null;
if (targetObject instanceof MessageSelector) {
selector = (MessageSelector) targetObject;
}
else if (StringUtils.hasText(targetMethodName)) {
selector = new MethodInvokingSelector(targetObject, targetMethodName);
}
else {
selector = new MethodInvokingSelector(targetObject);
}
return this.createFilter(selector);
}
@Override
MessageHandler createExpressionEvaluatingHandler(String expression) {
return this.createFilter(new ExpressionEvaluatingSelector(expression));
}
private MessageFilter createFilter(MessageSelector selector) {
MessageFilter filter = new MessageFilter(selector);
if (this.throwExceptionOnRejection != null) {
filter.setThrowExceptionOnRejection(this.throwExceptionOnRejection);
}
if (this.discardChannel != null) {
filter.setDiscardChannel(discardChannel);
}
if (this.sendTimeout != null) {
filter.setSendTimeout(this.sendTimeout.longValue());
}
return filter;
}
}

View File

@@ -0,0 +1,134 @@
/*
* Copyright 2002-2010 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.config;
import org.springframework.integration.channel.ChannelResolver;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.message.MessageHandler;
import org.springframework.integration.router.AbstractChannelNameResolvingMessageRouter;
import org.springframework.integration.router.AbstractMessageRouter;
import org.springframework.integration.router.ExpressionEvaluatingRouter;
import org.springframework.integration.router.MethodInvokingRouter;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Factory bean for creating a Message Router.
*
* @author Mark Fisher
* @author Jonas Partner
*/
public class RouterFactoryBean extends AbstractMessageHandlerFactoryBean {
private volatile ChannelResolver channelResolver;
private volatile MessageChannel defaultOutputChannel;
private volatile Long timeout;
private volatile Boolean resolutionRequired;
private volatile Boolean ignoreChannelNameResolutionFailures;
private volatile Boolean applySequence;
private volatile Boolean ignoreSendFailures;
public void setChannelResolver(ChannelResolver channelResolver) {
this.channelResolver = channelResolver;
}
public void setDefaultOutputChannel(MessageChannel defaultOutputChannel) {
this.defaultOutputChannel = defaultOutputChannel;
}
public void setTimeout(Long timeout) {
this.timeout = timeout;
}
public void setResolutionRequired(Boolean resolutionRequired) {
this.resolutionRequired = resolutionRequired;
}
public void setIgnoreChannelNameResolutionFailures(Boolean ignoreChannelNameResolutionFailures) {
this.ignoreChannelNameResolutionFailures = ignoreChannelNameResolutionFailures;
}
public void setApplySequence(Boolean applySequence) {
this.applySequence = applySequence;
}
public void setIgnoreSendFailures(Boolean ignoreSendFailures) {
this.ignoreSendFailures = ignoreSendFailures;
}
@Override
MessageHandler createMethodInvokingHandler(Object targetObject, String targetMethodName) {
Assert.notNull(targetObject, "target object must not be null");
AbstractMessageRouter router = this.createRouter(targetObject, targetMethodName);
return this.configureRouter(router);
}
@Override
MessageHandler createExpressionEvaluatingHandler(String expression) {
return this.configureRouter(new ExpressionEvaluatingRouter(expression));
}
private AbstractMessageRouter createRouter(Object targetObject, String targetMethodName) {
if (targetObject instanceof AbstractMessageRouter) {
Assert.isTrue(!StringUtils.hasText(targetMethodName),
"target method should not be provided when the target " +
"object is an implementation of AbstractMessageRouter");
return (AbstractMessageRouter) targetObject;
}
MethodInvokingRouter router = (StringUtils.hasText(targetMethodName))
? new MethodInvokingRouter(targetObject, targetMethodName)
: new MethodInvokingRouter(targetObject);
return router;
}
private AbstractMessageRouter configureRouter(AbstractMessageRouter router) {
if (this.channelResolver != null &&
router instanceof AbstractChannelNameResolvingMessageRouter) {
((AbstractChannelNameResolvingMessageRouter) router).setChannelResolver(this.channelResolver);
}
if (this.defaultOutputChannel != null) {
router.setDefaultOutputChannel(this.defaultOutputChannel);
}
if (this.timeout != null) {
router.setTimeout(timeout.longValue());
}
if (this.ignoreChannelNameResolutionFailures != null) {
Assert.isTrue(router instanceof AbstractChannelNameResolvingMessageRouter,
"The 'ignoreChannelNameResolutionFailures' property can only be set on routers that extend "
+ AbstractChannelNameResolvingMessageRouter.class.getName());
((AbstractChannelNameResolvingMessageRouter) router).setIgnoreChannelNameResolutionFailures(ignoreChannelNameResolutionFailures);
}
if (this.applySequence != null) {
router.setApplySequence(this.applySequence);
}
if (this.ignoreSendFailures != null) {
router.setIgnoreSendFailures(this.ignoreSendFailures);
}
if (this.resolutionRequired != null) {
router.setResolutionRequired(this.resolutionRequired);
}
return router;
}
}

View File

@@ -0,0 +1,177 @@
/*
* Copyright 2002-2009 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.config;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.SmartLifecycle;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
import org.springframework.integration.message.MessageSource;
import org.springframework.integration.scheduling.PollerMetadata;
import org.springframework.util.Assert;
/**
* FactoryBean for creating a SourcePollingChannelAdapter instance.
*
* @author Mark Fisher
*/
public class SourcePollingChannelAdapterFactoryBean implements FactoryBean, BeanFactoryAware, BeanNameAware,
BeanClassLoaderAware, InitializingBean, SmartLifecycle {
private volatile MessageSource<?> source;
private volatile MessageChannel outputChannel;
private volatile PollerMetadata pollerMetadata;
private volatile boolean autoStartup = true;
private volatile String beanName;
private volatile ConfigurableBeanFactory beanFactory;
private volatile ClassLoader beanClassLoader;
private volatile SourcePollingChannelAdapter adapter;
private volatile boolean initialized;
private final Object initializationMonitor = new Object();
public void setSource(MessageSource<?> source) {
this.source = source;
}
public void setOutputChannel(MessageChannel outputChannel) {
this.outputChannel = outputChannel;
}
public void setPollerMetadata(PollerMetadata pollerMetadata) {
this.pollerMetadata = pollerMetadata;
}
public void setAutoStartup(boolean autoStartup) {
this.autoStartup = autoStartup;
}
public void setBeanFactory(BeanFactory beanFactory) {
Assert.isInstanceOf(ConfigurableBeanFactory.class, beanFactory,
"a ConfigurableBeanFactory is required");
this.beanFactory = (ConfigurableBeanFactory) beanFactory;
}
public void setBeanClassLoader(ClassLoader classLoader) {
this.beanClassLoader = classLoader;
}
public void setBeanName(String beanName) {
this.beanName = beanName;
}
public void afterPropertiesSet() throws Exception {
this.initializeAdapter();
}
public Object getObject() throws Exception {
if (this.adapter == null) {
this.initializeAdapter();
}
return this.adapter;
}
public Class<?> getObjectType() {
return SourcePollingChannelAdapter.class;
}
public boolean isSingleton() {
return true;
}
private void initializeAdapter() {
synchronized (this.initializationMonitor) {
if (this.initialized) {
return;
}
Assert.notNull(this.source, "source is required");
Assert.notNull(this.outputChannel, "outputChannel is required");
SourcePollingChannelAdapter spca = new SourcePollingChannelAdapter();
spca.setSource(this.source);
spca.setOutputChannel(this.outputChannel);
if (this.pollerMetadata == null) {
this.pollerMetadata = IntegrationContextUtils.getDefaultPollerMetadata(this.beanFactory);
Assert.notNull(this.pollerMetadata, "No poller has been defined for channel-adapter '"
+ this.beanName + "', and no default poller is available within the context.");
}
spca.setTrigger(this.pollerMetadata.getTrigger());
spca.setMaxMessagesPerPoll(this.pollerMetadata.getMaxMessagesPerPoll());
spca.setTaskExecutor(this.pollerMetadata.getTaskExecutor());
spca.setTransactionManager(this.pollerMetadata.getTransactionManager());
spca.setTransactionDefinition(this.pollerMetadata.getTransactionDefinition());
spca.setAdviceChain(this.pollerMetadata.getAdviceChain());
spca.setAutoStartup(this.autoStartup);
spca.setBeanName(this.beanName);
spca.setBeanFactory(this.beanFactory);
spca.setBeanClassLoader(this.beanClassLoader);
spca.afterPropertiesSet();
this.adapter = spca;
this.initialized = true;
}
}
/*
* SmartLifecycle implementation (delegates to the created adapter)
*/
public boolean isAutoStartup() {
return (this.adapter != null) ? this.adapter.isAutoStartup() : true;
}
public int getPhase() {
return (this.adapter != null) ? this.adapter.getPhase() : 0;
}
public boolean isRunning() {
return (this.adapter != null) ? this.adapter.isRunning() : false;
}
public void start() {
if (this.adapter != null) {
this.adapter.start();
}
}
public void stop() {
if (this.adapter != null) {
this.adapter.stop();
}
}
public void stop(Runnable callback) {
if (this.adapter != null) {
this.adapter.stop(callback);
}
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2002-2009 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.config;
import org.springframework.integration.message.MessageHandler;
import org.springframework.integration.splitter.AbstractMessageSplitter;
import org.springframework.integration.splitter.DefaultMessageSplitter;
import org.springframework.integration.splitter.ExpressionEvaluatingSplitter;
import org.springframework.integration.splitter.MethodInvokingSplitter;
import org.springframework.util.StringUtils;
/**
* Factory bean for creating a Message Splitter.
*
* @author Mark Fisher
*/
public class SplitterFactoryBean extends AbstractMessageHandlerFactoryBean {
private volatile Long sendTimeout;
public void setSendTimeout(Long sendTimeout) {
this.sendTimeout = sendTimeout;
}
@Override
MessageHandler createMethodInvokingHandler(Object targetObject, String targetMethodName) {
AbstractMessageSplitter splitter = null;
if (targetObject instanceof AbstractMessageSplitter) {
splitter = (AbstractMessageSplitter) targetObject;
}
else {
splitter = (StringUtils.hasText(targetMethodName))
? new MethodInvokingSplitter(targetObject, targetMethodName)
: new MethodInvokingSplitter(targetObject);
}
return this.configureSplitter(splitter);
}
@Override
MessageHandler createExpressionEvaluatingHandler(String expression) {
return this.configureSplitter(new ExpressionEvaluatingSplitter(expression));
}
@Override
MessageHandler createDefaultHandler() {
return this.configureSplitter(new DefaultMessageSplitter());
}
private AbstractMessageSplitter configureSplitter(AbstractMessageSplitter splitter) {
if (this.sendTimeout != null) {
splitter.setSendTimeout(sendTimeout);
}
return splitter;
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2002-2009 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.config;
import org.springframework.integration.message.MessageHandler;
import org.springframework.integration.transformer.ExpressionEvaluatingTransformer;
import org.springframework.integration.transformer.MessageTransformingHandler;
import org.springframework.integration.transformer.MethodInvokingTransformer;
import org.springframework.integration.transformer.Transformer;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Factory bean for creating a Message Transformer.
*
* @author Mark Fisher
*/
public class TransformerFactoryBean extends AbstractMessageHandlerFactoryBean {
private volatile Long sendTimeout;
public void setSendTimeout(Long sendTimeout) {
this.sendTimeout = sendTimeout;
}
@Override
MessageHandler createMethodInvokingHandler(Object targetObject, String targetMethodName) {
Assert.notNull(targetObject, "targetObject must not be null");
Transformer transformer = null;
if (targetObject instanceof Transformer) {
transformer = (Transformer) targetObject;
}
else if (StringUtils.hasText(targetMethodName)) {
transformer = new MethodInvokingTransformer(targetObject, targetMethodName);
}
else {
transformer = new MethodInvokingTransformer(targetObject);
}
return this.createHandler(transformer);
}
@Override
MessageHandler createExpressionEvaluatingHandler(String expression) {
Transformer transformer = new ExpressionEvaluatingTransformer(expression);
return this.createHandler(transformer);
}
private MessageTransformingHandler createHandler(Transformer transformer) {
MessageTransformingHandler handler = new MessageTransformingHandler(transformer);
if (this.sendTimeout != null) {
handler.setSendTimeout(this.sendTimeout.longValue());
}
return handler;
}
}

View File

@@ -0,0 +1,104 @@
/*
* Copyright 2002-2009 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.config.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.integration.channel.BeanFactoryChannelResolver;
import org.springframework.integration.channel.ChannelResolver;
import org.springframework.integration.channel.SubscribableChannel;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.endpoint.AbstractEndpoint;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.handler.AbstractMessageHandler;
import org.springframework.integration.message.MessageHandler;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Base class for Method-level annotation post-processors.
*
* @author Mark Fisher
*/
public abstract class AbstractMethodAnnotationPostProcessor<T extends Annotation> implements MethodAnnotationPostProcessor<T> {
private static final String INPUT_CHANNEL_ATTRIBUTE = "inputChannel";
protected final BeanFactory beanFactory;
protected final ChannelResolver channelResolver;
public AbstractMethodAnnotationPostProcessor(ListableBeanFactory beanFactory) {
Assert.notNull(beanFactory, "BeanFactory must not be null");
this.beanFactory = beanFactory;
this.channelResolver = new BeanFactoryChannelResolver(beanFactory);
}
public Object postProcess(Object bean, String beanName, Method method, T annotation) {
MessageHandler handler = this.createHandler(bean, method, annotation);
if (handler instanceof AbstractMessageHandler) {
Order orderAnnotation = AnnotationUtils.findAnnotation(method, Order.class);
if (orderAnnotation != null) {
((AbstractMessageHandler) handler).setOrder(orderAnnotation.value());
}
}
if (beanFactory instanceof ConfigurableListableBeanFactory) {
handler = (MessageHandler) ((ConfigurableListableBeanFactory) beanFactory).initializeBean(handler, "_initHandlerFor_" + beanName);
}
AbstractEndpoint endpoint = this.createEndpoint(handler, annotation);
if (endpoint != null) {
return endpoint;
}
return handler;
}
protected boolean shouldCreateEndpoint(T annotation) {
return (StringUtils.hasText((String) AnnotationUtils.getValue(annotation, INPUT_CHANNEL_ATTRIBUTE)));
}
private AbstractEndpoint createEndpoint(MessageHandler handler, T annotation) {
AbstractEndpoint endpoint = null;
String inputChannelName = (String) AnnotationUtils.getValue(annotation, INPUT_CHANNEL_ATTRIBUTE);
if (StringUtils.hasText(inputChannelName)) {
MessageChannel inputChannel = this.channelResolver.resolveChannelName(inputChannelName);
Assert.notNull(inputChannel, "failed to resolve inputChannel '" + inputChannelName + "'");
Assert.isTrue(inputChannel instanceof SubscribableChannel,
"The input channel for an Annotation-based endpoint must be a SubscribableChannel.");
endpoint = new EventDrivenConsumer((SubscribableChannel) inputChannel, handler);
if (handler instanceof BeanFactoryAware) {
((BeanFactoryAware) handler).setBeanFactory(this.beanFactory);
}
}
return endpoint;
}
/**
* Subclasses must implement this method to create the MessageHandler.
*/
protected abstract MessageHandler createHandler(Object bean, Method method, T annotation);
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright 2002-2009 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.config.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.concurrent.atomic.AtomicReference;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.integration.aggregator.CorrelatingMessageHandler;
import org.springframework.integration.aggregator.CorrelationStrategyAdapter;
import org.springframework.integration.aggregator.MethodInvokingMessageGroupProcessor;
import org.springframework.integration.aggregator.ReleaseStrategyAdapter;
import org.springframework.integration.annotation.Aggregator;
import org.springframework.integration.annotation.CorrelationStrategy;
import org.springframework.integration.annotation.ReleaseStrategy;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.message.MessageHandler;
import org.springframework.integration.store.SimpleMessageStore;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
* Post-processor for the {@link Aggregator @Aggregator} annotation.
*
* @author Mark Fisher
*/
public class AggregatorAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor<Aggregator> {
public AggregatorAnnotationPostProcessor(ListableBeanFactory beanFactory) {
super(beanFactory);
}
@Override
protected MessageHandler createHandler(Object bean, Method method, Aggregator annotation) {
MethodInvokingMessageGroupProcessor processor = new MethodInvokingMessageGroupProcessor(bean, method.getName());
ReleaseStrategyAdapter ReleaseStrategy = getReleaseStrategy(bean);
CorrelationStrategyAdapter correlationStrategy = getCorrelationStrategy(bean);
CorrelatingMessageHandler handler = new CorrelatingMessageHandler(processor, new SimpleMessageStore(), correlationStrategy, ReleaseStrategy);
String discardChannelName = annotation.discardChannel();
if (StringUtils.hasText(discardChannelName)) {
MessageChannel discardChannel = this.channelResolver.resolveChannelName(discardChannelName);
Assert.notNull(discardChannel, "failed to resolve discardChannel '" + discardChannelName + "'");
handler.setDiscardChannel(discardChannel);
}
String outputChannelName = annotation.outputChannel();
if (StringUtils.hasText(outputChannelName)) {
handler.setOutputChannel(this.channelResolver.resolveChannelName(outputChannelName));
}
handler.setSendTimeout(annotation.sendTimeout());
handler.setSendPartialResultOnExpiry(annotation.sendPartialResultsOnExpiry());
handler.setBeanFactory(this.beanFactory);
handler.afterPropertiesSet();
return handler;
}
private ReleaseStrategyAdapter getReleaseStrategy(final Object bean) {
final AtomicReference<ReleaseStrategyAdapter> reference = new AtomicReference<ReleaseStrategyAdapter>();
ReflectionUtils.doWithMethods(bean.getClass(), new ReflectionUtils.MethodCallback() {
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
Annotation annotation = AnnotationUtils.getAnnotation(method, ReleaseStrategy.class);
if (annotation != null) {
reference.set(new ReleaseStrategyAdapter(bean, method));
}
}
});
return reference.get();
}
private CorrelationStrategyAdapter getCorrelationStrategy(final Object bean) {
final AtomicReference<CorrelationStrategyAdapter> reference = new AtomicReference<CorrelationStrategyAdapter>();
ReflectionUtils.doWithMethods(bean.getClass(), new ReflectionUtils.MethodCallback() {
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
Annotation annotation = AnnotationUtils.getAnnotation(method, CorrelationStrategy.class);
if (annotation != null) {
reference.set(new CorrelationStrategyAdapter(bean, method));
}
}
});
return reference.get();
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2002-2010 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.config.annotation;
import java.lang.reflect.Method;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.integration.annotation.Filter;
import org.springframework.integration.filter.MessageFilter;
import org.springframework.integration.filter.MethodInvokingSelector;
import org.springframework.integration.message.MessageHandler;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Post-processor for Methods annotated with {@link Filter @Filter}.
*
* @author Mark Fisher
* @since 2.0
*/
public class FilterAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor<Filter> {
public FilterAnnotationPostProcessor(ListableBeanFactory beanFactory) {
super(beanFactory);
}
@Override
protected MessageHandler createHandler(Object bean, Method method, Filter annotation) {
Assert.isTrue(boolean.class.equals(method.getReturnType()) || Boolean.class.equals(method.getReturnType()),
"The Filter annotation may only be applied to methods with a boolean return type.");
MethodInvokingSelector selector = new MethodInvokingSelector(bean, method);
MessageFilter filter = new MessageFilter(selector);
String outputChannelName = annotation.outputChannel();
if (StringUtils.hasText(outputChannelName)) {
filter.setOutputChannel(this.channelResolver.resolveChannelName(outputChannelName));
}
return filter;
}
}

View File

@@ -0,0 +1,227 @@
/*
* Copyright 2002-2010 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.config.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.Lifecycle;
import org.springframework.context.SmartLifecycle;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.integration.annotation.Aggregator;
import org.springframework.integration.annotation.Filter;
import org.springframework.integration.annotation.Router;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.annotation.Splitter;
import org.springframework.integration.annotation.Transformer;
import org.springframework.integration.endpoint.AbstractEndpoint;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
* A {@link BeanPostProcessor} implementation that processes method-level
* messaging annotations such as @Transformer, @Splitter, @Router, and @Filter.
*
* @author Mark Fisher
* @author Marius Bogoevici
*/
public class MessagingAnnotationPostProcessor implements BeanPostProcessor, BeanFactoryAware, InitializingBean, Lifecycle, ApplicationListener {
private final Log logger = LogFactory.getLog(this.getClass());
private volatile ConfigurableListableBeanFactory beanFactory;
private final Map<Class<? extends Annotation>, MethodAnnotationPostProcessor<?>> postProcessors =
new HashMap<Class<? extends Annotation>, MethodAnnotationPostProcessor<?>>();
private final Set<ApplicationListener> listeners = new HashSet<ApplicationListener>();
private final Set<Lifecycle> lifecycles = new HashSet<Lifecycle>();
private volatile boolean running = true;
public void setBeanFactory(BeanFactory beanFactory) {
Assert.isAssignable(ConfigurableListableBeanFactory.class, beanFactory.getClass(),
"a ConfigurableListableBeanFactory is required");
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
}
public void afterPropertiesSet() {
Assert.notNull(this.beanFactory, "BeanFactory must not be null");
postProcessors.put(Filter.class, new FilterAnnotationPostProcessor(this.beanFactory));
postProcessors.put(Router.class, new RouterAnnotationPostProcessor(this.beanFactory));
postProcessors.put(Transformer.class, new TransformerAnnotationPostProcessor(this.beanFactory));
postProcessors.put(ServiceActivator.class, new ServiceActivatorAnnotationPostProcessor(this.beanFactory));
postProcessors.put(Splitter.class, new SplitterAnnotationPostProcessor(this.beanFactory));
postProcessors.put(Aggregator.class, new AggregatorAnnotationPostProcessor(this.beanFactory));
}
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
Assert.notNull(this.beanFactory, "BeanFactory must not be null");
final Class<?> beanClass = this.getBeanClass(bean);
if (!this.isStereotype(beanClass)) {
// we only post-process stereotype components
return bean;
}
ReflectionUtils.doWithMethods(beanClass, new ReflectionUtils.MethodCallback() {
@SuppressWarnings("unchecked")
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
Annotation[] annotations = AnnotationUtils.getAnnotations(method);
for (Annotation annotation : annotations) {
MethodAnnotationPostProcessor postProcessor = postProcessors.get(annotation.annotationType());
if (postProcessor != null && shouldCreateEndpoint(annotation)) {
Object result = postProcessor.postProcess(bean, beanName, method, annotation);
if (result != null && result instanceof AbstractEndpoint) {
String endpointBeanName = generateBeanName(beanName, method, annotation.annotationType());
if (result instanceof BeanNameAware) {
((BeanNameAware) result).setBeanName(endpointBeanName);
}
beanFactory.registerSingleton(endpointBeanName, result);
if (result instanceof BeanFactoryAware) {
((BeanFactoryAware) result).setBeanFactory(beanFactory);
}
if (result instanceof InitializingBean) {
try {
((InitializingBean) result).afterPropertiesSet();
}
catch (Exception e) {
throw new BeanInitializationException("failed to initialize annotated component", e);
}
}
if (result instanceof Lifecycle) {
lifecycles.add((Lifecycle) result);
if (result instanceof SmartLifecycle && ((SmartLifecycle) result).isAutoStartup()) {
((SmartLifecycle) result).start();
}
}
if (result instanceof ApplicationListener) {
listeners.add((ApplicationListener) result);
}
}
}
}
}
});
return bean;
}
private boolean shouldCreateEndpoint(Annotation annotation) {
Object inputChannel = AnnotationUtils.getValue(annotation, "inputChannel");
return (inputChannel != null && inputChannel instanceof String
&& StringUtils.hasText((String) inputChannel));
}
private Class<?> getBeanClass(Object bean) {
Class<?> targetClass = AopUtils.getTargetClass(bean);
return (targetClass != null) ? targetClass : bean.getClass();
}
private boolean isStereotype(Class<?> beanClass) {
List<Annotation> annotations = new ArrayList<Annotation>(Arrays.asList(beanClass.getAnnotations()));
Class<?>[] interfaces = beanClass.getInterfaces();
for (Class<?> iface : interfaces) {
annotations.addAll(Arrays.asList(iface.getAnnotations()));
}
for (Annotation annotation : annotations) {
Class<? extends Annotation> annotationType = annotation.annotationType();
if (annotationType.equals(Component.class) || annotationType.isAnnotationPresent(Component.class)) {
return true;
}
}
return false;
}
private String generateBeanName(String originalBeanName, Method method, Class<? extends Annotation> annotationType) {
String baseName = originalBeanName + "." + method.getName() + "." + ClassUtils.getShortNameAsProperty(annotationType);
String name = baseName;
int count = 1;
while (this.beanFactory.containsBean(name)) {
name = baseName + "#" + (++count);
}
return name;
}
public void onApplicationEvent(ApplicationEvent event) {
for (ApplicationListener listener : listeners) {
try {
listener.onApplicationEvent(event);
}
catch (ClassCastException e) {
if (logger.isWarnEnabled() && event != null) {
logger.warn("ApplicationEvent of type [" + event.getClass() +
"] not accepted by ApplicationListener [" + listener + "]");
}
}
}
}
// Lifecycle implementation
public boolean isRunning() {
return this.running;
}
public void start() {
for (Lifecycle lifecycle : this.lifecycles) {
if (!lifecycle.isRunning()) {
lifecycle.start();
}
}
this.running = true;
}
public void stop() {
for (Lifecycle lifecycle : this.lifecycles) {
if (lifecycle.isRunning()) {
lifecycle.stop();
}
}
this.running = false;
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.config.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
/**
* Strategy interface for post-processing annotated methods.
*
* @author Mark Fisher
*/
public interface MethodAnnotationPostProcessor<T extends Annotation> {
Object postProcess(Object bean, String beanName, Method method, T annotation);
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.config.annotation;
import java.lang.reflect.Method;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.integration.annotation.Router;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.message.MessageHandler;
import org.springframework.integration.router.MethodInvokingRouter;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Post-processor for Methods annotated with {@link Router @Router}.
*
* @author Mark Fisher
*/
public class RouterAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor<Router> {
public RouterAnnotationPostProcessor(ListableBeanFactory beanFactory) {
super(beanFactory);
}
@Override
protected MessageHandler createHandler(Object bean, Method method, Router annotation) {
MethodInvokingRouter router = new MethodInvokingRouter(bean, method);
router.setChannelResolver(this.channelResolver);
String defaultOutputChannelName = annotation.defaultOutputChannel();
if (StringUtils.hasText(defaultOutputChannelName)) {
MessageChannel defaultOutputChannel = this.channelResolver.resolveChannelName(defaultOutputChannelName);
Assert.notNull(defaultOutputChannel, "unable to resolve defaultOutputChannel '" + defaultOutputChannelName + "'");
router.setDefaultOutputChannel(defaultOutputChannel);
}
return router;
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.config.annotation;
import java.lang.reflect.Method;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.handler.ServiceActivatingHandler;
import org.springframework.integration.message.MessageHandler;
import org.springframework.util.StringUtils;
/**
* Post-processor for Methods annotated with {@link ServiceActivator @ServiceActivator}.
*
* @author Mark Fisher
*/
public class ServiceActivatorAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor<ServiceActivator> {
public ServiceActivatorAnnotationPostProcessor(ListableBeanFactory beanFactory) {
super(beanFactory);
}
@Override
protected MessageHandler createHandler(Object bean, Method method, ServiceActivator annotation) {
ServiceActivatingHandler serviceActivator = new ServiceActivatingHandler(bean, method);
String outputChannelName = annotation.outputChannel();
if (StringUtils.hasText(outputChannelName)) {
serviceActivator.setOutputChannel(this.channelResolver.resolveChannelName(outputChannelName));
}
return serviceActivator;
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.config.annotation;
import java.lang.reflect.Method;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.integration.annotation.Splitter;
import org.springframework.integration.message.MessageHandler;
import org.springframework.integration.splitter.MethodInvokingSplitter;
import org.springframework.util.StringUtils;
/**
* Post-processor for Methods annotated with {@link Splitter @Splitter}.
*
* @author Mark Fisher
*/
public class SplitterAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor<Splitter> {
public SplitterAnnotationPostProcessor(ListableBeanFactory beanFactory) {
super(beanFactory);
}
@Override
protected MessageHandler createHandler(Object bean, Method method, Splitter annotation) {
MethodInvokingSplitter splitter = new MethodInvokingSplitter(bean, method);
String outputChannelName = annotation.outputChannel();
if (StringUtils.hasText(outputChannelName)) {
splitter.setOutputChannel(this.channelResolver.resolveChannelName(outputChannelName));
}
return splitter;
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2002-2009 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.config.annotation;
import java.lang.reflect.Method;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.integration.annotation.Transformer;
import org.springframework.integration.message.MessageHandler;
import org.springframework.integration.transformer.MessageTransformingHandler;
import org.springframework.integration.transformer.MethodInvokingTransformer;
import org.springframework.util.StringUtils;
/**
* Post-processor for Methods annotated with {@link Transformer @Transformer}.
*
* @author Mark Fisher
*/
public class TransformerAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor<Transformer> {
public TransformerAnnotationPostProcessor(ListableBeanFactory beanFactory) {
super(beanFactory);
}
@Override
protected MessageHandler createHandler(Object bean, Method method, Transformer annotation) {
MethodInvokingTransformer transformer = new MethodInvokingTransformer(bean, method);
MessageTransformingHandler handler = new MessageTransformingHandler(transformer);
String outputChannelName = annotation.outputChannel();
if (StringUtils.hasText(outputChannelName)) {
handler.setOutputChannel(this.channelResolver.resolveChannelName(outputChannelName));
}
return handler;
}
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.config.xml;
import org.w3c.dom.Element;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
/**
* Base parser for Channel Adapters.
*
* @author Mark Fisher
*/
public abstract class AbstractChannelAdapterParser extends AbstractBeanDefinitionParser {
@Override
protected final String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) throws BeanDefinitionStoreException {
String id = element.getAttribute("id");
if (!element.hasAttribute("channel")) {
// the created channel will get the 'id', so the adapter's bean name includes a suffix
id = id + ".adapter";
}
else if (!StringUtils.hasText(id)) {
id = parserContext.getReaderContext().generateBeanName(definition);
}
return id;
}
@Override
protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
String channelName = element.getAttribute("channel");
if (!StringUtils.hasText(channelName)) {
channelName = this.createDirectChannel(element, parserContext);
}
return doParse(element, parserContext, channelName);
}
private String createDirectChannel(Element element, ParserContext parserContext) {
String channelId = element.getAttribute("id");
if (!StringUtils.hasText(channelId)) {
parserContext.getReaderContext().error("The channel-adapter's 'id' attribute is required when no 'channel' "
+ "reference has been provided, because that 'id' would be used for the created channel.", element);
}
BeanDefinitionBuilder channelBuilder = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".channel.DirectChannel");
BeanDefinitionHolder holder = new BeanDefinitionHolder(channelBuilder.getBeanDefinition(), channelId);
BeanDefinitionReaderUtils.registerBeanDefinition(holder, parserContext.getRegistry());
return channelId;
}
/**
* Subclasses must implement this method to parse the adapter element.
* The name of the MessageChannel bean is provided.
*/
protected abstract AbstractBeanDefinition doParse(Element element, ParserContext parserContext, String channelName);
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2002-2010 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.config.xml;
import java.util.List;
import org.w3c.dom.Element;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.xml.DomUtils;
/**
* Base parser for routers that create instances that are subclasses of AbstractChannelNameResolvingMessageRouter.
*
* @author Mark Fisher
*/
public abstract class AbstractChannelNameResolvingRouterParser extends AbstractRouterParser {
@Override
protected final BeanDefinition parseRouter(Element element, ParserContext parserContext) {
BeanDefinition beanDefinition = this.doParseRouter(element, parserContext);
if (beanDefinition != null) {
// check if mapping is provided otherwise returned values will be treated as channel names
List<Element> childElements = DomUtils.getChildElementsByTagName(element, "mapping");
if (childElements != null && childElements.size() > 0) {
BeanDefinitionBuilder channelResolverBuilder = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".channel.MapBasedChannelResolver");
ManagedMap<String, RuntimeBeanReference> channelMap = new ManagedMap<String, RuntimeBeanReference>();
for (Element childElement : childElements) {
channelMap.put(childElement.getAttribute("value"),
new RuntimeBeanReference(childElement.getAttribute("channel")));
}
channelResolverBuilder.addPropertyValue("channelMap", channelMap);
beanDefinition.getPropertyValues().add("channelResolver", channelResolverBuilder.getBeanDefinition());
}
}
return beanDefinition;
}
protected abstract BeanDefinition doParseRouter(Element element, ParserContext parserContext);
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2002-2009 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.config.xml;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
/**
* Base class for channel parsers.
*
* @author Mark Fisher
*/
public abstract class AbstractChannelParser extends AbstractBeanDefinitionParser {
@Override
@SuppressWarnings("unchecked")
protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = this.buildBeanDefinition(element, parserContext);
ManagedList interceptors = null;
Element interceptorsElement = DomUtils.getChildElementByTagName(element, "interceptors");
if (interceptorsElement != null) {
ChannelInterceptorParser interceptorParser = new ChannelInterceptorParser();
interceptors = interceptorParser.parseInterceptors(interceptorsElement, parserContext);
}
if (interceptors == null) {
interceptors = new ManagedList();
}
String datatypeAttr = element.getAttribute("datatype");
if (StringUtils.hasText(datatypeAttr)) {
// TODO: remove this once the editor fallback is working (3.0 GA)
// it should be replaced with: builder.addPropertyValue("datatypes", datatypeAttr);
String[] classnames = StringUtils.commaDelimitedListToStringArray(datatypeAttr);
Class<?>[] datatypes = new Class<?>[classnames.length];
int i = 0;
for (String classname : classnames) {
datatypes[i++] = ClassUtils.resolveClassName(classname.trim(), this.getClass().getClassLoader());
}
builder.addPropertyValue("datatypes", datatypes);
}
builder.addPropertyValue("interceptors", interceptors);
return builder.getBeanDefinition();
}
/**
* Subclasses must implement this method to create the bean definition.
* The class must be defined, and any implementation-specific constructor
* arguments or properties should be configured. This base class will
* configure the interceptors including the 'datatype' interceptor if
* the 'datatype' attribute is defined on the channel element.
*/
protected abstract BeanDefinitionBuilder buildBeanDefinition(Element element, ParserContext parserContext);
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright 2002-2009 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.config.xml;
import java.util.List;
import org.w3c.dom.Element;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.CollectionUtils;
import org.springframework.util.xml.DomUtils;
/**
* Base class parser for elements that create Message Endpoints.
*
* @author Mark Fisher
* @author Oleg Zhurakousky
*/
public abstract class AbstractConsumerEndpointParser extends AbstractBeanDefinitionParser {
protected static final String REF_ATTRIBUTE = "ref";
protected static final String METHOD_ATTRIBUTE = "method";
protected static final String EXPRESSION_ATTRIBUTE = "expression";
@Override
protected boolean shouldGenerateId() {
return false;
}
@Override
protected boolean shouldGenerateIdAsFallback() {
return true;
}
/**
* Parse the MessageHandler.
*/
protected abstract BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext);
protected String getInputChannelAttributeName() {
return "input-channel";
}
@Override
protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
BeanDefinitionBuilder handlerBuilder = this.parseHandler(element, parserContext);
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(handlerBuilder, element, "output-channel");
IntegrationNamespaceUtils.setValueIfAttributeDefined(handlerBuilder, element, "order");
AbstractBeanDefinition handlerBeanDefinition = handlerBuilder.getBeanDefinition();
String inputChannelAttributeName = this.getInputChannelAttributeName();
if (!element.hasAttribute(inputChannelAttributeName)) {
if (!parserContext.isNested()) {
String elementDescription = IntegrationNamespaceUtils.createElementDescription(element);
parserContext.getReaderContext().error("The '" + inputChannelAttributeName
+ "' attribute is required for the top-level endpoint element "
+ elementDescription + ".", element);
}
return handlerBeanDefinition;
}
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".config.ConsumerEndpointFactoryBean");
String handlerBeanName = BeanDefinitionReaderUtils.registerWithGeneratedName(handlerBeanDefinition, parserContext.getRegistry());
builder.addPropertyReference("handler", handlerBeanName);
String inputChannelName = element.getAttribute(inputChannelAttributeName);
boolean channelExists = false;
if (parserContext.getRegistry() instanceof BeanFactory) {
// BeanFactory also checks ancestor contexts in a hierarchy
channelExists = ((BeanFactory) parserContext.getRegistry()).containsBean(inputChannelName);
}
else {
channelExists = parserContext.getRegistry().containsBeanDefinition(inputChannelName);
}
if (!channelExists) {
// create a default DirectChannel instance
BeanDefinitionBuilder channelDef = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".channel.DirectChannel");
BeanDefinitionHolder holder = new BeanDefinitionHolder(channelDef.getBeanDefinition(), inputChannelName);
BeanDefinitionReaderUtils.registerBeanDefinition(holder, parserContext.getRegistry());
}
builder.addPropertyValue("inputChannelName", inputChannelName);
List<Element> pollerElementList = DomUtils.getChildElementsByTagName(element, "poller");
if (!CollectionUtils.isEmpty(pollerElementList)) {
if (pollerElementList.size() != 1) {
parserContext.getReaderContext().error(
"at most one poller element may be configured for an endpoint", element);
}
IntegrationNamespaceUtils.configurePollerMetadata(pollerElementList.get(0), builder, parserContext);
}
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "auto-startup");
return builder.getBeanDefinition();
}
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright 2002-2009 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.config.xml;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
/**
* Base parser class for endpoints that delegate to a method invoker or
* expression evaluator when handling consumed Messages. These classes
* use a FactoryBean implementation to construct the actual endpoint
* instance.
*
* @author Mark Fisher
* @author Oleg Zhurakousky
*/
abstract class AbstractDelegatingConsumerEndpointParser extends AbstractConsumerEndpointParser {
@Override
protected final BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(this.getFactoryBeanClassName());
BeanComponentDefinition innerDefinition = IntegrationNamespaceUtils.parseInnerHandlerDefinition(element, parserContext);
String ref = element.getAttribute(REF_ATTRIBUTE);
String expression = element.getAttribute(EXPRESSION_ATTRIBUTE);
boolean hasRef = StringUtils.hasText(ref);
boolean hasExpression = StringUtils.hasText(expression);
if (innerDefinition != null) {
if (hasRef || hasExpression) {
parserContext.getReaderContext().error(
"Neither 'ref' nor 'expression' are permitted when an inner bean (<bean/>) is configured.", element);
return null;
}
builder.addPropertyValue("targetObject", innerDefinition);
}
else if (hasRef) {
builder.addPropertyReference("targetObject", ref);
}
else if (hasExpression) {
builder.addPropertyValue("expression", expression);
}
else if (!this.hasDefaultOption()) {
parserContext.getReaderContext().error("Exactly one of the 'ref' attribute, 'expression' attribute, " +
"or inner bean (<bean/>) definition is required for this '" + element.getLocalName() + "' endpoint.",
element);
return null;
}
String method = element.getAttribute(METHOD_ATTRIBUTE);
if (StringUtils.hasText(method)) {
if (hasExpression) {
parserContext.getReaderContext().error(
"A 'method' attribute is not permitted when configuring an 'expression'.", element);
}
if (hasRef || innerDefinition != null) {
builder.addPropertyValue("targetMethodName", method);
}
else {
parserContext.getReaderContext().error("A 'method' attribute is only permitted when either " +
"a 'ref' or inner-bean definition is provided.", element);
}
}
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "send-timeout");
this.postProcess(builder, element, parserContext);
return builder;
}
/**
* Subclasses may override this no-op method to provide additional configuration.
*/
void postProcess(BeanDefinitionBuilder builder, Element element, ParserContext parserContext) {
}
abstract boolean hasDefaultOption();
abstract String getFactoryBeanClassName();
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2002-2010 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.config.xml;
import org.w3c.dom.Element;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSimpleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Base class for inbound gateway parsers.
*
* @author Mark Fisher
*/
public abstract class AbstractInboundGatewayParser extends AbstractSimpleBeanDefinitionParser {
@Override
protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext)
throws BeanDefinitionStoreException {
String id = super.resolveId(element, definition, parserContext);
if (!StringUtils.hasText(id)) {
id = element.getAttribute("name");
}
if (!StringUtils.hasText(id)) {
id = parserContext.getReaderContext().generateBeanName(definition);
}
return id;
}
@Override
protected boolean isEligibleAttribute(String attributeName) {
return !attributeName.equals("name") && !attributeName.equals("request-channel")
&& !attributeName.equals("reply-channel") && super.isEligibleAttribute(attributeName);
}
@Override
protected final void postProcess(BeanDefinitionBuilder builder, Element element) {
String requestChannelRef = element.getAttribute("request-channel");
Assert.hasText(requestChannelRef, "a 'request-channel' reference is required");
builder.addPropertyReference("requestChannel", requestChannelRef);
String replyChannel = element.getAttribute("reply-channel");
if (StringUtils.hasText(replyChannel)) {
builder.addPropertyReference("replyChannel", replyChannel);
}
String autoStartup = element.getAttribute("auto-startup");
if (StringUtils.hasText(autoStartup)) {
builder.addPropertyValue("autoStartup", autoStartup);
}
this.doPostProcess(builder, element);
}
/**
* Subclasses may add to the bean definition by overriding this method.
*/
protected void doPostProcess(BeanDefinitionBuilder builder, Element element) {
}
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright 2002-2009 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.config.xml;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.xml.BeanDefinitionDecorator;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.NamespaceHandler;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.ObjectUtils;
/**
* Base class for NamespaceHandlers that registers a BeanFactoryPostProcessor
* for configuring default bean definitions.
*
* @author Mark Fisher
*/
public abstract class AbstractIntegrationNamespaceHandler implements NamespaceHandler {
private static final String DEFAULT_CONFIGURING_POSTPROCESSOR_SIMPLE_CLASS_NAME =
"DefaultConfiguringBeanFactoryPostProcessor";
private static final String DEFAULT_CONFIGURING_POSTPROCESSOR_BEAN_NAME =
IntegrationNamespaceUtils.BASE_PACKAGE + ".internal" + DEFAULT_CONFIGURING_POSTPROCESSOR_SIMPLE_CLASS_NAME;
private final NamespaceHandlerDelegate delegate = new NamespaceHandlerDelegate();
public final BeanDefinition parse(Element element, ParserContext parserContext) {
this.registerDefaultConfiguringBeanFactoryPostProcessorIfNecessary(parserContext);
return this.delegate.parse(element, parserContext);
}
public final BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder definition, ParserContext parserContext) {
return this.delegate.decorate(source, definition, parserContext);
}
private void registerDefaultConfiguringBeanFactoryPostProcessorIfNecessary(ParserContext parserContext) {
boolean alreadyRegistered = false;
if (parserContext.getRegistry() instanceof ListableBeanFactory) {
alreadyRegistered = ObjectUtils.containsElement(
BeanFactoryUtils.beanNamesForTypeIncludingAncestors((ListableBeanFactory) parserContext.getRegistry(),
DefaultConfiguringBeanFactoryPostProcessor.class, false, false),
DEFAULT_CONFIGURING_POSTPROCESSOR_BEAN_NAME);
}
else {
alreadyRegistered = parserContext.getRegistry().isBeanNameInUse(DEFAULT_CONFIGURING_POSTPROCESSOR_BEAN_NAME);
}
if (!alreadyRegistered) {
BeanDefinitionBuilder postProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".config.xml." + DEFAULT_CONFIGURING_POSTPROCESSOR_SIMPLE_CLASS_NAME);
BeanDefinitionHolder postProcessorHolder = new BeanDefinitionHolder(
postProcessorBuilder.getBeanDefinition(), DEFAULT_CONFIGURING_POSTPROCESSOR_BEAN_NAME);
BeanDefinitionReaderUtils.registerBeanDefinition(postProcessorHolder, parserContext.getRegistry());
}
}
protected final void registerBeanDefinitionDecorator(String elementName, BeanDefinitionDecorator decorator) {
this.delegate.doRegisterBeanDefinitionDecorator(elementName, decorator);
}
protected final void registerBeanDefinitionDecoratorForAttribute(String attributeName, BeanDefinitionDecorator decorator) {
this.delegate.doRegisterBeanDefinitionDecoratorForAttribute(attributeName, decorator);
}
protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
this.delegate.doRegisterBeanDefinitionParser(elementName, parser);
}
private class NamespaceHandlerDelegate extends NamespaceHandlerSupport {
public void init() {
AbstractIntegrationNamespaceHandler.this.init();
}
private void doRegisterBeanDefinitionDecorator(String elementName, BeanDefinitionDecorator decorator) {
super.registerBeanDefinitionDecorator(elementName, decorator);
}
private void doRegisterBeanDefinitionDecoratorForAttribute(String attributeName, BeanDefinitionDecorator decorator) {
super.registerBeanDefinitionDecoratorForAttribute(attributeName, decorator);
}
private void doRegisterBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
super.registerBeanDefinitionParser(elementName, parser);
}
}
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright 2002-2009 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.config.xml;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
/**
* Base class for outbound Channel Adapter parsers.
*
* @author Mark Fisher
*/
public abstract class AbstractOutboundChannelAdapterParser extends AbstractChannelAdapterParser {
@Override
protected AbstractBeanDefinition doParse(Element element, ParserContext parserContext, String channelName) {
Element pollerElement = DomUtils.getChildElementByTagName(element, "poller");
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".config.ConsumerEndpointFactoryBean");
builder.addPropertyReference("handler", this.parseAndRegisterConsumer(element, parserContext));
if (pollerElement != null) {
if (!StringUtils.hasText(channelName)) {
parserContext.getReaderContext().error(
"outbound channel adapter with a 'poller' requires a 'channel' to poll", element);
}
IntegrationNamespaceUtils.configurePollerMetadata(pollerElement, builder, parserContext);
}
builder.addPropertyValue("inputChannelName", channelName);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "auto-startup");
return builder.getBeanDefinition();
}
/**
* Override this method to control the registration process and return the bean name.
* If parsing a bean definition whose name can be auto-generated, consider using
* {@link #parseConsumer(Element, ParserContext)} instead.
*/
protected String parseAndRegisterConsumer(Element element, ParserContext parserContext) {
AbstractBeanDefinition definition = this.parseConsumer(element, parserContext);
if (definition == null) {
parserContext.getReaderContext().error(
"Consumer parsing must return a BeanComponentDefinition.", element);
}
String order = element.getAttribute("order");
if (StringUtils.hasText(order)) {
definition.getPropertyValues().addPropertyValue("order", order);
}
return BeanDefinitionReaderUtils.registerWithGeneratedName(
definition, parserContext.getRegistry());
}
/**
* Override this method to return the BeanDefinition for the MessageConsumer. It will
* be registered with a generated name.
*/
protected abstract AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext);
}

View File

@@ -0,0 +1,81 @@
/*
* Copyright 2002-2010 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.config.xml;
import org.w3c.dom.Element;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
/**
* Base class for url-based outbound gateway parsers.
*
* @author Mark Fisher
*/
public abstract class AbstractOutboundGatewayParser extends AbstractConsumerEndpointParser {
protected abstract String getGatewayClassName(Element element);
@Override
protected String getInputChannelAttributeName() {
return "request-channel";
}
@Override
protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext)
throws BeanDefinitionStoreException {
String id = super.resolveId(element, definition, parserContext);
if (!StringUtils.hasText(id)) {
id = element.getAttribute("name");
}
if (!StringUtils.hasText(id)) {
id = parserContext.getReaderContext().generateBeanName(definition);
}
return id;
}
@Override
protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(this.getGatewayClassName(element));
String url = this.parseUrl(element, parserContext);
builder.addConstructorArgValue(url);
String replyChannel = element.getAttribute("reply-channel");
if (StringUtils.hasText(replyChannel)) {
builder.addPropertyReference("replyChannel", replyChannel);
}
this.postProcessGateway(builder, element, parserContext);
return builder;
}
protected String parseUrl(Element element, ParserContext parserContext) {
String url = element.getAttribute("url");
if (!StringUtils.hasText(url)) {
parserContext.getReaderContext().error("The 'url' attribute is required.", element);
}
return url;
}
/**
* Subclasses may override this method for additional configuration.
*/
protected void postProcessGateway(BeanDefinitionBuilder builder, Element element, ParserContext parserContext) {
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.config.xml;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
/**
* Base parser for inbound Channel Adapters that poll a source.
*
* @author Mark Fisher
*/
public abstract class AbstractPollingInboundChannelAdapterParser extends AbstractChannelAdapterParser {
@Override
protected AbstractBeanDefinition doParse(Element element, ParserContext parserContext, String channelName) {
String source = this.parseSource(element, parserContext);
if (!StringUtils.hasText(source)) {
parserContext.getReaderContext().error("failed to parse source", element);
}
BeanDefinitionBuilder adapterBuilder = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".config.SourcePollingChannelAdapterFactoryBean");
adapterBuilder.addPropertyReference("source", source);
adapterBuilder.addPropertyReference("outputChannel", channelName);
Element pollerElement = DomUtils.getChildElementByTagName(element, "poller");
if (pollerElement != null) {
IntegrationNamespaceUtils.configurePollerMetadata(pollerElement, adapterBuilder, parserContext);
}
IntegrationNamespaceUtils.setValueIfAttributeDefined(adapterBuilder, element, "auto-startup");
return adapterBuilder.getBeanDefinition();
}
/**
* Subclasses must implement this method to parse the PollableSource instance
* which the created Channel Adapter will poll.
*/
protected abstract String parseSource(Element element, ParserContext parserContext);
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2002-2010 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.config.xml;
import org.w3c.dom.Element;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
/**
* Base parser for routers.
*
* @author Mark Fisher
*/
public abstract class AbstractRouterParser extends AbstractConsumerEndpointParser {
@Override
protected final BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".config.RouterFactoryBean");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "default-output-channel");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "timeout");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "resolution-required");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "apply-sequence");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "ignore-send-failures");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "ignore-channel-name-resolution-failures");
BeanDefinition targetRouterBeanDefinition = this.parseRouter(element, parserContext);
builder.addPropertyValue("targetObject", targetRouterBeanDefinition);
return builder;
}
protected abstract BeanDefinition parseRouter(Element element, ParserContext parserContext);
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.config.xml;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.xml.ParserContext;
/**
* @author Mark Fisher
*/
public abstract class AbstractTransformerParser extends AbstractConsumerEndpointParser {
@Override
protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.MessageTransformingHandler");
BeanDefinitionBuilder transformerBuilder =
BeanDefinitionBuilder.genericBeanDefinition(this.getTransformerClassName());
this.parseTransformer(element, parserContext, transformerBuilder);
String transformerBeanName = BeanDefinitionReaderUtils.registerWithGeneratedName(
transformerBuilder.getBeanDefinition(), parserContext.getRegistry());
builder.addConstructorArgReference(transformerBeanName);
return builder;
}
protected abstract String getTransformerClassName();
protected abstract void parseTransformer(Element element, ParserContext parserContext, BeanDefinitionBuilder builder);
}

View File

@@ -0,0 +1,137 @@
/*
* Copyright 2002-2010 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.config.xml;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
/**
* Parser for the <em>aggregator</em> element of the integration namespace. Registers the annotation-driven
* post-processors.
*
* @author Marius Bogoevici
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Dave Syer
*/
public class AggregatorParser extends AbstractConsumerEndpointParser {
private static final String RELEASE_STRATEGY_REF_ATTRIBUTE = "release-strategy";
private static final String RELEASE_STRATEGY_METHOD_ATTRIBUTE = "release-strategy-method";
private static final String CORRELATION_STRATEGY_REF_ATTRIBUTE = "correlation-strategy";
private static final String CORRELATION_STRATEGY_METHOD_ATTRIBUTE = "correlation-strategy-method";
private static final String MESSAGE_STORE_ATTRIBUTE = "message-store";
private static final String OUTPUT_CHANNEL_ATTRIBUTE = "output-channel";
private static final String DISCARD_CHANNEL_ATTRIBUTE = "discard-channel";
private static final String SEND_TIMEOUT_ATTRIBUTE = "send-timeout";
private static final String SEND_PARTIAL_RESULT_ON_EXPIRY_ATTRIBUTE = "send-partial-result-on-expiry";
private static final String RELEASE_STRATEGY_PROPERTY = "releaseStrategy";
private static final String CORRELATION_STRATEGY_PROPERTY = "correlationStrategy";
@Override
protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) {
BeanComponentDefinition innerHandlerDefinition = IntegrationNamespaceUtils.parseInnerHandlerDefinition(element, parserContext);
String ref = element.getAttribute(REF_ATTRIBUTE);
BeanDefinitionBuilder builder;
builder = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".aggregator.CorrelatingMessageHandler");
BeanDefinitionBuilder processorBuilder = null;
if (innerHandlerDefinition != null || StringUtils.hasText(ref)) {
processorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".aggregator.MethodInvokingMessageGroupProcessor");
builder.addConstructorArgValue(processorBuilder.getBeanDefinition());
} else {
builder.addConstructorArgValue(BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".aggregator.DefaultAggregatingMessageGroupProcessor").getBeanDefinition());
}
if (innerHandlerDefinition != null) {
processorBuilder.addConstructorArgValue(innerHandlerDefinition);
} else {
if (StringUtils.hasText(ref)) {
processorBuilder.addConstructorArgReference(ref);
}
}
if (StringUtils.hasText(element.getAttribute(METHOD_ATTRIBUTE))) {
String method = element.getAttribute(METHOD_ATTRIBUTE);
processorBuilder.getRawBeanDefinition().getConstructorArgumentValues().addGenericArgumentValue(method, "java.lang.String");
}
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element,
MESSAGE_STORE_ATTRIBUTE);
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element,
DISCARD_CHANNEL_ATTRIBUTE);
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element,
OUTPUT_CHANNEL_ATTRIBUTE);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element,
SEND_TIMEOUT_ATTRIBUTE);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element,
SEND_PARTIAL_RESULT_ON_EXPIRY_ATTRIBUTE);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "auto-startup");
this.injectPropertyWithBean(RELEASE_STRATEGY_REF_ATTRIBUTE,
RELEASE_STRATEGY_METHOD_ATTRIBUTE, RELEASE_STRATEGY_PROPERTY,
"ReleaseStrategyAdapter", element, builder, parserContext);
this.injectPropertyWithBean(CORRELATION_STRATEGY_REF_ATTRIBUTE,
CORRELATION_STRATEGY_METHOD_ATTRIBUTE, CORRELATION_STRATEGY_PROPERTY,
"CorrelationStrategyAdapter", element, builder, parserContext);
return builder;
}
private void injectPropertyWithBean(String beanRefAttribute, String methodRefAttribute,
String beanProperty, String adapterClass, Element element,
BeanDefinitionBuilder builder, ParserContext parserContext) {
final String beanRef = element.getAttribute(beanRefAttribute);
final String beanMethod = element.getAttribute(methodRefAttribute);
if (StringUtils.hasText(beanRef)) {
if (StringUtils.hasText(beanMethod)) {
String adapterBeanName = this.createAdapter(beanRef, beanMethod, adapterClass,
parserContext);
builder.addPropertyReference(beanProperty, adapterBeanName);
} else {
builder.addPropertyReference(beanProperty, beanRef);
}
}
}
private String createAdapter(String ref, String method, String unqualifiedClassName,
ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".aggregator." + unqualifiedClassName);
builder.addConstructorArgReference(ref);
builder.getRawBeanDefinition().getConstructorArgumentValues().addGenericArgumentValue(method, "java.lang.String");
return BeanDefinitionReaderUtils.registerWithGeneratedName(builder.getBeanDefinition(),
parserContext.getRegistry());
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.config.xml;
import org.w3c.dom.Element;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
/**
* Parser for the &lt;annotation-config&gt; element of the integration namespace.
* Adds a {@link org.springframework.integration.config.annotation.MessagingAnnotationPostProcessor}
* to the application context.
*
* @author Mark Fisher
*/
public class AnnotationConfigParser extends AbstractSingleBeanDefinitionParser {
private final static String PACKAGE_NAME = IntegrationNamespaceUtils.BASE_PACKAGE + ".config.annotation";
@Override
protected String getBeanClassName(Element element) {
return PACKAGE_NAME + ".MessagingAnnotationPostProcessor";
}
@Override
protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext)
throws BeanDefinitionStoreException {
return PACKAGE_NAME + ".internalMessagingAnnotationPostProcessor";
}
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2002-2009 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.config.xml;
import java.util.concurrent.CopyOnWriteArraySet;
import org.w3c.dom.Element;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.core.SpringVersion;
import org.springframework.util.StringUtils;
/**
* Parser for the &lt;application-event-multicaster&gt; element of the
* integration namespace.
*
* @author Mark Fisher
*/
public class ApplicationEventMulticasterParser extends AbstractSingleBeanDefinitionParser {
@Override
protected String getBeanClassName(Element element) {
return "org.springframework.context.event.SimpleApplicationEventMulticaster";
}
@Override
protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext)
throws BeanDefinitionStoreException {
return AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME;
}
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
String taskExecutorRef = element.getAttribute("task-executor");
if (StringUtils.hasText(taskExecutorRef)) {
builder.addPropertyReference("taskExecutor", taskExecutorRef);
}
else {
BeanDefinitionBuilder executorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
"org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor");
executorBuilder.addPropertyValue("corePoolSize", 1);
executorBuilder.addPropertyValue("maxPoolSize", 10);
executorBuilder.addPropertyValue("queueCapacity", 0);
executorBuilder.addPropertyValue("threadNamePrefix", "event-multicaster-");
String executorBeanName = BeanDefinitionReaderUtils.registerWithGeneratedName(
executorBuilder.getBeanDefinition(), parserContext.getRegistry());
builder.addPropertyReference("taskExecutor", executorBeanName);
}
String springVersion = SpringVersion.getVersion();
if (springVersion != null && springVersion.startsWith("2")) {
builder.addPropertyValue("collectionClass", CopyOnWriteArraySet.class);
}
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.config.xml;
import org.w3c.dom.Element;
import org.springframework.beans.factory.xml.ParserContext;
/**
* Simple strategy interface for parsers that are responsible
* for parsing an element, creating a bean definition, and then
* registering the bean. The {@link #parse(Element, ParserContext)}
* method should return the name of the registered bean.
*
* @author Mark Fisher
*/
public interface BeanDefinitionRegisteringParser {
String parse(Element element, ParserContext parserContext);
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.config.xml;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
/**
* Parser for the &lt;bridge&gt; element.
*
* @author Mark Fisher
*/
public class BridgeParser extends AbstractConsumerEndpointParser {
@Override
protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) {
return BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".handler.BridgeHandler");
}
}

Some files were not shown because too many files have changed in this diff Show More