Fixed bounding issue in SimpleMessageStore and introduced UpperBound to avoid code duplication with PriorityChannel

This commit is contained in:
Iwein Fuld
2010-04-25 18:49:39 +00:00
parent fc96b55cf3
commit f3b36361c6
5 changed files with 159 additions and 32 deletions

View File

@@ -118,7 +118,7 @@ public class CorrelatingMessageHandler extends AbstractMessageHandler implements
}
public CorrelatingMessageHandler(MessageGroupProcessor processor) {
this(new SimpleMessageStore(100), new HeaderAttributeCorrelationStrategy(MessageHeaders.CORRELATION_ID),
this(new SimpleMessageStore(0), new HeaderAttributeCorrelationStrategy(MessageHeaders.CORRELATION_ID),
new SequenceSizeCompletionStrategy(), processor);
}

View File

@@ -18,11 +18,10 @@ 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.core.Message;
import org.springframework.integration.core.MessagePriority;
import org.springframework.integration.util.UpperBound;
/**
* A message channel that prioritizes messages based on a {@link Comparator}.
@@ -32,7 +31,7 @@ import org.springframework.integration.core.MessagePriority;
*/
public class PriorityChannel extends QueueChannel {
private final Semaphore semaphore;
private final UpperBound upperBound;
/**
@@ -45,7 +44,7 @@ public class PriorityChannel extends QueueChannel {
public PriorityChannel(int capacity, Comparator<Message<?>> comparator) {
super(new PriorityBlockingQueue<Message<?>>(11,
(comparator != null) ? comparator : new MessagePriorityComparator()));
this.semaphore = (capacity > 0) ? new Semaphore(capacity, true) : null;
this.upperBound = new UpperBound(capacity);
}
/**
@@ -77,7 +76,7 @@ public class PriorityChannel extends QueueChannel {
@Override
protected boolean doSend(Message<?> message, long timeout) {
if (!acquirePermitIfNecessary(timeout)) {
if (!upperBound.tryAcquire(timeout)) {
return false;
}
return super.doSend(message, 0);
@@ -87,32 +86,12 @@ public class PriorityChannel extends QueueChannel {
protected Message<?> doReceive(long timeout) {
Message<?> message = super.doReceive(timeout);
if (message != null) {
this.releasePermitIfNecessary();
upperBound.release();
return message;
}
return null;
}
private boolean acquirePermitIfNecessary(long timeoutInMilliseconds) {
if (this.semaphore != null) {
try {
return this.semaphore.tryAcquire(timeoutInMilliseconds, TimeUnit.MILLISECONDS);
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
return true;
}
private void releasePermitIfNecessary() {
if (this.semaphore != null) {
this.semaphore.release();
}
}
private static class MessagePriorityComparator implements Comparator<Message<?>> {
public int compare(Message<?> message1, Message<?> message2) {

View File

@@ -17,6 +17,8 @@
package org.springframework.integration.store;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessagingException;
import org.springframework.integration.util.UpperBound;
import org.springframework.util.Assert;
import java.util.ArrayList;
@@ -35,15 +37,31 @@ import java.util.concurrent.ConcurrentHashMap;
public class SimpleMessageStore implements MessageStore {
private final Map<Object, Message<?>> map;
private final UpperBound upperBound;
public SimpleMessageStore(int capacity) {
this.map = new ConcurrentHashMap<Object, Message<?>>(capacity);
/**
* Creates a SimpleMessageStore with a maximum size limited by the given capacity, or unlimited
* size if the given capacity is less than 1.
*/
public SimpleMessageStore(int capacity) {
this.map = new ConcurrentHashMap<Object, Message<?>>();
this.upperBound = new UpperBound(capacity);
}
/**
* Creates a SimpleMessageStore with unlimited capacity
*/
public SimpleMessageStore() {
this(0);
}
@SuppressWarnings("unchecked")
@SuppressWarnings("unchecked")
public <T> Message<T> put(Message<T> message) {
if (!upperBound.tryAcquire(0)){
throw new MessagingException(this.getClass().getSimpleName() +
" was out of capacity at, try constructing it with a larger capacity.");
}
return (Message<T>) this.map.put(message.getHeaders().getId(), message);
}
@@ -56,7 +74,11 @@ public class SimpleMessageStore implements MessageStore {
}
public Message<?> delete(Object key) {
return (key != null) ? this.map.remove(key) : null;
if (key != null) {
upperBound.release();
return this.map.remove(key);
}
else return null;
}
public int size() {

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.util;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* Thin wrapper around a Semaphore that allows to create a potentially unlimited upper bound
* to by used in buffers of messages (e.g. a QueueChannel or a MessageStore).
*
* @author Mark Fisher
* @author Iwein Fuld
* @since 2.0
*/
public final class UpperBound {
public final Semaphore semaphore;
/**
* Create an UpperBound with the given capacity. If the given capacity is less than 1
* an infinite UpperBound is created.
*
* @param capacity
*/
public UpperBound(int capacity) {
this.semaphore = (capacity > 0) ? new Semaphore(capacity, true) : null;
}
/**
* Acquires a lock on the underlying semaphore if this UpperBound is bounded and returns true
* if it succeeds within the given timeout
*/
public boolean tryAcquire(long timeoutInMilliseconds) {
if (this.semaphore != null) {
try {
return this.semaphore.tryAcquire(timeoutInMilliseconds, TimeUnit.MILLISECONDS);
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
return true;
}
/**
* Releases one lock on the underlying semaphore. This is typically not done by the same Thread
* that acquired the lock, but by the thread that picked up the message.
*/
public void release() {
if (this.semaphore != null) {
this.semaphore.release();
}
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.store;
import org.junit.Test;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessagingException;
import org.springframework.integration.message.MessageBuilder;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
/**
* @author Iwein Fuld
*/
public class SimpleMessageStoreTest {
@Test
public void shouldRetainMessage() {
SimpleMessageStore store = new SimpleMessageStore();
Message<String> testMessage1 = MessageBuilder.withPayload("foo").build();
store.put(testMessage1);
assertThat((Message<String>) store.get(testMessage1.getHeaders().getId()), is(testMessage1));
}
@Test(expected = MessagingException.class)
public void shouldNotHoldMoreThanCapacity() {
SimpleMessageStore store = new SimpleMessageStore(1);
Message<String> testMessage1 = MessageBuilder.withPayload("foo").build();
Message<String> testMessage2 = MessageBuilder.withPayload("bar").build();
store.put(testMessage1);
store.put(testMessage2);
}
@Test
public void shouldHoldCapacityExactly() {
SimpleMessageStore store = new SimpleMessageStore(2);
Message<String> testMessage1 = MessageBuilder.withPayload("foo").build();
Message<String> testMessage2 = MessageBuilder.withPayload("bar").build();
store.put(testMessage1);
store.put(testMessage2);
}
}