Added @Concurrency Annotation (INT-118).

This commit is contained in:
Mark Fisher
2008-03-05 20:42:51 +00:00
parent 4d6292b35a
commit 753ce56354
11 changed files with 226 additions and 19 deletions

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

@@ -16,8 +16,10 @@
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;
@@ -40,7 +42,9 @@ import org.springframework.integration.channel.SimpleChannel;
import org.springframework.integration.dispatcher.DefaultMessageDispatcher;
import org.springframework.integration.dispatcher.SchedulingMessageDispatcher;
import org.springframework.integration.endpoint.ConcurrencyPolicy;
import org.springframework.integration.endpoint.DefaultEndpointRegistry;
import org.springframework.integration.endpoint.DefaultMessageEndpoint;
import org.springframework.integration.endpoint.EndpointRegistry;
import org.springframework.integration.endpoint.MessageEndpoint;
import org.springframework.integration.handler.MessageHandler;
import org.springframework.integration.scheduling.MessagePublishingErrorHandler;
@@ -57,7 +61,7 @@ import org.springframework.util.Assert;
*
* @author Mark Fisher
*/
public class MessageBus implements ChannelRegistry, ApplicationContextAware, Lifecycle {
public class MessageBus implements ChannelRegistry, EndpointRegistry, ApplicationContextAware, Lifecycle {
public static final String ERROR_CHANNEL_NAME = "errorChannel";
@@ -68,7 +72,7 @@ public class MessageBus implements ChannelRegistry, ApplicationContextAware, Lif
private final ChannelRegistry channelRegistry = new DefaultChannelRegistry();
private final Map<String, MessageEndpoint> endpoints = new ConcurrentHashMap<String, MessageEndpoint>();
private final EndpointRegistry endpointRegistry = new DefaultEndpointRegistry();
private final Map<MessageChannel, SchedulingMessageDispatcher> dispatchers = new ConcurrentHashMap<MessageChannel, SchedulingMessageDispatcher>();
@@ -253,7 +257,7 @@ public class MessageBus implements ChannelRegistry, ApplicationContextAware, Lif
if (endpoint.getConcurrencyPolicy() == null && endpoint instanceof DefaultMessageEndpoint) {
((DefaultMessageEndpoint) endpoint).setConcurrencyPolicy(this.defaultConcurrencyPolicy);
}
this.endpoints.put(name, endpoint);
this.endpointRegistry.registerEndpoint(name, endpoint);
if (this.isRunning()) {
activateEndpoint(endpoint);
}
@@ -262,9 +266,37 @@ public class MessageBus implements ChannelRegistry, ApplicationContextAware, Lif
}
}
public MessageEndpoint unregisterEndpoint(String name) {
MessageEndpoint endpoint = this.endpointRegistry.unregisterEndpoint(name);
if (endpoint == null) {
return null;
}
Collection<SchedulingMessageDispatcher> dispatchers = this.dispatchers.values();
boolean removed = false;
for (SchedulingMessageDispatcher dispatcher : dispatchers) {
removed = (removed || dispatcher.removeHandler(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() {
for (MessageEndpoint endpoint : this.endpoints.values()) {
this.activateEndpoint(endpoint);
Set<String> endpointNames = this.endpointRegistry.getEndpointNames();
for (String name : endpointNames) {
MessageEndpoint endpoint = this.endpointRegistry.lookupEndpoint(name);
if (endpoint != null) {
this.activateEndpoint(endpoint);
}
}
}

View File

@@ -28,9 +28,9 @@ import org.springframework.util.Assert;
*/
public class DefaultChannelRegistry implements ChannelRegistry {
private Map<String, MessageChannel> channels = new ConcurrentHashMap<String, MessageChannel>();
private final Map<String, MessageChannel> channels = new ConcurrentHashMap<String, MessageChannel>();
private MessageChannel errorChannel;
private volatile MessageChannel errorChannel;
public void setErrorChannel(MessageChannel errorChannel) {

View File

@@ -42,6 +42,7 @@ import org.springframework.integration.bus.MessageBus;
import org.springframework.integration.channel.ChannelRegistryAware;
import org.springframework.integration.channel.MessageChannel;
import org.springframework.integration.channel.SimpleChannel;
import org.springframework.integration.endpoint.ConcurrencyPolicy;
import org.springframework.integration.endpoint.DefaultMessageEndpoint;
import org.springframework.integration.handler.MessageHandler;
import org.springframework.integration.handler.MessageHandlerChain;
@@ -103,16 +104,18 @@ public class MessageEndpointAnnotationPostProcessor implements BeanPostProcessor
if (endpointAnnotation == null) {
return bean;
}
if (this.messageBus == null) {
if (logger.isWarnEnabled()) {
logger.warn(this.getClass().getSimpleName() + " is disabled since no 'messageBus' was provided");
}
return bean;
}
MessageHandlerChain handlerChain = this.createHandlerChain(bean);
DefaultMessageEndpoint endpoint = new DefaultMessageEndpoint(handlerChain);
this.configureInput(bean, beanName, endpointAnnotation, endpoint);
this.configureDefaultOutput(bean, beanName, endpointAnnotation, endpoint);
Concurrency concurrencyAnnotation = bean.getClass().getAnnotation(Concurrency.class);
if (concurrencyAnnotation != null) {
ConcurrencyPolicy concurrencyPolicy = new ConcurrencyPolicy(
concurrencyAnnotation.coreSize(), concurrencyAnnotation.maxSize());
concurrencyPolicy.setKeepAliveSeconds(concurrencyAnnotation.keepAliveSeconds());
concurrencyPolicy.setQueueCapacity(concurrencyAnnotation.queueCapacity());
endpoint.setConcurrencyPolicy(concurrencyPolicy);
}
if (endpoint.getHandler() == null) {
endpoint.setHandler(new MessageHandler() {
public Message<?> handle(Message<?> message) {

View File

@@ -111,6 +111,15 @@ public class DefaultMessageDispatcher implements SchedulingMessageDispatcher {
}
}
public boolean removeHandler(MessageHandler handler) {
boolean removed = false;
Collection<List<MessageHandler>> handlerLists = this.scheduledHandlers.values();
for (List<MessageHandler> handlers : handlerLists) {
removed = (removed || handlers.remove(handler));
}
return removed;
}
public boolean isRunning() {
return this.running;
}

View File

@@ -27,6 +27,8 @@ public interface MessageDispatcher {
void addHandler(MessageHandler handler);
boolean removeHandler(MessageHandler handler);
int dispatch();
}

View File

@@ -89,4 +89,9 @@ public class ConcurrencyPolicy implements EndpointPolicy {
this.keepAliveSeconds = keepAliveSeconds;
}
public String toString() {
return "[coreSize=" + this.coreSize + ", maxSize=" + this.maxSize +
", queueCapacity=" + this.queueCapacity + ", keepAliveSeconds=" + this.keepAliveSeconds + "]";
}
}

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,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

@@ -17,17 +17,16 @@
package org.springframework.integration.config;
import java.util.List;
import java.util.Map;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.integration.bus.MessageBus;
import org.springframework.integration.endpoint.ConcurrentHandler;
import org.springframework.integration.endpoint.DefaultMessageEndpoint;
import org.springframework.integration.endpoint.MessageEndpoint;
import org.springframework.integration.handler.MessageHandlerChain;
import org.springframework.integration.router.AggregatingMessageHandler;
import org.springframework.integration.router.SequenceSizeCompletionStrategy;
@@ -85,10 +84,7 @@ public class AggregatorAnnotationTests {
private DirectFieldAccessor getDirectFieldAccessorForAggregatingHandler(ApplicationContext context,
final String endpointName) {
MessageBus messageBus = getMessageBus(context);
DirectFieldAccessor messageBusAccessor = new DirectFieldAccessor(messageBus);
Map<String, MessageEndpoint> endpoints = (Map<String, MessageEndpoint>) messageBusAccessor
.getPropertyValue("endpoints");
DefaultMessageEndpoint endpoint = (DefaultMessageEndpoint) endpoints.get(endpointName + "-endpoint");
DefaultMessageEndpoint endpoint = (DefaultMessageEndpoint) messageBus.lookupEndpoint(endpointName + "-endpoint");
ConcurrentHandler handler = (ConcurrentHandler) endpoint.getHandler();
DirectFieldAccessor concurrentHandlerAccessor = new DirectFieldAccessor(handler);
MessageHandlerChain messageHandlerChain = (MessageHandlerChain) concurrentHandlerAccessor

View File

@@ -25,6 +25,7 @@ import org.junit.Test;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.integration.annotation.Concurrency;
import org.springframework.integration.annotation.DefaultOutput;
import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.annotation.Polled;
@@ -32,6 +33,8 @@ import org.springframework.integration.bus.MessageBus;
import org.springframework.integration.channel.MessageChannel;
import org.springframework.integration.channel.SimpleChannel;
import org.springframework.integration.config.MessageEndpointAnnotationPostProcessor;
import org.springframework.integration.endpoint.ConcurrencyPolicy;
import org.springframework.integration.endpoint.DefaultMessageEndpoint;
import org.springframework.integration.message.Message;
import org.springframework.integration.message.StringMessage;
@@ -97,6 +100,21 @@ public class MessageEndpointAnnotationPostProcessorTests {
messageBus.stop();
}
@Test
public void testConcurrencyAnnotationWithValues() {
MessageBus messageBus = new MessageBus();
MessageEndpointAnnotationPostProcessor postProcessor =
new MessageEndpointAnnotationPostProcessor(messageBus);
ConcurrencyAnnotationTestBean testBean = new ConcurrencyAnnotationTestBean();
postProcessor.postProcessAfterInitialization(testBean, "testBean");
DefaultMessageEndpoint endpoint = (DefaultMessageEndpoint) messageBus.lookupEndpoint("testBean-endpoint");
ConcurrencyPolicy concurrencyPolicy = endpoint.getConcurrencyPolicy();
assertEquals(17, concurrencyPolicy.getCoreSize());
assertEquals(42, concurrencyPolicy.getMaxSize());
assertEquals(11, concurrencyPolicy.getQueueCapacity());
assertEquals(123, concurrencyPolicy.getKeepAliveSeconds());
}
@Test(expected=IllegalArgumentException.class)
public void testPostProcessorWithNullMessageBus() {
new MessageEndpointAnnotationPostProcessor(null);
@@ -136,4 +154,10 @@ public class MessageEndpointAnnotationPostProcessorTests {
}
}
@MessageEndpoint(input="inputChannel")
@Concurrency(coreSize=17, maxSize=42, keepAliveSeconds=123, queueCapacity=11)
private static class ConcurrencyAnnotationTestBean {
}
}