Updated the names of the projects

This commit is contained in:
Ben Hale
2008-05-20 21:41:01 +00:00
parent 37f8d925c8
commit 6696064dd0
531 changed files with 48 additions and 48 deletions

View File

@@ -0,0 +1 @@
http\://www.springframework.org/schema/integration=org.springframework.integration.config.IntegrationNamespaceHandler

View File

@@ -0,0 +1 @@
http\://www.springframework.org/schema/integration/spring-integration-core-1.0.xsd=org/springframework/integration/config/spring-integration-core-1.0.xsd

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;
/**
* Exception that indicates an incorrectly configured integration component.
*
* @author Mark Fisher
*/
@SuppressWarnings("serial")
public class ConfigurationException extends RuntimeException {
public ConfigurationException(String description) {
super(description);
}
public ConfigurationException(String description, Throwable cause) {
super(description, cause);
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2002-2007 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.router.AggregatingMessageHandler;
/**
* Indicates that a method is capable of aggregating messages.
*
* @author Marius Bogoevici
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Handler
public @interface Aggregator {
String defaultReplyChannel() default "";
String discardChannel() default "";
long sendTimeout() default AggregatingMessageHandler.DEFAULT_SEND_TIMEOUT;
long timeout() default AggregatingMessageHandler.DEFAULT_TIMEOUT;
boolean sendPartialResultsOnTimeout() default false;
long reaperInterval() default AggregatingMessageHandler.DEFAULT_REAPER_INTERVAL;
int trackedCorrelationIdCapacity() default AggregatingMessageHandler.DEFAULT_TRACKED_CORRRELATION_ID_CAPACITY;
}

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 CompletionStrategy {
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2002-2007 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.integration.endpoint.ConcurrencyPolicy;
/**
* Defines the {@link ConcurrencyPolicy} settings for a {@link MessageEndpoint @MessageEndpoint}.
*
* @author Mark Fisher
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Concurrency {
int coreSize() default ConcurrencyPolicy.DEFAULT_CORE_SIZE;
int maxSize() default ConcurrencyPolicy.DEFAULT_MAX_SIZE;
int queueCapacity() default ConcurrencyPolicy.DEFAULT_QUEUE_CAPACITY;
int keepAliveSeconds() default ConcurrencyPolicy.DEFAULT_KEEP_ALIVE_SECONDS;
}

View File

@@ -0,0 +1,41 @@
/*
* 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 org.springframework.integration.message.Message;
/**
* Indicates that a method is capable of sending messages. The method must
* accept a single parameter that is either a {@link Message} or an Object to
* be passed as a message payload. The enclosing class should be annotated with
* {@link MessageEndpoint @MessageEndpoint}.
*
* @author Mark Fisher
*/
@java.lang.annotation.Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface DefaultOutput {
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2002-2007 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.
* The method may only accept a single parameter, and the enclosing class should
* be annotated with {@link MessageEndpoint @MessageEndpoint}.
*
* @author Mark Fisher
*/
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Handler {
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2002-2007 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;
/**
* Indicates 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 {
String input() default "";
String output() default "";
int pollPeriod() default 0;
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2002-2007 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 java.util.concurrent.TimeUnit;
import org.springframework.integration.scheduling.PollingSchedule;
/**
* Annotation that can be specified at class-level alongside a
* {@link MessageEndpoint @MessageEndpoint} annotation in order to provide the
* scheduling information for that endpoint. Alternatively, as a method-level
* annotation, this indicates that a method is capable of providing messages.
* The method must not accept any parameters but can return either a single
* object or collection. The enclosing class should be annotated with
* {@link MessageEndpoint @MessageEndpoint}.
*
* @author Mark Fisher
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Polled {
int period() default 0;
long initialDelay() default PollingSchedule.DEFAULT_INITIAL_DELAY;
boolean fixedRate() default PollingSchedule.DEFAULT_FIXED_RATE;
TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2002-2007 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 the method's return value should be published to the specified
* channel. The value will only be published if non-null.
*
* @author Mark Fisher
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Publisher {
String channel();
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2002-2007 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 payload, message attribute, or message property.
*
* @author Mark Fisher
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Handler
public @interface Router {
}

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;
/**
* Indicates that a method is capable of splitting a single message or message
* payload to produce multiple messages or payloads.
*
* @author Mark Fisher
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Handler
public @interface Splitter {
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2002-2007 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-invoking handler adapter should delegate to this
* method.
*
* @author Mark Fisher
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Subscriber {
String channel();
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2002-2007 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 org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.support.AopUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.integration.channel.ChannelRegistry;
import org.springframework.integration.channel.MessageChannel;
import org.springframework.util.Assert;
/**
* {@link MessagePublishingInterceptor} that resolves the channel from the
* publisher annotation of the invoked method.
*
* @author Mark Fisher
*/
public class AnnotationAwareMessagePublishingInterceptor extends MessagePublishingInterceptor {
private Class<? extends Annotation> publisherAnnotationType;
private String channelAttributeName;
private ChannelRegistry channelRegistry;
public AnnotationAwareMessagePublishingInterceptor(Class<? extends Annotation> publisherAnnotationType,
String channelAttributeName, ChannelRegistry channelRegistry) {
Assert.notNull(publisherAnnotationType, "'publisherAnnotationType' must not be null");
Assert.notNull(channelAttributeName, "'channelAttributeName' must not be null");
Assert.notNull(channelRegistry, "'channelRegistry' must not be null");
this.publisherAnnotationType = publisherAnnotationType;
this.channelAttributeName = channelAttributeName;
this.channelRegistry = channelRegistry;
}
@Override
protected MessageChannel resolveChannel(MethodInvocation invocation) {
Class<?> targetClass = AopUtils.getTargetClass(invocation.getThis());
Method method = AopUtils.getMostSpecificMethod(invocation.getMethod(), targetClass);
Annotation annotation = AnnotationUtils.getAnnotation(method, this.publisherAnnotationType);
if (annotation != null) {
String channelName = (String) AnnotationUtils.getValue(annotation, this.channelAttributeName);
if (channelName != null) {
MessageChannel channel = this.channelRegistry.lookupChannel(channelName);
if (channel != null) {
return channel;
}
}
}
return super.resolveChannel(invocation);
}
}

View File

@@ -0,0 +1,86 @@
/*
* 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.aop;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.integration.channel.MessageChannel;
import org.springframework.integration.message.GenericMessage;
import org.springframework.integration.message.Message;
import org.springframework.integration.message.MessageCreator;
/**
* Interceptor that publishes a target method's return value to a channel.
*
* @author Mark Fisher
*/
public class MessagePublishingInterceptor implements MethodInterceptor {
protected final Log logger = LogFactory.getLog(getClass());
private volatile MessageCreator messageCreator;
private volatile MessageChannel defaultChannel;
public void setDefaultChannel(MessageChannel defaultChannel) {
this.defaultChannel = defaultChannel;
}
/**
* Specify the {@link MessageCreator} to use when creating a message from the
* return value Object.
*
* @param messageCreator the MessageCreator to use
*/
public void setMessageCreator(MessageCreator messageCreator) {
this.messageCreator = messageCreator;
}
/**
* Invoke the target method and publish its return value.
*/
public Object invoke(MethodInvocation invocation) throws Throwable {
Object retval = invocation.proceed();
if (retval != null) {
MessageChannel channel = this.resolveChannel(invocation);
if (channel == null) {
if (logger.isWarnEnabled()) {
logger.warn("unable to resolve channel for intercepted method '" +
invocation.getMethod().getName() + "'");
}
}
else {
Message<?> message = (this.messageCreator != null) ? this.messageCreator.createMessage(retval) : new GenericMessage<Object>(retval);
channel.send(message);
}
}
return retval;
}
/**
* Subclasses may override this method to provide custom behavior.
*/
protected MessageChannel resolveChannel(MethodInvocation invocation) {
return this.defaultChannel;
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2002-2007 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 org.aopalliance.aop.Advice;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.AbstractPointcutAdvisor;
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
import org.springframework.integration.annotation.Publisher;
import org.springframework.integration.channel.ChannelRegistry;
import org.springframework.util.Assert;
/**
* Advisor whose pointcut matches a method annotation and whose advice will
* publish a message to the channel provided by that annotation.
*
* @author Mark Fisher
* @see Publisher
* @see AnnotationAwareMessagePublishingInterceptor
*/
@SuppressWarnings("serial")
public class PublisherAnnotationAdvisor extends AbstractPointcutAdvisor {
private AnnotationAwareMessagePublishingInterceptor advice;
private AnnotationMatchingPointcut pointcut;
public PublisherAnnotationAdvisor(ChannelRegistry channelRegistry) {
this(Publisher.class, "channel", channelRegistry);
}
public PublisherAnnotationAdvisor(Class<? extends Annotation> publisherAnnotationType, String channelNameAttribute,
ChannelRegistry channelRegistry) {
Assert.notNull(publisherAnnotationType, "'publisherAnnotationType' must not be null");
Assert.notNull(channelNameAttribute, "'channelNameAttribute' must not be null");
Assert.notNull(channelRegistry, "'channelRegistry' must not be null");
this.pointcut = AnnotationMatchingPointcut.forMethodAnnotation(publisherAnnotationType);
this.advice = new AnnotationAwareMessagePublishingInterceptor(publisherAnnotationType, channelNameAttribute,
channelRegistry);
}
public Pointcut getPointcut() {
return this.pointcut;
}
public Advice getAdvice() {
return this.advice;
}
}

View File

@@ -0,0 +1,72 @@
/*
* 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.bus;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.integration.channel.MessageChannel;
import org.springframework.integration.channel.RendezvousChannel;
import org.springframework.integration.channel.interceptor.ChannelInterceptorAdapter;
import org.springframework.integration.message.Message;
/**
* The default error channel implementation used by the {@link MessageBus}.
*
* @author Mark Fisher
*/
public class DefaultErrorChannel extends RendezvousChannel {
private final Log logger = LogFactory.getLog(this.getClass());
public DefaultErrorChannel() {
this.addInterceptor(new ErrorLoggingInterceptor());
}
private class ErrorLoggingInterceptor extends ChannelInterceptorAdapter {
@Override
public void postSend(Message<?> message, MessageChannel channel, boolean sent) {
if (!sent) {
if (logger.isWarnEnabled()) {
logger.warn("Error channel rejected Message. Are any handlers subscribed? " + message);
}
}
}
/**
* Even if the error channel has no subscribers, errors are at least visible at debug level.
*/
@Override
public boolean preSend(Message<?> message, MessageChannel channel) {
if (logger.isDebugEnabled()) {
String errorMessage = "Error Received. Message: " + message.toString();
Object payload = message.getPayload();
if (payload instanceof Throwable) {
logger.debug(errorMessage, (Throwable) payload);
}
else {
logger.debug(errorMessage);
}
}
return true;
}
}
}

View File

@@ -0,0 +1,520 @@
/*
* 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.bus;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.Lifecycle;
import org.springframework.context.event.ApplicationEventMulticaster;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.SimpleApplicationEventMulticaster;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.integration.ConfigurationException;
import org.springframework.integration.channel.ChannelRegistry;
import org.springframework.integration.channel.ChannelRegistryAware;
import org.springframework.integration.channel.DefaultChannelRegistry;
import org.springframework.integration.channel.MessageChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.channel.factory.ChannelFactory;
import org.springframework.integration.channel.factory.QueueChannelFactory;
import org.springframework.integration.endpoint.ConcurrencyPolicy;
import org.springframework.integration.endpoint.DefaultEndpointRegistry;
import org.springframework.integration.endpoint.EndpointRegistry;
import org.springframework.integration.endpoint.HandlerEndpoint;
import org.springframework.integration.endpoint.MessageEndpoint;
import org.springframework.integration.endpoint.SourceEndpoint;
import org.springframework.integration.endpoint.TargetEndpoint;
import org.springframework.integration.handler.MessageHandler;
import org.springframework.integration.message.Target;
import org.springframework.integration.scheduling.MessagePublishingErrorHandler;
import org.springframework.integration.scheduling.MessagingTask;
import org.springframework.integration.scheduling.MessagingTaskScheduler;
import org.springframework.integration.scheduling.Schedule;
import org.springframework.integration.scheduling.SimpleMessagingTaskScheduler;
import org.springframework.integration.scheduling.Subscription;
import org.springframework.util.Assert;
/**
* The messaging bus. Serves as a registry for channels and endpoints, manages their lifecycle,
* and activates subscriptions.
*
* @author Mark Fisher
* @author Marius Bogoevici
*/
public class MessageBus implements ChannelRegistry, EndpointRegistry, ApplicationContextAware, ApplicationListener, Lifecycle {
public static final String ERROR_CHANNEL_NAME = "errorChannel";
private static final int DEFAULT_DISPATCHER_POOL_SIZE = 10;
private final Log logger = LogFactory.getLog(this.getClass());
private volatile ChannelFactory channelFactory = new QueueChannelFactory();
private final ChannelRegistry channelRegistry = new DefaultChannelRegistry();
private final EndpointRegistry endpointRegistry = new DefaultEndpointRegistry();
private final Map<MessageChannel, SubscriptionManager> subscriptionManagers = new ConcurrentHashMap<MessageChannel, SubscriptionManager>();
private final List<Lifecycle> lifecycleEndpoints = new CopyOnWriteArrayList<Lifecycle>();
private volatile MessagingTaskScheduler taskScheduler;
private volatile ScheduledExecutorService executor;
private volatile ConcurrencyPolicy defaultConcurrencyPolicy;
private volatile boolean configureAsyncEventMulticaster = false;
private volatile boolean autoCreateChannels = false;
private volatile boolean autoStartup = true;
private volatile boolean initialized;
private volatile boolean initializing;
private volatile boolean starting;
private volatile boolean running;
private final Object lifecycleMonitor = new Object();
/**
* Set the {@link ChannelFactory} to use for auto-creating channels.
*/
public void setChannelFactory(ChannelFactory channelFactory) {
this.channelFactory = channelFactory;
}
public ChannelFactory getChannelFactory() {
return channelFactory;
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Assert.notNull(applicationContext, "'applicationContext' must not be null");
if (applicationContext.getBeanNamesForType(this.getClass()).length > 1) {
throw new ConfigurationException("Only one instance of '" + this.getClass().getSimpleName()
+ "' is allowed per ApplicationContext.");
}
this.registerChannels(applicationContext);
}
/**
* Set the {@link ScheduledExecutorService} to use for scheduling message dispatchers.
*/
public void setScheduledExecutorService(ScheduledExecutorService executor) {
this.executor = executor;
}
/**
* Specify the default concurrency policy to be used for any endpoint that
* is registered without an explicitly provided policy of its own.
*/
public void setDefaultConcurrencyPolicy(ConcurrencyPolicy defaultConcurrencyPolicy) {
this.defaultConcurrencyPolicy = defaultConcurrencyPolicy;
}
/**
* Set whether to automatically start the bus after initialization.
* <p>Default is 'true'; set this to 'false' to allow for manual startup
* through the {@link #start()} method.
*/
public void setAutoStartup(boolean autoStartup) {
this.autoStartup = autoStartup;
}
/**
* Set whether the bus should automatically create a channel when a
* subscription contains the name of a previously unregistered channel.
*/
public void setAutoCreateChannels(boolean autoCreateChannels) {
this.autoCreateChannels = autoCreateChannels;
}
/**
* Set whether the bus should configure its asynchronous task executor
* to also be used by the ApplicationContext's 'applicationEventMulticaster'.
* This will only apply if the multicaster defined within the context
* is an instance of SimpleApplicationEventMulticaster (the default).
* This property is 'false' by default.
*/
public void setConfigureAsyncEventMulticaster(boolean configureAsyncEventMulticaster) {
this.configureAsyncEventMulticaster = configureAsyncEventMulticaster;
}
@SuppressWarnings("unchecked")
private void registerChannels(ApplicationContext context) {
Map<String, MessageChannel> channelBeans =
(Map<String, MessageChannel>) context.getBeansOfType(MessageChannel.class);
for (Map.Entry<String, MessageChannel> entry : channelBeans.entrySet()) {
this.registerChannel(entry.getKey(), entry.getValue());
}
}
@SuppressWarnings("unchecked")
private void registerEndpoints(ApplicationContext context) {
Map<String, MessageEndpoint> endpointBeans =
(Map<String, MessageEndpoint>) context.getBeansOfType(MessageEndpoint.class);
for (Map.Entry<String, MessageEndpoint> entry : endpointBeans.entrySet()) {
this.registerEndpoint(entry.getKey(), entry.getValue());
}
}
public void initialize() {
synchronized (this.lifecycleMonitor) {
if (this.initialized || this.initializing) {
return;
}
this.initializing = true;
if (this.executor == null) {
this.executor = new ScheduledThreadPoolExecutor(DEFAULT_DISPATCHER_POOL_SIZE);
}
this.taskScheduler = new SimpleMessagingTaskScheduler(this.executor);
if (this.getErrorChannel() == null) {
this.setErrorChannel(new DefaultErrorChannel());
}
this.taskScheduler.setErrorHandler(new MessagePublishingErrorHandler(this.getErrorChannel()));
this.initialized = true;
this.initializing = false;
}
}
public MessageChannel getErrorChannel() {
return this.lookupChannel(ERROR_CHANNEL_NAME);
}
public void setErrorChannel(MessageChannel errorChannel) {
this.registerChannel(ERROR_CHANNEL_NAME, errorChannel);
}
public MessageChannel lookupChannel(String channelName) {
return this.channelRegistry.lookupChannel(channelName);
}
public void registerChannel(String name, MessageChannel channel) {
if (!this.initialized) {
this.initialize();
}
channel.setName(name);
SubscriptionManager manager = new SubscriptionManager(channel, this.taskScheduler);
this.subscriptionManagers.put(channel, manager);
this.channelRegistry.registerChannel(name, channel);
if (logger.isInfoEnabled()) {
logger.info("registered channel '" + name + "'");
}
}
public MessageChannel unregisterChannel(String name) {
MessageChannel removedChannel = this.channelRegistry.unregisterChannel(name);
if (removedChannel != null) {
SubscriptionManager manager = this.subscriptionManagers.remove(removedChannel);
if (manager != null && manager.isRunning()) {
manager.stop();
}
}
return removedChannel;
}
public void registerHandler(String name, MessageHandler handler, Subscription subscription) {
this.registerHandler(name, handler, subscription, this.defaultConcurrencyPolicy);
}
public void registerHandler(String name, MessageHandler handler, Subscription subscription, ConcurrencyPolicy concurrencyPolicy) {
Assert.notNull(handler, "'handler' must not be null");
HandlerEndpoint endpoint = new HandlerEndpoint(handler);
this.doRegisterEndpoint(name, endpoint, subscription, concurrencyPolicy);
}
public void registerTarget(String name, Target target, Subscription subscription) {
this.registerTarget(name, target, subscription, this.defaultConcurrencyPolicy);
}
public void registerTarget(String name, Target target, Subscription subscription, ConcurrencyPolicy concurrencyPolicy) {
Assert.notNull(target, "'target' must not be null");
TargetEndpoint endpoint = new TargetEndpoint(target);
this.doRegisterEndpoint(name, endpoint, subscription, concurrencyPolicy);
}
private void doRegisterEndpoint(String name, TargetEndpoint endpoint, Subscription subscription, ConcurrencyPolicy concurrencyPolicy) {
endpoint.setName(name);
endpoint.setSubscription(subscription);
endpoint.setConcurrencyPolicy(concurrencyPolicy);
this.registerEndpoint(name, endpoint);
}
public void registerEndpoint(String name, MessageEndpoint endpoint) {
if (!this.initialized) {
this.initialize();
}
if (endpoint instanceof ChannelRegistryAware) {
((ChannelRegistryAware) endpoint).setChannelRegistry(this.channelRegistry);
}
if (endpoint instanceof TargetEndpoint) {
this.registerTargetEndpoint(name, (TargetEndpoint) endpoint);
}
else if (endpoint instanceof SourceEndpoint) {
this.registerSourceEndpoint(name, (SourceEndpoint) endpoint);
}
this.endpointRegistry.registerEndpoint(name, endpoint);
if (this.isRunning()) {
activateEndpoint(endpoint);
}
if (logger.isInfoEnabled()) {
logger.info("registered endpoint '" + name + "'");
}
}
private void registerTargetEndpoint(String name, TargetEndpoint endpoint) {
if (endpoint.getConcurrencyPolicy() == null && this.defaultConcurrencyPolicy != null) {
endpoint.setConcurrencyPolicy(this.defaultConcurrencyPolicy);
}
endpoint.afterPropertiesSet();
}
public MessageEndpoint unregisterEndpoint(String name) {
MessageEndpoint endpoint = this.endpointRegistry.unregisterEndpoint(name);
if (endpoint == null) {
return null;
}
if (endpoint instanceof TargetEndpoint) {
Collection<SubscriptionManager> managers = this.subscriptionManagers.values();
boolean removed = false;
for (SubscriptionManager manager : managers) {
removed = (removed || manager.removeTarget((TargetEndpoint) endpoint));
}
if (removed) {
return endpoint;
}
}
return null;
}
public MessageEndpoint lookupEndpoint(String endpointName) {
return this.endpointRegistry.lookupEndpoint(endpointName);
}
public Set<String> getEndpointNames() {
return this.endpointRegistry.getEndpointNames();
}
private void activateEndpoints() {
Set<String> endpointNames = this.endpointRegistry.getEndpointNames();
for (String name : endpointNames) {
MessageEndpoint endpoint = this.endpointRegistry.lookupEndpoint(name);
if (endpoint != null) {
this.activateEndpoint(endpoint);
}
}
}
private void activateEndpoint(MessageEndpoint endpoint) {
if (endpoint instanceof TargetEndpoint) {
this.activateTargetEndpoint((TargetEndpoint) endpoint);
}
}
private void activateTargetEndpoint(TargetEndpoint endpoint) {
Subscription subscription = endpoint.getSubscription();
if (subscription == null) {
throw new ConfigurationException("Unable to register endpoint '" +
endpoint + "'. No subscription information is available.");
}
MessageChannel channel = subscription.getChannel();
if (channel == null) {
String channelName = subscription.getChannelName();
if (channelName == null) {
throw new ConfigurationException("endpoint '" + endpoint +
"' must provide either 'channel' or 'channelName' in its subscription metadata");
}
channel = this.lookupChannel(channelName);
if (channel == null) {
if (this.autoCreateChannels == false) {
throw new ConfigurationException("Cannot activate subscription, unknown channel '" + channelName +
"'. Consider enabling the 'autoCreateChannels' option for the message bus.");
}
if (this.logger.isInfoEnabled()) {
logger.info("auto-creating channel '" + channelName + "'");
}
channel = channelFactory.getChannel(null, null);
this.registerChannel(channelName, channel);
}
}
if (endpoint instanceof HandlerEndpoint) {
HandlerEndpoint handlerEndpoint = (HandlerEndpoint) endpoint;
String outputChannelName = handlerEndpoint.getOutputChannelName();
if (outputChannelName != null && this.lookupChannel(outputChannelName) == null) {
if (!this.autoCreateChannels) {
throw new ConfigurationException("Unknown channel '" + outputChannelName +
"' configured as output channel for endpoint '" + endpoint +
"'. Consider enabling the 'autoCreateChannels' option for the message bus.");
}
this.registerChannel(outputChannelName, new QueueChannel());
}
}
if (endpoint instanceof TargetEndpoint) {
TargetEndpoint targetEndpoint = (TargetEndpoint) endpoint;
if (!targetEndpoint.hasErrorHandler() && this.getErrorChannel() != null && !this.getErrorChannel().equals(channel)) {
targetEndpoint.setErrorHandler(new MessagePublishingErrorHandler(this.getErrorChannel()));
}
}
this.activateSubscription(channel, endpoint, subscription.getSchedule());
if (logger.isInfoEnabled()) {
logger.info("activated subscription to channel '" + channel.getName() +
"' for endpoint '" + endpoint + "'");
}
}
private void registerSourceEndpoint(String name, SourceEndpoint endpoint) {
if (!this.initialized) {
this.initialize();
}
if (endpoint instanceof MessagingTask) {
this.taskScheduler.schedule((MessagingTask) endpoint);
}
if (endpoint instanceof Lifecycle) {
this.lifecycleEndpoints.add((Lifecycle) endpoint);
if (this.isRunning()) {
((Lifecycle) endpoint).start();
}
}
if (logger.isInfoEnabled()) {
logger.info("registered source adapter '" + name + "'");
}
}
private void activateSubscription(MessageChannel channel, Target target, Schedule schedule) {
SubscriptionManager manager = this.subscriptionManagers.get(channel);
if (manager == null) {
if (logger.isWarnEnabled()) {
logger.warn("no subscription manager available for channel '" + channel + "', be sure to register the channel");
}
return;
}
manager.addTarget(target, schedule);
if (this.isRunning() && !manager.isRunning()) {
manager.start();
}
}
public boolean isRunning() {
synchronized (this.lifecycleMonitor) {
return this.running;
}
}
public void start() {
if (!this.initialized) {
this.initialize();
}
if (this.isRunning() || this.starting) {
return;
}
this.starting = true;
synchronized (this.lifecycleMonitor) {
this.activateEndpoints();
this.taskScheduler.start();
for (SubscriptionManager manager : this.subscriptionManagers.values()) {
manager.start();
if (logger.isInfoEnabled()) {
logger.info("started subscription manager '" + manager + "'");
}
}
for (Lifecycle endpoint : this.lifecycleEndpoints) {
endpoint.start();
if (logger.isInfoEnabled()) {
logger.info("started endpoint '" + endpoint + "'");
}
}
}
this.running = true;
this.starting = false;
if (logger.isInfoEnabled()) {
logger.info("message bus started");
}
}
public void stop() {
if (!this.isRunning()) {
return;
}
synchronized (this.lifecycleMonitor) {
this.running = false;
this.taskScheduler.stop();
for (Lifecycle endpoint : this.lifecycleEndpoints) {
endpoint.stop();
if (logger.isInfoEnabled()) {
logger.info("stopped endpoint '" + endpoint + "'");
}
}
for (SubscriptionManager manager : this.subscriptionManagers.values()) {
manager.stop();
if (logger.isInfoEnabled()) {
logger.info("stopped subscription manager '" + manager + "'");
}
}
}
if (logger.isInfoEnabled()) {
logger.info("message bus stopped");
}
}
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ContextRefreshedEvent) {
ApplicationContext context = ((ContextRefreshedEvent) event).getApplicationContext();
this.registerEndpoints(context);
if (this.configureAsyncEventMulticaster) {
this.initialize();
this.doConfigureAsyncEventMulticaster(context);
}
if (this.autoStartup) {
this.start();
}
}
}
private void doConfigureAsyncEventMulticaster(ApplicationContext context) {
String multicasterBeanName = AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME;
if (context.containsBean(multicasterBeanName)) {
ApplicationEventMulticaster multicaster =
(ApplicationEventMulticaster) context.getBean(multicasterBeanName);
if (multicaster instanceof SimpleApplicationEventMulticaster) {
((SimpleApplicationEventMulticaster) multicaster).setTaskExecutor(this.taskScheduler);
}
}
}
}

View File

@@ -0,0 +1,28 @@
/*
* 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.bus;
/**
* Interface to be implemented by classes which need access to the {@link MessageBus}.
* @author Marius Bogoevici
*
*/
public interface MessageBusAware {
public void setMessageBus(MessageBus messageBus);
}

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.bus;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.util.Assert;
/**
* A bean post processor which injects all {@link MessageBusAware} beans with a
* reference to the {@link MessageBus}.
*
* @author Marius Bogoevici
*
*/
public class MessageBusAwareBeanPostProcessor implements BeanPostProcessor {
private final MessageBus messageBus;
public MessageBusAwareBeanPostProcessor(MessageBus messageBus) {
Assert.notNull(messageBus, "'messageBus' must not be null");
this.messageBus = messageBus;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof MessageBusAware) {
((MessageBusAware) bean).setMessageBus(messageBus);
}
return bean;
}
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}

View File

@@ -0,0 +1,187 @@
/*
* 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.bus;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.Lifecycle;
import org.springframework.integration.ConfigurationException;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.MessageChannel;
import org.springframework.integration.dispatcher.DefaultPollingDispatcher;
import org.springframework.integration.dispatcher.PollingDispatcherTask;
import org.springframework.integration.endpoint.TargetEndpoint;
import org.springframework.integration.message.MessagingException;
import org.springframework.integration.message.Target;
import org.springframework.integration.scheduling.MessagingTaskScheduler;
import org.springframework.integration.scheduling.PollingSchedule;
import org.springframework.integration.scheduling.Schedule;
import org.springframework.integration.util.ErrorHandler;
import org.springframework.util.Assert;
/**
* Manages subscriptions for {@link Target Targets} to a {@link MessageChannel}
* including the creation, scheduling, and lifecycle management of dispatchers.
*
* @author Mark Fisher
*/
public class SubscriptionManager {
private final Log logger = LogFactory.getLog(this.getClass());
private final MessageChannel channel;
private final MessagingTaskScheduler scheduler;
private volatile Schedule defaultSchedule = new PollingSchedule(5);
private final ConcurrentMap<Schedule, PollingDispatcherTask> dispatcherTasks = new ConcurrentHashMap<Schedule, PollingDispatcherTask>();
private final List<Lifecycle> lifecycleTargets = new CopyOnWriteArrayList<Lifecycle>();
private volatile boolean running;
private final Object lifecycleMonitor = new Object();
public SubscriptionManager(MessageChannel channel, MessagingTaskScheduler scheduler) {
Assert.notNull(channel, "channel must not be null");
Assert.notNull(scheduler, "scheduler must not be null");
this.channel = channel;
this.scheduler = scheduler;
}
public void setDefaultSchedule(Schedule defaultSchedule) {
Assert.notNull(defaultSchedule, "'defaultSchedule' must not be null");
this.defaultSchedule = defaultSchedule;
}
public void addTarget(Target target) {
this.addTarget(target, null);
}
public void addTarget(Target target, Schedule schedule) {
Assert.notNull(target, "'target' must not be null");
if (schedule == null) {
schedule = this.defaultSchedule;
}
else if (this.channel instanceof DirectChannel) {
if (logger.isInfoEnabled()) {
logger.info("Subscribing to a SynchronousChannel. The provided schedule will be ignored.");
}
}
else if (this.channel.getDispatcherPolicy().isPublishSubscribe()) {
if (logger.isInfoEnabled()) {
logger.info("This dispatcher broadcasts messages for a publish-subscribe channel. " +
"Therefore all targets are scheduled with its 'defaultSchedule', " +
"and the provided schedule will be ignored.");
}
schedule = this.defaultSchedule;
}
if (target instanceof Lifecycle) {
this.lifecycleTargets.add((Lifecycle) target);
if (this.isRunning()) {
((Lifecycle) target).start();
}
}
if (this.channel instanceof DirectChannel) {
((DirectChannel) this.channel).subscribe(target);
if (target instanceof TargetEndpoint) {
((TargetEndpoint) target).setErrorHandler(new ErrorHandler() {
public void handle(Throwable t) {
if (t instanceof MessagingException) {
throw (MessagingException) t;
}
throw new MessagingException("error occurred in handler", t);
}
});
}
return;
}
PollingDispatcherTask dispatcherTask = this.dispatcherTasks.get(schedule);
if (dispatcherTask == null) {
DefaultPollingDispatcher dispatcher = new DefaultPollingDispatcher(this.channel);
dispatcherTask = this.dispatcherTasks.putIfAbsent(schedule, new PollingDispatcherTask(dispatcher, schedule));
}
this.dispatcherTasks.get(schedule).getDispatcher().subscribe(target);
if (dispatcherTask == null && this.isRunning()) {
this.scheduleDispatcherTask(schedule);
}
}
public boolean removeTarget(Target target) {
boolean removed = false;
Collection<PollingDispatcherTask> dispatcherTaskValues = this.dispatcherTasks.values();
for (PollingDispatcherTask dispatcherTask : dispatcherTaskValues) {
removed = (removed || dispatcherTask.getDispatcher().unsubscribe(target));
}
return removed;
}
public boolean isRunning() {
return this.running;
}
public void start() {
synchronized (this.lifecycleMonitor) {
if (this.running) {
return;
}
if (this.scheduler == null) {
throw new ConfigurationException("scheduler is required");
}
if (!this.scheduler.isRunning()) {
this.scheduler.start();
}
for (Lifecycle target : lifecycleTargets) {
target.start();
}
for (Schedule schedule : this.dispatcherTasks.keySet()) {
this.scheduleDispatcherTask(schedule);
}
this.running = true;
}
}
private void scheduleDispatcherTask(Schedule schedule) {
PollingDispatcherTask dispatcherTask = this.dispatcherTasks.get(schedule);
if (dispatcherTask != null) {
this.scheduler.schedule(dispatcherTask);
}
}
public void stop() {
synchronized (this.lifecycleMonitor) {
if (!this.running) {
return;
}
for (Lifecycle target : lifecycleTargets) {
target.stop();
}
this.running = false;
}
}
}

View File

@@ -0,0 +1,257 @@
/*
* 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.List;
import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.integration.message.Message;
/**
* Base class for {@link MessageChannel} implementations providing common
* properties such as the channel name and {@link DispatcherPolicy}. 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 implements MessageChannel, BeanNameAware {
private final Log logger = LogFactory.getLog(this.getClass());
private volatile String name;
private final ChannelInterceptorList interceptors = new ChannelInterceptorList();
private final DispatcherPolicy dispatcherPolicy;
/**
* Create a channel with the given dispatcher policy.
*/
public AbstractMessageChannel(DispatcherPolicy dispatcherPolicy) {
this.dispatcherPolicy = dispatcherPolicy;
}
/**
* Set the name of this channel.
*/
public void setName(String name) {
this.name = name;
}
/**
* Return the name of this channel.
*/
public String getName() {
return this.name;
}
/**
* Set the name of this channel to its bean name. This will be invoked
* automatically whenever the channel is configured explicitly with a bean
* definition.
*/
public void setBeanName(String beanName) {
this.setName(beanName);
}
/**
* Set the list of channel interceptors. This will clear any existing
* interceptors.
*/
public void setInterceptors(List<ChannelInterceptor> interceptors) {
this.interceptors.set(interceptors);
}
/**
* Add a channel interceptor to the end of the list.
*/
public void addInterceptor(ChannelInterceptor interceptor) {
this.interceptors.add(interceptor);
}
/**
* Return the dispatcher policy for this channel.
*/
public DispatcherPolicy getDispatcherPolicy() {
return this.dispatcherPolicy;
}
/**
* 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) {
if (!this.interceptors.preSend(message, this)) {
return false;
}
boolean sent = this.doSend(message, timeout);
this.interceptors.postSend(message, this, sent);
return sent;
}
/**
* 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.interceptors.preReceive(this)) {
return null;
}
Message<?> message = this.doReceive(timeout);
this.interceptors.postReceive(message, this);
return message;
}
public String toString() {
return (this.name != null) ? this.name : super.toString();
}
/**
* 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);
/**
* 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);
/**
* A convenience wrapper class for the list of ChannelInterceptors.
*/
private 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 boolean preSend(Message<?> message, MessageChannel channel) {
if (logger.isDebugEnabled()) {
logger.debug("preSend on channel '" + channel + "', message: " + message);
}
for (ChannelInterceptor interceptor : interceptors) {
if (!interceptor.preSend(message, channel)) {
return false;
}
}
return true;
}
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.isDebugEnabled()) {
logger.debug("preReceive on channel '" + channel + "'");
}
for (ChannelInterceptor interceptor : interceptors) {
if (!interceptor.preReceive(channel)) {
return false;
}
}
return true;
}
public void postReceive(Message<?> message, MessageChannel channel) {
if (logger.isDebugEnabled()) {
logger.debug("postReceive on channel '" + channel + "', message: " + message);
}
for (ChannelInterceptor interceptor : interceptors) {
interceptor.postReceive(message, channel);
}
}
}
}

View File

@@ -0,0 +1,38 @@
/*
* 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.message.Message;
/**
* 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 {
boolean preSend(Message<?> message, MessageChannel channel);
void postSend(Message<?> message, MessageChannel channel, boolean sent);
boolean preReceive(MessageChannel channel);
void postReceive(Message<?> message, MessageChannel channel);
}

View File

@@ -0,0 +1,77 @@
/*
* 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.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.integration.message.Message;
import org.springframework.util.Assert;
/**
* Sends to a channel and provides a configurable timeout. Convenient for either
* subclassing or delegation from components that need to publish to a channel.
*
* @author Mark Fisher
*/
public class ChannelPublisher {
private final Log logger = LogFactory.getLog(this.getClass());
private volatile MessageChannel channel;
private volatile long timeout = 0;
public ChannelPublisher() {
}
public ChannelPublisher(MessageChannel channel) {
this.setChannel(channel);
}
public void setChannel(MessageChannel channel) {
Assert.notNull(channel, "channel must not be null");
this.channel = channel;
}
public void setTimeout(long timeout) {
this.timeout = timeout;
}
protected MessageChannel getChannel() {
return this.channel;
}
public boolean publish(Message<?> message) {
if (this.channel == null) {
if (logger.isWarnEnabled()) {
logger.warn("unable to send message, no channel available");
}
return false;
}
if (message == null) {
if (logger.isWarnEnabled()) {
logger.warn("null messages are not supported");
}
return false;
}
return (this.timeout < 0) ? this.channel.send(message) : this.channel.send(message, this.timeout);
}
}

View File

@@ -0,0 +1,75 @@
/*
* 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.ArrayList;
import java.util.List;
import org.springframework.integration.message.Message;
import org.springframework.integration.message.selector.MessageSelector;
import org.springframework.util.Assert;
/**
* A utility class for purging {@link Message Messages} from one or more
* {@link MessageChannel MessageChannels}. 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 MessageChannel[] channels;
private final MessageSelector selector;
public ChannelPurger(MessageChannel ... channels) {
this(null, channels);
}
public ChannelPurger(MessageSelector selector, MessageChannel ... 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 (MessageChannel 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,32 @@
/*
* Copyright 2002-2007 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;
/**
* A strategy interface for registration and lookup of message channels by name.
*
* @author Mark Fisher
*/
public interface ChannelRegistry {
void registerChannel(String name, MessageChannel channel);
MessageChannel unregisterChannel(String name);
MessageChannel lookupChannel(String channelName);
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2002-2007 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;
/**
* Interface to be implemented by components that need access to the
* {@link ChannelRegistry}.
*
* @author Mark Fisher
*/
public interface ChannelRegistryAware {
void setChannelRegistry(ChannelRegistry channelRegistry);
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2002-2007 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.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.util.Assert;
/**
* A simple map-backed implementation of {@link ChannelRegistry}.
*
* @author Mark Fisher
*/
public class DefaultChannelRegistry implements ChannelRegistry {
private final Map<String, MessageChannel> channels = new ConcurrentHashMap<String, MessageChannel>();
public MessageChannel lookupChannel(String channelName) {
return this.channels.get(channelName);
}
public void registerChannel(String name, MessageChannel channel) {
Assert.notNull(name, "'name' must not be null");
Assert.notNull(channel, "'channel' must not be null");
this.channels.put(name, channel);
}
public MessageChannel unregisterChannel(String name) {
return (name != null) ? this.channels.remove(name) : null;
}
}

View File

@@ -0,0 +1,112 @@
/*
* 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.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import org.springframework.integration.dispatcher.SimpleDispatcher;
import org.springframework.integration.handler.MessageHandler;
import org.springframework.integration.message.Message;
import org.springframework.integration.message.Source;
import org.springframework.integration.message.Subscribable;
import org.springframework.integration.message.Target;
import org.springframework.integration.message.selector.MessageSelector;
/**
* A channel that invokes the subscribed {@link MessageHandler handler(s)} in a
* sender's thread (returning after at most one handles the message). If a
* {@link Source} is provided, then that source will likewise be polled
* within a receiver's thread.
*
* @author Dave Syer
* @author Mark Fisher
*/
public class DirectChannel extends AbstractMessageChannel implements Subscribable {
private volatile Source<?> source;
private final SimpleDispatcher dispatcher;
private final AtomicInteger handlerCount = new AtomicInteger();
public DirectChannel() {
this(null);
}
public DirectChannel(Source<?> source) {
super(defaultDispatcherPolicy());
this.source = source;
this.dispatcher = new SimpleDispatcher(this.getDispatcherPolicy());
}
public boolean subscribe(Target target) {
boolean added = this.dispatcher.subscribe(target);
if (added) {
this.handlerCount.incrementAndGet();
}
return added;
}
public boolean unsubscribe(Target target) {
boolean removed = this.dispatcher.unsubscribe(target);
if (removed) {
this.handlerCount.decrementAndGet();
}
return removed;
}
@Override
protected Message<?> doReceive(long timeout) {
if (this.source != null) {
return this.source.receive();
}
return null;
}
@Override
protected boolean doSend(Message<?> message, long timeout) {
if (message != null && this.handlerCount.get() > 0) {
return this.dispatcher.dispatch(message);
}
return false;
}
public List<Message<?>> clear() {
return new ArrayList<Message<?>>();
}
public List<Message<?>> purge(MessageSelector selector) {
return new ArrayList<Message<?>>();
}
private static DispatcherPolicy defaultDispatcherPolicy() {
DispatcherPolicy dispatcherPolicy = new DispatcherPolicy(false);
dispatcherPolicy.setMaxMessagesPerTask(1);
dispatcherPolicy.setReceiveTimeout(0);
dispatcherPolicy.setRejectionLimit(1);
dispatcherPolicy.setRetryInterval(0);
dispatcherPolicy.setShouldFailOnRejectionLimit(false);
return dispatcherPolicy;
}
}

View File

@@ -0,0 +1,150 @@
/*
* Copyright 2002-2007 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.MessageDispatcher;
import org.springframework.util.Assert;
/**
* Metadata for a {@link MessageDispatcher}.
*
* @author Mark Fisher
*/
public class DispatcherPolicy {
public final static int DEFAULT_MAX_MESSAGES_PER_TASK = 1;
public final static long DEFAULT_RECEIVE_TIMEOUT = 1000;
public final static int DEFAULT_REJECTION_LIMIT = 5;
public final static long DEFAULT_RETRY_INTERVAL = 1000;
private final boolean publishSubscribe;
private volatile int maxMessagesPerTask = DEFAULT_MAX_MESSAGES_PER_TASK;
private volatile long receiveTimeout = DEFAULT_RECEIVE_TIMEOUT;
private volatile int rejectionLimit = DEFAULT_REJECTION_LIMIT;
private volatile long retryInterval = DEFAULT_RETRY_INTERVAL;
private volatile boolean shouldFailOnRejectionLimit = true;
public DispatcherPolicy() {
this.publishSubscribe = false;
}
/**
* Create a DispatcherPolicy.
*
* @param publishSubscribe whether the dispatcher should attempt to publish
* to all of its subscribed handlers. If '<code>false</code>' it will attempt
* to send to a single handler (point-to-point).
*/
public DispatcherPolicy(boolean publishSubscribe) {
this.publishSubscribe = publishSubscribe;
}
/**
* Return whether the dispatcher should attempt to publish to all of its handlers.
* This property is immutable.
*/
public boolean isPublishSubscribe() {
return this.publishSubscribe;
}
/**
* Return the maximum number of messages for each retrieval attempt.
*/
public int getMaxMessagesPerTask() {
return this.maxMessagesPerTask;
}
/**
* Set the maximum number of messages for each retrieval attempt.
*/
public void setMaxMessagesPerTask(int maxMessagesPerTask) {
Assert.isTrue(maxMessagesPerTask > 0, "'maxMessagePerTask' must be at least 1");
this.maxMessagesPerTask = maxMessagesPerTask;
}
/**
* Return the maximum amount of time in milliseconds to wait for a message to be available.
*/
public long getReceiveTimeout() {
return this.receiveTimeout;
}
/**
* Set the maximum amount of time in milliseconds to wait for a message to be available.
*/
public void setReceiveTimeout(long receiveTimeout) {
this.receiveTimeout = receiveTimeout;
}
/**
* Return the maximum number of retries upon rejection.
*/
public int getRejectionLimit() {
return this.rejectionLimit;
}
/**
* Set the maximum number of retries upon rejection.
*/
public void setRejectionLimit(int rejectionLimit) {
Assert.isTrue(rejectionLimit > 0, "'rejectionLimit' must be at least 1");
this.rejectionLimit = rejectionLimit;
}
/**
* Return the amount of time in milliseconds to wait between rejections.
*/
public long getRetryInterval() {
return this.retryInterval;
}
/**
* Set the amount of time in milliseconds to wait between rejections.
*/
public void setRetryInterval(long retryInterval) {
Assert.isTrue(retryInterval >= 0, "'retryInterval' must not be negative");
this.retryInterval = retryInterval;
}
/**
* Return whether an exception should be thrown when this dispatcher's
* {@link #rejectionLimit} is reached.
*/
public boolean getShouldFailOnRejectionLimit() {
return this.shouldFailOnRejectionLimit;
}
/**
* Specify whether an exception should be thrown when this dispatcher's
* {@link #rejectionLimit} is reached. The default value is 'true'.
*/
public void setShouldFailOnRejectionLimit(boolean shouldFailOnRejectionLimit) {
this.shouldFailOnRejectionLimit = shouldFailOnRejectionLimit;
}
}

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.channel;
import java.util.List;
import org.springframework.integration.message.BlockingSource;
import org.springframework.integration.message.BlockingTarget;
import org.springframework.integration.message.Message;
import org.springframework.integration.message.selector.MessageSelector;
/**
* Base channel interface defining common behavior for message sending and receiving.
*
* @author Mark Fisher
*/
public interface MessageChannel extends BlockingSource, BlockingTarget {
/**
* Return the name of this channel.
*/
String getName();
/**
* Set the name of this channel.
*/
void setName(String name);
/**
* Return this channel's dispatcher policy
*/
DispatcherPolicy getDispatcherPolicy();
/**
* Remove all {@link Message Messages} from this channel.
*/
List<Message<?>> clear();
/**
* Remove any {@link Message Messages} that are not accepted by the provided selector.
*/
List<Message<?>> purge(MessageSelector selector);
}

View File

@@ -0,0 +1,107 @@
/*
* 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.Comparator;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import org.springframework.integration.message.Message;
import org.springframework.integration.message.MessageHeader;
import org.springframework.integration.message.MessagePriority;
/**
* 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 Semaphore semaphore;
/**
* Create a channel with the specified queue capacity and dispatcher policy.
* Priority will be based upon the provided {@link Comparator}.
*/
public PriorityChannel(int capacity, DispatcherPolicy dispatcherPolicy, Comparator<Message<?>> comparator) {
super(new PriorityBlockingQueue<Message<?>>(capacity, comparator), dispatcherPolicy);
this.semaphore = new Semaphore(capacity, true);
}
/**
* Create a channel with the specified queue capacity and dispatcher policy.
* Priority will be based upon the value of {@link MessageHeader#getPriority()}.
*/
public PriorityChannel(int capacity, DispatcherPolicy dispatcherPolicy) {
this(capacity, dispatcherPolicy, new MessagePriorityComparator());
}
/**
* Create a channel with the specified queue capacity and default dispatcher
* policy. Priority will be based on the value of {@link MessageHeader#getPriority()}.
*/
public PriorityChannel(int capacity) {
this(capacity, null, new MessagePriorityComparator());
}
/**
* Create a channel with the default queue capacity and dispatcher policy.
* Priority will be based on the value of {@link MessageHeader#getPriority()}.
*/
public PriorityChannel() {
this(DEFAULT_CAPACITY, null, new MessagePriorityComparator());
}
@Override
protected boolean doSend(Message<?> message, long timeout) {
try {
if (!this.semaphore.tryAcquire(timeout, TimeUnit.MILLISECONDS)) {
return false;
}
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
return super.doSend(message, 0);
}
@Override
protected Message<?> doReceive(long timeout) {
Message<?> message = super.doReceive(timeout);
if (message != null) {
this.semaphore.release();
return message;
}
return null;
}
private static class MessagePriorityComparator implements Comparator<Message<?>> {
public int compare(Message<?> message1, Message<?> message2) {
MessagePriority priority1 = message1.getHeader().getPriority();
MessagePriority priority2 = message2.getHeader().getPriority();
return priority1.compareTo(priority2);
}
}
}

View File

@@ -0,0 +1,136 @@
/*
* 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.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import org.springframework.integration.message.Message;
import org.springframework.integration.message.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 AbstractMessageChannel {
public static final int DEFAULT_CAPACITY = 100;
private final BlockingQueue<Message<?>> queue;
/**
* Create a channel with the specified queue and dispatcher policy.
*/
public QueueChannel(BlockingQueue<Message<?>> queue, DispatcherPolicy dispatcherPolicy) {
super((dispatcherPolicy != null) ? dispatcherPolicy : new DispatcherPolicy());
Assert.notNull(queue, "'queue' must not be null");
this.queue = queue;
}
/**
* Create a channel with the specified queue capacity and dispatcher policy.
*/
public QueueChannel(int capacity, DispatcherPolicy dispatcherPolicy) {
super((dispatcherPolicy != null) ? dispatcherPolicy : new DispatcherPolicy());
Assert.isTrue(capacity > 0, "The capacity must be a positive integer. " +
"For a zero-capacity alternative, consider '" + RendezvousChannel.class + "'");
this.queue = new LinkedBlockingQueue<Message<?>>(capacity);
}
/**
* Create a channel with the specified queue capacity.
*/
public QueueChannel(int capacity) {
this(capacity, null);
}
/**
* Create a channel with the default queue capacity.
* @see #DEFAULT_CAPACITY
*/
public QueueChannel() {
this(DEFAULT_CAPACITY, null);
}
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;
}
}
public List<Message<?>> clear() {
List<Message<?>> clearedMessages = new ArrayList<Message<?>>();
this.queue.drainTo(clearedMessages);
return clearedMessages;
}
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;
}
}

View File

@@ -0,0 +1,40 @@
/*
* 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.message.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(DispatcherPolicy dispatcherPolicy) {
super(new SynchronousQueue<Message<?>>(), dispatcherPolicy);
}
public RendezvousChannel() {
this(null);
}
}

View File

@@ -0,0 +1,269 @@
/*
* 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.List;
import org.springframework.integration.ConfigurationException;
import org.springframework.integration.bus.MessageBus;
import org.springframework.integration.bus.MessageBusAware;
import org.springframework.integration.endpoint.EndpointRegistry;
import org.springframework.integration.endpoint.HandlerEndpoint;
import org.springframework.integration.handler.ReplyHandler;
import org.springframework.integration.handler.ReplyMessageCorrelator;
import org.springframework.integration.message.Message;
import org.springframework.integration.message.MessagingException;
import org.springframework.integration.message.selector.MessageSelector;
import org.springframework.integration.scheduling.Subscription;
/**
* A template that facilitates the implementation of request-reply usage
* scenarios above one-way {@link MessageChannel MessageChannels}.
*
* @author Mark Fisher
*/
public class RequestReplyTemplate implements MessageBusAware {
private MessageChannel requestChannel;
private MessageChannel replyChannel;
private volatile long requestTimeout = -1;
private volatile long replyTimeout = -1;
private ReplyMessageCorrelator replyMessageCorrelator;
private EndpointRegistry endpointRegistry;
private final Object replyMessageCorrelatorMonitor = new Object();
/**
* Create a RequestReplyTemplate.
*
* @param requestChannel the channel to which request messages will be sent
* @param replyChannel the channel from which reply messages will be received
*/
public RequestReplyTemplate(MessageChannel requestChannel, MessageChannel replyChannel) {
this.requestChannel = requestChannel;
this.replyChannel = replyChannel;
}
/**
* Create a RequestReplyTemplate that will use anonymous temporary channels for replies.
*
* @param requestChannel the channel to which request messages will be sent
*/
public RequestReplyTemplate(MessageChannel requestChannel) {
this(requestChannel, null);
}
public RequestReplyTemplate() {
}
/**
* Set the request channel.
*
* @param requestChannel the channel to which request messages will be sent
*/
public void setRequestChannel(MessageChannel requestChannel) {
this.requestChannel = requestChannel;
}
/**
* Set the reply channel. If no reply channel is provided, this template will
* always use an anonymous, temporary channel for handling replies.
*
* @param replyChannel the channel from which reply messages will be received
*/
public void setReplyChannel(MessageChannel replyChannel) {
this.replyChannel = replyChannel;
}
/**
* Set the timeout value for sending request messages. If not
* explicitly configured, the default is an indefinite timeout.
*
* @param requestTimeout the timeout value in milliseconds
*/
public void setRequestTimeout(long requestTimeout) {
this.requestTimeout = requestTimeout;
}
/**
* Set the timeout value for receiving reply messages. If not
* explicitly configured, the default is an indefinite timeout.
*
* @param replyTimeout the timeout value in milliseconds
*/
public void setReplyTimeout(long replyTimeout) {
this.replyTimeout = replyTimeout;
}
public void setEndpointRegistry(EndpointRegistry endpointRegistry) {
this.endpointRegistry = endpointRegistry;
}
public void setMessageBus(MessageBus messageBus) {
if (this.endpointRegistry == null) {
this.setEndpointRegistry(messageBus);
}
}
public boolean send(Message<?> message) {
if (message == null) {
throw new MessagingException("Message must not be null.");
}
if (this.requestChannel == null) {
throw new MessagingException("No request channel has been configured. Cannot send message.");
}
boolean sent = (this.requestTimeout >= 0) ?
this.requestChannel.send(message, this.requestTimeout) : this.requestChannel.send(message);
if (!sent) {
throw new MessagingException("Failed to send request message.");
}
return true;
}
public Message<?> receive() {
if (this.replyChannel == null) {
throw new MessagingException("No reply channel has been configured. Cannot perform receive only operation.");
}
return this.receiveResponse(this.replyChannel);
}
/**
* Send a request message whose reply should be handled be the provided callback.
*/
public boolean request(Message<?> message, ReplyHandler replyHandler) {
MessageChannel replyChannelAdapter = new ReplyHandlingChannelAdapter(message, replyHandler);
message.getHeader().setReturnAddress(replyChannelAdapter);
return this.send(message);
}
/**
* Send a request message and wait for a reply message using the configured
* timeout values.
*
* @param requestMessage the request message to send
*
* @return the reply message or <code>null</code>
*/
public Message<?> request(Message<?> message) {
if (this.requestChannel == null) {
throw new MessagingException("No request channel available. Cannot send request message.");
}
if (this.replyChannel != null) {
return this.sendAndReceiveWithReplyMessageCorrelator(message);
}
else {
return this.sendAndReceiveWithTemporaryChannel(message);
}
}
private Message<?> sendAndReceiveWithReplyMessageCorrelator(Message<?> message) {
if (this.replyMessageCorrelator == null) {
this.registerReplyMessageCorrelator();
}
message.getHeader().setReturnAddress(this.replyChannel);
this.send(message);
return (this.replyTimeout >= 0) ? this.replyMessageCorrelator.getReply(message.getId(), this.replyTimeout) :
this.replyMessageCorrelator.getReply(message.getId());
}
private Message<?> sendAndReceiveWithTemporaryChannel(Message<?> message) {
RendezvousChannel temporaryChannel = new RendezvousChannel();
message.getHeader().setReturnAddress(temporaryChannel);
this.send(message);
return this.receiveResponse(temporaryChannel);
}
private Message<?> receiveResponse(MessageChannel channel) {
return (this.replyTimeout >= 0) ? channel.receive(this.replyTimeout) : channel.receive();
}
private void registerReplyMessageCorrelator() {
synchronized (this.replyMessageCorrelatorMonitor) {
if (this.replyMessageCorrelator != null) {
return;
}
if (this.endpointRegistry == null) {
throw new ConfigurationException("No EndpointRegistry available. Cannot register ResponseCorrelator.");
}
ReplyMessageCorrelator correlator = new ReplyMessageCorrelator(10);
HandlerEndpoint endpoint = new HandlerEndpoint(correlator);
endpoint.setSubscription(new Subscription(this.replyChannel));
this.endpointRegistry.registerEndpoint("internal.correlator." + this, endpoint);
this.replyMessageCorrelator = correlator;
}
}
private static class ReplyHandlingChannelAdapter implements MessageChannel {
private final Message<?> originalMessage;
private final ReplyHandler replyHandler;
ReplyHandlingChannelAdapter(Message<?> originalMessage, ReplyHandler replyHandler) {
this.originalMessage = originalMessage;
this.replyHandler = replyHandler;
}
public List<Message<?>> clear() {
return null;
}
public DispatcherPolicy getDispatcherPolicy() {
return null;
}
public String getName() {
return null;
}
public List<Message<?>> purge(MessageSelector selector) {
return null;
}
public void setName(String name) {
}
public Message receive() {
return null;
}
public Message receive(long timeout) {
return null;
}
public boolean send(Message<?> message) {
this.replyHandler.handle(message, originalMessage.getHeader());
return true;
}
public boolean send(Message<?> message, long timeout) {
return this.send(message);
}
}
}

View File

@@ -0,0 +1,108 @@
/*
* 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.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
import org.springframework.integration.message.Message;
import org.springframework.integration.message.selector.MessageSelector;
/**
* 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.
*
* @author Dave Syer
* @author Mark Fisher
*/
public class ThreadLocalChannel extends AbstractMessageChannel {
private static final ThreadLocalMessageHolder messageHolder = new ThreadLocalMessageHolder();
public ThreadLocalChannel() {
super(defaultDispatcherPolicy());
}
@Override
protected Message<?> doReceive(long timeout) {
return messageHolder.get().poll();
}
@Override
protected boolean doSend(Message<?> message, long timeout) {
if (message == null) {
return false;
}
return messageHolder.get().add(message);
}
/**
* Remove and return any messages that are stored for the current thread.
*/
public List<Message<?>> clear() {
List<Message<?>> removedMessages = new ArrayList<Message<?>>();
Message<?> next = messageHolder.get().poll();
while (next != null) {
removedMessages.add(next);
next = messageHolder.get().poll();
}
return removedMessages;
}
/**
* Remove and return any messages that are stored for the current thread
* and do not match the provided selector.
*/
public List<Message<?>> purge(MessageSelector selector) {
List<Message<?>> removedMessages = new ArrayList<Message<?>>();
Object[] allMessages = messageHolder.get().toArray();
for (Object next : allMessages) {
Message<?> message = (Message<?>) next;
if (!selector.accept(message) && messageHolder.get().remove(message)) {
removedMessages.add(message);
}
}
return removedMessages;
}
private static DispatcherPolicy defaultDispatcherPolicy() {
DispatcherPolicy dispatcherPolicy = new DispatcherPolicy(false);
dispatcherPolicy.setMaxMessagesPerTask(1);
dispatcherPolicy.setReceiveTimeout(0);
dispatcherPolicy.setRejectionLimit(1);
dispatcherPolicy.setRetryInterval(0);
dispatcherPolicy.setShouldFailOnRejectionLimit(false);
return dispatcherPolicy;
}
private static class ThreadLocalMessageHolder extends ThreadLocal<Queue<Message<?>>> {
@Override
protected Queue<Message<?>> initialValue() {
return new LinkedBlockingQueue<Message<?>>();
}
}
}

View File

@@ -0,0 +1,130 @@
/*
* 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.config;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.channel.DispatcherPolicy;
import org.springframework.integration.channel.interceptor.MessageSelectingInterceptor;
import org.springframework.integration.message.selector.PayloadTypeSelector;
import org.springframework.util.StringUtils;
/**
* Base class for channel parsers.
*
* @author Mark Fisher
*/
public abstract class AbstractChannelParser extends AbstractSingleBeanDefinitionParser {
private static final String PUBLISH_SUBSCRIBE_ATTRIBUTE = "publish-subscribe";
private static final String DISPATCHER_POLICY_ELEMENT = "dispatcher-policy";
private static final String DATATYPE_ATTRIBUTE = "datatype";
private static final String INTERCEPTOR_ELEMENT = "interceptor";
private static final String INTERCEPTORS_PROPERTY = "interceptors";
@Override
protected boolean shouldGenerateId() {
return false;
}
@Override
protected boolean shouldGenerateIdAsFallback() {
return true;
}
@Override
protected abstract Class<?> getBeanClass(Element element);
protected abstract void configureConstructorArgs(
BeanDefinitionBuilder builder, Element element, DispatcherPolicy dispatcherPolicy);
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
boolean isPublishSubscribe = "true".equals(element.getAttribute(PUBLISH_SUBSCRIBE_ATTRIBUTE));
DispatcherPolicy dispatcherPolicy = new DispatcherPolicy(isPublishSubscribe);
ManagedList interceptors = new ManagedList();
NodeList childNodes = element.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
Node child = childNodes.item(i);
if (child.getNodeType() == Node.ELEMENT_NODE) {
String localName = child.getLocalName();
if (DISPATCHER_POLICY_ELEMENT.equals(localName)) {
configureDispatcherPolicy((Element) child, dispatcherPolicy);
}
else if (INTERCEPTOR_ELEMENT.equals(localName)) {
String ref = ((Element) child).getAttribute("ref");
interceptors.add(new RuntimeBeanReference(ref));
}
}
}
String datatypeAttr = element.getAttribute(DATATYPE_ATTRIBUTE);
if (StringUtils.hasText(datatypeAttr)) {
String[] datatypes = StringUtils.commaDelimitedListToStringArray(datatypeAttr);
RootBeanDefinition selectorDef = new RootBeanDefinition(PayloadTypeSelector.class);
selectorDef.getConstructorArgumentValues().addGenericArgumentValue(datatypes);
String selectorBeanName = parserContext.getReaderContext().generateBeanName(selectorDef);
BeanComponentDefinition selectorComponent = new BeanComponentDefinition(selectorDef, selectorBeanName);
parserContext.registerBeanComponent(selectorComponent);
RootBeanDefinition interceptorDef = new RootBeanDefinition(MessageSelectingInterceptor.class);
interceptorDef.getConstructorArgumentValues().addGenericArgumentValue(new RuntimeBeanReference(selectorBeanName));
String interceptorBeanName = parserContext.getReaderContext().generateBeanName(interceptorDef);
BeanComponentDefinition interceptorComponent = new BeanComponentDefinition(interceptorDef, interceptorBeanName);
parserContext.registerBeanComponent(interceptorComponent);
interceptors.add(new RuntimeBeanReference(interceptorBeanName));
}
builder.addPropertyValue(INTERCEPTORS_PROPERTY, interceptors);
this.configureConstructorArgs(builder, element, dispatcherPolicy);
}
private void configureDispatcherPolicy(Element element, DispatcherPolicy dispatcherPolicy) {
String maxMessagesPerTask = element.getAttribute("max-messages-per-task");
if (StringUtils.hasText(maxMessagesPerTask)) {
dispatcherPolicy.setMaxMessagesPerTask(Integer.parseInt(maxMessagesPerTask));
}
String receiveTimeout = element.getAttribute("receive-timeout");
if (StringUtils.hasText(receiveTimeout)) {
dispatcherPolicy.setReceiveTimeout(Long.parseLong(receiveTimeout));
}
String rejectionLimit = element.getAttribute("rejection-limit");
if (StringUtils.hasText(rejectionLimit)) {
dispatcherPolicy.setRejectionLimit(Integer.parseInt(rejectionLimit));
}
String retryInterval = element.getAttribute("retry-interval");
if (StringUtils.hasText(retryInterval)) {
dispatcherPolicy.setRetryInterval(Long.parseLong(retryInterval));
}
String shouldFailOnRejectionLimit = element.getAttribute("should-fail-on-rejection-limit");
if (StringUtils.hasText(shouldFailOnRejectionLimit)) {
dispatcherPolicy.setShouldFailOnRejectionLimit("true".equals(shouldFailOnRejectionLimit));
}
}
}

View File

@@ -0,0 +1,41 @@
/*
* 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.config;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.integration.channel.DispatcherPolicy;
import org.springframework.integration.channel.factory.DefaultChannelFactoryBean;
import org.w3c.dom.Element;
/**
* Parser for the &lt;channel&gt; element.
*
* @author Marius Bogoevici
*/
public class DefaultChannelParser extends AbstractChannelParser {
@Override
protected Class<?> getBeanClass(Element element) {
return DefaultChannelFactoryBean.class;
}
@Override
protected void configureConstructorArgs(BeanDefinitionBuilder builder, Element element, DispatcherPolicy dispatcherPolicy) {
builder.addConstructorArgValue(dispatcherPolicy);
}
}

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.config;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.DispatcherPolicy;
import org.springframework.util.StringUtils;
/**
* Parser for the &lt;direct-channel&gt; element.
*
* @author Mark Fisher
*/
public class DirectChannelParser extends AbstractChannelParser {
@Override
protected Class<?> getBeanClass(Element element) {
return DirectChannel.class;
}
@Override
protected void configureConstructorArgs(BeanDefinitionBuilder builder, Element element, DispatcherPolicy dispatcherPolicy) {
String source = element.getAttribute("source");
if (StringUtils.hasText(source)) {
builder.addConstructorArgReference(source);
}
}
}

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.channel.config;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.integration.channel.DispatcherPolicy;
import org.springframework.integration.channel.PriorityChannel;
import org.springframework.util.StringUtils;
/**
* Parser for the &lt;priority-channel&gt; element.
*
* @author Mark Fisher
*/
public class PriorityChannelParser extends QueueChannelParser {
@Override
protected Class<?> getBeanClass(Element element) {
return PriorityChannel.class;
}
@Override
protected void configureConstructorArgs(BeanDefinitionBuilder builder, Element element, DispatcherPolicy dispatcherPolicy) {
super.configureConstructorArgs(builder, element, dispatcherPolicy);
String comparator = element.getAttribute("comparator");
if (StringUtils.hasText(comparator)) {
builder.addConstructorArgReference(comparator);
}
}
}

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.channel.config;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.integration.channel.DispatcherPolicy;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.util.StringUtils;
/**
* Parser for the &lt;queue-channel&gt; element.
*
* @author Mark Fisher
*/
public class QueueChannelParser extends AbstractChannelParser {
@Override
protected Class<?> getBeanClass(Element element) {
return QueueChannel.class;
}
@Override
protected void configureConstructorArgs(BeanDefinitionBuilder builder, Element element, DispatcherPolicy dispatcherPolicy) {
String capacityAttribute = element.getAttribute("capacity");
int capacity = (StringUtils.hasText(capacityAttribute)) ?
Integer.parseInt(capacityAttribute) : QueueChannel.DEFAULT_CAPACITY;
builder.addConstructorArgValue(capacity);
builder.addConstructorArgValue(dispatcherPolicy);
}
}

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.channel.config;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.integration.channel.DispatcherPolicy;
import org.springframework.integration.channel.RendezvousChannel;
/**
* Parser for the &lt;rendezvous-channel&gt; element.
*
* @author Mark Fisher
*/
public class RendezvousChannelParser extends AbstractChannelParser {
@Override
protected Class<?> getBeanClass(Element element) {
return RendezvousChannel.class;
}
@Override
protected void configureConstructorArgs(BeanDefinitionBuilder builder, Element element, DispatcherPolicy dispatcherPolicy) {
builder.addConstructorArgValue(dispatcherPolicy);
}
}

View File

@@ -0,0 +1,41 @@
/*
* 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.config;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.integration.channel.DispatcherPolicy;
import org.springframework.integration.channel.ThreadLocalChannel;
/**
* Parser for the &lt;thread-local-channel&gt; element.
*
* @author Mark Fisher
*/
public class ThreadLocalChannelParser extends AbstractChannelParser {
@Override
protected Class<?> getBeanClass(Element element) {
return ThreadLocalChannel.class;
}
@Override
protected void configureConstructorArgs(BeanDefinitionBuilder builder, Element element, DispatcherPolicy dispatcherPolicy) {
}
}

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.channel.factory;
import java.util.List;
import org.springframework.integration.channel.AbstractMessageChannel;
import org.springframework.integration.channel.ChannelInterceptor;
import org.springframework.integration.channel.DispatcherPolicy;
import org.springframework.integration.channel.MessageChannel;
/**
* Base class for {@link ChannelFactory} implementations. Subclasses should
* override {@literal createChannelInternal()}.
*
* @author Marius Bogoevici
*/
public abstract class AbstractChannelFactory implements ChannelFactory {
public AbstractChannelFactory() {
super();
}
public final MessageChannel getChannel(DispatcherPolicy dispatcherPolicy, List<ChannelInterceptor> interceptors) {
AbstractMessageChannel channel = createChannelInternal(dispatcherPolicy);
if (null != interceptors) {
channel.setInterceptors(interceptors);
}
return channel;
}
/**
* Factory method to be overridden by subclasses. It assumes that subclasses will return
* subclasses of AbstractMessageChannel.
*/
protected abstract AbstractMessageChannel createChannelInternal(DispatcherPolicy dispatcherPolicy);
}

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.channel.factory;
import java.util.List;
import org.springframework.integration.channel.ChannelInterceptor;
import org.springframework.integration.channel.DispatcherPolicy;
import org.springframework.integration.channel.MessageChannel;
/**
* Interface for a channel factory.
* @author Marius Bogoevici
*/
public interface ChannelFactory {
/**
* Creates a channel, based on the provided dispatcher policy, and with the given interceptors.
* @return
*/
MessageChannel getChannel(DispatcherPolicy dispatcherPolicy, List<ChannelInterceptor> interceptors);
}

View File

@@ -0,0 +1,83 @@
/*
* 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.factory;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.integration.bus.MessageBus;
import org.springframework.integration.channel.ChannelInterceptor;
import org.springframework.integration.channel.DispatcherPolicy;
import org.springframework.integration.channel.MessageChannel;
import org.springframework.util.Assert;
/**
* Creates a channel by delegating to the current message bus' configured
* ChannelFactory. Tries to retrieve the {@link ChannelFactory} from the
* single {@link MessageBus} defined in the {@link ApplicationContext}.
* As a {@link FactoryBean}, this class is solely intended to be used within
* an ApplicationContext.
*
* @author Marius Bogoevici
*/
public class DefaultChannelFactoryBean implements ApplicationContextAware, FactoryBean{
private volatile ChannelFactory channelFactory;
private volatile List<ChannelInterceptor> interceptors;
private volatile DispatcherPolicy dispatcherPolicy;
public DefaultChannelFactoryBean(DispatcherPolicy dispatcherPolicy) {
this.dispatcherPolicy = dispatcherPolicy;
}
@SuppressWarnings("unchecked")
public void setApplicationContext(ApplicationContext applicationContext){
Map map = applicationContext.getBeansOfType(MessageBus.class);
Assert.state(map.size() <= 1, "There is more than one MessageBus in the ApplicationContext");
if (map.isEmpty()) {
this.channelFactory = new QueueChannelFactory();
}
else {
this.channelFactory = ((MessageBus) map.values().iterator().next()).getChannelFactory();
}
}
public void setInterceptors(List<ChannelInterceptor> interceptors) {
this.interceptors = interceptors;
}
public Object getObject() throws Exception {
Assert.notNull(channelFactory, "ChannelFactory not set on this instance. Is this used within an ApplicationContext?");
return channelFactory.getChannel(dispatcherPolicy, interceptors);
}
public Class<?> getObjectType() {
return MessageChannel.class;
}
public boolean isSingleton() {
return true;
}
}

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.channel.factory;
import org.springframework.integration.channel.AbstractMessageChannel;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.DispatcherPolicy;
/**
* A {@link ChannelFactory} for creating {@link DirectChannel} instances.
*
* @author Marius Bogoevici
*/
public class DirectChannelFactory extends AbstractChannelFactory {
@Override
protected AbstractMessageChannel createChannelInternal(DispatcherPolicy dispatcherPolicy) {
return new DirectChannel(null);
}
}

View File

@@ -0,0 +1,53 @@
/*
* 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.factory;
import java.util.Comparator;
import org.springframework.integration.channel.AbstractMessageChannel;
import org.springframework.integration.channel.DispatcherPolicy;
import org.springframework.integration.channel.PriorityChannel;
import org.springframework.integration.message.Message;
import org.springframework.util.Assert;
/**
* A {@link ChannelFactory} for creating {@link PriorityChannel} instances.
* @author Marius Bogoevici
*
*/
public class PriorityChannelFactory extends AbstractChannelFactory {
private int capacity = PriorityChannel.DEFAULT_CAPACITY;
private Comparator<Message<?>> comparator;
public void setCapacity(int capacity) {
Assert.isTrue(capacity > 0, "capacity must be a positive value");
this.capacity = capacity;
}
public void setComparator(Comparator<Message<?>> comparator) {
this.comparator = comparator;
}
@Override
protected AbstractMessageChannel createChannelInternal(DispatcherPolicy dispatcherPolicy) {
return new PriorityChannel(this.capacity, dispatcherPolicy, this.comparator);
}
}

View File

@@ -0,0 +1,53 @@
/*
* 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.factory;
import org.springframework.integration.channel.AbstractMessageChannel;
import org.springframework.integration.channel.DispatcherPolicy;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.util.Assert;
/**
* Default implementation of a {@link ChannelFactory}, which will create instances of a
* {@link QueueChannel}.
*
* @author Marius Bogoevici
*/
public class QueueChannelFactory extends AbstractChannelFactory{
int queueCapacity = QueueChannel.DEFAULT_CAPACITY;
public int getQueueCapacity() {
return queueCapacity;
}
/**
* Sets the queue capacity for the newly created channels.
* By default, the queue capacity is {@literal QueueChannel.DEFAULT_CAPACITY}
* @param queueCapacity
*/
public void setQueueCapacity(int queueCapacity) {
Assert.state(queueCapacity > 0, "Queue capacity must be greater than zero");
this.queueCapacity = queueCapacity;
}
protected AbstractMessageChannel createChannelInternal(DispatcherPolicy dispatcherPolicy) {
return new QueueChannel(queueCapacity, dispatcherPolicy);
}
}

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.channel.factory;
import org.springframework.integration.channel.AbstractMessageChannel;
import org.springframework.integration.channel.DispatcherPolicy;
import org.springframework.integration.channel.RendezvousChannel;
/**
* A {@link ChannelFactory} for creating {@link RendezvousChannel} instances.
*
* @author Marius Bogoevici
*/
public class RendezvousChannelFactory extends AbstractChannelFactory {
@Override
protected AbstractMessageChannel createChannelInternal(DispatcherPolicy dispatcherPolicy) {
return new RendezvousChannel(dispatcherPolicy);
}
}

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.channel.factory;
import org.springframework.integration.channel.AbstractMessageChannel;
import org.springframework.integration.channel.DispatcherPolicy;
import org.springframework.integration.channel.ThreadLocalChannel;
/**
* A {@link ChannelFactory} implementation for creating {@link ThreadLocalChannel} instances.
*
* @author Mark Fisher
*/
public class ThreadLocalChannelFactory extends AbstractChannelFactory {
@Override
protected AbstractMessageChannel createChannelInternal(DispatcherPolicy dispatcherPolicy) {
return new ThreadLocalChannel();
}
}

View File

@@ -0,0 +1,45 @@
/*
* 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.channel.MessageChannel;
import org.springframework.integration.message.Message;
/**
* 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 boolean preSend(Message<?> message, MessageChannel channel) {
return true;
}
public void postSend(Message<?> message, MessageChannel channel, boolean sent) {
}
public boolean preReceive(MessageChannel channel) {
return true;
}
public void postReceive(Message<?> message, MessageChannel channel) {
}
}

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.channel.MessageChannel;
import org.springframework.integration.message.Message;
import org.springframework.integration.message.MessageDeliveryException;
import org.springframework.integration.message.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 boolean 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 true;
}
}

View File

@@ -0,0 +1,129 @@
/*
* 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 java.util.concurrent.CopyOnWriteArrayList;
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.channel.MessageChannel;
import org.springframework.integration.message.GenericMessage;
import org.springframework.integration.message.Message;
import org.springframework.integration.message.selector.MessageSelector;
import org.springframework.util.Assert;
/**
* A {@link ChannelInterceptor} that publishes a copy of the intercepted message
* to a secondary channel while still sending the original message to the main channel.
*
* @author Mark Fisher
*/
public class WireTap extends ChannelInterceptorAdapter implements Lifecycle {
/** key for the attribute containing the original Message's id */
public final static String ORIGINAL_MESSAGE_ID_KEY = "_wireTap.originalMessageId";
private final Log logger = LogFactory.getLog(this.getClass());
private final MessageChannel secondaryChannel;
private final List<MessageSelector> selectors = new CopyOnWriteArrayList<MessageSelector>();
private volatile boolean running = true;
/**
* Create a new wire tap with <em>no</em> {@link MessageSelector MessageSelectors}.
*
* @param secondaryChannel the channel to which duplicate messages will be sent
*/
public WireTap(MessageChannel secondaryChannel) {
Assert.notNull(secondaryChannel, "'secondaryChannel' must not be null");
this.secondaryChannel = secondaryChannel;
}
/**
* Create a new wire tap with {@link MessageSelector MessageSelectors}.
*
* @param secondaryChannel the channel to which duplicate messages will be sent
* @param selectors the list of selectors that must accept a message for it to
* be sent to the secondary channel
*/
public WireTap(MessageChannel secondaryChannel, List<MessageSelector> selectors) {
this(secondaryChannel);
if (selectors != null) {
this.selectors.addAll(selectors);
}
}
/**
* 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;
}
@Override
public boolean preSend(Message<?> message, MessageChannel channel) {
if (this.running && this.selectorsAccept(message)) {
Message<?> duplicate = new GenericMessage<Object>(message.getPayload(), message.getHeader());
duplicate.getHeader().setAttribute(ORIGINAL_MESSAGE_ID_KEY, message.getId());
if (!this.secondaryChannel.send(duplicate, 0)) {
if (logger.isWarnEnabled()) {
logger.warn("Failed to send message to secondary channel '" + this.secondaryChannel.getName()
+ "'. Check its capacity and whether it has any subscribers.");
}
}
}
return true;
}
/**
* If this wire tap has any {@link MessageSelector MessageSelectors}, check
* whether they accept the current message. If any of them do not accept it,
* the message will <em>not</em> be sent to the secondary channel.
*/
private boolean selectorsAccept(Message<?> message) {
for (MessageSelector selector : this.selectors) {
if (!selector.accept(message)) {
return false;
}
}
return true;
}
}

View File

@@ -0,0 +1,169 @@
/*
* 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;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.ConfigurationException;
import org.springframework.integration.endpoint.ConcurrencyPolicy;
import org.springframework.integration.scheduling.PollingSchedule;
import org.springframework.integration.scheduling.Schedule;
import org.springframework.integration.scheduling.Subscription;
import org.springframework.util.StringUtils;
/**
* Base class for <em>target-endpoint</em> and <em>handler-endpoint</em> parsers.
*
* @author Mark Fisher
*/
public abstract class AbstractTargetEndpointParser extends AbstractSingleBeanDefinitionParser {
private static final String INPUT_CHANNEL_ATTRIBUTE = "input-channel";
private static final String SUBSCRIPTION_PROPERTY = "subscription";
private static final String SELECTOR_ELEMENT = "selector";
private static final String REF_ATTRIBUTE = "ref";
private static final String SELECTORS_PROPERTY = "messageSelectors";
private static final String ERROR_HANDLER_ATTRIBUTE = "error-handler";
private static final String ERROR_HANDLER_PROPERTY = "errorHandler";
private static final String PERIOD_ATTRIBUTE = "period";
private static final String SCHEDULE_ELEMENT = "schedule";
private static final String CONCURRENCY_ELEMENT = "concurrency";
private static final String CONCURRENCY_POLICY_PROPERTY = "concurrencyPolicy";
@Override
protected boolean shouldGenerateId() {
return false;
}
@Override
protected boolean shouldGenerateIdAsFallback() {
return true;
}
@Override
protected abstract Class<?> getBeanClass(Element element);
protected abstract String getTargetAttributeName();
protected abstract Class<?> getAdapterClass();
protected void postProcess(BeanDefinitionBuilder builder, Element element) {
}
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
this.parseTarget(element, this.getTargetAttributeName(), parserContext, builder);
String inputChannel = element.getAttribute(INPUT_CHANNEL_ATTRIBUTE);
Schedule schedule = null;
ManagedList selectors = new ManagedList();
NodeList childNodes = element.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
Node child = childNodes.item(i);
if (child.getNodeType() == Node.ELEMENT_NODE) {
String localName = child.getLocalName();
if (CONCURRENCY_ELEMENT.equals(localName)) {
parseConcurrencyPolicy((Element) child, builder);
}
else if (SELECTOR_ELEMENT.equals(localName)) {
String ref = ((Element) child).getAttribute(REF_ATTRIBUTE);
selectors.add(new RuntimeBeanReference(ref));
}
else if (SCHEDULE_ELEMENT.equals(localName)) {
schedule = this.parseSchedule((Element) child);
}
}
}
if (StringUtils.hasText(inputChannel)) {
RootBeanDefinition subscriptionDef = new RootBeanDefinition(Subscription.class);
subscriptionDef.getConstructorArgumentValues().addGenericArgumentValue(inputChannel);
if (schedule != null) {
subscriptionDef.getConstructorArgumentValues().addGenericArgumentValue(schedule);
}
String subscriptionBeanName = parserContext.getReaderContext().generateBeanName(subscriptionDef);
parserContext.registerBeanComponent(new BeanComponentDefinition(subscriptionDef, subscriptionBeanName));
builder.addPropertyReference(SUBSCRIPTION_PROPERTY, subscriptionBeanName);
}
if (selectors.size() > 0) {
builder.addPropertyValue(SELECTORS_PROPERTY, selectors);
}
String errorHandlerRef = element.getAttribute(ERROR_HANDLER_ATTRIBUTE);
if (StringUtils.hasText(errorHandlerRef)) {
builder.addPropertyReference(ERROR_HANDLER_PROPERTY, errorHandlerRef);
}
this.postProcess(builder, element);
}
private void parseTarget(Element element, String attribute, ParserContext parserContext, BeanDefinitionBuilder builder) {
String ref = element.getAttribute(attribute);
if (!StringUtils.hasText(ref)) {
throw new ConfigurationException("The '" + attribute + "' attribute is required.");
}
String method = element.getAttribute("method");
if (StringUtils.hasText(method)) {
String adapterBeanName = this.parseAdapter(ref, method, parserContext);
builder.addConstructorArgReference(adapterBeanName);
}
else {
builder.addConstructorArgReference(ref);
}
}
private String parseAdapter(String ref, String method, ParserContext parserContext) {
BeanDefinition adapterDef = new RootBeanDefinition(this.getAdapterClass());
adapterDef.getPropertyValues().addPropertyValue("object", new RuntimeBeanReference(ref));
adapterDef.getPropertyValues().addPropertyValue("methodName", method);
String adapterBeanName = parserContext.getReaderContext().generateBeanName(adapterDef);
parserContext.registerBeanComponent(new BeanComponentDefinition(adapterDef, adapterBeanName));
return adapterBeanName;
}
private void parseConcurrencyPolicy(Element concurrencyElement, BeanDefinitionBuilder builder) {
ConcurrencyPolicy policy = IntegrationNamespaceUtils.parseConcurrencyPolicy(concurrencyElement);
builder.addPropertyValue(CONCURRENCY_POLICY_PROPERTY, policy);
}
private Schedule parseSchedule(Element scheduleElement) {
PollingSchedule schedule = new PollingSchedule(5);
String period = scheduleElement.getAttribute(PERIOD_ATTRIBUTE);
if (StringUtils.hasText(period)) {
schedule.setPeriod(Integer.parseInt(period));
}
return schedule;
}
}

View File

@@ -0,0 +1,164 @@
/*
* 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;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.ConfigurationException;
import org.springframework.integration.router.AggregatingMessageHandler;
import org.springframework.integration.router.AggregatorAdapter;
import org.springframework.integration.router.CompletionStrategyAdapter;
import org.springframework.util.StringUtils;
/**
* Parser for the <em>aggregator</em> element of the integration namespace.
* Registers the annotation-driven post-processors.
*
* @author Marius Bogoevici
*/
public class AggregatorParser implements BeanDefinitionParser {
public static final String ID_ATTRIBUTE = "id";
public static final String REF_ATTRIBUTE = "ref";
public static final String METHOD_ATTRIBUTE = "method";
public static final String COMPLETION_STRATEGY_ATTRIBUTE = "completion-strategy";
public static final String DEFAULT_REPLY_CHANNEL_ATTRIBUTE = "default-reply-channel";
public static final String DISCARD_CHANNEL_ATTRIBUTE = "discard-channel";
public static final String SEND_TIMEOUT_ATTRIBUTE = "send-timeout";
public static final String SEND_PARTIAL_RESULT_ON_TIMEOUT_ATTRIBUTE = "send-partial-result-on-timeout";
public static final String REAPER_INTERVAL_ATTRIBUTE = "reaper-interval";
public static final String TRACKED_CORRELATION_ID_CAPACITY_ATTRIBUTE = "tracked-correlation-id-capacity";
public static final String TIMEOUT_ATTRIBUTE = "timeout";
private static final String COMPLETION_STRATEGY_PROPERTY = "completionStrategy";
private static final String DEFAULT_REPLY_CHANNEL_PROPERTY = "defaultReplyChannel";
private static final String DISCARD_CHANNEL_PROPERTY = "discardChannel";
private static final String SEND_TIMEOUT_PROPERTY = "sendTimeout";
private static final String SEND_PARTIAL_RESULT_ON_TIMEOUT_PROPERTY = "sendPartialResultOnTimeout";
private static final String REAPER_INTERVAL_PROPERTY = "reaperInterval";
public static final String TRACKED_CORRELATION_ID_CAPACITY_PROPERTY = "trackedCorrelationIdCapacity";
public static final String TIMEOUT = "timeout";
public static final String AGGREGATOR_ELEMENT = "aggregator";
public static final String COMPLETION_STRATEGY_ELEMENT = "completion-strategy";
public BeanDefinition parse(Element element, ParserContext parserContext) {
return parseAggregatorElement(element, parserContext, true);
}
private BeanDefinition parseAggregatorElement(Element element, ParserContext parserContext, boolean topLevel) {
final RootBeanDefinition aggregatorDef = new RootBeanDefinition(AggregatingMessageHandler.class);
aggregatorDef.setSource(parserContext.extractSource(element));
final String id = element.getAttribute(ID_ATTRIBUTE);
final String ref = element.getAttribute(REF_ATTRIBUTE);
final String method = element.getAttribute(METHOD_ATTRIBUTE);
final String completionStrategyRef = element.getAttribute(COMPLETION_STRATEGY_ATTRIBUTE);
final NodeList completionStrategyChildElements = element.getElementsByTagName(COMPLETION_STRATEGY_ELEMENT);
if (!StringUtils.hasText(ref)) {
throw new ConfigurationException("The 'ref' attribute must be present");
}
if (!topLevel && StringUtils.hasText(id)) {
parserContext.getReaderContext().error(
"The 'id' attribute is only supported for top-level <aggregator> elements.",
parserContext.extractSource(element));
}
if (completionStrategyChildElements.getLength() > 0 && StringUtils.hasText(completionStrategyRef)) {
parserContext
.getReaderContext()
.error(
"The 'completion-strategy' element is only supported when no 'completion-strategy' attribute is specified.",
parserContext.extractSource(element));
}
if (!StringUtils.hasText(method)) {
aggregatorDef.getConstructorArgumentValues().addGenericArgumentValue(new RuntimeBeanReference(ref));
}
else {
String adapterBeanName = createAdapterAndReturnBeanName(parserContext, ref, method, AggregatorAdapter.class);
aggregatorDef.getConstructorArgumentValues().addGenericArgumentValue(
new RuntimeBeanReference(adapterBeanName));
}
if (StringUtils.hasText(completionStrategyRef)) {
aggregatorDef.getPropertyValues().addPropertyValue(COMPLETION_STRATEGY_PROPERTY,
new RuntimeBeanReference(completionStrategyRef));
}
else if (completionStrategyChildElements.getLength() > 0) {
Element completionStrategyElement = (Element) completionStrategyChildElements.item(0);
String childCompletionStrategyReference = completionStrategyElement.getAttribute(REF_ATTRIBUTE);
String childCompletionStrategyMethod = completionStrategyElement.getAttribute(METHOD_ATTRIBUTE);
String adapterBeanName = createAdapterAndReturnBeanName(parserContext, childCompletionStrategyReference,
childCompletionStrategyMethod, CompletionStrategyAdapter.class);
aggregatorDef.getPropertyValues().addPropertyValue(COMPLETION_STRATEGY_PROPERTY,
new RuntimeBeanReference(adapterBeanName));
}
IntegrationNamespaceUtils.setBeanReferenceIfAttributeDefined(aggregatorDef, DEFAULT_REPLY_CHANNEL_PROPERTY,
element, DEFAULT_REPLY_CHANNEL_ATTRIBUTE);
IntegrationNamespaceUtils.setBeanReferenceIfAttributeDefined(aggregatorDef, DISCARD_CHANNEL_PROPERTY, element,
DISCARD_CHANNEL_ATTRIBUTE);
IntegrationNamespaceUtils.setValueIfAttributeDefined(aggregatorDef, SEND_TIMEOUT_PROPERTY, element,
SEND_TIMEOUT_ATTRIBUTE);
IntegrationNamespaceUtils.setValueIfAttributeDefined(aggregatorDef, SEND_PARTIAL_RESULT_ON_TIMEOUT_PROPERTY,
element, SEND_PARTIAL_RESULT_ON_TIMEOUT_ATTRIBUTE);
IntegrationNamespaceUtils.setValueIfAttributeDefined(aggregatorDef, REAPER_INTERVAL_PROPERTY, element,
REAPER_INTERVAL_ATTRIBUTE);
IntegrationNamespaceUtils.setValueIfAttributeDefined(aggregatorDef, TRACKED_CORRELATION_ID_CAPACITY_PROPERTY,
element, TRACKED_CORRELATION_ID_CAPACITY_ATTRIBUTE);
IntegrationNamespaceUtils.setValueIfAttributeDefined(aggregatorDef, TIMEOUT, element, TIMEOUT_ATTRIBUTE);
String beanName = StringUtils.hasText(id) ? id : parserContext.getReaderContext().generateBeanName(
aggregatorDef);
parserContext.registerBeanComponent(new BeanComponentDefinition(aggregatorDef, beanName));
return aggregatorDef;
}
private String createAdapterAndReturnBeanName(ParserContext parserContext, final String ref, final String method,
Class<?> adapterClass) {
BeanDefinition adapterDefinition = new RootBeanDefinition(adapterClass);
adapterDefinition.getConstructorArgumentValues().addGenericArgumentValue(new RuntimeBeanReference(ref));
adapterDefinition.getConstructorArgumentValues().addGenericArgumentValue(method);
String adapterBeanName = parserContext.getReaderContext().generateBeanName(adapterDefinition);
parserContext.registerBeanComponent(new BeanComponentDefinition(adapterDefinition, adapterBeanName));
return adapterBeanName;
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2002-2007 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.w3c.dom.Element;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
/**
* Parser for the <em>annotation-driven</em> element of the integration
* namespace. Registers the annotation-driven post-processors.
*
* @author Mark Fisher
*/
public class AnnotationDrivenParser implements BeanDefinitionParser {
private static final String PUBLISHER_ANNOTATION_POST_PROCESSOR_BEAN_NAME =
"internal.PublisherAnnotationPostProcessor";
private static final String SUBSCRIBER_ANNOTATION_POST_PROCESSOR_BEAN_NAME =
"internal.SubscriberAnnotationPostProcessor";
private static final String MESSAGE_ENDPOINT_ANNOTATION_POST_PROCESSOR_BEAN_NAME =
"internal.MessageEndpointAnnotationPostProcessor";
public BeanDefinition parse(Element element, ParserContext parserContext) {
this.createPublisherPostProcessor(parserContext);
this.createSubscriberPostProcessor(parserContext);
this.createMessageEndpointPostProcessor(parserContext);
return null;
}
private void createPublisherPostProcessor(ParserContext parserContext) {
BeanDefinition bd = new RootBeanDefinition(PublisherAnnotationPostProcessor.class);
bd.getPropertyValues().addPropertyValue("channelRegistry",
new RuntimeBeanReference(MessageBusParser.MESSAGE_BUS_BEAN_NAME));
BeanComponentDefinition bcd = new BeanComponentDefinition(
bd, PUBLISHER_ANNOTATION_POST_PROCESSOR_BEAN_NAME);
parserContext.registerBeanComponent(bcd);
}
private void createSubscriberPostProcessor(ParserContext parserContext) {
BeanDefinition bd = new RootBeanDefinition(SubscriberAnnotationPostProcessor.class);
bd.getPropertyValues().addPropertyValue("messageBus",
new RuntimeBeanReference(MessageBusParser.MESSAGE_BUS_BEAN_NAME));
BeanComponentDefinition bcd = new BeanComponentDefinition(
bd, SUBSCRIBER_ANNOTATION_POST_PROCESSOR_BEAN_NAME);
parserContext.registerBeanComponent(bcd);
}
private void createMessageEndpointPostProcessor(ParserContext parserContext) {
BeanDefinition bd = new RootBeanDefinition(MessageEndpointAnnotationPostProcessor.class);
bd.getConstructorArgumentValues().addGenericArgumentValue(
new RuntimeBeanReference(MessageBusParser.MESSAGE_BUS_BEAN_NAME));
BeanComponentDefinition bcd = new BeanComponentDefinition(
bd, MESSAGE_ENDPOINT_ANNOTATION_POST_PROCESSOR_BEAN_NAME);
parserContext.registerBeanComponent(bcd);
}
}

View File

@@ -0,0 +1,74 @@
/*
* 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;
import org.w3c.dom.Element;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.integration.endpoint.HandlerEndpoint;
import org.springframework.integration.handler.DefaultMessageHandlerAdapter;
import org.springframework.util.StringUtils;
/**
* Parser for the <em>handler-endpoint</em> element of the integration namespace.
*
* @author Mark Fisher
*/
public class HandlerEndpointParser extends AbstractTargetEndpointParser {
private static final String OUTPUT_CHANNEL_ATTRIBUTE = "output-channel";
private static final String OUTPUT_CHANNEL_PROPERTY = "outputChannelName";
private static final String RETURN_ADDRESS_OVERRIDES_ATTRIBUTE = "return-address-overrides";
private static final String REPLY_HANDLER_ATTRIBUTE = "reply-handler";
private static final String REPLY_HANDLER_PROPERTY = "replyHandler";
@Override
protected Class<?> getBeanClass(Element element) {
return HandlerEndpoint.class;
}
protected String getTargetAttributeName() {
return "handler";
}
@Override
protected Class<?> getAdapterClass() {
return DefaultMessageHandlerAdapter.class;
}
@Override
protected void postProcess(BeanDefinitionBuilder builder, Element element) {
String outputChannel = element.getAttribute(OUTPUT_CHANNEL_ATTRIBUTE);
if (StringUtils.hasText(outputChannel)) {
builder.addPropertyValue(OUTPUT_CHANNEL_PROPERTY, outputChannel);
}
String returnAddressOverridesAttribute = element.getAttribute(RETURN_ADDRESS_OVERRIDES_ATTRIBUTE);
boolean returnAddressOverrides = "true".equals(returnAddressOverridesAttribute);
builder.addPropertyValue("returnAddressOverrides", returnAddressOverrides);
String replyHandler = element.getAttribute(REPLY_HANDLER_ATTRIBUTE);
if (StringUtils.hasText(replyHandler)) {
builder.addPropertyValue(REPLY_HANDLER_PROPERTY, new RuntimeBeanReference(replyHandler));
}
}
}

View File

@@ -0,0 +1,127 @@
/*
* 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;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.handler.DefaultMessageHandlerAdapter;
import org.springframework.integration.handler.MessageHandlerChain;
import org.springframework.util.StringUtils;
/**
* Parser for the &lt;handler/&gt; element.
*
* @author Mark Fisher
*/
public class HandlerParser implements BeanDefinitionParser {
private static final String HANDLER_CHAIN_ELEMENT = "handler-chain";
private static final String HANDLER_ELEMENT = "handler";
private static final String HANDLERS_PROPERTY = "handlers";
private static final String OBJECT_PROPERTY = "object";
private static final String METHOD_NAME_PROPERTY = "methodName";
public BeanDefinition parse(Element element, ParserContext parserContext) {
if (HANDLER_CHAIN_ELEMENT.equals(element.getLocalName())) {
return this.parseHandlerChain(element, parserContext);
}
else if (HANDLER_ELEMENT.equals(element.getLocalName())) {
return this.parseHandler(element, parserContext, null);
}
return null;
}
private BeanDefinition parseHandlerChain(Element element, ParserContext parserContext) {
RootBeanDefinition beanDefinition = new RootBeanDefinition(MessageHandlerChain.class);
ManagedList handlers = new ManagedList();
NodeList childNodes = element.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
Node child = childNodes.item(i);
if (child.getNodeType() == Node.ELEMENT_NODE) {
String localName = child.getLocalName();
if (HANDLER_ELEMENT.equals(localName)) {
parseHandler((Element) child, parserContext, handlers);
}
}
}
beanDefinition.getPropertyValues().addPropertyValue(HANDLERS_PROPERTY, handlers);
String id = element.getAttribute("id");
String beanName = (StringUtils.hasText(id)) ? id : parserContext.getReaderContext().generateBeanName(beanDefinition);
parserContext.registerBeanComponent(new BeanComponentDefinition(beanDefinition, beanName));
return beanDefinition;
}
private BeanDefinition parseHandler(Element element, ParserContext parserContext, ManagedList handlers) {
boolean isInnerHandler = (handlers != null);
String ref = element.getAttribute("ref");
String method = element.getAttribute("method");
String id = element.getAttribute("id");
if (!isInnerHandler && (!StringUtils.hasText(id) || !StringUtils.hasText(ref) || !StringUtils.hasText(method))) {
parserContext.getReaderContext().error("Top-level <handler> elements must provide 'id', 'ref', and 'method' attributes.",
parserContext.extractSource(element));
}
if (isInnerHandler && StringUtils.hasText(id)) {
parserContext.getReaderContext().error("The 'id' attribute is only supported for top-level <handler> elements.",
parserContext.extractSource(element));
}
if (StringUtils.hasText(method)) {
BeanDefinitionHolder bdh = this.parseHandlerAdapter(id, ref, method, parserContext, isInnerHandler);
if (handlers != null) {
handlers.add(bdh.getBeanDefinition());
return null;
}
return bdh.getBeanDefinition();
}
if (StringUtils.hasText(id)) {
parserContext.getReaderContext().error("The 'id' attribute is only supported for handler adapters (when 'method' is also provided).",
parserContext.extractSource(element));
}
if (handlers != null) {
handlers.add(new RuntimeBeanReference(ref));
}
return null;
}
private BeanDefinitionHolder parseHandlerAdapter(String id, String handlerRef, String handlerMethod, ParserContext parserContext, boolean isInnerHandler) {
BeanDefinition handlerAdapterDef = new RootBeanDefinition(DefaultMessageHandlerAdapter.class);
handlerAdapterDef.getPropertyValues().addPropertyValue(OBJECT_PROPERTY, new RuntimeBeanReference(handlerRef));
handlerAdapterDef.getPropertyValues().addPropertyValue(METHOD_NAME_PROPERTY, handlerMethod);
String adapterBeanName = (StringUtils.hasText(id)) ? id :
BeanDefinitionReaderUtils.generateBeanName(handlerAdapterDef, parserContext.getRegistry(), isInnerHandler);
if (!isInnerHandler) {
parserContext.registerBeanComponent(new BeanComponentDefinition(handlerAdapterDef, adapterBeanName));
}
return new BeanDefinitionHolder(handlerAdapterDef, adapterBeanName);
}
}

View File

@@ -0,0 +1,118 @@
/*
* 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;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.integration.channel.config.DefaultChannelParser;
import org.springframework.integration.channel.config.DirectChannelParser;
import org.springframework.integration.channel.config.PriorityChannelParser;
import org.springframework.integration.channel.config.QueueChannelParser;
import org.springframework.integration.channel.config.RendezvousChannelParser;
import org.springframework.integration.channel.config.ThreadLocalChannelParser;
import org.springframework.integration.router.config.RouterParser;
import org.springframework.integration.router.config.SplitterParser;
import org.springframework.util.ClassUtils;
/**
* Namespace handler for the integration namespace.
*
* @author Mark Fisher
* @author Marius Bogoevici
*/
public class IntegrationNamespaceHandler extends NamespaceHandlerSupport {
private static final String ADAPTER_PARSER_MAPPINGS_LOCATION = "META-INF/spring-integration.parsers";
private final Log logger = LogFactory.getLog(this.getClass());
public void init() {
registerBeanDefinitionParser("message-bus", new MessageBusParser());
registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenParser());
registerBeanDefinitionParser("channel", new DefaultChannelParser());
registerBeanDefinitionParser("queue-channel", new QueueChannelParser());
registerBeanDefinitionParser("direct-channel", new DirectChannelParser());
registerBeanDefinitionParser("priority-channel", new PriorityChannelParser());
registerBeanDefinitionParser("rendezvous-channel", new RendezvousChannelParser());
registerBeanDefinitionParser("thread-local-channel", new ThreadLocalChannelParser());
registerBeanDefinitionParser("source-adapter", new MethodInvokingAdapterParser());
registerBeanDefinitionParser("target-adapter", new MethodInvokingAdapterParser());
registerBeanDefinitionParser("source-endpoint", new SourceEndpointParser());
registerBeanDefinitionParser("handler-endpoint", new HandlerEndpointParser());
registerBeanDefinitionParser("target-endpoint", new TargetEndpointParser());
registerBeanDefinitionParser("handler", new HandlerParser());
registerBeanDefinitionParser("handler-chain", new HandlerParser());
registerBeanDefinitionParser("router", new RouterParser());
registerBeanDefinitionParser("splitter", new SplitterParser());
registerBeanDefinitionParser("aggregator", new AggregatorParser());
Map<String, Class<? extends BeanDefinitionParser>> parserMappings = this.loadAdapterParserMappings();
try {
for (Map.Entry<String, Class<? extends BeanDefinitionParser>> entry : parserMappings.entrySet()) {
registerBeanDefinitionParser(entry.getKey(), (BeanDefinitionParser) entry.getValue().newInstance());
}
}
catch (Exception e) {
throw new IllegalStateException("Failed to instantiate BeanDefinitionParser.", e);
}
}
private Map<String, Class<? extends BeanDefinitionParser>> loadAdapterParserMappings() {
Map<String, Class<? extends BeanDefinitionParser>> parserMappings =
new HashMap<String, Class<? extends BeanDefinitionParser>>();
ClassLoader classLoader = getClass().getClassLoader();
try {
Properties mappings =
PropertiesLoaderUtils.loadAllProperties(ADAPTER_PARSER_MAPPINGS_LOCATION, classLoader);
if (logger.isDebugEnabled()) {
logger.debug("Loaded parser mappings [" + mappings + "]");
}
Enumeration<?> propertyNames = mappings.propertyNames();
while (propertyNames.hasMoreElements()) {
String name = (String) propertyNames.nextElement();
String classname = mappings.getProperty(name);
Class<?> parserClass = ClassUtils.forName(classname, classLoader);
if (!BeanDefinitionParser.class.isAssignableFrom(parserClass)) {
throw new IllegalStateException("Expected class of type BeanDefinitionParser, but '" +
name + "' was of type '" + parserClass.getSimpleName() + "'");
}
parserMappings.put(name, (Class<? extends BeanDefinitionParser>) parserClass);
}
return parserMappings;
}
catch (IOException e) {
throw new IllegalStateException(
"Unable to load BeanDefinitionParser mappings from location [" +
ADAPTER_PARSER_MAPPINGS_LOCATION + "]. Root cause: " + e);
}
catch (ClassNotFoundException e) {
throw new IllegalStateException("Failed to load BeanDefinitionParser.", e);
}
}
}

View File

@@ -0,0 +1,102 @@
/*
* Copyright 2002-2007 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.w3c.dom.Element;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.integration.endpoint.ConcurrencyPolicy;
import org.springframework.util.StringUtils;
/**
* Shared utility methods for integration namespace parsers.
*
* @author Mark Fisher
* @author Marius Bogoevici
*/
public abstract class IntegrationNamespaceUtils {
private static final String CORE_SIZE_ATTRIBUTE = "core";
private static final String MAX_SIZE_ATTRIBUTE = "max";
private static final String QUEUE_CAPACITY_ATTRIBUTE = "queue-capacity";
private static final String KEEP_ALIVE_ATTRIBUTE = "keep-alive";
public static ConcurrencyPolicy parseConcurrencyPolicy(Element element) {
ConcurrencyPolicy policy = new ConcurrencyPolicy();
String coreSize = element.getAttribute(CORE_SIZE_ATTRIBUTE);
String maxSize = element.getAttribute(MAX_SIZE_ATTRIBUTE);
String queueCapacity = element.getAttribute(QUEUE_CAPACITY_ATTRIBUTE);
String keepAlive = element.getAttribute(KEEP_ALIVE_ATTRIBUTE);
if (StringUtils.hasText(coreSize)) {
policy.setCoreSize(Integer.parseInt(coreSize));
}
if (StringUtils.hasText(maxSize)) {
policy.setMaxSize(Integer.parseInt(maxSize));
}
if (StringUtils.hasText(queueCapacity)) {
policy.setQueueCapacity(Integer.parseInt(queueCapacity));
}
if (StringUtils.hasText(keepAlive)) {
policy.setKeepAliveSeconds(Integer.parseInt(keepAlive));
}
return policy;
}
/**
* Populates the property identified by propertyName on the bean definition
* to the value of the attribute specified by attributeName, if that
* attribute is defined in the element
*
* @param beanDefinition - the bean definition to be configured
* @param propertyName - the name of the bean property to be set
* @param element - the XML element where the attribute should be defined
* @param attributeName - the name of the attribute whose value will be set
* on the property
*/
public static void setValueIfAttributeDefined(RootBeanDefinition beanDefinition, String propertyName,
Element element, String attributeName) {
final String attributeValue = element.getAttribute(attributeName);
if (StringUtils.hasText(attributeValue)) {
beanDefinition.getPropertyValues().addPropertyValue(propertyName, attributeValue);
}
}
/**
* Populates the property given by propertyName on the given bean definition
* to a reference to a bean identified by the value of the attribute
* specified by attributeName, if that attribute is defined in the element
*
* @param beanDefinition - the bean definition to be configured
* @param propertyName - the name of the bean property to be set
* @param element - the XML element where the attribute should be defined
* @param attributeName - the id of the bean which will be used to populate
* the property
*/
public static void setBeanReferenceIfAttributeDefined(RootBeanDefinition beanDefinition, String propertyName,
Element element, String attributeName) {
final String attributeValue = element.getAttribute(attributeName);
if (StringUtils.hasText(attributeValue)) {
beanDefinition.getPropertyValues().addPropertyValue(propertyName, new RuntimeBeanReference(attributeValue));
}
}
}

View File

@@ -0,0 +1,128 @@
/*
* 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;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.AbstractSimpleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.core.Conventions;
import org.springframework.integration.ConfigurationException;
import org.springframework.integration.bus.MessageBus;
import org.springframework.integration.bus.MessageBusAwareBeanPostProcessor;
import org.springframework.util.StringUtils;
/**
* Parser for the <em>message-bus</em> element of the integration namespace.
*
* @author Mark Fisher
* @author Marius Bogoevici
*/
public class MessageBusParser extends AbstractSimpleBeanDefinitionParser {
public static final String MESSAGE_BUS_BEAN_NAME = "internal.MessageBus";
public static final String MESSAGE_BUS_AWARE_POST_PROCESSOR_BEAN_NAME = "internal.MessageBusAwareBeanPostProcessor";
private static final Class<?> MESSAGE_BUS_CLASS = MessageBus.class;
private static final String ERROR_CHANNEL_ATTRIBUTE = "error-channel";
private static final String DEFAULT_CONCURRENCY_ELEMENT = "default-concurrency";
private static final String DEFAULT_CONCURRENCY_PROPERTY = "defaultConcurrencyPolicy";
private static final String CHANNEL_FACTORY_ATTRIBUTE = "channel-factory";
@Override
protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext)
throws BeanDefinitionStoreException {
if (parserContext.getRegistry().containsBeanDefinition(MESSAGE_BUS_BEAN_NAME)) {
throw new ConfigurationException("Only one instance of '" + MESSAGE_BUS_CLASS.getSimpleName()
+ "' is allowed per ApplicationContext.");
}
return MESSAGE_BUS_BEAN_NAME;
}
@Override
protected Class<?> getBeanClass(Element element) {
return MESSAGE_BUS_CLASS;
}
@Override
protected boolean isEligibleAttribute(String attributeName) {
return !ERROR_CHANNEL_ATTRIBUTE.equals(attributeName) &&
!CHANNEL_FACTORY_ATTRIBUTE.equals(attributeName) &&
super.isEligibleAttribute(attributeName);
}
@Override
protected void postProcess(BeanDefinitionBuilder beanDefinition, Element element) {
String errorChannelRef = element.getAttribute(ERROR_CHANNEL_ATTRIBUTE);
if (StringUtils.hasText(errorChannelRef)) {
beanDefinition.addPropertyReference(Conventions.attributeNameToPropertyName(
ERROR_CHANNEL_ATTRIBUTE), errorChannelRef);
}
String channelFactoryRef = element.getAttribute(CHANNEL_FACTORY_ATTRIBUTE);
if (StringUtils.hasText(channelFactoryRef)) {
beanDefinition.addPropertyReference(Conventions.attributeNameToPropertyName(
CHANNEL_FACTORY_ATTRIBUTE), channelFactoryRef);
}
this.processChildElements(beanDefinition, element);
}
private void processChildElements(BeanDefinitionBuilder beanDefinition, Element element) {
NodeList childNodes = element.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
Node child = childNodes.item(i);
if (child.getNodeType() == Node.ELEMENT_NODE) {
String localName = child.getLocalName();
if (DEFAULT_CONCURRENCY_ELEMENT.equals(localName)) {
beanDefinition.addPropertyValue(DEFAULT_CONCURRENCY_PROPERTY,
IntegrationNamespaceUtils.parseConcurrencyPolicy((Element) child));
}
}
}
}
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
super.doParse(element, parserContext, builder);
this.addPostProcessors(parserContext);
}
/**
* Adds extra post-processors to the context, to inject the objects configured by the MessageBus
*/
private void addPostProcessors(ParserContext parserContext) {
BeanDefinition postProcessorDefinition = new RootBeanDefinition(MessageBusAwareBeanPostProcessor.class);
postProcessorDefinition.getConstructorArgumentValues().addGenericArgumentValue(
new RuntimeBeanReference(MessageBusParser.MESSAGE_BUS_BEAN_NAME));
parserContext.getRegistry().registerBeanDefinition(MESSAGE_BUS_AWARE_POST_PROCESSOR_BEAN_NAME,
postProcessorDefinition);
}
}

View File

@@ -0,0 +1,298 @@
/*
* 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;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
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.InitializingBean;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.OrderComparator;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.integration.ConfigurationException;
import org.springframework.integration.annotation.Aggregator;
import org.springframework.integration.annotation.CompletionStrategy;
import org.springframework.integration.annotation.Concurrency;
import org.springframework.integration.annotation.DefaultOutput;
import org.springframework.integration.annotation.Handler;
import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.annotation.Polled;
import org.springframework.integration.annotation.Router;
import org.springframework.integration.annotation.Splitter;
import org.springframework.integration.bus.MessageBus;
import org.springframework.integration.channel.ChannelRegistryAware;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.endpoint.ConcurrencyPolicy;
import org.springframework.integration.endpoint.HandlerEndpoint;
import org.springframework.integration.endpoint.SourceEndpoint;
import org.springframework.integration.handler.AbstractMessageHandlerAdapter;
import org.springframework.integration.handler.MessageHandler;
import org.springframework.integration.handler.MessageHandlerChain;
import org.springframework.integration.handler.MethodInvokingTarget;
import org.springframework.integration.handler.config.DefaultMessageHandlerCreator;
import org.springframework.integration.handler.config.MessageHandlerCreator;
import org.springframework.integration.message.MethodInvokingSource;
import org.springframework.integration.router.AggregatingMessageHandler;
import org.springframework.integration.router.CompletionStrategyAdapter;
import org.springframework.integration.router.config.AggregatorMessageHandlerCreator;
import org.springframework.integration.router.config.RouterMessageHandlerCreator;
import org.springframework.integration.router.config.SplitterMessageHandlerCreator;
import org.springframework.integration.scheduling.PollingSchedule;
import org.springframework.integration.scheduling.Subscription;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
* A {@link BeanPostProcessor} implementation that generates endpoints for
* classes annotated with {@link MessageEndpoint @MessageEndpoint}.
*
* @author Mark Fisher
* @author Marius Bogoevici
*/
public class MessageEndpointAnnotationPostProcessor implements BeanPostProcessor, InitializingBean {
private final Log logger = LogFactory.getLog(this.getClass());
private final Map<Class<? extends Annotation>, MessageHandlerCreator> handlerCreators = new ConcurrentHashMap<Class<? extends Annotation>, MessageHandlerCreator>();
private final MessageBus messageBus;
public MessageEndpointAnnotationPostProcessor(MessageBus messageBus) {
Assert.notNull(messageBus, "'messageBus' must not be null");
this.messageBus = messageBus;
}
public void setCustomHandlerCreators(Map<Class<? extends Annotation>, MessageHandlerCreator> customHandlerCreators) {
for (Map.Entry<Class<? extends Annotation>, MessageHandlerCreator> entry : customHandlerCreators.entrySet()) {
this.handlerCreators.put(entry.getKey(), entry.getValue());
}
}
public void afterPropertiesSet() {
this.handlerCreators.put(Handler.class, new DefaultMessageHandlerCreator());
this.handlerCreators.put(Router.class, new RouterMessageHandlerCreator());
this.handlerCreators.put(Splitter.class, new SplitterMessageHandlerCreator());
this.handlerCreators.put(Aggregator.class, new AggregatorMessageHandlerCreator(messageBus));
}
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
Class<?> beanClass = this.getBeanClass(bean);
MessageEndpoint endpointAnnotation = AnnotationUtils.findAnnotation(beanClass, MessageEndpoint.class);
if (endpointAnnotation == null) {
return bean;
}
if (bean instanceof ChannelRegistryAware) {
((ChannelRegistryAware) bean).setChannelRegistry(this.messageBus);
}
String outputChannelName = endpointAnnotation.output();
MessageHandlerChain handlerChain = this.createHandlerChain(bean, outputChannelName);
if (handlerChain == null) {
throw new ConfigurationException("@MessageEndpoint has no handler method");
}
HandlerEndpoint endpoint = new HandlerEndpoint(handlerChain);
Polled polledAnnotation = AnnotationUtils.findAnnotation(beanClass, Polled.class);
this.configureInput(bean, beanName, endpointAnnotation, polledAnnotation, endpoint);
if (StringUtils.hasText(outputChannelName)) {
endpoint.setOutputChannelName(outputChannelName);
}
else {
this.configureOutput(bean, beanName, endpoint);
}
Concurrency concurrencyAnnotation = AnnotationUtils.findAnnotation(beanClass, Concurrency.class);
if (concurrencyAnnotation != null) {
ConcurrencyPolicy concurrencyPolicy = new ConcurrencyPolicy(concurrencyAnnotation.coreSize(),
concurrencyAnnotation.maxSize());
concurrencyPolicy.setKeepAliveSeconds(concurrencyAnnotation.keepAliveSeconds());
concurrencyPolicy.setQueueCapacity(concurrencyAnnotation.queueCapacity());
endpoint.setConcurrencyPolicy(concurrencyPolicy);
}
this.configureCompletionStrategy(bean, endpoint);
this.messageBus.registerEndpoint(beanName + "-endpoint", endpoint);
return bean;
}
private void configureInput(final Object bean, final String beanName, MessageEndpoint annotation,
Polled polledAnnotation, final HandlerEndpoint endpoint) {
String channelName = annotation.input();
if (StringUtils.hasText(channelName)) {
PollingSchedule schedule = null;
if (polledAnnotation != null) {
schedule = new PollingSchedule(polledAnnotation.period());
schedule.setInitialDelay(polledAnnotation.initialDelay());
schedule.setFixedRate(polledAnnotation.fixedRate());
schedule.setTimeUnit(polledAnnotation.timeUnit());
}
Subscription subscription = new Subscription(channelName, schedule);
endpoint.setSubscription(subscription);
}
ReflectionUtils.doWithMethods(this.getBeanClass(bean), new ReflectionUtils.MethodCallback() {
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
Annotation annotation = AnnotationUtils.getAnnotation(method, Polled.class);
if (annotation != null) {
Polled polledAnnotation = (Polled) annotation;
int period = polledAnnotation.period();
long initialDelay = polledAnnotation.initialDelay();
boolean fixedRate = polledAnnotation.fixedRate();
MethodInvokingSource source = new MethodInvokingSource();
source.setObject(bean);
source.setMethod(method.getName());
DirectChannel channel = new DirectChannel();
PollingSchedule schedule = new PollingSchedule(period);
schedule.setInitialDelay(initialDelay);
schedule.setFixedRate(fixedRate);
SourceEndpoint sourceEndpoint = new SourceEndpoint(source, channel, schedule);
String channelName = beanName + "-inputChannel";
messageBus.registerChannel(channelName, channel);
messageBus.registerEndpoint(beanName + "-sourceEndpoint", sourceEndpoint);
Subscription subscription = new Subscription(channel);
endpoint.setSubscription(subscription);
}
}
});
}
private void configureOutput(final Object bean, final String beanName, final HandlerEndpoint endpoint) {
ReflectionUtils.doWithMethods(this.getBeanClass(bean), new ReflectionUtils.MethodCallback() {
boolean foundOutput = false;
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
Annotation annotation = AnnotationUtils.getAnnotation(method, DefaultOutput.class);
if (annotation != null) {
if (foundOutput) {
throw new ConfigurationException("only one @DefaultOutput allowed per endpoint");
}
MethodInvokingTarget target = new MethodInvokingTarget();
target.setObject(bean);
target.setMethodName(method.getName());
target.afterPropertiesSet();
MessageHandler handler = endpoint.getHandler();
((MessageHandlerChain) handler).add(target);
foundOutput = true;
return;
}
}
});
}
private void configureCompletionStrategy(final Object bean, final HandlerEndpoint endpoint) {
ReflectionUtils.doWithMethods(bean.getClass(), new ReflectionUtils.MethodCallback() {
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
Annotation annotation = AnnotationUtils.getAnnotation(method, CompletionStrategy.class);
if (annotation != null) {
final MessageHandler endpointHandler = endpoint.getHandler();
AggregatingMessageHandler aggregatingMessageHandler = null;
if (endpointHandler != null) {
if (endpointHandler instanceof MessageHandlerChain) {
for (MessageHandler handlerInChain : ((MessageHandlerChain) endpointHandler).getHandlers()) {
if (handlerInChain instanceof AggregatingMessageHandler) {
aggregatingMessageHandler = (AggregatingMessageHandler) handlerInChain;
break;
}
}
}
else if (endpointHandler instanceof AggregatingMessageHandler) {
aggregatingMessageHandler = (AggregatingMessageHandler) endpointHandler;
}
}
if (aggregatingMessageHandler == null) {
throw new ConfigurationException(
"@CompletionStrategy supported only when @Aggregator is present");
}
else {
aggregatingMessageHandler.setCompletionStrategy(new CompletionStrategyAdapter(bean, method));
}
}
}
});
}
@SuppressWarnings("unchecked")
private MessageHandlerChain createHandlerChain(final Object bean, final String outputChannelName) {
final List<MessageHandler> handlers = new ArrayList<MessageHandler>();
ReflectionUtils.doWithMethods(this.getBeanClass(bean), new ReflectionUtils.MethodCallback() {
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
Annotation[] annotations = AnnotationUtils.getAnnotations(method);
for (Annotation annotation : annotations) {
if (isHandlerAnnotation(annotation)) {
Map<String, Object> attributes = AnnotationUtils.getAnnotationAttributes(annotation);
attributes.put(AbstractMessageHandlerAdapter.OUTPUT_CHANNEL_NAME_KEY, outputChannelName);
MessageHandlerCreator handlerCreator = handlerCreators.get(annotation.annotationType());
if (handlerCreator == null) {
if (logger.isWarnEnabled()) {
logger.warn("No handler creator has been registered for handler annotation '"
+ annotation.annotationType() + "'");
}
}
else {
MessageHandler handler = handlerCreator.createHandler(bean, method, attributes);
if (handler instanceof ChannelRegistryAware) {
((ChannelRegistryAware) handler).setChannelRegistry(messageBus);
}
if (handler instanceof InitializingBean) {
try {
((InitializingBean) handler).afterPropertiesSet();
}
catch (Exception e) {
throw new ConfigurationException("failed to create handler", e);
}
}
if (handler != null) {
handlers.add(handler);
}
}
}
}
}
});
if (handlers.size() > 0) {
MessageHandlerChain handlerChain = new MessageHandlerChain();
Collections.sort(handlers, new OrderComparator());
for (MessageHandler handler : handlers) {
handlerChain.add(handler);
}
return handlerChain;
}
return null;
}
private Class<?> getBeanClass(Object bean) {
return AopUtils.getTargetClass(bean);
}
private boolean isHandlerAnnotation(Annotation annotation) {
return annotation.annotationType().equals(Handler.class)
|| annotation.annotationType().isAnnotationPresent(Handler.class)
|| this.handlerCreators.keySet().contains(annotation.annotationType());
}
}

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.config;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.integration.ConfigurationException;
import org.springframework.integration.handler.MethodInvokingTarget;
import org.springframework.integration.message.MethodInvokingSource;
import org.springframework.util.StringUtils;
/**
* Parser for &lt;source-adapter&gt; and &lt;target-adapter&gt;.
* Creates a {@link MethodInvokingSource} or {@link MethodInvokingTarget}.
*
* @author Mark Fisher
*/
public class MethodInvokingAdapterParser extends AbstractSingleBeanDefinitionParser {
@Override
protected Class<?> getBeanClass(Element element) {
return "source-adapter".equals(element.getLocalName()) ? MethodInvokingSource.class : MethodInvokingTarget.class;
}
@Override
protected void doParse(Element element, BeanDefinitionBuilder builder) {
String ref = element.getAttribute("ref");
String method = element.getAttribute("method");
if (!StringUtils.hasText(ref)) {
throw new ConfigurationException("The 'ref' attribute is required.");
}
if (!StringUtils.hasText(method)) {
throw new ConfigurationException("The 'method' attribute is required.");
}
builder.addPropertyReference("object", ref);
builder.addPropertyValue("method", method);
}
}

View File

@@ -0,0 +1,108 @@
/*
* 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;
import java.lang.annotation.Annotation;
import org.springframework.aop.Advisor;
import org.springframework.aop.framework.Advised;
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.config.BeanPostProcessor;
import org.springframework.integration.annotation.Publisher;
import org.springframework.integration.aop.PublisherAnnotationAdvisor;
import org.springframework.integration.channel.ChannelRegistry;
import org.springframework.util.Assert;
/**
* A {@link BeanPostProcessor} that adds a message publishing interceptor when
* it discovers annotated methods.
*
* @author Mark Fisher
*/
public class PublisherAnnotationPostProcessor implements BeanPostProcessor, BeanClassLoaderAware {
private Class<? extends Annotation> publisherAnnotationType = Publisher.class;
private String channelNameAttribute = "channel";
private ChannelRegistry channelRegistry;
private Advisor advisor;
private ClassLoader beanClassLoader;
public void setBeanClassLoader(ClassLoader beanClassLoader) {
Assert.notNull(beanClassLoader, "'beanClassLoader' must not be null");
this.beanClassLoader = beanClassLoader;
}
public void setPublisherAnnotationType(Class<? extends Annotation> publisherAnnotationType) {
Assert.notNull(publisherAnnotationType, "'publisherAnnotationType' must not be null");
this.publisherAnnotationType = publisherAnnotationType;
}
public void setChannelNameAttribute(String channelNameAttribute) {
Assert.notNull(channelNameAttribute, "'channelNameAttribute' must not be null");
this.channelNameAttribute = channelNameAttribute;
}
public void setChannelRegistry(ChannelRegistry channelRegistry) {
Assert.notNull(channelRegistry, "'channelRegistry' must not be null");
this.channelRegistry = channelRegistry;
}
private void createAdvisor() {
if (this.channelRegistry == null) {
throw new IllegalStateException("'channelRegistry' is required");
}
this.advisor = new PublisherAnnotationAdvisor(this.publisherAnnotationType, this.channelNameAttribute,
this.channelRegistry);
}
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 (advisor == null) {
createAdvisor();
}
if (AopUtils.canApply(this.advisor, targetClass)) {
if (bean instanceof Advised) {
((Advised) bean).addAdvisor(this.advisor);
return bean;
}
else {
ProxyFactory pf = new ProxyFactory(bean);
pf.addAdvisor(this.advisor);
return pf.getProxy(this.beanClassLoader);
}
}
else {
return bean;
}
}
}

View File

@@ -0,0 +1,90 @@
/*
* 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;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSimpleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.ConfigurationException;
import org.springframework.integration.endpoint.SourceEndpoint;
import org.springframework.integration.scheduling.PollingSchedule;
import org.springframework.integration.scheduling.Schedule;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
/**
* Parser for the <source-endpoint/> element.
*
* @author Mark Fisher
*/
public class SourceEndpointParser extends AbstractSimpleBeanDefinitionParser {
protected final Class<?> getBeanClass(Element element) {
return SourceEndpoint.class;
}
protected boolean shouldGenerateId() {
return false;
}
protected boolean shouldGenerateIdAsFallback() {
return true;
}
protected boolean isEligibleAttribute(String name) {
return (!"source".equals(name) && !"channel".equals(name) && super.isEligibleAttribute(name));
}
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
String source = element.getAttribute("source");
if (!StringUtils.hasText(source)) {
throw new ConfigurationException("'source' is required");
}
String output = element.getAttribute("channel");
if (!StringUtils.hasText(output)) {
throw new ConfigurationException("'channel' is required");
}
builder.addConstructorArgReference(source);
builder.addConstructorArgReference(output);
Element scheduleElement = this.getScheduleElement(element);
if (scheduleElement == null) {
throw new ConfigurationException("The <schedule/> sub-element is required for a <source-endpoint/>.");
}
builder.addConstructorArgValue(this.parseSchedule(scheduleElement));
}
/**
* Subclasses may override this method to control the creation of the {@link Schedule}. The default
* implementation creates a {@link PollingSchedule} instance based on the provided "period" attribute.
*/
protected Schedule parseSchedule(Element element) {
String period = element.getAttribute("period");
if (!StringUtils.hasText(period)) {
throw new ConfigurationException("The 'period' attribute is required for the 'schedule' element.");
}
PollingSchedule schedule = new PollingSchedule(Long.valueOf(period));
return schedule;
}
private Element getScheduleElement(Element element) {
return DomUtils.getChildElementByTagName(element, "schedule");
}
}

View File

@@ -0,0 +1,103 @@
/*
* 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;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
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.config.BeanPostProcessor;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.integration.annotation.Subscriber;
import org.springframework.integration.bus.MessageBus;
import org.springframework.integration.handler.DefaultMessageHandlerAdapter;
import org.springframework.integration.scheduling.Subscription;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
/**
* A {@link BeanPostProcessor} that creates a method-invoking handler adapter
* when it discovers methods annotated with {@link Subscriber @Subscriber}.
*
* @author Mark Fisher
*/
public class SubscriberAnnotationPostProcessor implements BeanPostProcessor {
private Log logger = LogFactory.getLog(this.getClass());
private Class<? extends Annotation> subscriberAnnotationType = Subscriber.class;
private String channelNameAttribute = "channel";
private MessageBus messageBus;
public void setSubscriberAnnotationType(Class<? extends Annotation> subscriberAnnotationType) {
Assert.notNull(subscriberAnnotationType, "'subscriberAnnotationType' must not be null");
this.subscriberAnnotationType = subscriberAnnotationType;
}
public void setChannelNameAttribute(String channelNameAttribute) {
Assert.notNull(channelNameAttribute, "'channelNameAttribute' must not be null");
this.channelNameAttribute = channelNameAttribute;
}
public void setMessageBus(MessageBus messageBus) {
Assert.notNull(messageBus, "'messageBus' must not be null");
this.messageBus = messageBus;
}
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
public Object postProcessAfterInitialization(final Object bean, String beanName) throws BeansException {
final Class<?> targetClass = AopUtils.getTargetClass(bean);
if (targetClass == null) {
return bean;
}
if (this.messageBus == null) {
if (logger.isWarnEnabled()) {
logger.warn(this.getClass().getSimpleName() + " is disabled since no 'messageBus' was provided");
}
return bean;
}
ReflectionUtils.doWithMethods(targetClass, new ReflectionUtils.MethodCallback() {
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
Annotation annotation = method.getAnnotation(subscriberAnnotationType);
if (annotation != null) {
String channelName = (String) AnnotationUtils.getValue(annotation, channelNameAttribute);
DefaultMessageHandlerAdapter adapter = new DefaultMessageHandlerAdapter();
adapter.setMethodName(method.getName());
adapter.setObject(bean);
adapter.afterPropertiesSet();
String adapterName = ClassUtils.getShortNameAsProperty(targetClass) +
"-" + method.getName() + "-endpoint";
Subscription subscription = new Subscription(channelName);
messageBus.registerHandler(adapterName, adapter, subscription);
}
}
});
return bean;
}
}

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.config;
import org.w3c.dom.Element;
import org.springframework.integration.endpoint.TargetEndpoint;
import org.springframework.integration.handler.MethodInvokingTarget;
/**
* Parser for the &lt;target-endpoint&gt; element.
*
* @author Mark Fisher
*/
public class TargetEndpointParser extends AbstractTargetEndpointParser {
@Override
protected Class<?> getBeanClass(Element element) {
return TargetEndpoint.class;
}
@Override
protected String getTargetAttributeName() {
return "target";
}
@Override
protected Class<?> getAdapterClass() {
return MethodInvokingTarget.class;
}
}

View File

@@ -0,0 +1,385 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.springframework.org/schema/integration"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:tool="http://www.springframework.org/schema/tool"
targetNamespace="http://www.springframework.org/schema/integration"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/beans"/>
<xsd:import namespace="http://www.springframework.org/schema/tool"/>
<xsd:annotation>
<xsd:documentation><![CDATA[
Defines the core configuration elements for Spring Integration.
]]></xsd:documentation>
</xsd:annotation>
<xsd:element name="message-bus">
<xsd:complexType>
<xsd:annotation>
<xsd:documentation>
Defines a message bus.
</xsd:documentation>
</xsd:annotation>
<xsd:sequence>
<xsd:element name="default-concurrency" type="concurrencyType" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="auto-startup" type="xsd:boolean"/>
<xsd:attribute name="auto-create-channels" type="xsd:boolean"/>
<xsd:attribute name="channel-factory" type="xsd:string"/>
<xsd:attribute name="error-channel" type="xsd:string"/>
<xsd:attribute name="dispatcher-pool-size" type="xsd:int"/>
<xsd:attribute name="configure-async-event-multicaster" type="xsd:boolean"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="annotation-driven">
<xsd:complexType>
<xsd:annotation>
<xsd:documentation>
Enables the annotation-driven post-processors.
</xsd:documentation>
</xsd:annotation>
</xsd:complexType>
</xsd:element>
<xsd:element name="channel" type="channelType">
<xsd:annotation>
<xsd:documentation>
Defines a generic channel type. The actual channel type
will be determined by the channel factory set on the message bus.
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="queue-channel" type="capacityChannelType">
<xsd:annotation>
<xsd:documentation>
Defines a channel that buffers messages in a queue.
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="direct-channel">
<xsd:complexType>
<xsd:annotation>
<xsd:documentation>
Defines a channel that invokes its handlers directly in the sender's thread.
</xsd:documentation>
</xsd:annotation>
<xsd:complexContent>
<xsd:extension base="channelType">
<xsd:attribute name="source" type="xsd:string"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="priority-channel">
<xsd:complexType>
<xsd:annotation>
<xsd:documentation>
Defines a channel with priority-ordering for message reception.
</xsd:documentation>
</xsd:annotation>
<xsd:complexContent>
<xsd:extension base="capacityChannelType">
<xsd:attribute name="comparator" type="xsd:string"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="rendezvous-channel" type="channelType"/>
<xsd:element name="thread-local-channel" type="channelType"/>
<xsd:complexType name="capacityChannelType">
<xsd:annotation>
<xsd:documentation>
Defines a channel with a configurable capacity.
</xsd:documentation>
</xsd:annotation>
<xsd:complexContent>
<xsd:extension base="channelType">
<xsd:attribute name="capacity" type="xsd:integer"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="channelType">
<xsd:annotation>
<xsd:documentation>
Defines a message channel.
</xsd:documentation>
</xsd:annotation>
<xsd:sequence>
<xsd:element ref="dispatcher-policy" minOccurs="0" maxOccurs="1"/>
<xsd:element ref="interceptor" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:ID" use="required"/>
<xsd:attribute name="publish-subscribe" type="xsd:boolean" default="false"/>
<xsd:attribute name="datatype" type="xsd:string"/>
</xsd:complexType>
<xsd:element name="interceptor">
<xsd:complexType>
<xsd:annotation>
<xsd:documentation>
Provides a channel interceptor reference.
</xsd:documentation>
</xsd:annotation>
<xsd:attribute name="ref" type="xsd:string" use="required"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="source-endpoint">
<xsd:complexType>
<xsd:annotation>
<xsd:documentation>
Defines a source endpoint.
</xsd:documentation>
</xsd:annotation>
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:sequence>
<xsd:element ref="schedule" minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="source" type="xsd:string" use="required"/>
<xsd:attribute name="channel" type="xsd:string" use="required"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="source-adapter" type="methodInvokingAdapterType">
<xsd:annotation>
<xsd:documentation>
Defines a MethodInvokingSource.
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="target-endpoint">
<xsd:complexType>
<xsd:annotation>
<xsd:documentation>
Defines a target endpoint.
</xsd:documentation>
</xsd:annotation>
<xsd:complexContent>
<xsd:extension base="targetEndpointType">
<xsd:attribute name="target" type="xsd:string" use="required"/>
<xsd:attribute name="method" type="xsd:string"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="target-adapter" type="methodInvokingAdapterType">
<xsd:annotation>
<xsd:documentation>
Defines a MethodInvokingTarget.
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="handler-endpoint">
<xsd:complexType>
<xsd:annotation>
<xsd:documentation>
Defines an endpoint for a MessageHandler.
</xsd:documentation>
</xsd:annotation>
<xsd:complexContent>
<xsd:extension base="targetEndpointType">
<xsd:attribute name="handler" type="xsd:string" use="required"/>
<xsd:attribute name="method" type="xsd:string"/>
<xsd:attribute name="output-channel" type="xsd:string"/>
<xsd:attribute name="reply-handler" type="xsd:string"/>
<xsd:attribute name="return-address-overrides" type="xsd:boolean" default="false"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="schedule">
<xsd:complexType>
<xsd:annotation>
<xsd:documentation>
Defines a schedule.
</xsd:documentation>
</xsd:annotation>
<xsd:attribute name="period" type="xsd:int"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="selector">
<xsd:complexType>
<xsd:annotation>
<xsd:documentation>
Provides a message selector reference.
</xsd:documentation>
</xsd:annotation>
<xsd:attribute name="ref" type="xsd:string" use="required"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="handler-chain">
<xsd:complexType>
<xsd:annotation>
<xsd:documentation>
Defines a MessageHandler chain.
</xsd:documentation>
</xsd:annotation>
<xsd:sequence>
<xsd:element ref="handler" minOccurs="1" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:ID"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="handler">
<xsd:complexType>
<xsd:annotation>
<xsd:documentation>
Defines a handler.
</xsd:documentation>
</xsd:annotation>
<xsd:attribute name="id" type="xsd:ID"/>
<xsd:attribute name="ref" type="xsd:string" use="required"/>
<xsd:attribute name="method" type="xsd:string"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="dispatcher-policy">
<xsd:complexType>
<xsd:annotation>
<xsd:documentation>
Defines a dispatcher policy.
</xsd:documentation>
</xsd:annotation>
<xsd:attribute name="max-messages-per-task" type="xsd:int"/>
<xsd:attribute name="receive-timeout" type="xsd:long"/>
<xsd:attribute name="rejection-limit" type="xsd:int"/>
<xsd:attribute name="retry-interval" type="xsd:long"/>
<xsd:attribute name="should-fail-on-rejection-limit" type="xsd:boolean"/>
</xsd:complexType>
</xsd:element>
<xsd:complexType name="concurrencyType">
<xsd:annotation>
<xsd:documentation>
Defines a concurrency policy.
</xsd:documentation>
</xsd:annotation>
<xsd:attribute name="core" type="xsd:int"/>
<xsd:attribute name="max" type="xsd:int"/>
<xsd:attribute name="queue-capacity" type="xsd:int"/>
<xsd:attribute name="keep-alive" type="xsd:int"/>
</xsd:complexType>
<xsd:element name="router">
<xsd:complexType>
<xsd:annotation>
<xsd:documentation>
Defines a Router.
</xsd:documentation>
</xsd:annotation>
<xsd:attribute name="id" type="xsd:ID"/>
<xsd:attribute name="ref" type="xsd:string" use="required"/>
<xsd:attribute name="method" type="xsd:string" use="required"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="splitter">
<xsd:complexType>
<xsd:annotation>
<xsd:documentation>
Defines a Splitter.
</xsd:documentation>
</xsd:annotation>
<xsd:attribute name="id" type="xsd:ID"/>
<xsd:attribute name="ref" type="xsd:string" use="required"/>
<xsd:attribute name="method" type="xsd:string" use="required"/>
<xsd:attribute name="output-channel" type="xsd:string" use="required"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="aggregator">
<xsd:complexType>
<xsd:annotation>
<xsd:documentation>
Defines an aggregating message handler.
</xsd:documentation>
</xsd:annotation>
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:sequence>
<xsd:element name="completion-strategy" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="ref" type="xsd:string" use="required"/>
<xsd:attribute name="method" type="xsd:string" use="optional"/>
<xsd:attribute name="completion-strategy" type="xsd:string" use="optional"/>
<xsd:attribute name="completion-strategy-method" type="xsd:string" use="optional"/>
<xsd:attribute name="default-reply-channel" type="xsd:string" use="optional"/>
<xsd:attribute name="discard-channel" type="xsd:string" use="optional"/>
<xsd:attribute name="send-timeout" type="xsd:long" use="optional"/>
<xsd:attribute name="send-partial-result-on-timeout" type="xsd:boolean" use="optional"/>
<xsd:attribute name="tracked-correlation-id-capacity" type="xsd:int" use="optional"/>
<xsd:attribute name="reaper-interval" type="xsd:long" use="optional"/>
<xsd:attribute name="timeout" type="xsd:long" use="optional"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="completion-strategy">
<xsd:complexType>
<xsd:annotation>
<xsd:documentation>
Defines a completion strategy.
</xsd:documentation>
</xsd:annotation>
<xsd:attribute name="ref" type="xsd:string" use="required"/>
<xsd:attribute name="method" type="xsd:string" use="optional"/>
</xsd:complexType>
</xsd:element>
<xsd:complexType name="targetEndpointType">
<xsd:annotation>
<xsd:documentation>
Defines common configuration properties of target message endpoints.
</xsd:documentation>
</xsd:annotation>
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:sequence>
<xsd:element ref="schedule" minOccurs="0" maxOccurs="1"/>
<xsd:element name="concurrency" type="concurrencyType" minOccurs="0" maxOccurs="1"/>
<xsd:element ref="selector" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="input-channel" type="xsd:string" use="required"/>
<xsd:attribute name="error-handler" type="xsd:string"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="methodInvokingAdapterType">
<xsd:annotation>
<xsd:documentation>
Base type for method-invoking adapters.
</xsd:documentation>
</xsd:annotation>
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:attribute name="ref" type="xsd:string" use="required"/>
<xsd:attribute name="method" type="xsd:string" use="required"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:schema>

View File

@@ -0,0 +1,97 @@
/*
* 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.dispatcher;
import java.util.LinkedList;
import java.util.List;
import org.springframework.integration.channel.DispatcherPolicy;
import org.springframework.integration.channel.MessageChannel;
import org.springframework.integration.message.BlockingSource;
import org.springframework.integration.message.Message;
import org.springframework.integration.message.MessageDeliveryAware;
import org.springframework.integration.message.MessageDeliveryException;
import org.springframework.integration.message.Source;
import org.springframework.util.Assert;
/**
* A subclass of {@link SimpleDispatcher} that adds message retrieval
* capabilities by polling a {@link PollableSource} for {@link Message Messages}.
* The number of messages retrieved per poll is limited by the '<em>maxMessagesPerTask</em>'
* property of the provided {@link DispatcherPolicy}, and the timeout for each
* receive call is determined by the policy's '<em>receiveTimeout</em>'
* property. In general, it is recommended to use a value of 1 (the default) for
* 'maxMessagesPerTask' whenever a significant timeout is provided. Otherwise
* the poller may be holding on to available messages while waiting for
* additional messages. Note that the 'timeout' value is only relevant if the
* specified source is an implementation of {@link BlockingSource}. The default
* timeout value is 0 indicating that the method should return immediately
* rather than waiting for a {@link Message} to become available.
*
* @author Mark Fisher
*/
public class DefaultPollingDispatcher extends SimpleDispatcher implements PollingDispatcher {
private final Source<?> source;
public DefaultPollingDispatcher(MessageChannel channel) {
this(channel, channel.getDispatcherPolicy());
}
public DefaultPollingDispatcher(Source<?> source, DispatcherPolicy dispatcherPolicy) {
super(dispatcherPolicy);
Assert.notNull(source, "source must not be null");
this.source = source;
this.dispatcherPolicy.setReceiveTimeout(0);
}
@Override
public boolean dispatch(Message<?> message) {
boolean sent = super.dispatch(message);
if (this.source instanceof MessageDeliveryAware) {
if (sent) {
((MessageDeliveryAware) this.source).onSend(message);
}
else {
((MessageDeliveryAware) this.source).onFailure(new MessageDeliveryException(message, "failed to send message"));
}
}
return sent;
}
public List<Message<?>> poll() {
List<Message<?>> messages = new LinkedList<Message<?>>();
int limit = this.dispatcherPolicy.getMaxMessagesPerTask();
while (messages.size() < limit) {
Message<?> message = null;
long timeout = this.dispatcherPolicy.getReceiveTimeout();
if (this.source instanceof BlockingSource && timeout >= 0) {
message = ((BlockingSource<?>) this.source).receive(timeout);
}
else {
message = this.source.receive();
}
if (message == null) {
return messages;
}
messages.add(message);
}
return messages;
}
}

View File

@@ -0,0 +1,32 @@
/*
* 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.dispatcher;
import org.springframework.integration.message.Message;
/**
* Strategy interface for dispatching messages.
*
* @author Mark Fisher
*/
public interface MessageDispatcher {
boolean dispatch(Message<?> message);
void setSendTimeout(long timeout);
}

View File

@@ -0,0 +1,27 @@
/*
* 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.dispatcher;
import org.springframework.integration.message.Poller;
import org.springframework.integration.message.Subscribable;
/**
* @author Mark Fisher
*/
public interface PollingDispatcher extends Poller, MessageDispatcher, Subscribable {
}

View File

@@ -0,0 +1,60 @@
/*
* 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.dispatcher;
import java.util.List;
import org.springframework.integration.message.Message;
import org.springframework.integration.scheduling.MessagingTask;
import org.springframework.integration.scheduling.Schedule;
import org.springframework.util.Assert;
/**
* A {@link MessagingTask} that combines polling and dispatching.
*
* @author Mark Fisher
*/
public class PollingDispatcherTask implements MessagingTask {
private final PollingDispatcher dispatcher;
private final Schedule schedule;
public PollingDispatcherTask(PollingDispatcher dispatcher, Schedule schedule) {
Assert.notNull(dispatcher, "dispatcher must not be null");
this.dispatcher = dispatcher;
this.schedule = schedule;
}
public PollingDispatcher getDispatcher() {
return this.dispatcher;
}
public Schedule getSchedule() {
return this.schedule;
}
public void run() {
List<Message<?>> messages = this.dispatcher.poll();
for (Message<?> message : messages) {
this.dispatcher.dispatch(message);
}
}
}

View File

@@ -0,0 +1,134 @@
/*
* 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.dispatcher;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.integration.channel.DispatcherPolicy;
import org.springframework.integration.handler.MessageHandlerNotRunningException;
import org.springframework.integration.handler.MessageHandlerRejectedExecutionException;
import org.springframework.integration.message.BlockingTarget;
import org.springframework.integration.message.Message;
import org.springframework.integration.message.MessageDeliveryException;
import org.springframework.integration.message.Subscribable;
import org.springframework.integration.message.Target;
/**
* Basic implementation of {@link MessageDispatcher}.
*
* @author Mark Fisher
*/
public class SimpleDispatcher implements MessageDispatcher, Subscribable {
protected final Log logger = LogFactory.getLog(this.getClass());
private final List<Target> targets = new CopyOnWriteArrayList<Target>();
protected final DispatcherPolicy dispatcherPolicy;
private volatile long sendTimeout;
public SimpleDispatcher(DispatcherPolicy dispatcherPolicy) {
this.dispatcherPolicy = (dispatcherPolicy != null) ? dispatcherPolicy : new DispatcherPolicy();
}
public void setSendTimeout(long sendTimeout) {
this.sendTimeout = sendTimeout;
}
public boolean subscribe(Target target) {
return this.targets.add(target);
}
public boolean unsubscribe(Target target) {
return this.targets.remove(target);
}
public boolean dispatch(Message<?> message) {
int attempts = 0;
List<Target> targetList = new ArrayList<Target>(this.targets);
while (attempts < this.dispatcherPolicy.getRejectionLimit()) {
if (attempts > 0) {
if (logger.isDebugEnabled()) {
logger.debug("target(s) rejected message after " + attempts +
" attempt(s), will try again after 'retryInterval' of " +
this.dispatcherPolicy.getRetryInterval() + " milliseconds");
}
try {
Thread.sleep(this.dispatcherPolicy.getRetryInterval());
}
catch (InterruptedException iex) {
Thread.currentThread().interrupt();
return false;
}
}
Iterator<Target> iter = targetList.iterator();
if (!iter.hasNext()) {
if (logger.isWarnEnabled()) {
logger.warn("no active targets");
}
return false;
}
boolean rejected = false;
while (iter.hasNext()) {
Target target = iter.next();
try {
boolean sent = (target instanceof BlockingTarget && this.sendTimeout >= 0) ?
((BlockingTarget) target).send(message, this.sendTimeout) : target.send(message);
if (!this.dispatcherPolicy.isPublishSubscribe() && sent) {
return true;
}
if (!sent && logger.isDebugEnabled()) {
logger.debug("target rejected message, continuing with other targets if available");
}
iter.remove();
}
catch (MessageHandlerNotRunningException e) {
if (logger.isDebugEnabled()) {
logger.debug("target is not running, continuing with other targets if available", e);
}
}
catch (MessageHandlerRejectedExecutionException e) {
rejected = true;
if (logger.isDebugEnabled()) {
logger.debug("target is busy, continuing with other targets if available", e);
}
}
}
if (!rejected) {
return true;
}
attempts++;
}
if (this.dispatcherPolicy.getShouldFailOnRejectionLimit()) {
throw new MessageDeliveryException(message, "Dispatcher reached rejection limit of "
+ this.dispatcherPolicy.getRejectionLimit()
+ ". Consider increasing the target's concurrency and/or "
+ "the dispatcherPolicy's 'rejectionLimit'.");
}
return false;
}
}

View File

@@ -0,0 +1,50 @@
/*
* 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.endpoint;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Base class for {@link MessageEndpoint} implementations.
*
* @author Mark Fisher
*/
public abstract class AbstractEndpoint implements MessageEndpoint {
protected final Log logger = LogFactory.getLog(this.getClass());
private volatile String name;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public void setBeanName(String beanName) {
this.setName(beanName);
}
public String toString() {
return (this.name != null) ? this.name : super.toString();
}
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright 2002-2007 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.endpoint;
import org.springframework.util.Assert;
/**
* Metadata for configuring a pool of concurrent threads.
*
* @author Mark Fisher
*/
public class ConcurrencyPolicy implements EndpointPolicy {
public static final int DEFAULT_CORE_SIZE = 1;
public static final int DEFAULT_MAX_SIZE = 10;
public static final int DEFAULT_QUEUE_CAPACITY = 0;
public static final int DEFAULT_KEEP_ALIVE_SECONDS = 60;
private int coreSize = DEFAULT_CORE_SIZE;
private int maxSize = DEFAULT_MAX_SIZE;
private int queueCapacity = DEFAULT_QUEUE_CAPACITY;
private int keepAliveSeconds = DEFAULT_KEEP_ALIVE_SECONDS;
public ConcurrencyPolicy() {
}
public ConcurrencyPolicy(int coreSize, int maxSize) {
Assert.isTrue(maxSize >= coreSize, "'coreSize' must not exceed 'maxSize'");
this.setCoreSize(coreSize);
this.setMaxSize(maxSize);
}
public int getCoreSize() {
return this.coreSize;
}
public void setCoreSize(int coreSize) {
Assert.isTrue(coreSize > 0, "'coreSize' must be at least 1");
this.coreSize = coreSize;
}
public int getMaxSize() {
return this.maxSize;
}
public void setMaxSize(int maxSize) {
Assert.isTrue(maxSize > 0, "'maxSize' must be at least 1");
this.maxSize = maxSize;
}
public int getQueueCapacity() {
return this.queueCapacity;
}
public void setQueueCapacity(int queueCapacity) {
Assert.isTrue(queueCapacity >= 0, "'queueCapacity' must not be negative");
this.queueCapacity = queueCapacity;
}
public int getKeepAliveSeconds() {
return this.keepAliveSeconds;
}
public void setKeepAliveSeconds(int keepAliveSeconds) {
Assert.isTrue(keepAliveSeconds >= 0, "'keepAliveSeconds' must not be negative");
this.keepAliveSeconds = keepAliveSeconds;
}
public String toString() {
return "[coreSize=" + this.coreSize + ", maxSize=" + this.maxSize +
", queueCapacity=" + this.queueCapacity + ", keepAliveSeconds=" + this.keepAliveSeconds + "]";
}
}

View File

@@ -0,0 +1,110 @@
/*
* 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.endpoint;
import java.util.concurrent.ExecutorService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.integration.handler.MessageHandler;
import org.springframework.integration.handler.MessageHandlerNotRunningException;
import org.springframework.integration.handler.MessageHandlerRejectedExecutionException;
import org.springframework.integration.message.Message;
import org.springframework.integration.message.MessageDeliveryException;
import org.springframework.integration.message.Target;
import org.springframework.integration.util.ErrorHandler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.util.Assert;
/**
* A {@link Target} implementation that encapsulates an Executor and delegates
* to a wrapped target for concurrent, asynchronous message handling.
*
* @author Mark Fisher
*/
public class ConcurrentTarget implements Target, DisposableBean {
private final Log logger = LogFactory.getLog(this.getClass());
private final Target target;
private final ExecutorService executor;
private volatile ErrorHandler errorHandler;
public ConcurrentTarget(Target target, ExecutorService executor) {
Assert.notNull(target, "'target' must not be null");
Assert.notNull(executor, "'executor' must not be null");
this.target = target;
this.executor = executor;
}
public void setErrorHandler(ErrorHandler errorHandler) {
this.errorHandler = errorHandler;
}
public void destroy() {
this.executor.shutdown();
}
public boolean send(Message<?> message) {
if (this.executor.isShutdown()) {
throw new MessageHandlerNotRunningException(message);
}
try {
this.executor.execute(new TargetTask(message));
return true;
}
catch (RuntimeException e) {
throw new MessageHandlerRejectedExecutionException(message, e);
}
}
private class TargetTask implements Runnable {
private Message<?> message;
TargetTask(Message<?> message) {
this.message = message;
}
public void run() {
try {
if (!target.send(this.message)) {
throw new MessageDeliveryException(message, "failed to send message to target");
}
}
catch (Throwable t) {
if (logger.isDebugEnabled()) {
logger.debug("error occurred in handler execution", t);
}
if (errorHandler != null) {
errorHandler.handle(t);
}
else if (logger.isWarnEnabled() && !logger.isDebugEnabled()) {
logger.warn("error occurred in handler execution", t);
}
}
}
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2002-2007 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.endpoint;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.util.Assert;
/**
* A simple map-backed implementation of {@link EndpointRegistry}.
*
* @author Mark Fisher
*/
public class DefaultEndpointRegistry implements EndpointRegistry {
private final Map<String, MessageEndpoint> endpoints = new ConcurrentHashMap<String, MessageEndpoint>();
public MessageEndpoint lookupEndpoint(String endpointName) {
return this.endpoints.get(endpointName);
}
public void registerEndpoint(String name, MessageEndpoint endpoint) {
Assert.notNull(name, "'name' must not be null");
Assert.notNull(endpoint, "'endpoint' must not be null");
this.endpoints.put(name, endpoint);
}
public MessageEndpoint unregisterEndpoint(String name) {
return (name != null) ? this.endpoints.remove(name) : null;
}
public Set<String> getEndpointNames() {
return this.endpoints.keySet();
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2002-2007 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.endpoint;
/**
* A marker interface for endpoint metadata.
*
* @author Mark Fisher
*/
public interface EndpointPolicy {
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2002-2007 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.endpoint;
import java.util.Set;
/**
* A strategy interface for registration and lookup of message endpoints by name.
*
* @author Mark Fisher
*/
public interface EndpointRegistry {
void registerEndpoint(String name, MessageEndpoint endpoint);
MessageEndpoint unregisterEndpoint(String name);
MessageEndpoint lookupEndpoint(String endpointName);
Set<String> getEndpointNames();
}

View File

@@ -0,0 +1,192 @@
/*
* 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.endpoint;
import org.springframework.integration.channel.ChannelRegistry;
import org.springframework.integration.channel.ChannelRegistryAware;
import org.springframework.integration.channel.MessageChannel;
import org.springframework.integration.handler.MessageHandler;
import org.springframework.integration.handler.ReplyHandler;
import org.springframework.integration.message.Message;
import org.springframework.integration.message.MessageDeliveryException;
import org.springframework.integration.message.MessageHandlingException;
import org.springframework.integration.message.MessageHeader;
import org.springframework.integration.message.Target;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Implementation of the {@link MessageEndpoint} interface for invoking
* {@link MessageHandler MessageHandlers}.
*
* @author Mark Fisher
*/
public class HandlerEndpoint extends TargetEndpoint {
private volatile MessageHandler handler;
private volatile ReplyHandler replyHandler = new EndpointReplyHandler();
private volatile long replyTimeout = 1000;
private volatile String outputChannelName;
private volatile boolean returnAddressOverrides = false;
public HandlerEndpoint(MessageHandler handler) {
Assert.notNull(handler, "handler must not be null");
this.handler = handler;
}
public MessageHandler getHandler() {
return this.handler;
}
public void setReplyHandler(ReplyHandler replyHandler) {
Assert.notNull(replyHandler, "'replyHandler' must not be null");
this.replyHandler = replyHandler;
}
/**
* Set the timeout in milliseconds to be enforced when this endpoint sends a
* reply message. If the message is not sent successfully within the
* allotted time, then a MessageDeliveryException will be thrown.
* The default <code>replyTimeout</code> value is 1000 milliseconds.
*/
public void setReplyTimeout(long replyTimeout) {
this.replyTimeout = replyTimeout;
}
/**
* Set the name of the channel to which this endpoint should send reply
* messages.
*/
public void setOutputChannelName(String outputChannelName) {
this.outputChannelName = outputChannelName;
}
public String getOutputChannelName() {
return this.outputChannelName;
}
public void setReturnAddressOverrides(boolean returnAddressOverrides) {
this.returnAddressOverrides = returnAddressOverrides;
}
public void afterPropertiesSet() {
Assert.notNull(this.handler, "handler must not be null");
if (this.handler instanceof ChannelRegistryAware) {
((ChannelRegistryAware) this.handler).setChannelRegistry(this.getChannelRegistry());
}
super.setTarget(new HandlerInvokingTarget(this.handler, this.replyHandler));
super.afterPropertiesSet();
}
private MessageChannel resolveReplyChannel(MessageHeader originalMessageHeader) {
if (this.returnAddressOverrides) {
MessageChannel channel = this.getReturnAddress(originalMessageHeader);
if (channel == null) {
channel = this.getOutputChannel();
}
return channel;
}
else {
MessageChannel channel = this.getOutputChannel();
if (channel == null) {
channel = this.getReturnAddress(originalMessageHeader);
}
return channel;
}
}
private MessageChannel getReturnAddress(MessageHeader originalMessageHeader) {
Object returnAddress = originalMessageHeader.getReturnAddress();
if (returnAddress != null) {
if (returnAddress instanceof MessageChannel) {
return (MessageChannel) returnAddress;
}
ChannelRegistry registry = this.getChannelRegistry();
if (returnAddress instanceof String && registry != null) {
String channelName = (String) returnAddress;
if (StringUtils.hasText(channelName)) {
return registry.lookupChannel(channelName);
}
}
}
return null;
}
private MessageChannel getOutputChannel() {
ChannelRegistry registry = this.getChannelRegistry();
if (this.outputChannelName != null && registry != null) {
return registry.lookupChannel(this.outputChannelName);
}
return null;
}
private static class HandlerInvokingTarget implements Target {
private final MessageHandler handler;
private final ReplyHandler replyHandler;
public HandlerInvokingTarget(MessageHandler handler, ReplyHandler replyHandler) {
this.handler = handler;
this.replyHandler = replyHandler;
}
public boolean send(Message<?> message) {
Message<?> replyMessage = this.handler.handle(message);
if (replyMessage != null) {
if (replyMessage.getHeader().getCorrelationId() == null) {
replyMessage.getHeader().setCorrelationId(message.getId());
}
this.replyHandler.handle(replyMessage, message.getHeader());
}
return true;
}
}
private class EndpointReplyHandler implements ReplyHandler {
public void handle(Message<?> replyMessage, MessageHeader originalMessageHeader) {
if (replyMessage == null) {
return;
}
MessageChannel replyChannel = resolveReplyChannel(originalMessageHeader);
if (replyChannel == null) {
throw new MessageHandlingException(replyMessage, "Unable to determine reply channel for message. " +
"Provide an 'outputChannelName' on the message endpoint or a 'returnAddress' in the message header");
}
if (logger.isDebugEnabled()) {
logger.debug("endpoint '" + HandlerEndpoint.this + "' replying to channel '" + replyChannel + "' with message: " + replyMessage);
}
if (!replyChannel.send(replyMessage, replyTimeout)) {
throw new MessageDeliveryException(replyMessage,
"unable to send reply message within alloted timeout of " + replyTimeout + " milliseconds");
}
}
}
}

View File

@@ -0,0 +1,30 @@
/*
* 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.endpoint;
import org.springframework.beans.factory.BeanNameAware;
/**
* Base interface for message endpoints.
*
* @author Mark Fisher
*/
public interface MessageEndpoint extends BeanNameAware {
String getName();
}

View File

@@ -0,0 +1,159 @@
/*
* 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.endpoint;
import java.lang.reflect.Method;
import java.util.List;
import org.aopalliance.aop.Advice;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.integration.channel.DispatcherPolicy;
import org.springframework.integration.channel.MessageChannel;
import org.springframework.integration.dispatcher.DefaultPollingDispatcher;
import org.springframework.integration.dispatcher.PollingDispatcher;
import org.springframework.integration.dispatcher.PollingDispatcherTask;
import org.springframework.integration.message.Source;
import org.springframework.integration.scheduling.MessagingTask;
import org.springframework.integration.scheduling.Schedule;
import org.springframework.util.Assert;
/**
* A channel adapter that retrieves messages from a {@link Source}
* and then sends the resulting messages to the provided {@link MessageChannel}.
*
* @author Mark Fisher
*/
public class SourceEndpoint extends AbstractEndpoint implements MessagingTask, InitializingBean {
private final Schedule schedule;
private final DispatcherPolicy dispatcherPolicy = new DispatcherPolicy();
private volatile PollingDispatcher dispatcher;
private volatile List<Advice> dispatchAdviceChain;
private volatile MessagingTask task;
private volatile List<Advice> taskAdviceChain;
private volatile boolean taskInitialized;
private final Object taskMonitor = new Object();
public SourceEndpoint(Source<?> source, MessageChannel channel, Schedule schedule) {
Assert.notNull(source, "source must not be null");
Assert.notNull(channel, "channel must not be null");
Assert.notNull(schedule, "schedule must not be null");
this.dispatcher = new DefaultPollingDispatcher(source, this.dispatcherPolicy);
this.dispatcher.subscribe(channel);
this.schedule = schedule;
}
public void setMaxMessagesPerTask(int maxMessagesPerTask) {
this.dispatcherPolicy.setMaxMessagesPerTask(maxMessagesPerTask);
}
public void setSendTimeout(long sendTimeout) {
this.dispatcher.setSendTimeout(sendTimeout);
}
public void setTaskAdviceChain(List<Advice> taskAdviceChain) {
this.taskAdviceChain = taskAdviceChain;
}
public void setDispatchAdviceChain(List<Advice> dispatchAdviceChain) {
this.dispatchAdviceChain = dispatchAdviceChain;
}
public Schedule getSchedule() {
return this.schedule;
}
public void afterPropertiesSet() {
this.initializeTask();
}
public void initializeTask() {
synchronized (this.taskMonitor) {
if (this.taskInitialized) {
return;
}
this.refreshTask();
this.taskInitialized = true;
}
}
public void refreshTask() {
synchronized (this.taskMonitor) {
PollingDispatcher dispatcherProxy = null;
if (this.dispatchAdviceChain != null && this.dispatchAdviceChain.size() > 0) {
ProxyFactory proxyFactory = new ProxyFactory(this.dispatcher);
proxyFactory.setInterfaces(new Class[] { PollingDispatcher.class });
for (Advice advice : this.dispatchAdviceChain) {
proxyFactory.addAdvisor(new MethodNameAdvisor(advice, "dispatch"));
}
dispatcherProxy = (PollingDispatcher) proxyFactory.getProxy();
}
this.task = new PollingDispatcherTask((dispatcherProxy != null) ? dispatcherProxy : this.dispatcher, this.schedule);
if (this.taskAdviceChain != null && this.taskAdviceChain.size() > 0) {
ProxyFactory proxyFactory = new ProxyFactory(this.task);
proxyFactory.setInterfaces(new Class[] { MessagingTask.class });
for (Advice advice : this.taskAdviceChain) {
proxyFactory.addAdvisor(new MethodNameAdvisor(advice, "run"));
}
this.task = (MessagingTask) proxyFactory.getProxy();
}
}
}
private MessagingTask getTask() {
synchronized (this.taskMonitor) {
if (!this.taskInitialized) {
this.initializeTask();
}
return this.task;
}
}
public void run() {
this.getTask().run();
}
@SuppressWarnings("serial")
private static class MethodNameAdvisor extends StaticMethodMatcherPointcutAdvisor {
private final String methodName;
MethodNameAdvisor(Advice advice, String methodName) {
super(advice);
this.methodName = methodName;
}
@SuppressWarnings("unchecked")
public boolean matches(Method method, Class targetClass) {
return method.getName().equals(methodName);
}
}
}

View File

@@ -0,0 +1,211 @@
/*
* 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.endpoint;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.Lifecycle;
import org.springframework.integration.channel.ChannelRegistry;
import org.springframework.integration.channel.ChannelRegistryAware;
import org.springframework.integration.handler.MessageHandlerNotRunningException;
import org.springframework.integration.handler.MessageHandlerRejectedExecutionException;
import org.springframework.integration.message.Message;
import org.springframework.integration.message.MessageHandlingException;
import org.springframework.integration.message.Target;
import org.springframework.integration.message.selector.MessageSelector;
import org.springframework.integration.scheduling.Subscription;
import org.springframework.integration.util.ErrorHandler;
import org.springframework.util.Assert;
/**
* Base class for {@link MessageEndpoint} implementations to which Messages may be sent.
*
* @author Mark Fisher
*/
public class TargetEndpoint extends AbstractEndpoint implements Target, ChannelRegistryAware, InitializingBean, Lifecycle {
private volatile Target target;
private volatile Subscription subscription;
private volatile ConcurrencyPolicy concurrencyPolicy;
private volatile ErrorHandler errorHandler;
private final List<MessageSelector> selectors = new CopyOnWriteArrayList<MessageSelector>();
private volatile ChannelRegistry channelRegistry;
private volatile boolean initialized;
private volatile boolean running;
public TargetEndpoint() {
}
public TargetEndpoint(Target target) {
Assert.notNull(target, "target must not be null");
this.target = target;
}
public Target getTarget() {
return this.target;
}
public void setTarget(Target target) {
Assert.notNull(target, "target must not be null");
this.target = target;
}
public void setMessageSelectors(List<MessageSelector> selectors) {
this.selectors.clear();
this.selectors.addAll(selectors);
}
public void addMessageSelector(MessageSelector messageSelector) {
Assert.notNull(messageSelector, "'messageSelector' must not be null");
this.selectors.add(messageSelector);
}
public Subscription getSubscription() {
return this.subscription;
}
public void setSubscription(Subscription subscription) {
this.subscription = subscription;
}
public ConcurrencyPolicy getConcurrencyPolicy() {
return this.concurrencyPolicy;
}
public void setConcurrencyPolicy(ConcurrencyPolicy concurrencyPolicy) {
this.concurrencyPolicy = concurrencyPolicy;
}
public void setErrorHandler(ErrorHandler errorHandler) {
this.errorHandler = errorHandler;
}
public boolean hasErrorHandler() {
return (this.errorHandler != null);
}
/**
* Set the channel registry to use for looking up channels by name.
*/
public void setChannelRegistry(ChannelRegistry channelRegistry) {
this.channelRegistry = channelRegistry;
}
protected ChannelRegistry getChannelRegistry() {
return this.channelRegistry;
}
public void afterPropertiesSet() {
if (this.target instanceof ChannelRegistryAware && this.channelRegistry != null) {
((ChannelRegistryAware) this.target).setChannelRegistry(this.channelRegistry);
}
if (this.concurrencyPolicy != null && !(this.target instanceof ConcurrentTarget)) {
int capacity = this.concurrencyPolicy.getQueueCapacity();
BlockingQueue<Runnable> queue = (capacity < 1) ? new SynchronousQueue<Runnable>() : new ArrayBlockingQueue<Runnable>(capacity);
ExecutorService executor = new ThreadPoolExecutor(this.concurrencyPolicy.getCoreSize(), this.concurrencyPolicy.getMaxSize(),
this.concurrencyPolicy.getKeepAliveSeconds(), TimeUnit.SECONDS, queue);
this.target = new ConcurrentTarget(this.target, executor);
}
if (this.target instanceof ConcurrentTarget) {
if (this.errorHandler != null) {
((ConcurrentTarget) this.target).setErrorHandler(this.errorHandler);
}
}
this.initialized = true;
}
public boolean isRunning() {
return this.running;
}
public void start() {
if (this.isRunning()) {
return;
}
if (!this.initialized) {
this.afterPropertiesSet();
}
this.running = true;
}
public void stop() {
if (!this.isRunning()) {
return;
}
if (this.target instanceof DisposableBean) {
try {
((DisposableBean) this.target).destroy();
}
catch (Exception e) {
if (logger.isWarnEnabled()) {
logger.warn("exception occurred when destroying target", e);
}
}
}
this.running = false;
}
public final boolean send(Message<?> message) {
if (logger.isDebugEnabled()) {
logger.debug("endpoint '" + this + "' handling message: " + message);
}
if (!this.isRunning()) {
throw new MessageHandlerNotRunningException(message);
}
for (MessageSelector selector : this.selectors) {
if (!selector.accept(message)) {
return false;
}
}
try {
return this.target.send(message);
}
catch (MessageHandlerRejectedExecutionException e) {
throw e;
}
catch (Throwable t) {
if (this.errorHandler == null) {
if (t instanceof MessageHandlingException) {
throw (MessageHandlingException) t;
}
throw new MessageHandlingException(message,
"error occurred in endpoint, and no 'errorHandler' available", t);
}
this.errorHandler.handle(t);
return false;
}
}
}

View File

@@ -0,0 +1,110 @@
/*
* 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.gateway;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.SimpleTypeConverter;
import org.springframework.beans.TypeConverter;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.integration.bus.MessageBus;
import org.springframework.integration.config.MessageBusParser;
import org.springframework.integration.message.Message;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Generates a proxy for the provided service interface to enable interaction
* with messaging components without application code being aware of them.
*
* @author Mark Fisher
*/
public class GatewayProxyFactoryBean extends SimpleMessagingGateway
implements FactoryBean, MethodInterceptor, InitializingBean, BeanClassLoaderAware, BeanFactoryAware {
private Class<?> serviceInterface;
private TypeConverter typeConverter = new SimpleTypeConverter();
private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
private Object serviceProxy;
public void setServiceInterface(Class<?> serviceInterface) {
this.serviceInterface = serviceInterface;
}
public void setTypeConverter(TypeConverter typeConverter) {
Assert.notNull(typeConverter, "typeConverter must not be null");
this.typeConverter = typeConverter;
}
public void setBeanClassLoader(ClassLoader beanClassLoader) {
this.beanClassLoader = beanClassLoader;
}
public void setBeanFactory(BeanFactory beanFactory) {
this.getRequestReplyTemplate().setMessageBus(
(MessageBus) beanFactory.getBean(MessageBusParser.MESSAGE_BUS_BEAN_NAME));
}
public void afterPropertiesSet() {
this.serviceProxy = new ProxyFactory(this.serviceInterface, this).getProxy(this.beanClassLoader);
}
public Object getObject() throws Exception {
return this.serviceProxy;
}
public Class<?> getObjectType() {
return this.serviceInterface;
}
public boolean isSingleton() {
return true;
}
public Object invoke(MethodInvocation invocation) throws Throwable {
Class<?> returnType = invocation.getMethod().getReturnType();
boolean shouldReturnMessage = Message.class.isAssignableFrom(returnType);
int paramCount = invocation.getMethod().getParameterTypes().length;
Object response = null;
if (paramCount == 0) {
if (shouldReturnMessage) {
return this.receive();
}
response = this.receive();
}
else {
Object payload = (paramCount == 1) ? invocation.getArguments()[0] : invocation.getArguments();
if (returnType.equals(void.class)) {
this.send(payload);
return null;
}
response = shouldReturnMessage ? this.sendAndReceiveMessage(payload) : this.sendAndReceive(payload);
}
return (response != null) ? this.typeConverter.convertIfNecessary(response, returnType) : null;
}
}

View File

@@ -0,0 +1,64 @@
/*
* 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.gateway;
import org.springframework.integration.channel.MessageChannel;
import org.springframework.integration.channel.RequestReplyTemplate;
/**
* A convenient base class providing access to a {@link RequestReplyTemplate} and exposing setter methods for
* configuring request and reply {@link MessageChannel MessageChannels}. May be used as a base class for framework
* components so that the details of messaging are well-encapsulated and hidden from application code. For example,
* see {@link SimpleMessagingGateway}.
*
* @author Mark Fisher
*/
public abstract class MessagingGatewaySupport {
private final RequestReplyTemplate requestReplyTemplate = new RequestReplyTemplate();
public MessagingGatewaySupport(MessageChannel requestChannel) {
this.requestReplyTemplate.setRequestChannel(requestChannel);
}
public MessagingGatewaySupport() {
super();
}
public void setRequestChannel(MessageChannel requestChannel) {
this.requestReplyTemplate.setRequestChannel(requestChannel);
}
public void setReplyChannel(MessageChannel replyChannel) {
this.requestReplyTemplate.setReplyChannel(replyChannel);
}
public void setRequestTimeout(long requestTimeout) {
this.requestReplyTemplate.setRequestTimeout(requestTimeout);
}
public void setReplyTimeout(long replyTimeout) {
this.requestReplyTemplate.setReplyTimeout(replyTimeout);
}
protected final RequestReplyTemplate getRequestReplyTemplate() {
return this.requestReplyTemplate;
}
}

View File

@@ -0,0 +1,99 @@
/*
* 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.gateway;
import org.springframework.integration.channel.MessageChannel;
import org.springframework.integration.message.DefaultMessageCreator;
import org.springframework.integration.message.DefaultMessageMapper;
import org.springframework.integration.message.Message;
import org.springframework.integration.message.MessageCreator;
import org.springframework.integration.message.MessageDeliveryException;
import org.springframework.integration.message.MessageMapper;
import org.springframework.util.Assert;
/**
* A general purpose class that supports a variety of message exchanges. Useful for connecting application code to
* {@link MessageChannel MessageChannels} for sending, receiving, or request-reply operations. The sending methods
* accept any Object as the parameter value (i.e. it is not required to be a Message). A custom {@link MessageCreator}
* may be provided for creating Messages from the Objects. Likewise return values may be any Object and a custom
* implementation of the {@link MessageMapper} strategy may be provided for mapping a reply Message to an Object.
*
* @author Mark Fisher
*/
public class SimpleMessagingGateway extends MessagingGatewaySupport {
private MessageCreator messageCreator = new DefaultMessageCreator();
private MessageMapper messageMapper = new DefaultMessageMapper();
public SimpleMessagingGateway(MessageChannel requestChannel) {
super(requestChannel);
}
public SimpleMessagingGateway() {
super();
}
public void setMessageCreator(MessageCreator<?, ?> messageCreator) {
Assert.notNull(messageCreator, "messageCreator must not be null");
this.messageCreator = messageCreator;
}
public void setMessageMapper(MessageMapper<?, ?> messageMapper) {
Assert.notNull(messageMapper, "messageMapper must not be null");
this.messageMapper = messageMapper;
}
public void send(Object object) {
Message<?> message = (object instanceof Message) ? (Message) object :
this.messageCreator.createMessage(object);
if (message != null) {
if (!this.getRequestReplyTemplate().send(message)) {
throw new MessageDeliveryException(message, "failed to send Message to channel");
}
}
}
public Object receive() {
Message<?> message = this.getRequestReplyTemplate().receive();
return (message != null) ? this.messageMapper.mapMessage(message) : null;
}
public Object sendAndReceive(Object object) {
return this.sendAndReceive(object, true);
}
public Message<?> sendAndReceiveMessage(Object object) {
return (Message<?>) this.sendAndReceive(object, false);
}
private Object sendAndReceive(Object object, boolean shouldMapMessage) {
Message<?> request = (object instanceof Message) ? (Message) object :
this.messageCreator.createMessage(object);
if (request == null) {
return null;
}
Message<?> reply = this.getRequestReplyTemplate().request(request);
if (!shouldMapMessage) {
return reply;
}
return (reply != null) ? this.messageMapper.mapMessage(reply) : null;
}
}

View File

@@ -0,0 +1,202 @@
/*
* 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.handler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.integration.ConfigurationException;
import org.springframework.integration.message.DefaultMessageCreator;
import org.springframework.integration.message.DefaultMessageMapper;
import org.springframework.integration.message.Message;
import org.springframework.integration.message.MessageCreator;
import org.springframework.integration.message.MessageMapper;
import org.springframework.integration.message.MessagingException;
import org.springframework.integration.util.DefaultMethodInvoker;
import org.springframework.integration.util.MethodInvoker;
import org.springframework.integration.util.NameResolvingMethodInvoker;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* An implementation of the {@link MessageHandler} interface that invokes the specified method and target object. Either
* a {@link Method} reference or a 'methodName' may be provided, but both are not necessary. In fact, while preference
* is given to a {@link Method} reference if available, an Exception will be thrown if a non-matching 'methodName' has
* also been provided. Therefore, to avoid such ambiguity, it is recommended to provide just one or the other.
* <p>
* This handler also accepts an implementation of the {@link MessageMapper} strategy interface which it uses for
* converting from the {@link Message} being handled to an Object prior to invoking the method. Likewise, if the method
* has a non-null return value, a reply message will be generated by the configured implementation of the
* {@link MessageCreator} strategy interface. In both cases, the default implementations will simply consider the
* message's payload.
*
* @author Mark Fisher
*/
public abstract class AbstractMessageHandlerAdapter implements MessageHandler, InitializingBean {
public static final String OUTPUT_CHANNEL_NAME_KEY = "outputChannelName";
protected final Log logger = LogFactory.getLog(this.getClass());
private volatile Object object;
private volatile Method method;
private volatile String methodName;
private volatile boolean methodExpectsMessage;
private volatile MessageMapper messageMapper = new DefaultMessageMapper();
private volatile MessageCreator messageCreator = new DefaultMessageCreator();
protected volatile MethodInvoker invoker;
private volatile boolean initialized;
private final Object initializationMonitor = new Object();
public void setObject(Object object) {
this.object = object;
}
protected Object getObject() {
return this.object;
}
public void setMethod(Method method) {
this.method = method;
}
protected Method getMethod() {
return this.method;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public void setMethodExpectsMessage(boolean methodExpectsMessage) {
this.methodExpectsMessage = methodExpectsMessage;
}
public void setMessageMapper(MessageMapper messageMapper) {
Assert.notNull(messageMapper, "'messageMapper' must not be null");
this.messageMapper = messageMapper;
}
public void setMessageCreator(MessageCreator messageCreator) {
Assert.notNull(messageCreator, "'messageCreator' must not be null");
this.messageCreator = messageCreator;
}
public void afterPropertiesSet() {
synchronized (this.initializationMonitor) {
if (this.initialized) {
return;
}
if (this.object == null) {
throw new ConfigurationException("The target 'object' must not be null.");
}
if (this.method == null && this.methodName == null) {
throw new ConfigurationException("Either a 'method' or 'methodName' is required.");
}
if (this.method != null) {
if (this.methodName != null && !this.methodName.equals(this.method.getName())) {
throw new ConfigurationException("An ambiguity exists between the 'method' and 'methodName' properties. " +
"Note that only one of them is required, but if both are provided they must match.");
}
this.invoker = new DefaultMethodInvoker(this.object, this.method);
}
else {
this.invoker = new NameResolvingMethodInvoker(this.object, this.methodName);
}
this.initialized = true;
}
this.initialize();
}
/**
* Subclasses may override this method for custom initialization requirements.
*/
protected void initialize() {
}
public Message<?> handle(Message<?> message) {
if (message == null) {
throw new IllegalArgumentException("message must not be null");
}
if (!this.initialized) {
this.afterPropertiesSet();
}
Object args[] = null;
Object mappingResult = (this.methodExpectsMessage) ? message : this.messageMapper.mapMessage(message);
if (mappingResult != null && mappingResult.getClass().isArray()) {
args = (Object[]) mappingResult;
}
else {
args = new Object[] { mappingResult };
}
try {
Object result = null;
try {
result = this.invoker.invokeMethod(args);
}
catch (NoSuchMethodException e) {
result = this.invoker.invokeMethod(message);
this.methodExpectsMessage = true;
}
if (result == null) {
return null;
}
return this.handleReturnValue(result, message);
}
catch (InvocationTargetException e) {
throw new MessagingException(
"Handler method '" + this.method + "' threw an Exception.", e.getTargetException());
}
catch (Throwable e) {
throw new MessagingException("Failed to invoke handler method '" + this.method +
"' with arguments: " + ObjectUtils.nullSafeToString(args), e);
}
}
protected Message<?> createReplyMessage(Object returnValue, Message<?> originalMessage) {
Message<?> reply = this.messageCreator.createMessage(returnValue);
if (reply != null) {
reply.copyHeader(originalMessage.getHeader(), false);
Object correlationId = reply.getHeader().getCorrelationId();
if (correlationId == null) {
Object orginalCorrelationId = originalMessage.getHeader().getCorrelationId();
reply.getHeader().setCorrelationId((orginalCorrelationId != null) ?
orginalCorrelationId : originalMessage.getId());
}
}
return reply;
}
/**
* Subclasses must implement this method to handle the return value.
*/
protected abstract Message<?> handleReturnValue(Object returnValue, Message<?> originalMessage);
}

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.handler;
import org.springframework.integration.message.Message;
import org.springframework.integration.message.MessageCreator;
/**
* An implementation of {@link MessageHandler} that invokes the specified method and target object.
* It will use the provided implementation of the {@link MessageCreator} strategy interface to convert
* the method invocation's return value to a reply Message.
*
* @author Mark Fisher
*/
public class DefaultMessageHandlerAdapter extends AbstractMessageHandlerAdapter {
protected Message<?> handleReturnValue(Object returnValue, Message<?> originalMessage) {
return this.createReplyMessage(returnValue, originalMessage);
}
}

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.handler;
import org.springframework.integration.message.Message;
import org.springframework.util.Assert;
/**
* A message handler implementation that intercepts calls to another handler.
*
* @author Mark Fisher
*/
public abstract class InterceptingMessageHandler implements MessageHandler {
private MessageHandler target;
public InterceptingMessageHandler(MessageHandler target) {
Assert.notNull(target, "target must not be null");
this.target = target;
}
public final Message<?> handle(Message<?> message) {
return handle(message, this.target);
}
/**
* The handler method for subclasses to implement.
*
* @param message the message to handle
* @param target the intercepted handler
* @return a reply message or null
*/
public abstract Message<?> handle(Message<?> message, MessageHandler target);
}

View File

@@ -0,0 +1,32 @@
/*
* 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.handler;
import org.springframework.integration.message.Message;
/**
* Base message handler interface. Typical implementations will translate
* between the generic Messages of the integration framework and the domain
* objects that are passed-to and returned-from business components.
*
* @author Mark Fisher
*/
public interface MessageHandler {
Message<?> handle(Message<?> message);
}

View File

@@ -0,0 +1,73 @@
/*
* 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.handler;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import org.springframework.integration.message.Message;
/**
* A message handler implementation that passes incoming messages through a
* chain of handlers. The chain will be broken by any handler throwing an
* exception or returning <em>null</em>.
*
* @author Mark Fisher
*/
public class MessageHandlerChain implements MessageHandler {
private final List<MessageHandler> handlers = new CopyOnWriteArrayList<MessageHandler>();
/**
* Add a handler to the end of the chain.
*/
public void add(MessageHandler handler) {
this.handlers.add(handler);
}
/**
* Add a handler to the chain at the specified index.
*/
public void add(int index, MessageHandler handler) {
this.handlers.add(index, handler);
}
public void setHandlers(List<MessageHandler> handlers) {
this.handlers.clear();
this.handlers.addAll(handlers);
}
/**
* Get an immutable list of handlers
*/
public List<MessageHandler> getHandlers() {
return Collections.unmodifiableList(handlers);
}
public final Message<?> handle(Message<?> message) {
for (MessageHandler next : handlers) {
message = next.handle(message);
if (message == null) {
return null;
}
}
return message;
}
}

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.handler;
import org.springframework.integration.message.Message;
import org.springframework.integration.message.MessageHandlingException;
/**
* An exception indicating that a handler is not currently running.
*
* @author Mark Fisher
*/
@SuppressWarnings("serial")
public class MessageHandlerNotRunningException extends MessageHandlingException {
public MessageHandlerNotRunningException(Message<?> message) {
super(message, "handler is not running");
}
}

View File

@@ -0,0 +1,39 @@
/*
* 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.handler;
import org.springframework.integration.message.Message;
import org.springframework.integration.message.MessageHandlingException;
/**
* An exception indicating that a message was rejected by a handler; typically
* this would be the result of a thread pool executor rejecting a handler task.
*
* @author Mark Fisher
*/
@SuppressWarnings("serial")
public class MessageHandlerRejectedExecutionException extends MessageHandlingException {
public MessageHandlerRejectedExecutionException(Message<?> message) {
super(message);
}
public MessageHandlerRejectedExecutionException(Message<?> message, Throwable cause) {
super(message, "handler rejected execution", cause);
}
}

View File

@@ -0,0 +1,44 @@
/*
* 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.handler;
import org.springframework.integration.message.Message;
import org.springframework.integration.message.MessagingException;
import org.springframework.integration.message.Target;
/**
* A messaging target that invokes the specified method on the provided object.
*
* @author Mark Fisher
*/
public class MethodInvokingTarget extends AbstractMessageHandlerAdapter implements Target {
public boolean send(Message<?> message) {
this.handle(message);
return true;
}
@Override
protected Message<?> handleReturnValue(Object returnValue, Message<?> originalMessage) {
if (returnValue != null) {
throw new MessagingException(originalMessage, "The target method returned a non-null Object. " +
"MethodInvokingTarget should only be used for methods that return no value (preferably void).");
}
return null;
}
}

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.handler;
import org.springframework.integration.message.Message;
import org.springframework.integration.message.MessageHeader;
/**
* Strategy interface for handling reply messages.
*
* @author Mark Fisher
*/
public interface ReplyHandler {
void handle(Message<?> replyMessage, MessageHeader originalMessageHeader);
}

View File

@@ -0,0 +1,76 @@
/*
* 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.handler;
import org.springframework.integration.message.Message;
import org.springframework.integration.message.MessageHandlingException;
import org.springframework.integration.message.RetrievalBlockingMessageStore;
import org.springframework.util.Assert;
/**
* A handler for receiving messages from a "reply channel". Any component that
* is expecting a reply message can poll by providing the correlation identifier.
*
* @author Mark Fisher
*/
public class ReplyMessageCorrelator implements MessageHandler {
private volatile long defaultTimeout = 5000;
private final RetrievalBlockingMessageStore messageStore;
public ReplyMessageCorrelator(int capacity) {
this.messageStore = new RetrievalBlockingMessageStore(capacity);
}
public void setDefaultTimeout(long defaultTimeout) {
Assert.isTrue(defaultTimeout >= 0, "'defaultTimeout' must not be negative");
this.defaultTimeout = defaultTimeout;
}
public Message<?> handle(Message<?> message) {
Object correlationId = this.getCorrelationId(message);
if (correlationId == null) {
throw new MessageHandlingException(message,
"unable to handle response, message has no correlationId: " + message);
}
this.messageStore.put(correlationId, message);
return null;
}
public Message<?> getReply(Object correlationId) {
return this.getReply(correlationId, this.defaultTimeout);
}
public Message<?> getReply(Object correlationId, long timeout) {
Assert.notNull(correlationId, "'correlationId' must not be null");
return this.messageStore.remove(correlationId, timeout);
}
/**
* Retrieve the correlation identifier from the provided message.
* <p>
* This method may be overridden by subclasses. The default implementation
* returns the 'correlationId' from the message header.
*/
protected Object getCorrelationId(final Message<?> message) {
return message.getHeader().getCorrelationId();
}
}

View File

@@ -0,0 +1,209 @@
/*
* 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.handler.annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.integration.ConfigurationException;
import org.springframework.integration.message.Message;
import org.springframework.integration.message.MessageHandlingException;
import org.springframework.integration.message.MessageHeader;
import org.springframework.integration.message.MessageMapper;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* A {@link MessageMapper} implementation for annotated handler methods.
* Method parameters are matched against the Message payload as well as its
* header attributes and properties. If a method parameter is annotated with
* {@link HeaderAttribute @HeaderAttribute} or {@link HeaderProperty @HeaderProperty},
* the annotation's value will be used as an attribute/property key. If such an
* annotation contains no value, then the parameter name will be used as long as
* the information is available in the class file (requires compilation with
* debug settings for parameter names). If neither annotation is present, then
* the parameter will typically match the Message payload. However, if a Map or
* Properties object is expected, and the paylaod is not itself assignable to
* that type, then the MessageHeader attributes will be passed in the case of
* a Map-typed parameter, or the MessageHeader properties will be passed in the
* case of a Properties-typed parameter.
*
* @author Mark Fisher
*/
public class AnnotationMethodMessageMapper implements MessageMapper {
private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
private final Method method;
private MethodParameterMetadata[] parameterMetadata;
private volatile boolean initialized;
private final Object initializationMonitor = new Object();
public AnnotationMethodMessageMapper(Method method) {
Assert.notNull(method, "method must not be null");
this.method = method;
}
public void initialize() {
synchronized (this.initializationMonitor) {
if (this.initialized) {
return;
}
Class<?>[] paramTypes = this.method.getParameterTypes();
this.parameterMetadata = new MethodParameterMetadata[paramTypes.length];
for (int i = 0; i < parameterMetadata.length; i++) {
MethodParameter methodParam = new MethodParameter(this.method, i);
methodParam.initParameterNameDiscovery(this.parameterNameDiscoverer);
GenericTypeResolver.resolveParameterType(methodParam, this.method.getDeclaringClass());
Object[] paramAnns = methodParam.getParameterAnnotations();
String attributeName = null;
String propertyName = null;
for (int j = 0; j < paramAnns.length; j++) {
Object paramAnn = paramAnns[j];
if (HeaderAttribute.class.isInstance(paramAnn)) {
HeaderAttribute headerAttribute = (HeaderAttribute) paramAnn;
attributeName = this.resolveParameterNameIfNecessary(headerAttribute.value(), methodParam);
parameterMetadata[i] = new MethodParameterMetadata(HeaderAttribute.class, attributeName, headerAttribute.required());
}
else if (HeaderProperty.class.isInstance(paramAnn)) {
HeaderProperty headerProperty = (HeaderProperty) paramAnn;
propertyName = this.resolveParameterNameIfNecessary(headerProperty.value(), methodParam);
parameterMetadata[i] = new MethodParameterMetadata(HeaderProperty.class, propertyName, headerProperty.required());
}
}
if (attributeName != null && propertyName != null) {
throw new ConfigurationException("The @HeaderAttribute and @HeaderProperty annotations " +
"are mutually exclusive. They should not both be provided on the same parameter.");
}
if (attributeName == null && propertyName == null) {
parameterMetadata[i] = new MethodParameterMetadata(methodParam.getParameterType(), null, false);
}
}
this.initialized = true;
}
}
public Object[] mapMessage(Message message) {
if (message == null) {
return null;
}
if (message.getPayload() == null) {
throw new IllegalArgumentException("Message payload must not be null.");
}
if (!this.initialized) {
this.initialize();
}
Object[] args = new Object[this.parameterMetadata.length];
for (int i = 0; i < this.parameterMetadata.length; i++) {
MethodParameterMetadata metadata = this.parameterMetadata[i];
Class<?> expectedType = metadata.type;
if (expectedType.equals(HeaderAttribute.class)) {
Object value = message.getHeader().getAttribute(metadata.key);
if (value == null && metadata.required) {
throw new MessageHandlingException(message,
"required attribute '" + metadata.key + "' not available");
}
args[i] = value;
}
else if (expectedType.equals(HeaderProperty.class)) {
Object value = message.getHeader().getProperty(metadata.key);
if (value == null && metadata.required) {
throw new MessageHandlingException(message,
"required property '" + metadata.key + "' not available");
}
args[i] = value;
}
else if (expectedType.isAssignableFrom(message.getClass())) {
args[i] = message;
}
else if (expectedType.isAssignableFrom(message.getPayload().getClass())) {
args[i] = message.getPayload();
}
else if (expectedType.equals(Map.class)) {
args[i] = this.getHeaderAttributes(message);
}
else if (expectedType.equals(Properties.class)) {
args[i] = this.getHeaderProperties(message);
}
else {
args[i] = message.getPayload();
}
}
return args;
}
private Map<String, Object> getHeaderAttributes(Message<?> message) {
Map<String, Object> attributes = new HashMap<String, Object>();
MessageHeader header = message.getHeader();
Set<String> attributeNames = header.getAttributeNames();
for (String name : attributeNames) {
attributes.put(name, header.getAttribute(name));
}
return attributes;
}
private Properties getHeaderProperties(Message<?> message) {
Properties properties = new Properties();
MessageHeader header = message.getHeader();
Set<String> propertyNames = header.getPropertyNames();
for (String name : propertyNames) {
properties.setProperty(name, header.getProperty(name));
}
return properties;
}
private String resolveParameterNameIfNecessary(String paramName, MethodParameter methodParam) {
if (!StringUtils.hasText(paramName)) {
paramName = methodParam.getParameterName();
if (paramName == null) {
throw new IllegalStateException("No parameter name specified and not available in class file.");
}
}
return paramName;
}
private static class MethodParameterMetadata {
private final Class<?> type;
private final String key;
private final boolean required;
MethodParameterMetadata(Class<?> type, String key, boolean required) {
this.type = type;
this.key = key;
this.required = required;
}
}
}

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.handler.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 an attribute in the message header. The value of
* the annotation provides the attribute key, and the optional
* 'required' property specifies whether the attribute value must
* be available within the header.
*
* @author Mark Fisher
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface HeaderAttribute {
String value() default "";
boolean required() default true;
}

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