From c558e498367de872ddc579c1b56dde407cc1cb05 Mon Sep 17 00:00:00 2001 From: Riccardo Sirchia Date: Tue, 21 May 2013 00:41:54 +0200 Subject: [PATCH] Added camel-disruptor component and managed required disruptor dependency --- components/camel-disruptor/.gitignore | 3 + components/camel-disruptor/pom.xml | 81 ++++ ...actLifecycleAwareExchangeEventHandler.java | 74 +++ .../disruptor/DisruptorComponent.java | 247 ++++++++++ .../disruptor/DisruptorConsumer.java | 197 ++++++++ .../disruptor/DisruptorEndpoint.java | 289 ++++++++++++ .../DisruptorNotStartedException.java | 40 ++ .../disruptor/DisruptorProducer.java | 201 ++++++++ .../disruptor/DisruptorProducerType.java | 45 ++ .../disruptor/DisruptorReference.java | 445 ++++++++++++++++++ .../disruptor/DisruptorWaitStrategy.java | 84 ++++ .../component/disruptor/ExchangeEvent.java | 81 ++++ .../disruptor/ExchangeEventFactory.java | 34 ++ .../LifecycleAwareExchangeEventHandler.java | 128 +++++ .../disruptor/vm/DisruptorVmComponent.java | 56 +++ .../org/apache/camel/component/disruptor | 18 + .../org/apache/camel/component/disruptor-vm | 18 + .../BasicDisruptorComponentTest.java | 136 ++++++ ...ectRequestReplyAndDisruptorInOnlyTest.java | 53 +++ ...equestReplyMultipleConsumersInOutTest.java | 81 ++++ .../disruptor/DisruptorAsyncRouteTest.java | 58 +++ .../disruptor/DisruptorBlockWhenFullTest.java | 80 ++++ .../disruptor/DisruptorBufferingTest.java | 119 +++++ .../disruptor/DisruptorComplexInOutTest.java | 53 +++ ...sruptorComponentReferenceEndpointTest.java | 89 ++++ ...ruptorConcurrentConsumersNPEIssueTest.java | 84 ++++ .../DisruptorConcurrentConsumersTest.java | 47 ++ .../disruptor/DisruptorConcurrentTest.java | 139 ++++++ .../disruptor/DisruptorConfigureTest.java | 158 +++++++ .../DisruptorConsumerSuspendResumeTest.java | 83 ++++ .../disruptor/DisruptorFromRouteIdTest.java | 57 +++ .../disruptor/DisruptorInOnlyChainedTest.java | 51 ++ .../disruptor/DisruptorInOnlyTest.java | 47 ++ .../DisruptorInOutBigChainedTest.java | 67 +++ .../disruptor/DisruptorInOutChainedTest.java | 52 ++ .../DisruptorInOutChainedTimeoutTest.java | 62 +++ ...uptorInOutChainedWithOnCompletionTest.java | 67 +++ .../disruptor/DisruptorInOutTest.java | 48 ++ ...orInOutWithErrorDeadLetterChannelTest.java | 52 ++ .../DisruptorInOutWithErrorTest.java | 56 +++ .../DisruptorMultipleConsumersTest.java | 95 ++++ .../disruptor/DisruptorNoConsumerTest.java | 54 +++ ...ruptorReconfigureWithBlockingProducer.java | 113 +++++ .../DisruptorRemoveRouteThenAddAgainTest.java | 65 +++ .../disruptor/DisruptorRingBufferTest.java | 52 ++ .../disruptor/DisruptorRouteTest.java | 105 +++++ .../DisruptorShouldNotUseSameThreadTest.java | 69 +++ .../DisruptorTimeoutDisabledTest.java | 48 ++ .../disruptor/DisruptorTimeoutTest.java | 86 ++++ .../disruptor/DisruptorUnitOfWorkTest.java | 104 ++++ ...sruptorWaitClaimStrategyComponentTest.java | 98 ++++ .../DisruptorWaitForTaskAsPropertyTest.java | 78 +++ ...orWaitForTaskCompleteOnCompletionTest.java | 83 ++++ .../DisruptorWaitForTaskCompleteTest.java | 70 +++ ...sruptorWaitForTaskIfReplyExpectedTest.java | 71 +++ ...uptorWaitForTaskNeverOnCompletionTest.java | 86 ++++ .../DisruptorWaitForTaskNeverTest.java | 71 +++ .../DisruptorWaitStrategyCreationTest.java | 40 ++ ...DisruptorShutdownCompleteAllTasksTest.java | 78 +++ .../MulticastDisruptorComponentTest.java | 149 ++++++ .../camel/component/disruptor/Readme.txt | 50 ++ .../disruptor/SedaDisruptorCompareTest.java | 428 +++++++++++++++++ ...uptorVmComponentReferenceEndpointTest.java | 89 ++++ .../DisruptorVmConcurrentConsumersTest.java | 46 ++ ...erentOptionsOnConsumerAndProducerTest.java | 64 +++ .../vm/DisruptorVmInOnlyChainedTest.java | 60 +++ .../disruptor/vm/DisruptorVmInOnlyTest.java | 54 +++ .../vm/DisruptorVmInOutChainedTest.java | 60 +++ .../DisruptorVmInOutChainedTimeoutTest.java | 76 +++ .../disruptor/vm/DisruptorVmInOutTest.java | 55 +++ .../vm/DisruptorVmInOutWithErrorTest.java | 63 +++ ...DisruptorVmMultipleConsumersIssueTest.java | 72 +++ ...ruptorVmMultipleContextsStartStopTest.java | 67 +++ .../disruptor/vm/DisruptorVmQueueTest.java | 57 +++ ...DisruptorVmShouldNotUseSameThreadTest.java | 69 +++ .../disruptor/vm/DisruptorVmSplitterTest.java | 81 ++++ .../vm/DisruptorVmTimeoutIssueTest.java | 88 ++++ .../vm/DisruptorVmUseSameQueueTest.java | 55 +++ .../DisruptorVmWaitForTaskCompleteTest.java | 75 +++ ...uptorVmWaitForTaskIfReplyExpectedTest.java | 75 +++ .../vm/DisruptorVmWaitForTaskNewerTest.java | 76 +++ ...SameDisruptorVmQueueSizeAndNoSizeTest.java | 79 ++++ .../src/test/resources/log4j.properties | 23 + components/pom.xml | 1 + parent/pom.xml | 13 + tests/camel-itest-osgi/pom.xml | 5 + .../itest/osgi/disruptor/DisruptorTest.java | 66 +++ .../osgi/disruptor/vm/DisruptorVmTest.java | 60 +++ 88 files changed, 7402 insertions(+) create mode 100644 components/camel-disruptor/.gitignore create mode 100644 components/camel-disruptor/pom.xml create mode 100644 components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/AbstractLifecycleAwareExchangeEventHandler.java create mode 100644 components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/DisruptorComponent.java create mode 100644 components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/DisruptorConsumer.java create mode 100644 components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/DisruptorEndpoint.java create mode 100644 components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/DisruptorNotStartedException.java create mode 100644 components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/DisruptorProducer.java create mode 100644 components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/DisruptorProducerType.java create mode 100644 components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/DisruptorReference.java create mode 100644 components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/DisruptorWaitStrategy.java create mode 100644 components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/ExchangeEvent.java create mode 100644 components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/ExchangeEventFactory.java create mode 100644 components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/LifecycleAwareExchangeEventHandler.java create mode 100644 components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/vm/DisruptorVmComponent.java create mode 100644 components/camel-disruptor/src/main/resources/META-INF/services/org/apache/camel/component/disruptor create mode 100644 components/camel-disruptor/src/main/resources/META-INF/services/org/apache/camel/component/disruptor-vm create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/BasicDisruptorComponentTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DirectRequestReplyAndDisruptorInOnlyTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DirectRequestReplyMultipleConsumersInOutTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorAsyncRouteTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorBlockWhenFullTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorBufferingTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorComplexInOutTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorComponentReferenceEndpointTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorConcurrentConsumersNPEIssueTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorConcurrentConsumersTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorConcurrentTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorConfigureTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorConsumerSuspendResumeTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorFromRouteIdTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorInOnlyChainedTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorInOnlyTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorInOutBigChainedTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorInOutChainedTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorInOutChainedTimeoutTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorInOutChainedWithOnCompletionTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorInOutTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorInOutWithErrorDeadLetterChannelTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorInOutWithErrorTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorMultipleConsumersTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorNoConsumerTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorReconfigureWithBlockingProducer.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorRemoveRouteThenAddAgainTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorRingBufferTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorRouteTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorShouldNotUseSameThreadTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorTimeoutDisabledTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorTimeoutTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorUnitOfWorkTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorWaitClaimStrategyComponentTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorWaitForTaskAsPropertyTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorWaitForTaskCompleteOnCompletionTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorWaitForTaskCompleteTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorWaitForTaskIfReplyExpectedTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorWaitForTaskNeverOnCompletionTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorWaitForTaskNeverTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorWaitStrategyCreationTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/FileDisruptorShutdownCompleteAllTasksTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/MulticastDisruptorComponentTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/Readme.txt create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/SedaDisruptorCompareTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmComponentReferenceEndpointTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmConcurrentConsumersTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmDifferentOptionsOnConsumerAndProducerTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmInOnlyChainedTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmInOnlyTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmInOutChainedTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmInOutChainedTimeoutTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmInOutTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmInOutWithErrorTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmMultipleConsumersIssueTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmMultipleContextsStartStopTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmQueueTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmShouldNotUseSameThreadTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmSplitterTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmTimeoutIssueTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmUseSameQueueTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmWaitForTaskCompleteTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmWaitForTaskIfReplyExpectedTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmWaitForTaskNewerTest.java create mode 100644 components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/SameDisruptorVmQueueSizeAndNoSizeTest.java create mode 100644 components/camel-disruptor/src/test/resources/log4j.properties create mode 100644 tests/camel-itest-osgi/src/test/java/org/apache/camel/itest/osgi/disruptor/DisruptorTest.java create mode 100644 tests/camel-itest-osgi/src/test/java/org/apache/camel/itest/osgi/disruptor/vm/DisruptorVmTest.java diff --git a/components/camel-disruptor/.gitignore b/components/camel-disruptor/.gitignore new file mode 100644 index 0000000000000..88b5123b82e68 --- /dev/null +++ b/components/camel-disruptor/.gitignore @@ -0,0 +1,3 @@ +target/ +/.classpath +/.project diff --git a/components/camel-disruptor/pom.xml b/components/camel-disruptor/pom.xml new file mode 100644 index 0000000000000..268224059d90b --- /dev/null +++ b/components/camel-disruptor/pom.xml @@ -0,0 +1,81 @@ + + + + + 4.0.0 + + + org.apache.camel + camel-parent + 2.12-SNAPSHOT + + + + Camel :: Disruptor + camel-disruptor + Camel :: Disruptor component + bundle + + + org.apache.camel.component.disruptor.* + org.apache.camel.spi.ComponentResolver;component=disruptor + + org.apache.camel.spi.ComponentResolver;component=disruptor-vm + + + + + + + org.apache.camel + camel-core + + + + com.lmax + disruptor + + + + + + org.apache.camel + camel-core + test-jar + + + + org.apache.camel + camel-test + test + + + + junit + junit + test + + + org.slf4j + slf4j-log4j12 + test + + + diff --git a/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/AbstractLifecycleAwareExchangeEventHandler.java b/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/AbstractLifecycleAwareExchangeEventHandler.java new file mode 100644 index 0000000000000..cfed29ce0befd --- /dev/null +++ b/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/AbstractLifecycleAwareExchangeEventHandler.java @@ -0,0 +1,74 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * This abstract base class is used to implement the {@link LifecycleAwareExchangeEventHandler} interface with added + * support to await starting/stopping by the Disruptor framework. + */ +abstract class AbstractLifecycleAwareExchangeEventHandler implements LifecycleAwareExchangeEventHandler { + + private volatile boolean started = false; + private volatile CountDownLatch startedLatch = new CountDownLatch(1); + private volatile CountDownLatch stoppedLatch = new CountDownLatch(1); + + @Override + public abstract void onEvent(final ExchangeEvent event, long sequence, boolean endOfBatch) + throws Exception; + + @Override + public void awaitStarted() throws InterruptedException { + if (!started) { + startedLatch.await(); + } + } + + @Override + public boolean awaitStarted(final long timeout, final TimeUnit unit) throws InterruptedException { + return started || startedLatch.await(timeout, unit); + } + + @Override + public void awaitStopped() throws InterruptedException { + if (started) { + stoppedLatch.await(); + } + } + + @Override + public boolean awaitStopped(final long timeout, final TimeUnit unit) throws InterruptedException { + return !started || stoppedLatch.await(timeout, unit); + } + + @Override + public void onStart() { + stoppedLatch = new CountDownLatch(1); + startedLatch.countDown(); + started = true; + } + + @Override + public void onShutdown() { + startedLatch = new CountDownLatch(1); + stoppedLatch.countDown(); + started = false; + } +} diff --git a/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/DisruptorComponent.java b/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/DisruptorComponent.java new file mode 100644 index 0000000000000..86cd39b70fb3c --- /dev/null +++ b/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/DisruptorComponent.java @@ -0,0 +1,247 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import org.apache.camel.Endpoint; +import org.apache.camel.impl.DefaultComponent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; + +/** + * An implementation of the Disruptor component + * for asynchronous SEDA exchanges on an + * LMAX Disruptor within a CamelContext + */ +public class DisruptorComponent extends DefaultComponent { + + private static final Logger LOGGER = LoggerFactory.getLogger(DisruptorComponent.class); + + public static final int DEFAULT_BUFFER_SIZE = 1024; + public static final int MAX_CONCURRENT_CONSUMERS = 500; + + + private int bufferSize = -1; + //for SEDA compatibility only + private int queueSize = -1; + + private int defaultConcurrentConsumers = 1; + private boolean defaultMultipleConsumers = false; + private DisruptorProducerType defaultProducerType = DisruptorProducerType.Multi; + private DisruptorWaitStrategy defaultWaitStrategy = DisruptorWaitStrategy.Blocking; + private boolean defaultBlockWhenFull = true; + + //synchronized access guarded by this + private final Map disruptors = new HashMap(); + + @Override + protected Endpoint createEndpoint(final String uri, final String remaining, + final Map parameters) throws Exception { + final int concurrentConsumers = getAndRemoveParameter(parameters, "concurrentConsumers", + Integer.class, defaultConcurrentConsumers); + final boolean limitConcurrentConsumers = getAndRemoveParameter(parameters, "limitConcurrentConsumers", + Boolean.class, true); + if (limitConcurrentConsumers && concurrentConsumers > MAX_CONCURRENT_CONSUMERS) { + throw new IllegalArgumentException( + "The limitConcurrentConsumers flag in set to true. ConcurrentConsumers cannot be set at a value greater than " + + MAX_CONCURRENT_CONSUMERS + " was " + concurrentConsumers); + } + + if (concurrentConsumers < 0) { + throw new IllegalArgumentException("concurrentConsumers found to be " + concurrentConsumers + + ", must be greater than 0"); + } + + int size = 0; + if (parameters.containsKey("size")) { + size = getAndRemoveParameter(parameters, "size", int.class); + if (size <= 0) { + throw new IllegalArgumentException("size found to be " + size + ", must be greater than 0"); + } + } + + // Check if the pollTimeout argument is set (may be the case if Disruptor component is used as drop-in + // replacement for the SEDA component. + if (parameters.containsKey("pollTimeout")) { + throw new IllegalArgumentException( + "The 'pollTimeout' argument is not supported by the Disruptor component"); + } + + final DisruptorWaitStrategy waitStrategy = getAndRemoveParameter(parameters, "waitStrategy", + DisruptorWaitStrategy.class, defaultWaitStrategy); + + final DisruptorProducerType producerType = getAndRemoveParameter(parameters, "producerType", + DisruptorProducerType.class, defaultProducerType); + + final boolean multipleConsumers = getAndRemoveParameter(parameters, "multipleConsumers", + boolean.class, defaultMultipleConsumers); + + final boolean blockWhenFull = getAndRemoveParameter(parameters, "blockWhenFull", boolean.class, + defaultBlockWhenFull); + + final DisruptorReference disruptorReference = getOrCreateDisruptor(uri, size, producerType, + waitStrategy); + final DisruptorEndpoint disruptorEndpoint = new DisruptorEndpoint(uri, this, disruptorReference, + concurrentConsumers, multipleConsumers, blockWhenFull); + disruptorEndpoint.configureProperties(parameters); + + return disruptorEndpoint; + } + + private DisruptorReference getOrCreateDisruptor(final String uri, final int size, + final DisruptorProducerType producerType, + final DisruptorWaitStrategy waitStrategy) + throws Exception { + final String key = getDisruptorKey(uri); + + int sizeToUse; + if (size > 0) { + sizeToUse = size; + } else if (bufferSize > 0) { + sizeToUse = bufferSize; + } else if (queueSize > 0) { + sizeToUse = queueSize; + } else { + sizeToUse = DEFAULT_BUFFER_SIZE; + } + sizeToUse = powerOfTwo(sizeToUse); + + synchronized (this) { + DisruptorReference ref = getDisruptors().get(key); + if (ref == null) { + LOGGER.debug("Creating new disruptor for key {}", key); + ref = new DisruptorReference(this, uri, sizeToUse, producerType, waitStrategy); + getDisruptors().put(key, ref); + } else { + //if size was explicitly requested, the size to use should match the retrieved DisruptorReference + if (size != 0 && ref.getBufferSize() != sizeToUse) { + // there is already a queue, so make sure the size matches + throw new IllegalArgumentException( + "Cannot use existing queue " + key + " as the existing queue size " + + ref.getBufferSize() + " does not match given queue size " + sizeToUse); + } + LOGGER.debug("Reusing disruptor {} for key {}", ref, key); + } + + return ref; + } + } + + private static int powerOfTwo(int size) { + size--; + size |= size >> 1; + size |= size >> 2; + size |= size >> 4; + size |= size >> 8; + size |= size >> 16; + size++; + return size; + } + + public static String getDisruptorKey(String uri) { + if (uri.contains("?")) { + // strip parameters + uri = uri.substring(0, uri.indexOf('?')); + } + return uri; + } + + @Override + protected void doStop() throws Exception { + synchronized (this) { + getDisruptors().clear(); + } + super.doStop(); + } + + public Map getDisruptors() { + return disruptors; + } + + public int getDefaultConcurrentConsumers() { + return defaultConcurrentConsumers; + } + + public void setDefaultConcurrentConsumers(final int defaultConcurrentConsumers) { + this.defaultConcurrentConsumers = defaultConcurrentConsumers; + } + + public boolean isDefaultMultipleConsumers() { + return defaultMultipleConsumers; + } + + public void setDefaultMultipleConsumers(final boolean defaultMultipleConsumers) { + this.defaultMultipleConsumers = defaultMultipleConsumers; + } + + public DisruptorProducerType getDefaultProducerType() { + return defaultProducerType; + } + + public void setDefaultProducerType(final DisruptorProducerType defaultProducerType) { + this.defaultProducerType = defaultProducerType; + } + + public DisruptorWaitStrategy getDefaultWaitStrategy() { + return defaultWaitStrategy; + } + + public void setDefaultWaitStrategy(final DisruptorWaitStrategy defaultWaitStrategy) { + this.defaultWaitStrategy = defaultWaitStrategy; + } + + public boolean isDefaultBlockWhenFull() { + return defaultBlockWhenFull; + } + + public void setDefaultBlockWhenFull(boolean defaultBlockWhenFull) { + this.defaultBlockWhenFull = defaultBlockWhenFull; + } + + @Deprecated + public void setQueueSize(final int size) { + LOGGER.warn("Using deprecated queueSize parameter for SEDA compatibility, use bufferSize instead"); + queueSize = size; + } + + @Deprecated + public int getQueueSize() { + LOGGER.warn("Using deprecated queueSize parameter for SEDA compatibility, use bufferSize instead"); + return queueSize; + } + + public void setBufferSize(final int size) { + bufferSize = size; + } + + public int getBufferSize() { + return bufferSize; + } + + public void onShutdownEndpoint(DisruptorEndpoint disruptorEndpoint) { + String disruptorKey = getDisruptorKey(disruptorEndpoint.getEndpointUri()); + DisruptorReference disruptorReference = getDisruptors().get(disruptorKey); + + if (disruptorReference.getEndpointCount() == 0) { + //the last disruptor has been removed, we can delete the disruptor + getDisruptors().remove(disruptorKey); + } + } +} diff --git a/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/DisruptorConsumer.java b/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/DisruptorConsumer.java new file mode 100644 index 0000000000000..51466691278a7 --- /dev/null +++ b/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/DisruptorConsumer.java @@ -0,0 +1,197 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import java.util.HashSet; +import java.util.Set; + +import org.apache.camel.AsyncCallback; +import org.apache.camel.AsyncProcessor; +import org.apache.camel.Consumer; +import org.apache.camel.Exchange; +import org.apache.camel.Processor; +import org.apache.camel.ShutdownRunningTask; +import org.apache.camel.SuspendableService; +import org.apache.camel.impl.LoggingExceptionHandler; +import org.apache.camel.spi.ExceptionHandler; +import org.apache.camel.spi.ShutdownAware; +import org.apache.camel.support.ServiceSupport; +import org.apache.camel.util.AsyncProcessorConverterHelper; +import org.apache.camel.util.AsyncProcessorHelper; +import org.apache.camel.util.ExchangeHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A Consumer for the Disruptor component. + */ +public class DisruptorConsumer extends ServiceSupport implements Consumer, SuspendableService, ShutdownAware { + + private static final Logger LOGGER = LoggerFactory.getLogger(DisruptorConsumer.class); + + private final DisruptorEndpoint endpoint; + private final AsyncProcessor processor; + private ExceptionHandler exceptionHandler; + + public DisruptorConsumer(final DisruptorEndpoint endpoint, final Processor processor) { + this.endpoint = endpoint; + this.processor = AsyncProcessorConverterHelper.convert(processor); + } + + public ExceptionHandler getExceptionHandler() { + if (exceptionHandler == null) { + exceptionHandler = new LoggingExceptionHandler(getClass()); + } + return exceptionHandler; + } + + public void setExceptionHandler(final ExceptionHandler exceptionHandler) { + this.exceptionHandler = exceptionHandler; + } + + @Override + public DisruptorEndpoint getEndpoint() { + return endpoint; + } + + @Override + protected void doStart() throws Exception { + getEndpoint().onStarted(this); + } + + @Override + protected void doStop() throws Exception { + getEndpoint().onStopped(this); + } + + @Override + protected void doSuspend() throws Exception { + getEndpoint().onStopped(this); + } + + @Override + protected void doResume() throws Exception { + getEndpoint().onStarted(this); + } + + Set createEventHandlers(final int concurrentConsumers) { + final Set eventHandlers + = new HashSet(); + + for (int i = 0; i < concurrentConsumers; ++i) { + eventHandlers.add(new ConsumerEventHandler(i, concurrentConsumers)); + } + + return eventHandlers; + } + + @Override + public boolean deferShutdown(final ShutdownRunningTask shutdownRunningTask) { + // deny stopping on shutdown as we want disruptor consumers to run in case some other queues + // depend on this consumer to run, so it can complete its exchanges + return true; + } + + @Override + public void prepareShutdown(final boolean forced) { + // nothing + } + + @Override + public int getPendingExchangesSize() { + return getEndpoint().getDisruptor().getPendingExchangeCount(); + } + + @Override + public String toString() { + return "DisruptorConsumer[" + endpoint + "]"; + } + + private Exchange prepareExchange(final Exchange exchange) { + // send a new copied exchange with new camel context + // don't copy handovers as they are handled by the Disruptor Event Handlers + final Exchange newExchange = ExchangeHelper + .copyExchangeAndSetCamelContext(exchange, endpoint.getCamelContext(), false); + // set the from endpoint + newExchange.setFromEndpoint(endpoint); + return newExchange; + } + + private void process(final ExchangeEvent exchangeEvent) { + Exchange exchange = exchangeEvent.getExchange(); + + final boolean ignore = exchange + .getProperty(DisruptorEndpoint.DISRUPTOR_IGNORE_EXCHANGE, false, boolean.class); + if (ignore) { + // Property was set and it was set to true, so don't process Exchange. + LOGGER.trace("Ignoring exchange {}", exchange); + return; + } + + // send a new copied exchange with new camel context + final Exchange result = prepareExchange(exchange); + // use the regular processor and use the asynchronous routing engine to support it + AsyncCallback callback = new AsyncCallback() { + @Override + public void done(boolean doneSync) { + exchangeEvent.consumed(result); + } + }; + AsyncProcessorHelper.process(processor, result, callback); + } + + /** + * Implementation of the {@link LifecycleAwareExchangeEventHandler} interface that passes all Exchanges to the + * {@link Processor} registered at this {@link DisruptorConsumer}. + */ + private class ConsumerEventHandler extends AbstractLifecycleAwareExchangeEventHandler { + + private final int ordinal; + + private final int concurrentConsumers; + + public ConsumerEventHandler(final int ordinal, final int concurrentConsumers) { + this.ordinal = ordinal; + this.concurrentConsumers = concurrentConsumers; + } + + @Override + public void onEvent(final ExchangeEvent event, final long sequence, final boolean endOfBatch) + throws Exception { + // Consumer threads are managed at the endpoint to achieve the optimal performance. + // However, both multiple consumers (pub-sub style multicasting) as well as 'worker-pool' consumers dividing + // exchanges amongst them are scheduled on their own threads and are provided with all exchanges. + // To prevent duplicate exchange processing by worker-pool event handlers, they are all given an ordinal, + // which can be used to determine whether he should process the exchange, or leave it for his brethren. + //see http://code.google.com/p/disruptor/wiki/FrequentlyAskedQuestions#How_do_you_arrange_a_Disruptor_with_multiple_consumers_so_that_e + if (sequence % concurrentConsumers == ordinal) { + try { + process(event); + } catch (Exception e) { + final Exchange exchange = event.getExchange(); + if (exchange != null) { + getExceptionHandler().handleException("Error processing exchange", exchange, e); + } else { + getExceptionHandler().handleException(e); + } + } + } + } + + } +} diff --git a/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/DisruptorEndpoint.java b/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/DisruptorEndpoint.java new file mode 100644 index 0000000000000..8a2e90159f14b --- /dev/null +++ b/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/DisruptorEndpoint.java @@ -0,0 +1,289 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +import com.lmax.disruptor.InsufficientCapacityException; + +import org.apache.camel.Component; +import org.apache.camel.Consumer; +import org.apache.camel.Exchange; +import org.apache.camel.MultipleConsumersSupport; +import org.apache.camel.Processor; +import org.apache.camel.Producer; +import org.apache.camel.WaitForTaskToComplete; +import org.apache.camel.api.management.ManagedAttribute; +import org.apache.camel.impl.DefaultEndpoint; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * An implementation of the Disruptor component + * for asynchronous SEDA exchanges on an + * LMAX Disruptor within a CamelContext + */ + +public class DisruptorEndpoint extends DefaultEndpoint implements MultipleConsumersSupport { + + private static final Logger LOGGER = LoggerFactory.getLogger(DisruptorEndpoint.class); + public static final String DISRUPTOR_IGNORE_EXCHANGE = "disruptor.ignoreExchange"; + + private final int concurrentConsumers; + private final boolean multipleConsumers; + private WaitForTaskToComplete waitForTaskToComplete = WaitForTaskToComplete.IfReplyExpected; + + private long timeout = 30000; + + private boolean blockWhenFull; + + private final Set producers = new CopyOnWriteArraySet(); + private final Set consumers = new CopyOnWriteArraySet(); + + private final DisruptorReference disruptorReference; + + public DisruptorEndpoint(final String endpointUri, final Component component, + final DisruptorReference disruptorReference, final int concurrentConsumers, + final boolean multipleConsumers, boolean blockWhenFull) throws Exception { + super(endpointUri, component); + this.disruptorReference = disruptorReference; + this.concurrentConsumers = concurrentConsumers; + this.multipleConsumers = multipleConsumers; + this.blockWhenFull = blockWhenFull; + } + + @ManagedAttribute(description = "Buffer max capacity") + public int getBufferSize() { + return disruptorReference.getBufferSize(); + } + + @ManagedAttribute(description = "Remaining capacity in ring buffer") + public long getRemainingCapacity() throws DisruptorNotStartedException { + return getDisruptor().getRemainingCapacity(); + } + + @ManagedAttribute(description = "Amount of pending exchanges waiting for consumption in ring buffer") + public long getPendingExchangeCount() throws DisruptorNotStartedException { + return getDisruptor().getPendingExchangeCount(); + } + + + @ManagedAttribute(description = "Number of concurrent consumers") + public int getConcurrentConsumers() { + return concurrentConsumers; + } + + public WaitForTaskToComplete getWaitForTaskToComplete() { + return waitForTaskToComplete; + } + + public void setWaitForTaskToComplete(final WaitForTaskToComplete waitForTaskToComplete) { + this.waitForTaskToComplete = waitForTaskToComplete; + } + + @ManagedAttribute + public long getTimeout() { + return timeout; + } + + public void setTimeout(final long timeout) { + this.timeout = timeout; + } + + @ManagedAttribute + public boolean isMultipleConsumers() { + return multipleConsumers; + } + + /** + * Returns the current active consumers on this endpoint + */ + public Set getConsumers() { + return Collections.unmodifiableSet(consumers); + } + + /** + * Returns the current active producers on this endpoint + */ + public Set getProducers() { + return Collections.unmodifiableSet(producers); + } + + @Override + @ManagedAttribute + public boolean isMultipleConsumersSupported() { + return isMultipleConsumers(); + } + + @ManagedAttribute + public boolean isBlockWhenFull() { + return blockWhenFull; + } + + public void setBlockWhenFull(boolean blockWhenFull) { + this.blockWhenFull = blockWhenFull; + } + + @Override + public boolean isSingleton() { + return true; + } + + @Override + public Producer createProducer() throws Exception { + if (getProducers().size() == 1 && getDisruptor().getProducerType() == DisruptorProducerType.Single) { + throw new IllegalStateException( + "Endpoint can't support multiple producers when ProducerType SINGLE is configured"); + } + return new DisruptorProducer(this, getWaitForTaskToComplete(), getTimeout(), isBlockWhenFull()); + } + + @Override + public Consumer createConsumer(final Processor processor) throws Exception { + return new DisruptorConsumer(this, processor); + } + + @Override + protected void doStart() throws Exception { + LOGGER.debug("Start enpoint {}", this); + // notify reference we are shutting down this endpoint + disruptorReference.addEndpoint(this); + + super.doStart(); //To change body of overridden methods use File | Settings | File Templates. + } + + @Override + protected void doStop() throws Exception { + LOGGER.debug("Stop enpoint {}", this); + // notify reference we are shutting down this endpoint + disruptorReference.removeEndpoint(this); + + super.doStop(); //To change body of overridden methods use File | Settings | File Templates. + } + + + @Override + protected void doShutdown() throws Exception { + // notify component we are shutting down this endpoint + if (getComponent() != null) { + getComponent().onShutdownEndpoint(this); + } + + super.doShutdown(); + } + + @Override + public DisruptorComponent getComponent() { + return (DisruptorComponent)super.getComponent(); + } + + void onStarted(final DisruptorConsumer consumer) throws Exception { + synchronized (this) { + // validate multiple consumers has been enabled is necessary + if (!consumers.isEmpty() && !isMultipleConsumersSupported()) { + throw new IllegalStateException( + "Multiple consumers for the same endpoint is not allowed: " + this); + } + + if (consumers.add(consumer)) { + LOGGER.debug("Starting consumer {} on endpoint {}", consumer, getEndpointUri()); + + getDisruptor().reconfigure(); + + } else { + LOGGER.debug("Tried to start Consumer {} on endpoint {} but it was already started", consumer, + getEndpointUri()); + } + } + + } + + + void onStopped(final DisruptorConsumer consumer) throws Exception { + synchronized (this) { + + if (consumers.remove(consumer)) { + LOGGER.debug("Stopping consumer {} on endpoint {}", consumer, getEndpointUri()); + + getDisruptor().reconfigure(); + + } else { + LOGGER.debug("Tried to stop Consumer {} on endpoint {} but it was already stopped", consumer, + getEndpointUri()); + } + + + } + } + + void onStarted(final DisruptorProducer producer) { + producers.add(producer); + } + + void onStopped(final DisruptorProducer producer) { + producers.remove(producer); + } + + Map> createConsumerEventHandlers() { + Map> result = + new HashMap>(); + + for (final DisruptorConsumer consumer : consumers) { + result.put(consumer, consumer.createEventHandlers(concurrentConsumers)); + } + + return result; + } + + /** + * Called by DisruptorProducers to publish new exchanges on the RingBuffer, blocking when full + * + * @param exchange + */ + void publish(final Exchange exchange) throws DisruptorNotStartedException { + disruptorReference.publish(exchange); + } + + /** + * Called by DisruptorProducers to publish new exchanges on the RingBuffer, throwing InsufficientCapacityException + * when full + * + * @param exchange + * @throws InsufficientCapacityException when the Ringbuffer is full. + */ + void tryPublish(final Exchange exchange) + throws DisruptorNotStartedException, InsufficientCapacityException { + disruptorReference.tryPublish(exchange); + } + + DisruptorReference getDisruptor() { + return disruptorReference; + } + + @Override + public boolean equals(Object object) { + boolean result = super.equals(object); + + return result && getCamelContext().equals(((DisruptorEndpoint)object).getCamelContext()); + } +} diff --git a/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/DisruptorNotStartedException.java b/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/DisruptorNotStartedException.java new file mode 100644 index 0000000000000..913822b7b1228 --- /dev/null +++ b/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/DisruptorNotStartedException.java @@ -0,0 +1,40 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +/** + * This exception is thrown when a producer attempts to publish an exchange while the Disruptor is not yet started or + * already shut down + */ +public class DisruptorNotStartedException extends Exception { + public DisruptorNotStartedException() { + super(); + } + + public DisruptorNotStartedException(String message) { + super(message); + } + + public DisruptorNotStartedException(String message, Throwable cause) { + super(message, cause); + } + + public DisruptorNotStartedException(Throwable cause) { + super(cause); + } +} diff --git a/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/DisruptorProducer.java b/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/DisruptorProducer.java new file mode 100644 index 0000000000000..1ae0ffd2ca8ec --- /dev/null +++ b/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/DisruptorProducer.java @@ -0,0 +1,201 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import com.lmax.disruptor.InsufficientCapacityException; + +import org.apache.camel.AsyncCallback; +import org.apache.camel.Exchange; +import org.apache.camel.ExchangeTimedOutException; +import org.apache.camel.WaitForTaskToComplete; +import org.apache.camel.impl.DefaultAsyncProducer; +import org.apache.camel.support.SynchronizationAdapter; +import org.apache.camel.util.ExchangeHelper; + +/** + * A Producer for the Disruptor component. + */ +public class DisruptorProducer extends DefaultAsyncProducer { + + private final WaitForTaskToComplete waitForTaskToComplete; + private final long timeout; + + private final DisruptorEndpoint endpoint; + private boolean blockWhenFull; + + public DisruptorProducer(final DisruptorEndpoint endpoint, + final WaitForTaskToComplete waitForTaskToComplete, + final long timeout, boolean blockWhenFull) { + super(endpoint); + this.waitForTaskToComplete = waitForTaskToComplete; + this.timeout = timeout; + this.endpoint = endpoint; + this.blockWhenFull = blockWhenFull; + } + + @Override + public DisruptorEndpoint getEndpoint() { + return endpoint; + } + + @Override + protected void doStart() throws Exception { + getEndpoint().onStarted(this); + } + + @Override + protected void doStop() throws Exception { + getEndpoint().onStopped(this); + } + + @Override + public boolean process(final Exchange exchange, final AsyncCallback callback) { + WaitForTaskToComplete wait = waitForTaskToComplete; + if (exchange.getProperty(Exchange.ASYNC_WAIT) != null) { + wait = exchange.getProperty(Exchange.ASYNC_WAIT, WaitForTaskToComplete.class); + } + + if (wait == WaitForTaskToComplete.Always + || (wait == WaitForTaskToComplete.IfReplyExpected && ExchangeHelper.isOutCapable(exchange))) { + + // do not handover the completion as we wait for the copy to complete, and copy its result back when it done + final Exchange copy = prepareCopy(exchange, false); + + // latch that waits until we are complete + final CountDownLatch latch = new CountDownLatch(1); + + // we should wait for the reply so install a on completion so we know when its complete + copy.addOnCompletion(new SynchronizationAdapter() { + @Override + public void onDone(final Exchange response) { + // check for timeout, which then already would have invoked the latch + if (latch.getCount() == 0) { + if (log.isTraceEnabled()) { + log.trace("{}. Timeout occurred so response will be ignored: {}", this, + response.hasOut() ? response.getOut() : response.getIn()); + } + } else { + if (log.isTraceEnabled()) { + log.trace("{} with response: {}", this, + response.hasOut() ? response.getOut() : response.getIn()); + } + try { + ExchangeHelper.copyResults(exchange, response); + } finally { + // always ensure latch is triggered + latch.countDown(); + } + } + } + + @Override + public boolean allowHandover() { + // do not allow handover as we want to seda producer to have its completion triggered + // at this point in the routing (at this leg), instead of at the very last (this ensure timeout is honored) + return false; + } + + @Override + public String toString() { + return "onDone at endpoint: " + endpoint; + } + }); + + doPublish(copy); + + if (timeout > 0) { + if (log.isTraceEnabled()) { + log.trace("Waiting for task to complete using timeout (ms): {} at [{}]", timeout, + endpoint.getEndpointUri()); + } + // lets see if we can get the task done before the timeout + boolean done = false; + try { + done = latch.await(timeout, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + // ignore + } + if (!done) { + // Remove timed out Exchange from disruptor endpoint. + + // We can't actually remove a published exchange from an active Disruptor. + // Instead we prevent processing of the exchange by setting a Property on the exchange and the value + // would be an AtomicBoolean. This is set by the Producer and the Consumer would look up that Property and + // check the AtomicBoolean. If the AtomicBoolean says that we are good to proceed, it will process the + // exchange. If false, it will simply disregard the exchange. + // But since the Property map is a Concurrent one, maybe we don't need the AtomicBoolean. Check with Simon. + // Also check the TimeoutHandler of the new Disruptor 3.0.0, consider making the switch to the latest version. + exchange.setProperty(DisruptorEndpoint.DISRUPTOR_IGNORE_EXCHANGE, true); + + exchange.setException(new ExchangeTimedOutException(exchange, timeout)); + + // count down to indicate timeout + latch.countDown(); + } + } else { + if (log.isTraceEnabled()) { + log.trace("Waiting for task to complete (blocking) at [{}]", endpoint.getEndpointUri()); + } + // no timeout then wait until its done + try { + latch.await(); + } catch (InterruptedException e) { + // ignore + } + } + } else { + // no wait, eg its a InOnly then just publish to the ringbuffer and return + // handover the completion so its the copy which performs that, as we do not wait + final Exchange copy = prepareCopy(exchange, true); + doPublish(copy); + } + + // we use OnCompletion on the Exchange to callback and wait for the Exchange to be done + // so we should just signal the callback we are done synchronously + callback.done(true); + return true; + } + + private void doPublish(Exchange exchange) { + log.trace("Publishing Exchange to disruptor ringbuffer: {}", exchange); + + try { + if (blockWhenFull) { + endpoint.publish(exchange); + } else { + endpoint.tryPublish(exchange); + } + } catch (DisruptorNotStartedException e) { + throw new IllegalStateException("Disruptor was not started", e); + } catch (InsufficientCapacityException e) { + throw new IllegalStateException("Disruptors ringbuffer was full", e); + } + } + + + private Exchange prepareCopy(final Exchange exchange, final boolean handover) { + // use a new copy of the exchange to route async + final Exchange copy = ExchangeHelper.createCorrelatedCopy(exchange, handover); + // set a new from endpoint to be the disruptor + copy.setFromEndpoint(endpoint); + return copy; + } +} diff --git a/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/DisruptorProducerType.java b/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/DisruptorProducerType.java new file mode 100644 index 0000000000000..313eca9edbfa0 --- /dev/null +++ b/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/DisruptorProducerType.java @@ -0,0 +1,45 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import com.lmax.disruptor.dsl.ProducerType; + +/** + * This enumeration re-enumerated the values of the {@link ProducerType} according to the Camel Case convention used + * in Camel. + * Multi is the default {@link ProducerType}. + */ +public enum DisruptorProducerType { + /** + * Create a RingBuffer with a single event publisher to the Disruptors RingBuffer + */ + Single(ProducerType.SINGLE), + /** + * Create a RingBuffer supporting multiple event publishers to the Disruptors RingBuffer + */ + Multi(ProducerType.MULTI); + private final ProducerType producerType; + + DisruptorProducerType(ProducerType producerType) { + this.producerType = producerType; + } + + public ProducerType getProducerType() { + return producerType; + } +} diff --git a/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/DisruptorReference.java b/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/DisruptorReference.java new file mode 100644 index 0000000000000..e2aa1bc5a7876 --- /dev/null +++ b/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/DisruptorReference.java @@ -0,0 +1,445 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicMarkableReference; +import java.util.concurrent.locks.LockSupport; + +import com.lmax.disruptor.InsufficientCapacityException; +import com.lmax.disruptor.RingBuffer; +import com.lmax.disruptor.dsl.Disruptor; + +import org.apache.camel.Exchange; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Holder for Disruptor references. + *

+ * This is used to keep track of the usages of the Disruptors, so we know when a Disruptor is no longer in use, and + * can safely be discarded. + */ +public class DisruptorReference { + private static final Logger LOGGER = LoggerFactory.getLogger(DisruptorReference.class); + + private final Set endpoints = Collections + .newSetFromMap(new WeakHashMap(4)); + private final DisruptorComponent component; + private final String uri; + + //The mark on the reference indicates if we are in the process of reconfiguring the Disruptor: + //(ref, mark) : Description + //(null, false) : not started or completely shut down + //(null, true) : in process of reconfiguring + //( x , false) : normally functioning Disruptor + //( x , true) : never set + private final AtomicMarkableReference> disruptor + = new AtomicMarkableReference>(null, false); + + private final DelayedExecutor delayedExecutor = new DelayedExecutor(); + + private final DisruptorProducerType producerType; + + private final int size; + + private final DisruptorWaitStrategy waitStrategy; + + private final Queue temporaryExchangeBuffer; + + //access guarded by this + private ExecutorService executor; + + private LifecycleAwareExchangeEventHandler[] handlers = new LifecycleAwareExchangeEventHandler[0]; + + private int uniqueConsumerCount = 0; + + DisruptorReference(final DisruptorComponent component, final String uri, final int size, + final DisruptorProducerType producerType, final DisruptorWaitStrategy waitStrategy) + throws Exception { + this.component = component; + this.uri = uri; + this.size = size; + this.producerType = producerType; + this.waitStrategy = waitStrategy; + temporaryExchangeBuffer = new ArrayBlockingQueue(size); + reconfigure(); + } + + public boolean hasNullReference() { + return disruptor.getReference() == null; + } + + private Disruptor getCurrentDisruptor() throws DisruptorNotStartedException { + Disruptor currentDisruptor = disruptor.getReference(); + + if (currentDisruptor == null) { + // no current Disruptor reference, we may be reconfiguring or it was not started + // check which by looking at the reference mark... + boolean[] changeIsPending = new boolean[1]; + + while (currentDisruptor == null) { + currentDisruptor = disruptor.get(changeIsPending); + //Check if we are reconfiguring + if (currentDisruptor == null && !changeIsPending[0]) { + throw new DisruptorNotStartedException( + "Disruptor is not yet started or already shut down."); + } else if (currentDisruptor == null && changeIsPending[0]) { + //We should be back shortly...keep trying but spare CPU resources + LockSupport.parkNanos(1L); + } + } + } + + return currentDisruptor; + } + + public void tryPublish(final Exchange exchange) + throws DisruptorNotStartedException, InsufficientCapacityException { + tryPublishExchangeOnRingBuffer(exchange, getCurrentDisruptor().getRingBuffer()); + } + + public void publish(final Exchange exchange) throws DisruptorNotStartedException { + publishExchangeOnRingBuffer(exchange, getCurrentDisruptor().getRingBuffer()); + } + + private void publishExchangeOnRingBuffer(final Exchange exchange, + final RingBuffer ringBuffer) { + final long sequence = ringBuffer.next(); + ringBuffer.get(sequence).setExchange(exchange, uniqueConsumerCount); + ringBuffer.publish(sequence); + } + + private void tryPublishExchangeOnRingBuffer(final Exchange exchange, + final RingBuffer ringBuffer) + throws InsufficientCapacityException { + final long sequence = ringBuffer.tryNext(); + ringBuffer.get(sequence).setExchange(exchange, uniqueConsumerCount); + ringBuffer.publish(sequence); + } + + public synchronized void reconfigure() throws Exception { + LOGGER.debug("Reconfiguring disruptor {}", this); + shutdownDisruptor(true); + + start(); + } + + private void start() throws Exception { + LOGGER.debug("Starting disruptor {}", this); + Disruptor newDisruptor = createDisruptor(); + + newDisruptor.start(); + + if (executor != null) { + //and use our delayed executor to really really execute the event handlers now + delayedExecutor.executeDelayedCommands(executor); + } + + //make sure all event handlers are correctly started before we continue + for (final LifecycleAwareExchangeEventHandler handler : handlers) { + boolean eventHandlerStarted = false; + while (!eventHandlerStarted) { + try { + //The disruptor start command executed above should have triggered a start signal to all + //event processors which, in their death, should notify our event handlers. They respond by + //switching a latch and we want to await that latch here to make sure they are started. + if (!handler.awaitStarted(10, TimeUnit.SECONDS)) { + //we wait for a relatively long, but limited amount of time to prevent an application using + //this component from hanging indefinitely + //Please report a bug if you can reproduce this + LOGGER.error("Disruptor/event handler failed to start properly, PLEASE REPORT"); + } + eventHandlerStarted = true; + } catch (InterruptedException e) { + //just retry + } + } + } + + publishBufferedExchanges(newDisruptor); + + disruptor.set(newDisruptor, false); + } + + private Disruptor createDisruptor() throws Exception { + //create a new Disruptor + final Disruptor newDisruptor = new Disruptor( + ExchangeEventFactory.INSTANCE, size, delayedExecutor, producerType.getProducerType(), + waitStrategy.createWaitStrategyInstance()); + + //determine the list of eventhandlers to be associated to the Disruptor + final ArrayList eventHandlers + = new ArrayList(); + + uniqueConsumerCount = 0; + + for (final DisruptorEndpoint endpoint : endpoints) { + final Map> consumerEventHandlers + = endpoint.createConsumerEventHandlers(); + + if (consumerEventHandlers != null) { + uniqueConsumerCount += consumerEventHandlers.keySet().size(); + + for (Collection lifecycleAwareExchangeEventHandlers : consumerEventHandlers + .values()) { + eventHandlers.addAll(lifecycleAwareExchangeEventHandlers); + } + + } + } + + LOGGER.debug("Disruptor created with {} event handlers", eventHandlers.size()); + handleEventsWith(newDisruptor, + eventHandlers.toArray(new LifecycleAwareExchangeEventHandler[eventHandlers.size()])); + + return newDisruptor; + } + + private void handleEventsWith(Disruptor newDisruptor, + final LifecycleAwareExchangeEventHandler[] newHandlers) { + if (newHandlers == null || newHandlers.length == 0) { + handlers = new LifecycleAwareExchangeEventHandler[1]; + handlers[0] = new BlockingExchangeEventHandler(); + } else { + handlers = newHandlers; + } + resizeThreadPoolExecutor(handlers.length); + newDisruptor.handleEventsWith(handlers); + } + + private void publishBufferedExchanges(Disruptor newDisruptor) { + //now empty out all buffered Exchange if we had any + final List exchanges = new ArrayList(temporaryExchangeBuffer.size()); + while (!temporaryExchangeBuffer.isEmpty()) { + exchanges.add(temporaryExchangeBuffer.remove()); + } + RingBuffer ringBuffer = newDisruptor.getRingBuffer(); + //and offer them again to our new ringbuffer + for (final Exchange exchange : exchanges) { + publishExchangeOnRingBuffer(exchange, ringBuffer); + } + } + + private void resizeThreadPoolExecutor(final int newSize) { + if (executor == null && newSize > 0) { + LOGGER.debug("Creating new executor with {} threads", newSize); + //no thread pool executor yet, create a new one + executor = component.getCamelContext().getExecutorServiceManager().newFixedThreadPool(this, uri, + newSize); + } else if (executor != null && newSize <= 0) { + LOGGER.debug("Shutting down executor"); + //we need to shut down our executor + component.getCamelContext().getExecutorServiceManager().shutdown(executor); + executor = null; + } else if (executor instanceof ThreadPoolExecutor) { + LOGGER.debug("Resizing existing executor to {} threads", newSize); + //our thread pool executor is of type ThreadPoolExecutor, we know how to resize it + final ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor)executor; + threadPoolExecutor.setCorePoolSize(newSize); + threadPoolExecutor.setMaximumPoolSize(newSize); + } else if (newSize > 0) { + LOGGER.debug("Shutting down old and creating new executor with {} threads", newSize); + //hmmm...no idea what kind of executor this is...just kill it and start fresh + component.getCamelContext().getExecutorServiceManager().shutdown(executor); + + executor = component.getCamelContext().getExecutorServiceManager().newFixedThreadPool(this, uri, + newSize); + } + } + + private synchronized void shutdownDisruptor(boolean isReconfiguring) { + LOGGER.debug("Shutting down disruptor {}, reconfiguring: {}", this, isReconfiguring); + Disruptor currentDisruptor = disruptor.getReference(); + disruptor.set(null, isReconfiguring); + + if (currentDisruptor != null) { + //check if we had a blocking event handler to keep an empty disruptor 'busy' + if (handlers != null && handlers.length == 1 + && handlers[0] instanceof BlockingExchangeEventHandler) { + // yes we did, unblock it so we can get rid of our backlog, + // The eventhandler will empty its pending exchanges in our temporary buffer + final BlockingExchangeEventHandler blockingExchangeEventHandler + = (BlockingExchangeEventHandler)handlers[0]; + blockingExchangeEventHandler.unblock(); + } + + currentDisruptor.shutdown(); + + //they have already been given a trigger to halt when they are done by shutting down the disruptor + //we do however want to await their completion before they are scheduled to process events from the new + for (final LifecycleAwareExchangeEventHandler eventHandler : handlers) { + boolean eventHandlerFinished = false; + //the disruptor is now empty and all consumers are either done or busy processing their last exchange + while (!eventHandlerFinished) { + try { + //The disruptor shutdown command executed above should have triggered a halt signal to all + //event processors which, in their death, should notify our event handlers. They respond by + //switching a latch and we want to await that latch here to make sure they are done. + if (!eventHandler.awaitStopped(10, TimeUnit.SECONDS)) { + //we wait for a relatively long, but limited amount of time to prevent an application using + //this component from hanging indefinitely + //Please report a bug if you can repruduce this + LOGGER.error( + "Disruptor/event handler failed to shut down properly, PLEASE REPORT"); + } + eventHandlerFinished = true; + } catch (InterruptedException e) { + //just retry + } + } + } + + handlers = new LifecycleAwareExchangeEventHandler[0]; + } + } + + private synchronized void shutdownExecutor() { + resizeThreadPoolExecutor(0); + } + + public long getRemainingCapacity() throws DisruptorNotStartedException { + return getCurrentDisruptor().getRingBuffer().remainingCapacity(); + } + + public DisruptorWaitStrategy getWaitStrategy() { + return waitStrategy; + } + + DisruptorProducerType getProducerType() { + return producerType; + } + + public int getBufferSize() { + return size; + } + + public int getPendingExchangeCount() { + try { + if (!hasNullReference()) { + return (int)(getBufferSize() - getRemainingCapacity() + temporaryExchangeBuffer.size()); + } + } catch (DisruptorNotStartedException e) { + //fall through... + } + return temporaryExchangeBuffer.size(); + } + + public synchronized void addEndpoint(final DisruptorEndpoint disruptorEndpoint) { + LOGGER.debug("Adding Endpoint: " + disruptorEndpoint); + endpoints.add(disruptorEndpoint); + LOGGER.debug("Endpoint added: {}, new total endpoints {}", disruptorEndpoint, endpoints.size()); + } + + public synchronized void removeEndpoint(final DisruptorEndpoint disruptorEndpoint) { + LOGGER.debug("Removing Endpoint: " + disruptorEndpoint); + if (getEndpointCount() == 1) { + LOGGER.debug("Last Endpoint removed, shutdown disruptor"); + //Shutdown our disruptor + shutdownDisruptor(false); + + //As there are no endpoints dependent on this Disruptor, we may also shutdown our executor + shutdownExecutor(); + } + endpoints.remove(disruptorEndpoint); + LOGGER.debug("Endpoint removed: {}, new total endpoints {}", disruptorEndpoint, endpoints.size()); + } + + public synchronized int getEndpointCount() { + return endpoints.size(); + } + + @Override + public String toString() { + return "DisruptorReference{" + + "uri='" + uri + '\'' + + ", endpoint count=" + endpoints.size() + + ", handler count=" + handlers.length + + '}'; + } + + /** + * Implementation of the {@link LifecycleAwareExchangeEventHandler} interface that blocks all calls to the #onEvent + * method until the #unblock method is called. + */ + private class BlockingExchangeEventHandler extends AbstractLifecycleAwareExchangeEventHandler { + + private final CountDownLatch blockingLatch = new CountDownLatch(1); + + @Override + public void onEvent(final ExchangeEvent event, final long sequence, final boolean endOfBatch) + throws Exception { + blockingLatch.await(); + final Exchange exchange = event.getExchange(); + + if (exchange.getProperty(DisruptorEndpoint.DISRUPTOR_IGNORE_EXCHANGE, false, boolean.class)) { + // Property was set and it was set to true, so don't process Exchange. + LOGGER.trace("Ignoring exchange {}", exchange); + } else { + temporaryExchangeBuffer.offer(exchange); + } + } + + public void unblock() { + blockingLatch.countDown(); + } + + } + + /** + * When a consumer is added or removed, we need to create a new Disruptor due to its static configuration. However, we + * would like to reuse our thread pool executor and only add or remove the threads we need. On a reconfiguraion of the + * Disruptor, we need to atomically swap the current RingBuffer with a new and fully configured one in order to keep + * the producers operational without the risk of losing messages. Configuration of a RingBuffer by the Disruptor's + * start method has a side effect that immediately starts execution of the event processors (consumers) on the + * Executor passed as a constructor argument which is stored in a final field. In order to be able to delay actual + * execution of the event processors until the event processors of the previous RingBuffer are done processing and the + * thread pool executor has been resized to match the new consumer count, we delay their execution using this class. + */ + private static class DelayedExecutor implements Executor { + + private final Queue delayedCommands = new LinkedList(); + + @Override + public void execute(final Runnable command) { + delayedCommands.offer(command); + } + + public void executeDelayedCommands(final Executor actualExecutor) { + Runnable command; + + while ((command = delayedCommands.poll()) != null) { + actualExecutor.execute(command); + } + } + } +} diff --git a/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/DisruptorWaitStrategy.java b/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/DisruptorWaitStrategy.java new file mode 100644 index 0000000000000..4f7aeb8d0d218 --- /dev/null +++ b/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/DisruptorWaitStrategy.java @@ -0,0 +1,84 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import com.lmax.disruptor.*; + +/** + * This enumeration holds all values that may be used as the {@link WaitStrategy} used by producers on a Disruptor. + * Blocking is the default {@link WaitStrategy}. + */ +public enum DisruptorWaitStrategy { + /** + * Blocking strategy that uses a lock and condition variable for {@link EventProcessor}s waiting on a barrier. + *

+ * This strategy can be used when throughput and low-latency are not as important as CPU resource. + */ + Blocking(BlockingWaitStrategy.class), + + /** + * Sleeping strategy that initially spins, then uses a Thread.yield(), and eventually for the minimum number of nanos + * the OS and JVM will allow while the {@link com.lmax.disruptor.EventProcessor}s are waiting on a barrier. + *

+ * This strategy is a good compromise between performance and CPU resource. Latency spikes can occur after quiet periods. + */ + Sleeping(SleepingWaitStrategy.class), + + /** + * Busy Spin strategy that uses a busy spin loop for {@link com.lmax.disruptor.EventProcessor}s waiting on a barrier. + *

+ * This strategy will use CPU resource to avoid syscalls which can introduce latency jitter. It is best + * used when threads can be bound to specific CPU cores. + */ + BusySpin(BusySpinWaitStrategy.class), + + /** + * Yielding strategy that uses a Thread.yield() for {@link com.lmax.disruptor.EventProcessor}s waiting on a barrier + * after an initially spinning. + *

+ * This strategy is a good compromise between performance and CPU resource without incurring significant latency spikes. + */ + Yielding(YieldingWaitStrategy.class); + +// TODO PhasedBackoffWaitStrategy constructor requires parameters, unlike the other strategies. We leave it out for now +// /** +// * Phased wait strategy for waiting {@link EventProcessor}s on a barrier.

+// * +// * This strategy can be used when throughput and low-latency are not as important as CPU resource.<\p> +// * +// * Spins, then yields, then blocks on the configured BlockingStrategy. +// */ +// PHASED_BACKOFF(PhasedBackoffWaitStrategy.class), + +// TODO TimeoutBlockingWaitStrategy constructor requires parameters, unlike the other strategies. We leave it out for now +// /** +// * TODO, wait for documentation from LMAX +// */ +// TIMEOUT_BLOCKING(TimeoutBlockingWaitStrategy.class); + + private final Class waitStrategyClass; + + private DisruptorWaitStrategy(final Class waitStrategyClass) { + + this.waitStrategyClass = waitStrategyClass; + } + + public WaitStrategy createWaitStrategyInstance() throws Exception { + return waitStrategyClass.getConstructor().newInstance(); + } +} diff --git a/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/ExchangeEvent.java b/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/ExchangeEvent.java new file mode 100644 index 0000000000000..718cfeb67d39f --- /dev/null +++ b/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/ExchangeEvent.java @@ -0,0 +1,81 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.camel.Exchange; +import org.apache.camel.util.UnitOfWorkHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This is a mutable reference to an {@link Exchange}, used as contents of the Disruptors ringbuffer + */ +public class ExchangeEvent { + + private Logger LOG = LoggerFactory.getLogger(ExchangeEvent.class); + + private Exchange exchange; + + private volatile int expectedConsumers = 0; + + private final AtomicInteger processedConsumers = new AtomicInteger(0); + + private final AtomicReference> results = new AtomicReference>(new ArrayList ()); + + public Exchange getExchange() { + return exchange; + } + + public void setExchange(final Exchange exchange, int expectedConsumers) { + this.exchange = exchange; + this.expectedConsumers = expectedConsumers; + processedConsumers.set(0); + } + + public void consumed(Exchange result) { + if (expectedConsumers > 1) { + results.get().add(result); + } + + if (processedConsumers.incrementAndGet() == expectedConsumers) { + // all consumers are done processing + if (expectedConsumers == 1) { + // this was the only consumer, call synchronizations with this result + UnitOfWorkHelper.doneSynchronizations(result, exchange.handoverCompletions(), LOG); + } else { + // this was the last consumer but we had more + // set the list of results as GROUPED_EXCHANGE property on the original exchange instead + List localResults = results.getAndSet(new ArrayList()); + exchange.setProperty(Exchange.GROUPED_EXCHANGE, localResults); + UnitOfWorkHelper.doneSynchronizations(exchange, exchange.handoverCompletions(), LOG); + } + } + } + + @Override + public String toString() { + return "ExchangeEvent{" + + "exchange=" + exchange + + '}'; + } +} diff --git a/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/ExchangeEventFactory.java b/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/ExchangeEventFactory.java new file mode 100644 index 0000000000000..e85fa94e5d7bc --- /dev/null +++ b/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/ExchangeEventFactory.java @@ -0,0 +1,34 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import com.lmax.disruptor.EventFactory; + +/** + * This class is used by the Disruptor to create new instanced of an {@link ExchangeEvent} to fill up a ringbuffer + * with mutable object references. + */ +class ExchangeEventFactory implements EventFactory { + + public static final ExchangeEventFactory INSTANCE = new ExchangeEventFactory(); + + @Override + public ExchangeEvent newInstance() { + return new ExchangeEvent(); + } +} diff --git a/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/LifecycleAwareExchangeEventHandler.java b/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/LifecycleAwareExchangeEventHandler.java new file mode 100644 index 0000000000000..cc917ee5d6f37 --- /dev/null +++ b/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/LifecycleAwareExchangeEventHandler.java @@ -0,0 +1,128 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import com.lmax.disruptor.EventHandler; +import com.lmax.disruptor.LifecycleAware; + +import java.util.concurrent.TimeUnit; + +/** + * This interface fuses the EventHandler and LifecycleAware interfaces. + * It also provides a handle to await the termination of this EventHandler. + */ +interface LifecycleAwareExchangeEventHandler extends EventHandler, LifecycleAware { + + /** + * Causes the current thread to wait until the event handler has been + * started, unless the thread is {@linkplain Thread#interrupt interrupted}. + *

+ *

If the event handler is already started then this method returns + * immediately. + *

+ *

If the current thread: + *

    + *
  • has its interrupted status set on entry to this method; or + *
  • is {@linkplain Thread#interrupt interrupted} while waiting, + *
+ * then {@link InterruptedException} is thrown and the current thread's + * interrupted status is cleared. + * + * @throws InterruptedException if the current thread is interrupted + * while waiting + */ + void awaitStarted() throws InterruptedException; + + /** + * Causes the current thread to wait until the event handler has been + * started, unless the thread is {@linkplain Thread#interrupt interrupted}, + * or the specified waiting time elapses. + *

+ *

If the event handler is already started then this method returns + * immediately with the value {@code true}. + *

+ *

If the current thread: + *

    + *
  • has its interrupted status set on entry to this method; “or + *
  • is {@linkplain Thread#interrupt interrupted} while waiting, + *
+ * then {@link InterruptedException} is thrown and the current thread's + * interrupted status is cleared. + *

+ *

If the specified waiting time elapses then the value {@code false} + * is returned. If the time is less than or equal to zero, the method + * will not wait at all. + * + * @param timeout the maximum time to wait + * @param unit the time unit of the {@code timeout} argument + * @return {@code true} if the event hanlder is stopped and {@code false} + * if the waiting time elapsed before the count reached zero + * @throws InterruptedException if the current thread is interrupted + * while waiting + */ + boolean awaitStarted(long timeout, TimeUnit unit) throws InterruptedException; + + /** + * Causes the current thread to wait until the event handler has been shut + * down, unless the thread is {@linkplain Thread#interrupt interrupted}. + *

+ *

If the event handler is not (yet) started then this method returns + * immediately. + *

+ *

If the current thread: + *

    + *
  • has its interrupted status set on entry to this method; or + *
  • is {@linkplain Thread#interrupt interrupted} while waiting, + *
+ * then {@link InterruptedException} is thrown and the current thread's + * interrupted status is cleared. + * + * @throws InterruptedException if the current thread is interrupted + * while waiting + */ + void awaitStopped() throws InterruptedException; + + /** + * Causes the current thread to wait until the event handler has been shut + * down, unless the thread is {@linkplain Thread#interrupt interrupted}, + * or the specified waiting time elapses. + *

+ *

If the event handler is not (yet) started then this method returns + * immediately with the value {@code true}. + *

+ *

If the current thread: + *

    + *
  • has its interrupted status set on entry to this method; “or + *
  • is {@linkplain Thread#interrupt interrupted} while waiting, + *
+ * then {@link InterruptedException} is thrown and the current thread's + * interrupted status is cleared. + *

+ *

If the specified waiting time elapses then the value {@code false} + * is returned. If the time is less than or equal to zero, the method + * will not wait at all. + * + * @param timeout the maximum time to wait + * @param unit the time unit of the {@code timeout} argument + * @return {@code true} if the event hanlder is stopped and {@code false} + * if the waiting time elapsed before the count reached zero + * @throws InterruptedException if the current thread is interrupted + * while waiting + */ + boolean awaitStopped(long timeout, TimeUnit unit) throws InterruptedException; +} diff --git a/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/vm/DisruptorVmComponent.java b/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/vm/DisruptorVmComponent.java new file mode 100644 index 0000000000000..34456a4b808f3 --- /dev/null +++ b/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/vm/DisruptorVmComponent.java @@ -0,0 +1,56 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor.vm; + +import org.apache.camel.component.disruptor.DisruptorComponent; +import org.apache.camel.component.disruptor.DisruptorReference; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * An implementation of the VM components + * for asynchronous SEDA exchanges on a + * LMAX Disruptor within the classloader tree containing + * the camel-disruptor.jar. i.e. to handle communicating across CamelContext instances and possibly across + * web application contexts, providing that camel-disruptor.jar is on the system classpath. + */ +public class DisruptorVmComponent extends DisruptorComponent { + protected static final Map DISRUPTORS + = new HashMap(); + private static final AtomicInteger START_COUNTER = new AtomicInteger(); + + @Override + public Map getDisruptors() { + return DISRUPTORS; + } + + @Override + protected void doStart() throws Exception { + super.doStart(); + START_COUNTER.incrementAndGet(); + } + + @Override + protected void doStop() throws Exception { + if (START_COUNTER.decrementAndGet() <= 0) { + // clear queues when no more vm components in use + getDisruptors().clear(); + } + } +} diff --git a/components/camel-disruptor/src/main/resources/META-INF/services/org/apache/camel/component/disruptor b/components/camel-disruptor/src/main/resources/META-INF/services/org/apache/camel/component/disruptor new file mode 100644 index 0000000000000..bf2d43ad3868e --- /dev/null +++ b/components/camel-disruptor/src/main/resources/META-INF/services/org/apache/camel/component/disruptor @@ -0,0 +1,18 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# + +class=org.apache.camel.component.disruptor.DisruptorComponent diff --git a/components/camel-disruptor/src/main/resources/META-INF/services/org/apache/camel/component/disruptor-vm b/components/camel-disruptor/src/main/resources/META-INF/services/org/apache/camel/component/disruptor-vm new file mode 100644 index 0000000000000..3f7666f0f7288 --- /dev/null +++ b/components/camel-disruptor/src/main/resources/META-INF/services/org/apache/camel/component/disruptor-vm @@ -0,0 +1,18 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# + +class=org.apache.camel.component.disruptor.vm.DisruptorVmComponent diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/BasicDisruptorComponentTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/BasicDisruptorComponentTest.java new file mode 100644 index 0000000000000..83c85ff13b0cc --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/BasicDisruptorComponentTest.java @@ -0,0 +1,136 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import org.apache.camel.*; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Assert; +import org.junit.Test; + +/** + * Tests some of the basic disruptor functionality + */ +public class BasicDisruptorComponentTest extends CamelTestSupport { + @EndpointInject(uri = "mock:result") + private MockEndpoint resultEndpoint; + + @Produce(uri = "disruptor:test") + private ProducerTemplate template; + + private static final Integer VALUE = Integer.valueOf(42); + + private final ThreadCounter threadCounter = new ThreadCounter(); + + @Test + public void testProduce() throws InterruptedException { + resultEndpoint.expectedBodiesReceived(VALUE); + resultEndpoint.setExpectedMessageCount(1); + + template.asyncSendBody("disruptor:test", VALUE); + + resultEndpoint.await(5, TimeUnit.SECONDS); + resultEndpoint.assertIsSatisfied(); + } + + + @Test + public void testAsynchronous() throws InterruptedException { + threadCounter.reset(); + + final int messagesSent = 1000; + + resultEndpoint.setExpectedMessageCount(messagesSent); + + final long currentThreadId = Thread.currentThread().getId(); + + for (int i = 0; i < messagesSent; ++i) { + template.asyncSendBody("disruptor:testAsynchronous", VALUE); + } + + resultEndpoint.await(20, TimeUnit.SECONDS); + resultEndpoint.assertIsSatisfied(); + + Assert.assertTrue(threadCounter.getThreadIdCount() > 0); + Assert.assertFalse(threadCounter.getThreadIds().contains(currentThreadId)); + } + + @Test + public void testMultipleConsumers() throws InterruptedException { + threadCounter.reset(); + + final int messagesSent = 1000; + + resultEndpoint.setExpectedMessageCount(messagesSent); + + for (int i = 0; i < messagesSent; ++i) { + template.asyncSendBody("disruptor:testMultipleConsumers?concurrentConsumers=4", VALUE); + } + + resultEndpoint.await(20, TimeUnit.SECONDS); + + //sleep for another second to check for duplicate messages in transit + Thread.sleep(1000); + + resultEndpoint.assertIsSatisfied(); + + Assert.assertEquals(4, threadCounter.getThreadIdCount()); + } + + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor:test").to("mock:result"); + from("disruptor:testAsynchronous").process(threadCounter).to("mock:result"); + from("disruptor:testMultipleConsumers?concurrentConsumers=4").process(threadCounter) + .to("mock:result"); + } + }; + } + + private static final class ThreadCounter implements Processor { + + private final Set threadIds = new HashSet(); + + public void reset() { + threadIds.clear(); + } + + @Override + public void process(final Exchange exchange) throws Exception { + threadIds.add(Thread.currentThread().getId()); + } + + public Set getThreadIds() { + return Collections.unmodifiableSet(threadIds); + } + + public int getThreadIdCount() { + return threadIds.size(); + } + } +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DirectRequestReplyAndDisruptorInOnlyTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DirectRequestReplyAndDisruptorInOnlyTest.java new file mode 100644 index 0000000000000..c2bf8ee6de6f1 --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DirectRequestReplyAndDisruptorInOnlyTest.java @@ -0,0 +1,53 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +/** + * @version + */ +public class DirectRequestReplyAndDisruptorInOnlyTest extends CamelTestSupport { + @Test + public void testInOut() throws Exception { + getMockEndpoint("mock:log").expectedBodiesReceived("Logging: Bye World"); + + final String out = template.requestBody("direct:start", "Hello World", String.class); + assertEquals("Bye World", out); + log.info("Got reply " + out); + + assertMockEndpointsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + // send the message as InOnly to DISRUPTOR as we want to continue routing + // (as we don't want to do request/reply over DISRUPTOR) + // In EIP patterns the WireTap pattern is what this would be + from("direct:start").transform(constant("Bye World")).inOnly("disruptor:log"); + + from("disruptor:log").delay(1000).transform(body().prepend("Logging: ")) + .to("log:log", "mock:log"); + } + }; + } +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DirectRequestReplyMultipleConsumersInOutTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DirectRequestReplyMultipleConsumersInOutTest.java new file mode 100644 index 0000000000000..2ce870c24fdf6 --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DirectRequestReplyMultipleConsumersInOutTest.java @@ -0,0 +1,81 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.camel.Exchange; +import org.apache.camel.Processor; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.apache.camel.util.CastUtils; +import org.junit.Test; + +/** + * @version + */ +public class DirectRequestReplyMultipleConsumersInOutTest extends CamelTestSupport { + @Test + public void testInOut() throws Exception { + List expectedBodies = new ArrayList(Arrays.asList("Bye World-1", "Bye World-2")); + getMockEndpoint("mock:log").expectedBodiesReceived(expectedBodies); + + final Exchange out = template.request("direct:start", new Processor() { + @Override + public void process(Exchange exchange) throws Exception { + exchange.getIn().setBody("Hello World"); + } + }); + + assertEquals("Bye World", getOutBody(out)); + final List groupedExchange = CastUtils.cast( + out.getProperty(Exchange.GROUPED_EXCHANGE, List.class)); + + assertNotNull("Expected a grouped exchange property, found none", groupedExchange); + for (Exchange exchange : groupedExchange) { + assertTrue("Body of grouped exchange property '" + getOutBody(exchange) + "' did not match any expected body", expectedBodies.remove(getOutBody(exchange))); + } + + log.info("Got reply " + out); + + assertMockEndpointsSatisfied(); + } + + private Object getOutBody(Exchange exchange) { + return (exchange.hasOut() ? exchange.getOut() : exchange.getIn()).getBody(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + // send the message as InOnly to DISRUPTOR as we want to continue routing + // (as we don't want to do request/reply over DISRUPTOR) + // In EIP patterns the WireTap pattern is what this would be + from("direct:start").transform(constant("Bye World")).inOut("disruptor:log?multipleConsumers=true"); + + from("disruptor:log?multipleConsumers=true").delay(100).transform(body().append("-1")) + .to("mock:log"); + from("disruptor:log?multipleConsumers=true").delay(1000).transform(body().append("-2")) + .to("mock:log"); + } + }; + } +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorAsyncRouteTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorAsyncRouteTest.java new file mode 100644 index 0000000000000..284f563d3f697 --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorAsyncRouteTest.java @@ -0,0 +1,58 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +/** + * Unit test based on user forum request. + */ +public class DisruptorAsyncRouteTest extends CamelTestSupport { + @Test + public void testSendAsync() throws Exception { + final MockEndpoint mock = getMockEndpoint("mock:result"); + mock.expectedBodiesReceived("Hello World"); + + // START SNIPPET: e2 + final Object out = template.requestBody("direct:start", "Hello World"); + assertEquals("OK", out); + // END SNIPPET: e2 + + assertMockEndpointsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + // START SNIPPET: e1 + @Override + public void configure() throws Exception { + from("direct:start") + // send it to the disruptor ring buffer that is async + .to("disruptor:next") + // return a constant response + .transform(constant("OK")); + + from("disruptor:next").to("mock:result"); + } + // END SNIPPET: e1 + }; + } +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorBlockWhenFullTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorBlockWhenFullTest.java new file mode 100644 index 0000000000000..23a42b0e6f7d0 --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorBlockWhenFullTest.java @@ -0,0 +1,80 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import org.apache.camel.CamelExecutionException; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +/** + * Tests that a Disruptor producer blocks when a message is sent while the ring buffer is full. + */ +public class DisruptorBlockWhenFullTest extends CamelTestSupport { + private static final int QUEUE_SIZE = 8; + + private static final int DELAY = 100; + + private static final String MOCK_URI = "mock:blockWhenFullOutput"; + + private static final String DEFAULT_URI = "disruptor:foo?size=" + QUEUE_SIZE; + private static final String EXCEPTION_WHEN_FULL_URI = "disruptor:foo?blockWhenFull=false&size=" + + QUEUE_SIZE; + + @Override + protected RouteBuilder createRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from(DEFAULT_URI).delay(DELAY).to(MOCK_URI); + } + }; + } + + @Test + public void testDisruptorBlockingWhenFull() throws Exception { + getMockEndpoint(MOCK_URI).setExpectedMessageCount(QUEUE_SIZE + 20); + + final DisruptorEndpoint disruptor = context.getEndpoint(DEFAULT_URI, DisruptorEndpoint.class); + assertEquals(QUEUE_SIZE, disruptor.getRemainingCapacity()); + + sendSoManyOverCapacity(DEFAULT_URI, QUEUE_SIZE, 20); + assertMockEndpointsSatisfied(); + } + + @Test(expected = CamelExecutionException.class) + public void testDisruptorExceptionWhenFull() throws Exception { + getMockEndpoint(MOCK_URI).setExpectedMessageCount(QUEUE_SIZE + 20); + + final DisruptorEndpoint disruptor = context.getEndpoint(DEFAULT_URI, DisruptorEndpoint.class); + assertEquals(QUEUE_SIZE, disruptor.getRemainingCapacity()); + + sendSoManyOverCapacity(EXCEPTION_WHEN_FULL_URI, QUEUE_SIZE, 20); + assertMockEndpointsSatisfied(); + } + + /** + * This method make sure that we hit the limit by sending 'soMany' messages over the given capacity which allows the + * delayer to kick in. + */ + private void sendSoManyOverCapacity(final String uri, final int capacity, final int soMany) { + for (int i = 0; i < (capacity + soMany); i++) { + template.sendBody(uri, "Message " + i); + } + } + +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorBufferingTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorBufferingTest.java new file mode 100644 index 0000000000000..947ccbfaa7003 --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorBufferingTest.java @@ -0,0 +1,119 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +/** + * This test suite is testing different scenarios where a disruptor is forced to + * buffer exchanges locally until a consumer is registered. + */ +public class DisruptorBufferingTest extends CamelTestSupport { + + @Test + public void testDisruptorBufferingWhileWaitingOnFirstConsumer() throws Exception { + template.sendBody("disruptor:foo", "A"); + template.sendBody("disruptor:foo", "B"); + template.sendBody("disruptor:foo", "C"); + + final DisruptorEndpoint disruptorEndpoint = getMandatoryEndpoint("disruptor:foo", + DisruptorEndpoint.class); + + assertEquals(5, disruptorEndpoint.getDisruptor().getRemainingCapacity()); + + // Add a first consumer on the endpoint + context.addRoutes(new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor:foo").routeId("bar").to("mock:bar"); + } + }); + + // Now that we have a consumer, the disruptor should send the buffered + // events downstream. Expect to receive the 3 original exchanges. + final MockEndpoint mockEndpoint = getMockEndpoint("mock:bar"); + mockEndpoint.expectedMessageCount(3); + mockEndpoint.assertIsSatisfied(200); + } + + @Test + public void testDisruptorBufferingWhileWaitingOnNextConsumer() throws Exception { + template.sendBody("disruptor:foo", "A"); + template.sendBody("disruptor:foo", "B"); + template.sendBody("disruptor:foo", "C"); + + final DisruptorEndpoint disruptorEndpoint = getMandatoryEndpoint("disruptor:foo", + DisruptorEndpoint.class); + + assertEquals(5, disruptorEndpoint.getDisruptor().getRemainingCapacity()); + + // Add a first consumer on the endpoint + context.addRoutes(new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor:foo").routeId("bar1").delay(200).to("mock:bar"); + } + }); + + // Now that we have a consumer, the disruptor should send the buffered + // events downstream. Wait until we have processed at least one + // exchange. + MockEndpoint mockEndpoint = getMockEndpoint("mock:bar"); + mockEndpoint.expectedMinimumMessageCount(1); + mockEndpoint.assertIsSatisfied(200); + + // Stop route and make sure all exchanges have been flushed. + context.stopRoute("bar1"); + mockEndpoint.expectedMessageCount(3); + mockEndpoint.assertIsSatisfied(); + + resetMocks(); + template.sendBody("disruptor:foo", "D"); + template.sendBody("disruptor:foo", "E"); + template.sendBody("disruptor:foo", "F"); + + // Add a new consumer on the endpoint + context.addRoutes(new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor:foo").routeId("bar2").to("mock:bar"); + } + }); + template.sendBody("disruptor:foo", "G"); + + // Make sure we have received the 3 buffered exchanges plus the one + // added late. + mockEndpoint = getMockEndpoint("mock:bar"); + mockEndpoint.expectedMessageCount(4); + mockEndpoint.assertIsSatisfied(100); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:start").routeId("foo").to("disruptor:foo?size=8"); + } + }; + + } +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorComplexInOutTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorComplexInOutTest.java new file mode 100644 index 0000000000000..eb1fe001a78cb --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorComplexInOutTest.java @@ -0,0 +1,53 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +/** + * @version + */ +public class DisruptorComplexInOutTest extends CamelTestSupport { + @Test + public void testInOut() throws Exception { + getMockEndpoint("mock:result").expectedBodiesReceived("Bye World"); + + final String out = template.requestBody("direct:start", "Hello World", String.class); + assertEquals("Bye World", out); + + assertMockEndpointsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + getContext().setTracing(true); + + from("direct:start").to("disruptor:a"); + + from("disruptor:a").to("log:bar", "disruptor:b"); + from("disruptor:b").delay(10).to("direct:c"); + + from("direct:c").transform(constant("Bye World")).to("mock:result"); + } + }; + } +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorComponentReferenceEndpointTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorComponentReferenceEndpointTest.java new file mode 100644 index 0000000000000..2abc771e32c4a --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorComponentReferenceEndpointTest.java @@ -0,0 +1,89 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +import java.util.Iterator; + +/** + * + */ +public class DisruptorComponentReferenceEndpointTest extends CamelTestSupport { + @Test + public void testDisruptorComponentReference() throws Exception { + final DisruptorComponent disruptor = context.getComponent("disruptor", DisruptorComponent.class); + + final String fooKey = DisruptorComponent.getDisruptorKey("disruptor://foo"); + assertEquals(1, disruptor.getDisruptors().get(fooKey).getEndpointCount()); + assertEquals(2, numberOfReferences(disruptor)); + + // add a second consumer on the endpoint + context.addRoutes(new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor:foo?concurrentConsumers=1").routeId("foo2").to("mock:foo2"); + } + }); + + assertEquals(2, disruptor.getDisruptors().get(fooKey).getEndpointCount()); + assertEquals(3, numberOfReferences(disruptor)); + + // remove the 1st route + context.stopRoute("foo"); + context.removeRoute("foo"); + + assertEquals(1, disruptor.getDisruptors().get(fooKey).getEndpointCount()); + assertEquals(2, numberOfReferences(disruptor)); + + // remove the 2nd route + context.stopRoute("foo2"); + context.removeRoute("foo2"); + + // and there is no longer disruptors for the foo key + assertTrue(disruptor.getDisruptors().get(fooKey) == null); + + // there should still be a bar + assertEquals(1, numberOfReferences(disruptor)); + final String barKey = DisruptorComponent.getDisruptorKey("disruptor://bar"); + assertEquals(1, disruptor.getDisruptors().get(barKey).getEndpointCount()); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor:foo").routeId("foo").to("mock:foo"); + + from("disruptor:bar").routeId("bar").to("mock:bar"); + } + }; + } + + private int numberOfReferences(final DisruptorComponent disruptor) { + int num = 0; + final Iterator it = disruptor.getDisruptors().values().iterator(); + while (it.hasNext()) { + num += it.next().getEndpointCount(); + } + return num; + } + +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorConcurrentConsumersNPEIssueTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorConcurrentConsumersNPEIssueTest.java new file mode 100644 index 0000000000000..52a7232a313ff --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorConcurrentConsumersNPEIssueTest.java @@ -0,0 +1,84 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import org.apache.camel.FailedToStartRouteException; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +/** + * @version + */ +public class DisruptorConcurrentConsumersNPEIssueTest extends CamelTestSupport { + @Test + public void testSendToDisruptor() throws Exception { + final MockEndpoint mock = getMockEndpoint("mock:result"); + mock.expectedBodiesReceived("Hello World"); + + template.sendBody("disruptor:foo", "Hello World"); + + assertMockEndpointsSatisfied(); + + try { + context.startRoute("first"); + fail("Should have thrown exception"); + } catch (FailedToStartRouteException e) { + assertEquals( + "Failed to start route first because of Multiple consumers for the same endpoint is not allowed:" + + " Endpoint[disruptor://foo?concurrentConsumers=5]", e.getMessage()); + } + } + + @Test + public void testStartThird() throws Exception { + final MockEndpoint mock = getMockEndpoint("mock:result"); + mock.expectedBodiesReceived("Hello World"); + + template.sendBody("disruptor:foo", "Hello World"); + + assertMockEndpointsSatisfied(); + + // this should be okay + context.startRoute("third"); + + try { + context.startRoute("first"); + fail("Should have thrown exception"); + } catch (FailedToStartRouteException e) { + assertEquals( + "Failed to start route first because of Multiple consumers for the same endpoint is not allowed:" + + " Endpoint[disruptor://foo?concurrentConsumers=5]", e.getMessage()); + } + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor:foo?concurrentConsumers=5").routeId("first").noAutoStartup() + .to("mock:result"); + + from("disruptor:foo?concurrentConsumers=5").routeId("second").to("mock:result"); + + from("direct:foo").routeId("third").noAutoStartup().to("mock:result"); + } + }; + } +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorConcurrentConsumersTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorConcurrentConsumersTest.java new file mode 100644 index 0000000000000..a5c6f76e8ff08 --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorConcurrentConsumersTest.java @@ -0,0 +1,47 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +/** + * @version + */ +public class DisruptorConcurrentConsumersTest extends CamelTestSupport { + @Test + public void testSendToDisruptor() throws Exception { + final MockEndpoint mock = getMockEndpoint("mock:result"); + mock.expectedBodiesReceived("Hello World"); + + template.sendBody("disruptor:foo?concurrentConsumers=5", "Hello World"); + + assertMockEndpointsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor:foo?concurrentConsumers=5").to("mock:result"); + } + }; + } +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorConcurrentTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorConcurrentTest.java new file mode 100644 index 0000000000000..f9ef2359d3fd0 --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorConcurrentTest.java @@ -0,0 +1,139 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import org.apache.camel.ProducerTemplate; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.impl.DefaultProducerTemplate; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +/** + * @version + */ +public class DisruptorConcurrentTest extends CamelTestSupport { + @Test + public void testDisruptorConcurrentInOnly() throws Exception { + final MockEndpoint mock = getMockEndpoint("mock:result"); + mock.expectedMessageCount(20); + + // should at least take 3 sec + mock.setMinimumResultWaitTime(3000); + + for (int i = 0; i < 20; i++) { + template.sendBody("disruptor:foo", "Message " + i); + } + + assertMockEndpointsSatisfied(); + } + + @Test + public void testDisruptorConcurrentInOnlyWithAsync() throws Exception { + final MockEndpoint mock = getMockEndpoint("mock:result"); + mock.expectedMessageCount(20); + + // should at least take 3 sec + mock.setMinimumResultWaitTime(3000); + + for (int i = 0; i < 20; i++) { + template.asyncSendBody("disruptor:foo", "Message " + i); + } + + assertMockEndpointsSatisfied(); + } + + @Test + public void testDisruptorConcurrentInOut() throws Exception { + final MockEndpoint mock = getMockEndpoint("mock:result"); + mock.expectedMessageCount(20); + mock.allMessages().body().startsWith("Bye"); + + // should at least take 3 sec + mock.setMinimumResultWaitTime(3000); + + final ExecutorService executors = Executors.newFixedThreadPool(10); + final List replies = new ArrayList(20); + for (int i = 0; i < 20; i++) { + final int num = i; + final Object out = executors.submit(new Callable() { + @Override + public Object call() throws Exception { + return template.requestBody("disruptor:bar", "Message " + num); + } + }); + replies.add(out); + } + + assertMockEndpointsSatisfied(); + + assertEquals(20, replies.size()); + executors.shutdownNow(); + } + + @Test + public void testDisruptorConcurrentInOutWithAsync() throws Exception { + final MockEndpoint mock = getMockEndpoint("mock:result"); + mock.expectedMessageCount(20); + mock.allMessages().body().startsWith("Bye"); + + // should at least take 3 sec + mock.setMinimumResultWaitTime(3000); + + // use our own template that has a higher thread pool than default camel that uses 5 + final ExecutorService executor = Executors.newFixedThreadPool(10); + final ProducerTemplate pt = new DefaultProducerTemplate(context, executor); + // must start the template + pt.start(); + + final List> replies = new ArrayList>(20); + for (int i = 0; i < 20; i++) { + final Future out = pt.asyncRequestBody("disruptor:bar", "Message " + i); + replies.add(out); + } + + assertMockEndpointsSatisfied(); + + assertEquals(20, replies.size()); + for (int i = 0; i < 20; i++) { + final String out = (String)replies.get(i).get(); + assertTrue(out.startsWith("Bye")); + } + pt.stop(); + executor.shutdownNow(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor:foo?concurrentConsumers=10").to("mock:before").delay(2000).to("mock:result"); + + from("disruptor:bar?concurrentConsumers=10").to("mock:before").delay(2000) + .transform(body().prepend("Bye ")).to("mock:result"); + } + }; + } +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorConfigureTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorConfigureTest.java new file mode 100644 index 0000000000000..4d964494e6419 --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorConfigureTest.java @@ -0,0 +1,158 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import org.apache.camel.ResolveEndpointFailedException; +import org.apache.camel.WaitForTaskToComplete; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +/** + * @version + */ +public class DisruptorConfigureTest extends CamelTestSupport { + @Test + public void testSizeConfigured() throws Exception { + final DisruptorEndpoint endpoint = resolveMandatoryEndpoint("disruptor:foo?size=2000", + DisruptorEndpoint.class); + assertEquals("size", 2048, endpoint.getBufferSize()); + assertEquals("getRemainingCapacity", 2048, endpoint.getRemainingCapacity()); + } + + @Test + public void testIllegalSizeZeroConfigured() throws Exception { + try { + resolveMandatoryEndpoint("disruptor:foo?size=0", DisruptorEndpoint.class); + fail("Should have thrown exception"); + } catch (ResolveEndpointFailedException e) { + assertEquals( + "Failed to resolve endpoint: disruptor://foo?size=0 due to: size found to be 0, must be greater than 0", + e.getMessage()); + } + } + + @Test + public void testSizeThroughBufferSizeComponentProperty() throws Exception { + final DisruptorComponent disruptor = context.getComponent("disruptor", DisruptorComponent.class); + disruptor.setBufferSize(2000); + assertEquals(2000, disruptor.getBufferSize()); + final DisruptorEndpoint endpoint = resolveMandatoryEndpoint("disruptor:foo", DisruptorEndpoint.class); + assertEquals("size", 2048, endpoint.getBufferSize()); + assertEquals("getRemainingCapacity", 2048, endpoint.getRemainingCapacity()); + } + + @Test + public void testSizeThroughQueueSizeComponentProperty() throws Exception { + final DisruptorComponent disruptor = context.getComponent("disruptor", DisruptorComponent.class); + disruptor.setQueueSize(2000); + assertEquals(2000, disruptor.getQueueSize()); + final DisruptorEndpoint endpoint = resolveMandatoryEndpoint("disruptor:foo", DisruptorEndpoint.class); + assertEquals("size", 2048, endpoint.getBufferSize()); + assertEquals("getRemainingCapacity", 2048, endpoint.getRemainingCapacity()); + } + + @Test + public void testMultipleConsumersConfigured() { + final DisruptorEndpoint endpoint = resolveMandatoryEndpoint("disruptor:foo?multipleConsumers=true", + DisruptorEndpoint.class); + assertEquals("multipleConsumers", true, endpoint.isMultipleConsumers()); + } + + @Test + public void testDefaultMultipleConsumersComponentProperty() throws Exception { + final DisruptorComponent disruptor = context.getComponent("disruptor", DisruptorComponent.class); + disruptor.setDefaultMultipleConsumers(true); + assertEquals(true, disruptor.isDefaultMultipleConsumers()); + final DisruptorEndpoint endpoint = resolveMandatoryEndpoint("disruptor:foo", DisruptorEndpoint.class); + assertEquals("multipleConsumers", true, endpoint.isMultipleConsumers()); + assertEquals("multipleConsumers", true, endpoint.isMultipleConsumersSupported()); + } + + @Test + public void testProducerTypeConfigured() { + final DisruptorEndpoint endpoint = resolveMandatoryEndpoint("disruptor:foo?producerType=Single", + DisruptorEndpoint.class); + assertEquals("producerType", DisruptorProducerType.Single, endpoint.getDisruptor().getProducerType()); + } + + @Test + public void testDefaultProducerTypeComponentProperty() throws Exception { + final DisruptorComponent disruptor = context.getComponent("disruptor", DisruptorComponent.class); + disruptor.setDefaultProducerType(DisruptorProducerType.Single); + assertEquals(DisruptorProducerType.Single, disruptor.getDefaultProducerType()); + final DisruptorEndpoint endpoint = resolveMandatoryEndpoint("disruptor:foo", DisruptorEndpoint.class); + assertEquals("producerType", DisruptorProducerType.Single, endpoint.getDisruptor().getProducerType()); + } + + @Test + public void testWaitStrategyConfigured() { + final DisruptorEndpoint endpoint = resolveMandatoryEndpoint("disruptor:foo?waitStrategy=BusySpin", + DisruptorEndpoint.class); + assertEquals("waitStrategy", DisruptorWaitStrategy.BusySpin, + endpoint.getDisruptor().getWaitStrategy()); + } + + @Test + public void testDefaultWaitStrategyComponentProperty() throws Exception { + final DisruptorComponent disruptor = context.getComponent("disruptor", DisruptorComponent.class); + disruptor.setDefaultWaitStrategy(DisruptorWaitStrategy.BusySpin); + assertEquals(DisruptorWaitStrategy.BusySpin, disruptor.getDefaultWaitStrategy()); + final DisruptorEndpoint endpoint = resolveMandatoryEndpoint("disruptor:foo", DisruptorEndpoint.class); + assertEquals("waitStrategy", DisruptorWaitStrategy.BusySpin, + endpoint.getDisruptor().getWaitStrategy()); + } + + @Test + public void testConcurrentConsumersConfigured() { + final DisruptorEndpoint endpoint = resolveMandatoryEndpoint("disruptor:foo?concurrentConsumers=5", + DisruptorEndpoint.class); + assertEquals("concurrentConsumers", 5, endpoint.getConcurrentConsumers()); + } + + @Test + public void testDefaultConcurrentConsumersComponentProperty() throws Exception { + final DisruptorComponent disruptor = context.getComponent("disruptor", DisruptorComponent.class); + disruptor.setDefaultConcurrentConsumers(5); + assertEquals(5, disruptor.getDefaultConcurrentConsumers()); + final DisruptorEndpoint endpoint = resolveMandatoryEndpoint("disruptor:foo", DisruptorEndpoint.class); + assertEquals("concurrentConsumers", 5, endpoint.getConcurrentConsumers()); + } + + @Test + public void testWaitForTaskToCompleteConfigured() { + final DisruptorEndpoint endpoint = resolveMandatoryEndpoint( + "disruptor:foo?waitForTaskToComplete=Never", DisruptorEndpoint.class); + assertEquals("waitForTaskToComplete", WaitForTaskToComplete.Never, + endpoint.getWaitForTaskToComplete()); + } + + @Test + public void testDefaults() { + final DisruptorEndpoint endpoint = resolveMandatoryEndpoint("disruptor:foo", DisruptorEndpoint.class); + assertEquals("concurrentConsumers: wrong default", 1, endpoint.getConcurrentConsumers()); + assertEquals("bufferSize: wrong default", DisruptorComponent.DEFAULT_BUFFER_SIZE, + endpoint.getBufferSize()); + assertEquals("timeout: wrong default", 30000L, endpoint.getTimeout()); + assertEquals("waitForTaskToComplete: wrong default", WaitForTaskToComplete.IfReplyExpected, + endpoint.getWaitForTaskToComplete()); + assertEquals("DisruptorWaitStrategy: wrong default", DisruptorWaitStrategy.Blocking, + endpoint.getDisruptor().getWaitStrategy()); + assertEquals("multipleConsumers: wrong default", false, endpoint.isMultipleConsumers()); + assertEquals("multipleConsumersSupported", false, endpoint.isMultipleConsumersSupported()); + assertEquals("producerType", DisruptorProducerType.Multi, endpoint.getDisruptor().getProducerType()); + } +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorConsumerSuspendResumeTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorConsumerSuspendResumeTest.java new file mode 100644 index 0000000000000..8ef6e9d1e9e7d --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorConsumerSuspendResumeTest.java @@ -0,0 +1,83 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.apache.camel.util.ServiceHelper; +import org.junit.Test; + +/** + * + */ +public class DisruptorConsumerSuspendResumeTest extends CamelTestSupport { + @Test + public void testSuspendResume() throws Exception { + final MockEndpoint mock = getMockEndpoint("mock:bar"); + mock.expectedMessageCount(1); + + template.sendBody("disruptor:foo", "A"); + + mock.assertIsSatisfied(); + + assertEquals("Started", context.getRouteStatus("foo").name()); + assertEquals("Started", context.getRouteStatus("bar").name()); + + // suspend bar consumer (not the route) + final DisruptorConsumer consumer = (DisruptorConsumer)context.getRoute("bar").getConsumer(); + + ServiceHelper.suspendService(consumer); + assertEquals("Suspended", consumer.getStatus().name()); + + // send a message to the route but the consumer is suspended + // so it should not route it + resetMocks(); + mock.expectedMessageCount(0); + + // wait a bit to ensure consumer is suspended, as it could be in a poll mode where + // it would poll and route (there is a little slack (up till 1 sec) before suspension is empowered) + Thread.sleep(2000); + + template.sendBody("disruptor:foo", "B"); + // wait 2 sec to ensure disruptor consumer thread would have tried to poll otherwise + mock.assertIsSatisfied(2000); + + // resume consumer + resetMocks(); + mock.expectedMessageCount(1); + + // resume bar consumer (not the route) + ServiceHelper.resumeService(consumer); + assertEquals("Started", consumer.getStatus().name()); + + // the message should be routed now + mock.assertIsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor:foo").routeId("foo").to("disruptor:bar"); + + from("disruptor:bar").routeId("bar").to("mock:bar"); + } + }; + } +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorFromRouteIdTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorFromRouteIdTest.java new file mode 100644 index 0000000000000..d3756902d0b30 --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorFromRouteIdTest.java @@ -0,0 +1,57 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +/** + * + */ +public class DisruptorFromRouteIdTest extends CamelTestSupport { + @Test + public void testDisruptorFromRouteId() throws Exception { + final MockEndpoint foo = getMockEndpoint("mock:foo"); + foo.expectedMessageCount(1); + + final MockEndpoint bar = getMockEndpoint("mock:bar"); + bar.expectedMessageCount(1); + + template.sendBody("disruptor:foo", "Hello World"); + + assertMockEndpointsSatisfied(); + + assertEquals("foo", foo.getReceivedExchanges().get(0).getFromRouteId()); + assertEquals("disruptor://foo", foo.getReceivedExchanges().get(0).getFromEndpoint().getEndpointUri()); + assertEquals("bar", bar.getReceivedExchanges().get(0).getFromRouteId()); + assertEquals("disruptor://bar", bar.getReceivedExchanges().get(0).getFromEndpoint().getEndpointUri()); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor:foo").routeId("foo").to("mock:foo").to("disruptor:bar"); + + from("disruptor:bar").routeId("bar").to("mock:bar"); + } + }; + } +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorInOnlyChainedTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorInOnlyChainedTest.java new file mode 100644 index 0000000000000..79a5e6135c1f0 --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorInOnlyChainedTest.java @@ -0,0 +1,51 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +/** + * @version + */ +public class DisruptorInOnlyChainedTest extends CamelTestSupport { + @Test + public void testInOnlyDisruptorChained() throws Exception { + getMockEndpoint("mock:a").expectedBodiesReceived("start"); + getMockEndpoint("mock:b").expectedBodiesReceived("start-a"); + getMockEndpoint("mock:c").expectedBodiesReceived("start-a-b"); + + template.sendBody("disruptor:a", "start"); + + assertMockEndpointsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor:a").to("mock:a").setBody(simple("${body}-a")).to("disruptor:b"); + + from("disruptor:b").to("mock:b").setBody(simple("${body}-b")).to("disruptor:c"); + + from("disruptor:c").to("mock:c").setBody(simple("${body}-c")); + } + }; + } +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorInOnlyTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorInOnlyTest.java new file mode 100644 index 0000000000000..3af72bf79be8d --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorInOnlyTest.java @@ -0,0 +1,47 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +/** + * @version + */ +public class DisruptorInOnlyTest extends CamelTestSupport { + @Test + public void testInOnly() throws Exception { + getMockEndpoint("mock:result").expectedBodiesReceived("Hello World"); + + template.sendBody("direct:start", "Hello World"); + + assertMockEndpointsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:start").to("disruptor:foo"); + + from("disruptor:foo").to("mock:result"); + } + }; + } +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorInOutBigChainedTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorInOutBigChainedTest.java new file mode 100644 index 0000000000000..3165a1945135b --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorInOutBigChainedTest.java @@ -0,0 +1,67 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +/** + * @version + */ +public class DisruptorInOutBigChainedTest extends CamelTestSupport { + @Test + public void testInOutBigDisruptorChained() throws Exception { + getMockEndpoint("mock:a").expectedBodiesReceived("start"); + getMockEndpoint("mock:b").expectedBodiesReceived("start-a"); + getMockEndpoint("mock:c").expectedBodiesReceived("start-a-b"); + getMockEndpoint("mock:d").expectedBodiesReceived("start-a-b-c"); + getMockEndpoint("mock:e").expectedBodiesReceived("start-a-b-c-d"); + getMockEndpoint("mock:f").expectedBodiesReceived("start-a-b-c-d-e"); + getMockEndpoint("mock:g").expectedBodiesReceived("start-a-b-c-d-e-f"); + getMockEndpoint("mock:h").expectedBodiesReceived("start-a-b-c-d-e-f-g"); + + final String reply = template.requestBody("disruptor:a", "start", String.class); + assertEquals("start-a-b-c-d-e-f-g-h", reply); + + assertMockEndpointsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor:a").to("mock:a").transform(simple("${body}-a")).to("disruptor:b"); + + from("disruptor:b").to("mock:b").transform(simple("${body}-b")).to("disruptor:c"); + + from("disruptor:c").to("mock:c").transform(simple("${body}-c")).to("disruptor:d"); + + from("disruptor:d").to("mock:d").transform(simple("${body}-d")).to("disruptor:e"); + + from("disruptor:e").to("mock:e").transform(simple("${body}-e")).to("disruptor:f"); + + from("disruptor:f").to("mock:f").transform(simple("${body}-f")).to("disruptor:g"); + + from("disruptor:g").to("mock:g").transform(simple("${body}-g")).to("disruptor:h"); + + from("disruptor:h").to("mock:h").transform(simple("${body}-h")); + } + }; + } +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorInOutChainedTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorInOutChainedTest.java new file mode 100644 index 0000000000000..26ea46473aaef --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorInOutChainedTest.java @@ -0,0 +1,52 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +/** + * @version + */ +public class DisruptorInOutChainedTest extends CamelTestSupport { + @Test + public void testInOutDisruptorChained() throws Exception { + getMockEndpoint("mock:a").expectedBodiesReceived("start"); + getMockEndpoint("mock:b").expectedBodiesReceived("start-a"); + getMockEndpoint("mock:c").expectedBodiesReceived("start-a-b"); + + final String reply = template.requestBody("disruptor:a", "start", String.class); + assertEquals("start-a-b-c", reply); + + assertMockEndpointsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor:a").to("mock:a").transform(simple("${body}-a")).to("disruptor:b"); + + from("disruptor:b").to("mock:b").transform(simple("${body}-b")).to("disruptor:c"); + + from("disruptor:c").to("mock:c").transform(simple("${body}-c")); + } + }; + } +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorInOutChainedTimeoutTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorInOutChainedTimeoutTest.java new file mode 100644 index 0000000000000..a9680af0069b5 --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorInOutChainedTimeoutTest.java @@ -0,0 +1,62 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import org.apache.camel.CamelExecutionException; +import org.apache.camel.ExchangeTimedOutException; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.apache.camel.util.StopWatch; +import org.junit.Test; + +/** + * @version + */ +public class DisruptorInOutChainedTimeoutTest extends CamelTestSupport { + @Test + public void testDisruptorInOutChainedTimeout() throws Exception { + // time timeout after 2 sec should trigger a immediately reply + final StopWatch watch = new StopWatch(); + try { + template.requestBody("disruptor:a?timeout=5000", "Hello World"); + fail("Should have thrown an exception"); + } catch (CamelExecutionException e) { + final ExchangeTimedOutException cause = assertIsInstanceOf(ExchangeTimedOutException.class, + e.getCause()); + assertEquals(2000, cause.getTimeout()); + } + final long delta = watch.stop(); + + assertTrue("Should be faster than 4000 millis, was: " + delta, delta < 4000); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + errorHandler(noErrorHandler()); + + from("disruptor:a").to("mock:a") + // this timeout will trigger an exception to occur + .to("disruptor:b?timeout=2000").to("mock:a2"); + + from("disruptor:b").to("mock:b").delay(3000).transform().constant("Bye World"); + } + }; + } +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorInOutChainedWithOnCompletionTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorInOutChainedWithOnCompletionTest.java new file mode 100644 index 0000000000000..f2d39083c1221 --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorInOutChainedWithOnCompletionTest.java @@ -0,0 +1,67 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import org.apache.camel.Exchange; +import org.apache.camel.Processor; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.support.SynchronizationAdapter; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +/** + * @version + */ +public class DisruptorInOutChainedWithOnCompletionTest extends CamelTestSupport { + @Test + public void testInOutDisruptorChainedWithCustomOnCompletion() throws Exception { + getMockEndpoint("mock:a").expectedBodiesReceived("start"); + getMockEndpoint("mock:b").expectedBodiesReceived("start-a"); + // the onCustomCompletion should be send very last (as it will be handed over) + getMockEndpoint("mock:c").expectedBodiesReceived("start-a-b", "onCustomCompletion"); + + final String reply = template.requestBody("disruptor:a", "start", String.class); + assertEquals("start-a-b-c", reply); + + assertMockEndpointsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor:a").process(new Processor() { + @Override + public void process(final Exchange exchange) throws Exception { + // should come in last + exchange.addOnCompletion(new SynchronizationAdapter() { + @Override + public void onDone(final Exchange exchange) { + template.sendBody("mock:c", "onCustomCompletion"); + } + }); + } + }).to("mock:a").transform(simple("${body}-a")).to("disruptor:b"); + + from("disruptor:b").to("mock:b").transform(simple("${body}-b")).to("disruptor:c"); + + from("disruptor:c").to("mock:c").transform(simple("${body}-c")); + } + }; + } +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorInOutTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorInOutTest.java new file mode 100644 index 0000000000000..080eef4942ecd --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorInOutTest.java @@ -0,0 +1,48 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +/** + * @version + */ +public class DisruptorInOutTest extends CamelTestSupport { + @Test + public void testInOut() throws Exception { + getMockEndpoint("mock:result").expectedBodiesReceived("Bye World"); + + final String out = template.requestBody("direct:start", "Hello World", String.class); + assertEquals("Bye World", out); + + assertMockEndpointsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:start").to("disruptor:foo"); + + from("disruptor:foo").transform(constant("Bye World")).to("mock:result"); + } + }; + } +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorInOutWithErrorDeadLetterChannelTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorInOutWithErrorDeadLetterChannelTest.java new file mode 100644 index 0000000000000..089726a30c83e --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorInOutWithErrorDeadLetterChannelTest.java @@ -0,0 +1,52 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +/** + * @version + */ +public class DisruptorInOutWithErrorDeadLetterChannelTest extends CamelTestSupport { + @Test + public void testInOutWithErrorUsingDLC() throws Exception { + getMockEndpoint("mock:result").expectedMessageCount(0); + getMockEndpoint("mock:dead").expectedMessageCount(1); + + template.requestBody("direct:start", "Hello World", String.class); + + assertMockEndpointsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + errorHandler(deadLetterChannel("mock:dead").maximumRedeliveries(2).redeliveryDelay(0)); + + from("direct:start").to("disruptor:foo"); + + from("disruptor:foo").transform(constant("Bye World")) + .throwException(new IllegalArgumentException("Damn I cannot do this")) + .to("mock:result"); + } + }; + } +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorInOutWithErrorTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorInOutWithErrorTest.java new file mode 100644 index 0000000000000..ecb72d50b3fec --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorInOutWithErrorTest.java @@ -0,0 +1,56 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import org.apache.camel.CamelExecutionException; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +/** + * @version + */ +public class DisruptorInOutWithErrorTest extends CamelTestSupport { + @Test + public void testInOutWithError() throws Exception { + getMockEndpoint("mock:result").expectedMessageCount(0); + + try { + template.requestBody("direct:start", "Hello World", String.class); + fail("Should have thrown an exception"); + } catch (CamelExecutionException e) { + assertIsInstanceOf(IllegalArgumentException.class, e.getCause()); + assertEquals("Damn I cannot do this", e.getCause().getMessage()); + } + + assertMockEndpointsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:start").to("disruptor:foo"); + + from("disruptor:foo").transform(constant("Bye World")) + .throwException(new IllegalArgumentException("Damn I cannot do this")) + .to("mock:result"); + } + }; + } +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorMultipleConsumersTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorMultipleConsumersTest.java new file mode 100644 index 0000000000000..cbf1bae431e01 --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorMultipleConsumersTest.java @@ -0,0 +1,95 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +/** + * @version + */ +public class DisruptorMultipleConsumersTest extends CamelTestSupport { + @Test + public void testDisruptorMultipleConsumers() throws Exception { + getMockEndpoint("mock:a").expectedBodiesReceivedInAnyOrder("Hello World", "Bye World"); + getMockEndpoint("mock:b").expectedBodiesReceivedInAnyOrder("Hello World", "Bye World"); + + template.sendBody("disruptor:foo", "Hello World"); + template.sendBody("disruptor:bar", "Bye World"); + + assertMockEndpointsSatisfied(); + } + + @Test + public void testDisruptorMultipleConsumersNewAdded() throws Exception { + getMockEndpoint("mock:a").expectedBodiesReceivedInAnyOrder("Hello World", "Bye World"); + getMockEndpoint("mock:b").expectedBodiesReceivedInAnyOrder("Hello World", "Bye World"); + getMockEndpoint("mock:c").expectedMessageCount(0); + + template.sendBody("disruptor:foo", "Hello World"); + template.sendBody("disruptor:bar", "Bye World"); + + assertMockEndpointsSatisfied(); + + context.addRoutes(new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor:foo?multipleConsumers=true").id("testRoute").to("mock:c"); + + } + + }); + resetMocks(); + + getMockEndpoint("mock:a").expectedMessageCount(20); + getMockEndpoint("mock:b").expectedMessageCount(20); + getMockEndpoint("mock:c").expectedMessageCount(20); + + for (int i = 0; i < 10; i++) { + template.sendBody("disruptor:foo", "Hello World"); + template.sendBody("disruptor:bar", "Bye World"); + } + assertMockEndpointsSatisfied(); + resetMocks(); + + context.suspendRoute("testRoute"); + getMockEndpoint("mock:a").expectedMessageCount(20); + getMockEndpoint("mock:b").expectedMessageCount(20); + getMockEndpoint("mock:c").expectedMessageCount(0); + + for (int i = 0; i < 10; i++) { + template.sendBody("disruptor:foo", "Hello World"); + template.sendBody("disruptor:bar", "Bye World"); + } + assertMockEndpointsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor:foo?multipleConsumers=true").to("mock:a"); + + from("disruptor:foo?multipleConsumers=true").to("mock:b"); + + from("disruptor:bar").to("disruptor:foo?multipleConsumers=true"); + } + }; + } +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorNoConsumerTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorNoConsumerTest.java new file mode 100644 index 0000000000000..66fa8ccff447f --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorNoConsumerTest.java @@ -0,0 +1,54 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import org.apache.camel.CamelExecutionException; +import org.apache.camel.ExchangeTimedOutException; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +/** + * @version + */ +public class DisruptorNoConsumerTest extends CamelTestSupport { + @Test + public void testInOnly() throws Exception { + // no problem for in only as we do not expect a reply + template.sendBody("direct:start", "Hello World"); + } + + @Test + public void testInOut() throws Exception { + try { + template.requestBody("direct:start", "Hello World"); + fail("Should throw an exception"); + } catch (CamelExecutionException e) { + assertIsInstanceOf(ExchangeTimedOutException.class, e.getCause()); + } + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:start").to("disruptor:foo?timeout=1000"); + } + }; + } +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorReconfigureWithBlockingProducer.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorReconfigureWithBlockingProducer.java new file mode 100644 index 0000000000000..7d25a4f1da172 --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorReconfigureWithBlockingProducer.java @@ -0,0 +1,113 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * @version + */ +public class DisruptorReconfigureWithBlockingProducer extends CamelTestSupport { + + @Test + public void testDisruptorReconfigureWithBlockingProducer() throws Exception { + getMockEndpoint("mock:a").expectedMessageCount(20); + getMockEndpoint("mock:b").expectedMinimumMessageCount(10); + + long beforeStart = System.currentTimeMillis(); + ProducerThread producerThread = new ProducerThread(); + producerThread.start(); + + //synchronize with the producer to the point that the buffer is full + assertTrue(producerThread.awaitFullBufferProduced()); + + context.addRoutes(new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor:foo?multipleConsumers=true&size=8").id("testRoute").to("mock:b"); + } + }); + + // adding the consumer may take place after the current buffer is flushed + // which will take approximately 8*200=1600 ms because of delay on route. + // If the reconfigure does not correctly hold back the producer thread on this request, + // it will take approximately 20*200=4000 ms. + // be on the safe side and check that it was at least faster than 2 seconds. + assertTrue("Reconfigure of Disruptor blocked", (System.currentTimeMillis() - beforeStart) < 2000); + + //Wait and check that the producer has produced all messages without throwing an exception + assertTrue(producerThread.checkResult()); + assertMockEndpointsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor:foo?multipleConsumers=true&size=8").delay(200).to("mock:a"); + } + }; + } + + private class ProducerThread extends Thread { + private final CountDownLatch startedLatch = new CountDownLatch(1); + private final CountDownLatch resultLatch = new CountDownLatch(1); + private Exception exception; + + @Override + public void run() { + for (int i = 0; i < 8; i++) { + template.sendBody("disruptor:foo", "Message"); + } + + startedLatch.countDown(); + + try { + for (int i = 0; i < 12; i++) { + template.sendBody("disruptor:foo", "Message"); + } + } catch (Exception e) { + exception = e; + } + + resultLatch.countDown(); + } + + public boolean awaitFullBufferProduced() throws InterruptedException { + return startedLatch.await(5, TimeUnit.SECONDS); + } + + public boolean checkResult() throws Exception { + if (exception != null) { + throw exception; + } + boolean result = resultLatch.await(5, TimeUnit.SECONDS); + if (exception != null) { + throw exception; + } + + return result; + } + } +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorRemoveRouteThenAddAgainTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorRemoveRouteThenAddAgainTest.java new file mode 100644 index 0000000000000..fc04262a7005c --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorRemoveRouteThenAddAgainTest.java @@ -0,0 +1,65 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +/** + * @version + */ +public class DisruptorRemoveRouteThenAddAgainTest extends CamelTestSupport { + @Test + public void testRemoveRouteAndThenAddAgain() throws Exception { + final MockEndpoint out = getMockEndpoint("mock:out"); + out.expectedMessageCount(1); + out.expectedBodiesReceived("before removing the route"); + + template.sendBody("disruptor:in", "before removing the route"); + + out.assertIsSatisfied(); + + out.reset(); + + // now stop & remove the route + context.stopRoute("disruptorToMock"); + context.removeRoute("disruptorToMock"); + + // and then add it back again + context.addRoutes(createRouteBuilder()); + + out.expectedMessageCount(1); + out.expectedBodiesReceived("after removing the route"); + + template.sendBody("disruptor:in", "after removing the route"); + + out.assertIsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor:in").routeId("disruptorToMock").to("mock:out"); + } + }; + } + +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorRingBufferTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorRingBufferTest.java new file mode 100644 index 0000000000000..83d333b7e5348 --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorRingBufferTest.java @@ -0,0 +1,52 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +/** + * @version + */ +public class DisruptorRingBufferTest extends CamelTestSupport { + @Test + public void testQueue() throws Exception { + final MockEndpoint mock = getMockEndpoint("mock:result"); + mock.expectedBodiesReceivedInAnyOrder("Hello World", "Bye World", "Goodday World", "Bar"); + + // Following 3 calls should all reference same Disruptor ring buffer. + template.sendBody("disruptor:foo", "Hello World"); + template.sendBody("disruptor:foo?size=1024", "Bye World"); + template.sendBody("disruptor:foo?concurrentConsumers=5", "Goodday World"); + + template.sendBody("disruptor:bar", "Bar"); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor:foo?concurrentConsumers=2").to("mock:result"); + + from("disruptor:bar").to("mock:result"); + } + }; + } +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorRouteTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorRouteTest.java new file mode 100644 index 0000000000000..cc558795e8e4a --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorRouteTest.java @@ -0,0 +1,105 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.apache.camel.*; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.impl.DefaultCamelContext; +import org.apache.camel.test.junit4.TestSupport; +import org.junit.Test; + +/** + * @version + */ +public class DisruptorRouteTest extends TestSupport { + @Test + public void testDisruptorQueue() throws Exception { + final CountDownLatch latch = new CountDownLatch(1); + + final CamelContext context = new DefaultCamelContext(); + + // lets add some routes + context.addRoutes(new RouteBuilder() { + @Override + public void configure() { + from("disruptor:test.a").to("disruptor:test.b"); + from("disruptor:test.b").process(new Processor() { + @Override + public void process(final Exchange e) { + log.debug("Received exchange: " + e.getIn()); + latch.countDown(); + } + }); + } + }); + + context.start(); + + // now lets fire in a message + final Endpoint endpoint = context.getEndpoint("disruptor:test.a"); + final Exchange exchange = endpoint.createExchange(); + exchange.getIn().setHeader("cheese", 123); + + final Producer producer = endpoint.createProducer(); + producer.process(exchange); + + // now lets sleep for a while + assertTrue(latch.await(5, TimeUnit.SECONDS)); + + context.stop(); + } + + @Test + public void testThatShowsEndpointResolutionIsNotConsistent() throws Exception { + final CountDownLatch latch = new CountDownLatch(1); + + final CamelContext context = new DefaultCamelContext(); + + // lets add some routes + context.addRoutes(new RouteBuilder() { + @Override + public void configure() { + from("disruptor:test.a").to("disruptor:test.b"); + from("disruptor:test.b").process(new Processor() { + @Override + public void process(final Exchange e) { + log.debug("Received exchange: " + e.getIn()); + latch.countDown(); + } + }); + } + }); + + context.start(); + + // now lets fire in a message + final Endpoint endpoint = context.getEndpoint("disruptor:test.a"); + final Exchange exchange = endpoint.createExchange(); + exchange.getIn().setHeader("cheese", 123); + + final Producer producer = endpoint.createProducer(); + producer.process(exchange); + + // now lets sleep for a while + assertTrue(latch.await(5, TimeUnit.SECONDS)); + + context.stop(); + } +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorShouldNotUseSameThreadTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorShouldNotUseSameThreadTest.java new file mode 100644 index 0000000000000..addcd6cbdd8f4 --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorShouldNotUseSameThreadTest.java @@ -0,0 +1,69 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import org.apache.camel.Exchange; +import org.apache.camel.Processor; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +/** + * Unit test to verify continuing using NOT same thread on the consumer side. + */ +public class DisruptorShouldNotUseSameThreadTest extends CamelTestSupport { + + private static long id; + + @Test + public void testNotUseSameThread() throws Exception { + final MockEndpoint mock = getMockEndpoint("mock:result"); + mock.expectedBodiesReceived("Hello World"); + + template.sendBody("direct:start", "Hello World"); + + assertMockEndpointsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + final ThreadLocal local = new ThreadLocal(); + + from("direct:start").process(new Processor() { + @Override + public void process(final Exchange exchange) throws Exception { + local.set("Hello"); + id = Thread.currentThread().getId(); + } + }).to("disruptor:foo"); + + from("disruptor:foo").process(new Processor() { + @Override + public void process(final Exchange exchange) throws Exception { + assertEquals(null, local.get()); + assertNotSame("Thread ids should not be same", id, Thread.currentThread().getId()); + } + }).to("mock:result"); + } + }; + } + +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorTimeoutDisabledTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorTimeoutDisabledTest.java new file mode 100644 index 0000000000000..6b588453d752e --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorTimeoutDisabledTest.java @@ -0,0 +1,48 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +/** + * @version + */ +public class DisruptorTimeoutDisabledTest extends CamelTestSupport { + @Test + public void testDisruptorNoTimeout() throws Exception { + final Future out = template + .asyncRequestBody("disruptor:foo?timeout=0", "World", String.class); + // use 5 sec failsafe in case something hangs + assertEquals("Bye World", out.get(5, TimeUnit.SECONDS)); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor:foo").to("mock:before").delay(500).transform(body().prepend("Bye ")) + .to("mock:result"); + } + }; + } +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorTimeoutTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorTimeoutTest.java new file mode 100644 index 0000000000000..93eeef68acc4c --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorTimeoutTest.java @@ -0,0 +1,86 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import org.apache.camel.CamelExecutionException; +import org.apache.camel.ExchangeTimedOutException; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +/** + * @version + */ +public class DisruptorTimeoutTest extends CamelTestSupport { + private int timeout = 100; + + @Test + public void testDisruptorNoTimeout() throws Exception { + final MockEndpoint result = getMockEndpoint("mock:result"); + result.setExpectedMessageCount(1); + final Future out = template.asyncRequestBody("disruptor:foo", "World", String.class); + assertEquals("Bye World", out.get()); + result.await(1, TimeUnit.SECONDS); + assertMockEndpointsSatisfied(); + } + + @Test + public void testDisruptorTimeout() throws Exception { + final MockEndpoint result = getMockEndpoint("mock:result"); + result.setExpectedMessageCount(0); + + final Future out = template + .asyncRequestBody("disruptor:foo?timeout=" + timeout, "World", String.class); + try { + out.get(); + fail("Should have thrown an exception"); + } catch (ExecutionException e) { + assertIsInstanceOf(CamelExecutionException.class, e.getCause()); + assertIsInstanceOf(ExchangeTimedOutException.class, e.getCause().getCause()); + + final DisruptorEndpoint de = (DisruptorEndpoint)context.getRoute("disruptor").getEndpoint(); + assertNotNull("Consumer endpoint cannot be null", de); + //we can't remove the exchange from a Disruptor once it is published, but it should never reach the + //mock:result endpoint because it should be filtered out by the DisruptorConsumer + result.await(1, TimeUnit.SECONDS); + assertMockEndpointsSatisfied(); + } + } + + @Test + public void testDisruptorTimeoutWithStoppedRoute() throws Exception { + context.stopRoute("disruptor"); + timeout = 500; + testDisruptorTimeout(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor:foo").routeId("disruptor").to("mock:before").delay(250) + .transform(body().prepend("Bye ")).to("mock:result"); + } + }; + } +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorUnitOfWorkTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorUnitOfWorkTest.java new file mode 100644 index 0000000000000..18113bf39c5d7 --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorUnitOfWorkTest.java @@ -0,0 +1,104 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import org.apache.camel.Exchange; +import org.apache.camel.Processor; +import org.apache.camel.builder.NotifyBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.spi.Synchronization; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +/** + * Unit test to verify unit of work with disruptor. That the UnitOfWork is able to route using disruptor but keeping the + * same UoW. + */ +public class DisruptorUnitOfWorkTest extends CamelTestSupport { + + private static volatile String sync; + + private static volatile String lastOne; + + @Test + public void testDisruptorUOW() throws Exception { + final NotifyBuilder notify = new NotifyBuilder(context).whenDone(2).create(); + + final MockEndpoint mock = getMockEndpoint("mock:result"); + mock.expectedMessageCount(1); + + template.sendBody("direct:start", "Hello World"); + + assertMockEndpointsSatisfied(); + notify.matchesMockWaitTime(); + + assertEquals("onCompleteA", sync); + assertEquals("onCompleteA", lastOne); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + context.setTracing(true); + + from("direct:start").process(new MyUOWProcessor("A")).to("disruptor:foo"); + + from("disruptor:foo").process(new Processor() { + @Override + public void process(final Exchange exchange) throws Exception { + assertEquals(null, sync); + } + }).process(new Processor() { + @Override + public void process(final Exchange exchange) throws Exception { + lastOne = "processor"; + } + }).to("mock:result"); + } + }; + } + + private static final class MyUOWProcessor implements Processor { + + private final String id; + + private MyUOWProcessor(final String id) { + this.id = id; + } + + @Override + public void process(final Exchange exchange) throws Exception { + exchange.getUnitOfWork().addSynchronization(new Synchronization() { + @Override + public void onComplete(final Exchange exchange) { + sync = "onComplete" + id; + lastOne = sync; + } + + @Override + public void onFailure(final Exchange exchange) { + sync = "onFailure" + id; + lastOne = sync; + } + }); + } + } + +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorWaitClaimStrategyComponentTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorWaitClaimStrategyComponentTest.java new file mode 100644 index 0000000000000..0a995eb7839e7 --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorWaitClaimStrategyComponentTest.java @@ -0,0 +1,98 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import org.apache.camel.EndpointInject; +import org.apache.camel.Produce; +import org.apache.camel.ProducerTemplate; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static org.junit.runners.Parameterized.Parameters; + +/** + * Tests the WaitStrategy and ClaimStrategy configuration of the disruptor component + */ +@RunWith(value = Parameterized.class) +public class DisruptorWaitClaimStrategyComponentTest extends CamelTestSupport { + @EndpointInject(uri = "mock:result") + protected MockEndpoint resultEndpoint; + + @Produce + protected ProducerTemplate template; + + private static final Integer VALUE = Integer.valueOf(42); + + private final String producerType; + private final String waitStrategy; + private String disruptorUri; + + public DisruptorWaitClaimStrategyComponentTest(final String waitStrategy, final String producerType) { + + this.waitStrategy = waitStrategy; + this.producerType = producerType; + } + + @Parameters + public static Collection strategies() { + final List strategies = new ArrayList(); + + for (final DisruptorWaitStrategy waitStrategy : DisruptorWaitStrategy.values()) { + for (final DisruptorProducerType producerType : DisruptorProducerType.values()) { + strategies.add(new String[] {waitStrategy.name(), producerType.name()}); + } + } + + return strategies; + } + + + @Test + public void testProduce() throws InterruptedException { + resultEndpoint.expectedBodiesReceived(VALUE); + resultEndpoint.setExpectedMessageCount(1); + + template.asyncSendBody(disruptorUri, VALUE); + + resultEndpoint.await(5, TimeUnit.SECONDS); + resultEndpoint.assertIsSatisfied(); + } + + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + + disruptorUri = "disruptor:test?waitStrategy=" + waitStrategy + "&producerType=" + producerType; + + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from(disruptorUri).to("mock:result"); + } + }; + } +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorWaitForTaskAsPropertyTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorWaitForTaskAsPropertyTest.java new file mode 100644 index 0000000000000..b8fae6bddb55c --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorWaitForTaskAsPropertyTest.java @@ -0,0 +1,78 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import org.apache.camel.Exchange; +import org.apache.camel.ExchangePattern; +import org.apache.camel.Processor; +import org.apache.camel.WaitForTaskToComplete; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +/** + * @version + */ +public class DisruptorWaitForTaskAsPropertyTest extends CamelTestSupport { + @Test + public void testInOut() throws Exception { + getMockEndpoint("mock:result").expectedBodiesReceived("Bye World"); + + final Exchange out = template.send("direct:start", new Processor() { + @Override + public void process(final Exchange exchange) throws Exception { + exchange.getIn().setBody("Hello World"); + exchange.setPattern(ExchangePattern.InOut); + exchange.setProperty(Exchange.ASYNC_WAIT, WaitForTaskToComplete.IfReplyExpected); + } + }); + assertEquals("Bye World", out.getOut().getBody()); + + assertMockEndpointsSatisfied(); + } + + @Test + public void testInOnly() throws Exception { + getMockEndpoint("mock:result").expectedBodiesReceived("Bye World"); + + final Exchange out = template.send("direct:start", new Processor() { + @Override + public void process(final Exchange exchange) throws Exception { + exchange.getIn().setBody("Hello World"); + exchange.setPattern(ExchangePattern.InOnly); + exchange.setProperty(Exchange.ASYNC_WAIT, WaitForTaskToComplete.IfReplyExpected); + } + }); + // we do not expecy a reply and thus do no wait so we just get our own input back + assertEquals("Hello World", out.getIn().getBody()); + assertEquals(null, out.getOut().getBody()); + + assertMockEndpointsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:start").to("disruptor:foo"); + + from("disruptor:foo").transform(constant("Bye World")).to("mock:result"); + } + }; + } +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorWaitForTaskCompleteOnCompletionTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorWaitForTaskCompleteOnCompletionTest.java new file mode 100644 index 0000000000000..80199de518c64 --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorWaitForTaskCompleteOnCompletionTest.java @@ -0,0 +1,83 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import org.apache.camel.CamelExecutionException; +import org.apache.camel.Exchange; +import org.apache.camel.Processor; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.support.SynchronizationAdapter; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +public class DisruptorWaitForTaskCompleteOnCompletionTest extends CamelTestSupport { + + private static String done = ""; + + @Test + public void testAlways() throws Exception { + getMockEndpoint("mock:result").expectedMessageCount(0); + + try { + template.sendBody("direct:start", "Hello World"); + fail("Should have thrown an exception"); + } catch (CamelExecutionException e) { + assertIsInstanceOf(IllegalArgumentException.class, e.getCause()); + assertEquals("Forced", e.getCause().getMessage()); + } + + assertMockEndpointsSatisfied(); + + // 3 + 1 C and A should be last + assertEquals("CCCCA", done); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + errorHandler(defaultErrorHandler().maximumRedeliveries(3).redeliveryDelay(0)); + + from("direct:start").process(new Processor() { + @Override + public void process(final Exchange exchange) throws Exception { + exchange.addOnCompletion(new SynchronizationAdapter() { + @Override + public void onDone(final Exchange exchange) { + done += "A"; + } + }); + } + }).to("disruptor:foo?waitForTaskToComplete=Always").process(new Processor() { + @Override + public void process(final Exchange exchange) throws Exception { + done += "B"; + } + }).to("mock:result"); + + from("disruptor:foo").errorHandler(noErrorHandler()).process(new Processor() { + @Override + public void process(final Exchange exchange) throws Exception { + done = done + "C"; + } + }).throwException(new IllegalArgumentException("Forced")); + } + }; + } +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorWaitForTaskCompleteTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorWaitForTaskCompleteTest.java new file mode 100644 index 0000000000000..83481ea65988c --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorWaitForTaskCompleteTest.java @@ -0,0 +1,70 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import org.apache.camel.Exchange; +import org.apache.camel.ExchangePattern; +import org.apache.camel.Processor; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +/** + * @version + */ +public class DisruptorWaitForTaskCompleteTest extends CamelTestSupport { + @Test + public void testInOut() throws Exception { + getMockEndpoint("mock:result").expectedBodiesReceived("Bye World"); + + final String out = template.requestBody("direct:start", "Hello World", String.class); + assertEquals("Bye World", out); + + assertMockEndpointsSatisfied(); + } + + @Test + public void testInOnly() throws Exception { + getMockEndpoint("mock:result").expectedBodiesReceived("Bye World"); + + // we send an in only but we use Always to wait for it to complete + // and since the route changes the payload we can get the response anyway + final Exchange out = template.send("direct:start", new Processor() { + @Override + public void process(final Exchange exchange) throws Exception { + exchange.getIn().setBody("Hello World"); + exchange.setPattern(ExchangePattern.InOnly); + } + }); + assertEquals("Bye World", out.getIn().getBody()); + + assertMockEndpointsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:start").to("disruptor:foo?waitForTaskToComplete=Always"); + + from("disruptor:foo?waitForTaskToComplete=Always").transform(constant("Bye World")) + .to("mock:result"); + } + }; + } +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorWaitForTaskIfReplyExpectedTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorWaitForTaskIfReplyExpectedTest.java new file mode 100644 index 0000000000000..a66de105ff6ef --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorWaitForTaskIfReplyExpectedTest.java @@ -0,0 +1,71 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import org.apache.camel.Exchange; +import org.apache.camel.ExchangePattern; +import org.apache.camel.Processor; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +/** + * @version + */ + +public class DisruptorWaitForTaskIfReplyExpectedTest extends CamelTestSupport { + + @Test + public void testInOut() throws Exception { + getMockEndpoint("mock:result").expectedBodiesReceived("Bye World"); + final String out = template.requestBody("direct:start", "Hello World", String.class); + assertEquals("Bye World", out); + assertMockEndpointsSatisfied(); + + } + + @Test + public void testInOnly() throws Exception { + getMockEndpoint("mock:result").expectedBodiesReceived("Bye World"); + final Exchange out = template.send("direct:start", new Processor() { + @Override + public void process(final Exchange exchange) throws Exception { + exchange.getIn().setBody("Hello World"); + exchange.setPattern(ExchangePattern.InOnly); + } + }); + + // we do not expecy a reply and thus do no wait so we just get our own + // input back + assertEquals("Hello World", out.getIn().getBody()); + assertEquals(null, out.getOut().getBody()); + assertMockEndpointsSatisfied(); + + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:start").to("disruptor:foo?waitForTaskToComplete=IfReplyExpected"); + from("disruptor:foo").transform(constant("Bye World")).to("mock:result"); + } + }; + } +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorWaitForTaskNeverOnCompletionTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorWaitForTaskNeverOnCompletionTest.java new file mode 100644 index 0000000000000..78fe8c16a277d --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorWaitForTaskNeverOnCompletionTest.java @@ -0,0 +1,86 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.apache.camel.Exchange; +import org.apache.camel.Processor; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.support.SynchronizationAdapter; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +/** + * @version + */ +public class DisruptorWaitForTaskNeverOnCompletionTest extends CamelTestSupport { + + private static String done = ""; + + private final CountDownLatch latch = new CountDownLatch(1); + + @Test + public void testNever() throws Exception { + getMockEndpoint("mock:dead").expectedMessageCount(0); + getMockEndpoint("mock:result").expectedMessageCount(1); + + template.sendBody("direct:start", "Hello World"); + + assertMockEndpointsSatisfied(); + + assertTrue(latch.await(5, TimeUnit.SECONDS)); + // B should be first because we do not wait + assertEquals("BCA", done); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + errorHandler(deadLetterChannel("mock:dead").maximumRedeliveries(3).redeliveryDelay(0)); + + from("direct:start").process(new Processor() { + @Override + public void process(final Exchange exchange) throws Exception { + exchange.addOnCompletion(new SynchronizationAdapter() { + @Override + public void onDone(final Exchange exchange) { + done = done + "A"; + latch.countDown(); + } + }); + } + }).to("disruptor:foo?waitForTaskToComplete=Never").process(new Processor() { + @Override + public void process(final Exchange exchange) throws Exception { + done = done + "B"; + } + }).to("mock:result"); + + from("disruptor:foo").errorHandler(noErrorHandler()).delay(1000).process(new Processor() { + @Override + public void process(final Exchange exchange) throws Exception { + done = done + "C"; + } + }).throwException(new IllegalArgumentException("Forced")); + } + }; + } +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorWaitForTaskNeverTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorWaitForTaskNeverTest.java new file mode 100644 index 0000000000000..4019066b60e47 --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorWaitForTaskNeverTest.java @@ -0,0 +1,71 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import org.apache.camel.Exchange; +import org.apache.camel.ExchangePattern; +import org.apache.camel.Processor; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +/** + * @version + */ +public class DisruptorWaitForTaskNeverTest extends CamelTestSupport { + @Test + public void testInOut() throws Exception { + getMockEndpoint("mock:result").expectedBodiesReceived("Bye World"); + + final String out = template.requestBody("direct:start", "Hello World", String.class); + // we do not wait for the response so we just get our own input back + assertEquals("Hello World", out); + + assertMockEndpointsSatisfied(); + } + + @Test + public void testInOnly() throws Exception { + getMockEndpoint("mock:result").expectedBodiesReceived("Bye World"); + + final Exchange out = template.send("direct:start", new Processor() { + @Override + public void process(final Exchange exchange) throws Exception { + exchange.getIn().setBody("Hello World"); + exchange.setPattern(ExchangePattern.InOnly); + } + }); + // we do not wait for the response so we just get our own input back + assertEquals("Hello World", out.getIn().getBody()); + assertEquals(null, out.getOut().getBody()); + + assertMockEndpointsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:start").to("disruptor:foo?waitForTaskToComplete=Never"); + + from("disruptor:foo?waitForTaskToComplete=Never").transform(constant("Bye World")) + .to("mock:result"); + } + }; + } +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorWaitStrategyCreationTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorWaitStrategyCreationTest.java new file mode 100644 index 0000000000000..7f29dae034baf --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/DisruptorWaitStrategyCreationTest.java @@ -0,0 +1,40 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import com.lmax.disruptor.WaitStrategy; + +import org.junit.Test; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * Tests correct creation of all supposedly possible wait strategies. + */ +public class DisruptorWaitStrategyCreationTest { + @Test + public void testCreateWaitStrategyInstance() throws Exception { + for (final DisruptorWaitStrategy strategy : DisruptorWaitStrategy.values()) { + final WaitStrategy waitStrategyInstance = strategy.createWaitStrategyInstance(); + + assertNotNull(waitStrategyInstance); + assertTrue(waitStrategyInstance instanceof WaitStrategy); + } + } +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/FileDisruptorShutdownCompleteAllTasksTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/FileDisruptorShutdownCompleteAllTasksTest.java new file mode 100644 index 0000000000000..7cbf088d5bb34 --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/FileDisruptorShutdownCompleteAllTasksTest.java @@ -0,0 +1,78 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import org.apache.camel.Exchange; +import org.apache.camel.ShutdownRunningTask; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +/** + * @version + */ +public class FileDisruptorShutdownCompleteAllTasksTest extends CamelTestSupport { + + @Override + public void setUp() throws Exception { + deleteDirectory("target/disruptor"); + super.setUp(); + } + + @Test + public void testShutdownCompleteAllTasks() throws Exception { + final String url = "file:target/disruptor"; + template.sendBodyAndHeader(url, "A", Exchange.FILE_NAME, "a.txt"); + template.sendBodyAndHeader(url, "B", Exchange.FILE_NAME, "b.txt"); + template.sendBodyAndHeader(url, "C", Exchange.FILE_NAME, "c.txt"); + template.sendBodyAndHeader(url, "D", Exchange.FILE_NAME, "d.txt"); + template.sendBodyAndHeader(url, "E", Exchange.FILE_NAME, "e.txt"); + + // give it 20 seconds to shutdown + context.getShutdownStrategy().setTimeout(20 * 100000); + + context.addRoutes(new RouteBuilder() { + @Override + public void configure() throws Exception { + from(url).routeId("route1") + // let it complete all tasks during shutdown + .shutdownRunningTask(ShutdownRunningTask.CompleteAllTasks).to("log:delay").delay(1000) + .to("disruptor:foo?size=8"); + + from("disruptor:foo?size=8").routeId("route2").to("log:bar").to("mock:bar"); + } + }); + context.start(); + + final MockEndpoint bar = getMockEndpoint("mock:bar"); + bar.expectedMinimumMessageCount(1); + + assertMockEndpointsSatisfied(); + + // shutdown during processing + context.stop(); + + // should route all 5 + assertEquals("Should complete all messages", 5, bar.getReceivedCounter()); + } + + @Override + public boolean isUseRouteBuilder() { + return false; + } +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/MulticastDisruptorComponentTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/MulticastDisruptorComponentTest.java new file mode 100644 index 0000000000000..330dd3e353d2f --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/MulticastDisruptorComponentTest.java @@ -0,0 +1,149 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import java.util.concurrent.TimeUnit; + +import org.apache.camel.EndpointInject; +import org.apache.camel.Produce; +import org.apache.camel.ProducerTemplate; +import org.apache.camel.ShutdownRoute; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +/** + * Tests that multicast functionality works correctly + */ +public class MulticastDisruptorComponentTest extends CamelTestSupport { + private static final String MULTIPLE_CONSUMERS_ENDPOINT_URI = "disruptor:test?multipleConsumers=true"; + + @EndpointInject(uri = "mock:result1") + protected MockEndpoint resultEndpoint1; + + @EndpointInject(uri = "mock:result2") + protected MockEndpoint resultEndpoint2; + + @Produce(uri = "disruptor:test") + protected ProducerTemplate template; + + private static final Integer VALUE = Integer.valueOf(42); + +// private ThreadCounter threadCounter = new ThreadCounter(); + + @Test + public void testMulticastProduce() throws InterruptedException { + resultEndpoint1.expectedBodiesReceived(VALUE); + resultEndpoint1.setExpectedMessageCount(1); + + resultEndpoint2.expectedBodiesReceived(VALUE); + resultEndpoint2.setExpectedMessageCount(1); + + template.asyncSendBody(MULTIPLE_CONSUMERS_ENDPOINT_URI, VALUE); + + resultEndpoint1.await(5, TimeUnit.SECONDS); + resultEndpoint1.assertIsSatisfied(1); + resultEndpoint2.await(5, TimeUnit.SECONDS); + resultEndpoint2.assertIsSatisfied(1); + } + +// +// @Test +// public void testAsynchronous() throws InterruptedException { +// threadCounter.reset(); +// +// int messagesSent = 1000; +// +// resultEndpoint.setExpectedMessageCount(messagesSent); +// +// long currentThreadId = Thread.currentThread().getId(); +// +// for (int i = 0; i < messagesSent; ++i) { +// template.asyncSendBody("disruptor:testAsynchronous", VALUE); +// } +// +// resultEndpoint.await(20, TimeUnit.SECONDS); +// resultEndpoint.assertIsSatisfied(); +// +// Assert.assertTrue(threadCounter.getThreadIdCount() > 0); +// Assert.assertFalse(threadCounter.getThreadIds().contains(currentThreadId)); +// } +// +// @Test +// public void testMultipleConsumers() throws InterruptedException { +// threadCounter.reset(); +// +// int messagesSent = 1000; +// +// resultEndpoint.setExpectedMessageCount(messagesSent); +// +// for (int i = 0; i < messagesSent; ++i) { +// template.asyncSendBody("disruptor:testMultipleConsumers?concurrentConsumers=4", VALUE); +// } +// +// resultEndpoint.await(20, TimeUnit.SECONDS); +// +// //sleep for another second to check for duplicate messages in transit +// Thread.sleep(1000); +// +// System.out.println("count = " + resultEndpoint.getReceivedCounter()); +// resultEndpoint.assertIsSatisfied(); +// +// Assert.assertEquals(4, threadCounter.getThreadIdCount()); +// } +// + + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor:test?multipleConsumers=true").to("mock:result1") + .setShutdownRoute(ShutdownRoute.Defer); + from("disruptor:test?multipleConsumers=true").to("mock:result2") + .setShutdownRoute(ShutdownRoute.Defer); +// from("disruptor:testAsynchronous").process(threadCounter).to("mock:result"); +// from("disruptor:testMultipleConsumers?concurrentConsumers=4").process(threadCounter).to("mock:result"); + } + }; + } + +// private static final class ThreadCounter implements Processor { +// +// private Set threadIds = new HashSet(); +// +// public void reset() { +// threadIds.clear(); +// } +// +// @Override +// public void process(Exchange exchange) throws Exception { +// threadIds.add(Thread.currentThread().getId()); +// } +// +// public Set getThreadIds() { +// return Collections.unmodifiableSet(threadIds); +// } +// +// public int getThreadIdCount() { +// return threadIds.size(); +// } +// } +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/Readme.txt b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/Readme.txt new file mode 100644 index 0000000000000..4baac3d4a0dae --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/Readme.txt @@ -0,0 +1,50 @@ +Seda and Disruptor unit tests comparaison +----------------------------------------- + +The Seda Camel component has a lot of similarities with the Disruptor and it +just seems fair to copy the unit tests from Seda and apply them to the Disruptor. +Here is a list of all the unit tests found in Camel 2.10.1 and their correspondence: + +Original Camel Test class Status Status extra information +------------------------- ------ ------------------------ +CollectionProducerTest.java *NOT COPIED* Did not really apply to disruptor +DirectRequestReplyAndSedaInOnlyTest.java *COPIED* DirectRequestReplyAndDisruptorInOnlyTest.java +FileSedaShutdownCompleteAllTasksTest.java *COPIED* FileDisruptorShutdownCompleteAllTasksTest.java +SedaAsyncProducerTest.java *NOT COPIED* Test case doesn't have any relationhsip with disruptor, only applies to old seda implementation +SedaAsyncRouteTest.java *COPIED* DisruptorAsyncRouteTest.java +SedaBlockWhenFullTest.java *COPIED* DisruptorBlockWhenFullTest.java +SedaComplexInOutTest.java *COPIED* DisruptorComplexInOutTest.java +SedaComponentReferenceEndpointTest.java *COPIED* DisruptorComponentReferenceEndpointTest.java +SedaConcurrentConsumersNPEIssueTest.java *COPIED* DisruptorConcurrentConsumersNPEIssueTest.java +SedaConcurrentConsumersTest.java *COPIED* DisruptorConcurrentConsumersTest.java +SedaConcurrentTest.java *COPIED* DisruptorConcurrentTest.java +SedaConfigureTest.java *COPIED* DisruptorConfigureTest.java +SedaConsumerSuspendResumeTest.java *COPIED* DisruptorConsumerSuspendResumeTest.java +SedaDefaultUnboundedQueueSizeTest.java *NOT COPIED* Disruptor doesn't support an upper limit and waits by default when ring buffer is full +SedaEndpointTest.java *NOT COPIED* Test case did not seem fit for disruptor. Could be revisited if needed +SedaFromRouteIdTest.java *COPIED* DisruptorFromRouteIdTest.java +SedaInOnlyChainedTest.java *COPIED* DisruptorInOnlyChainedTest.java +SedaInOnlyTest.java *COPIED* DisruptorInOnlyTest.java +SedaInOutBigChainedTest.java *COPIED* DisruptorInOutBigChainedTest.java +SedaInOutChainedTest.java *COPIED* DisruptorInOutChainedTest.java +SedaInOutChainedTimeoutTest.java *COPIED* DisruptorInOutChainedTimeoutTest.java +SedaInOutChainedWithOnCompletionTest.java *COPIED* DisruptorInOutChainedWithOnCompletionTest.java +SedaInOutTest.java *COPIED* DisruptorInOutTest.java +SedaInOutWithErrorDeadLetterChannelTest.java *COPIED* DisruptorInOutWithErrorDeadLetterChannelTest.java +SedaInOutWithErrorTest.java *COPIED* DisruptorInOutWithErrorTest.java +SedaMultipleConsumersTest.java *COPIED* DisruptorMultipleConsumersTest.java +SedaNoConsumerTest.java *COPIED* DisruptorNoConsumerTest.java +SedaQueueTest.java *COPIED* DisruptorRingBufferTest.java +SedaRemoveRouteThenAddAgainTest.java *COPIED* DisruptorRemoveRouteThenAddAgainTest.java +SedaRouteTest.java *COPIED* DisruptorRouteTest.java +SedaShouldNotUseSameThreadTest.java *COPIED* DisruptorShouldNotUseSameThreadTest.java +SedaTimeoutDisabledTest.java *COPIED* DisruptorTimeoutDisabledTest.java +SedaTimeoutTest.java *COPIED* DisruptorTimeoutTest.java +SedaUnitOfWorkTest.java *COPIED* DisruptorUnitOfWorkTest.java +SedaWaitForTaskAsPropertyTest.java *COPIED* DisruptorWaitForTaskAsPropertyTest.java +SedaWaitForTaskCompleteOnCompletionTest.java *COPIED* DisruptorWaitForTaskCompleteOnCompletionTest.java +SedaWaitForTaskCompleteTest.java *COPIED* DisruptorWaitForTaskCompleteTest.java +SedaWaitForTaskIfReplyExpectedTest.java *COPIED* DisruptorWaitForTaskIfReplyExpectedTest.java +SedaWaitForTaskNewerOnCompletionTest.java *COPIED* DisruptorWaitForTaskNeverOnCompletionTest.java +SedaWaitForTaskNewerTest.java *COPIED* DisruptorWaitForTaskNeverTest.java +TracingWithDelayTest.java *NOT COPIED* No direct association with disruptor diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/SedaDisruptorCompareTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/SedaDisruptorCompareTest.java new file mode 100644 index 0000000000000..7354a63c75481 --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/SedaDisruptorCompareTest.java @@ -0,0 +1,428 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor; + +import com.lmax.disruptor.collections.Histogram; + +import org.apache.camel.*; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.seda.SedaEndpoint; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.*; + +/** + * This class does not perform any functional test, but instead makes a comparison between the performance of the + * Disruptor and SEDA component in several use cases. + *

+ * As memory management may have great impact on the results, it is adviced to run this test with a large, fixed heap (e.g. run with -Xmx1024m -Xms1024m JVM parameters) + */ +@Ignore +@RunWith(value = Parameterized.class) +public class SedaDisruptorCompareTest extends CamelTestSupport { + + @Produce + protected ProducerTemplate producerTemplate; + + // Use '0' for default value, '1'+ for specific value to be used by both SEDA and DISRUPTOR. + private static final int SIZE_PARAMETER_VALUE = 1024; + private static final int SPEED_TEST_EXCHANGE_COUNT = 80000; + private static final long[] LATENCY_HISTOGRAM_BOUNDS = new long[] {1, 2, 5, 10, 20, 50, 100, 200, 500, + 1000, 2000, 5000}; + private static final long[] DISRUPTOR_SIZE_HISTOGRAM_BOUNDS = generateLinearHistogramBounds( + SIZE_PARAMETER_VALUE == 0 ? 1024 : SIZE_PARAMETER_VALUE, 8); + private static final long[] SEDA_SIZE_HISTOGRAM_BOUNDS = generateLinearHistogramBounds( + SIZE_PARAMETER_VALUE == 0 ? SPEED_TEST_EXCHANGE_COUNT : SIZE_PARAMETER_VALUE, 10); + + private final ExchangeAwaiter[] exchangeAwaiters; + private final String componentName; + private final String endpointUri; + private final int amountProducers; + private final long[] sizeHistogramBounds; + + private final Queue endpointSizeQueue = new ConcurrentLinkedQueue(); + + @BeforeClass + public static void legend() { + System.out.println("-----------------------"); + System.out.println("- Tests output legend -"); + System.out.println("-----------------------"); + System.out.println( + "P: Number of concurrent Producer(s) sharing the load for publishing exchanges to the disruptor."); + System.out.println( + "C: Number of Consumer(s) receiving a copy of each exchange from the disruptor (pub/sub)."); + System.out.println( + "CCT: Number of ConcurrentConsumerThreads sharing the load for consuming exchanges from the disruptor."); + System.out.println( + "SIZE: Maximum number of elements a SEDA or disruptor endpoint can have in memory before blocking the Producer thread(s)."); + System.out.println(" 0 means default value, so unbounded for SEDA and 1024 for disruptor."); + System.out.println("Each test is creating " + SPEED_TEST_EXCHANGE_COUNT + " exchanges."); + System.out.println(); + } + + private static long[] generateLinearHistogramBounds(final int maxValue, final int nbSlots) { + final long slotSize = maxValue / nbSlots; + final long[] bounds = new long[nbSlots]; + for (int i = 0; i < nbSlots; i++) { + bounds[i] = slotSize * (i + 1); + } + return bounds; + } + + public SedaDisruptorCompareTest(final String componentName, final String endpointUri, + final int amountProducers, final int amountConsumers, + final int concurrentConsumerThreads, final long[] sizeHistogramBounds) { + this.componentName = componentName; + this.endpointUri = endpointUri; + this.amountProducers = amountProducers; + this.sizeHistogramBounds = sizeHistogramBounds; + exchangeAwaiters = new ExchangeAwaiter[amountConsumers]; + for (int i = 0; i < amountConsumers; ++i) { + exchangeAwaiters[i] = new ExchangeAwaiter(SPEED_TEST_EXCHANGE_COUNT); + } + } + + private static int singleProducer() { + return 1; + } + + private static int multipleProducers() { + return 4; + } + + private static int singleConsumer() { + return 1; + } + + private static int multipleConsumers() { + return 4; + } + + private static int singleConcurrentConsumerThread() { + return 1; + } + + private static int multipleConcurrentConsumerThreads() { + return 2; + } + + @Parameterized.Parameters(name = "{index}: {0}") + public static Collection parameters() { + final List parameters = new ArrayList(); + + // This parameter set can be compared to the next and shows the impact of a 'long' endpoint name + // It defines all parameters to the same values as the default, so the result should be the same as + // 'seda:speedtest'. This shows that disruptor has a slight disadvantage as its name is longer than 'seda' :) + // The reason why this test takes so long is because Camel has a SLF4J call in ProducerCache: + // LOG.debug(">>>> {} {}", endpoint, exchange); + // and the DefaultEndpoint.toString() method will use a Matcher to sanitize the URI. There should be a guard + // before the debug() call to only evaluate the args when required: if(LOG.isDebugEnabled())... + if (SIZE_PARAMETER_VALUE == 0) { + parameters.add(new Object[] {"SEDA LONG {P=1, C=1, CCT=1, SIZE=0}", + "seda:speedtest?concurrentConsumers=1&waitForTaskToComplete=IfReplyExpected&timeout=30000&multipleConsumers=false&limitConcurrentConsumers=true&blockWhenFull=false", + singleProducer(), singleConsumer(), + singleConcurrentConsumerThread(), SEDA_SIZE_HISTOGRAM_BOUNDS}); + } else { + parameters.add(new Object[] {"SEDA LONG {P=1, C=1, CCT=1, SIZE=" + SIZE_PARAMETER_VALUE + "}", + "seda:speedtest?concurrentConsumers=1&waitForTaskToComplete=IfReplyExpected&timeout=30000&multipleConsumers=false&limitConcurrentConsumers=true&blockWhenFull=true&size=" + + SIZE_PARAMETER_VALUE, singleProducer(), singleConsumer(), + singleConcurrentConsumerThread(), + SEDA_SIZE_HISTOGRAM_BOUNDS}); + } + addParameterPair(parameters, singleProducer(), singleConsumer(), singleConcurrentConsumerThread()); + addParameterPair(parameters, singleProducer(), singleConsumer(), multipleConcurrentConsumerThreads()); + addParameterPair(parameters, singleProducer(), multipleConsumers(), singleConcurrentConsumerThread()); + addParameterPair(parameters, singleProducer(), multipleConsumers(), + multipleConcurrentConsumerThreads()); + addParameterPair(parameters, multipleProducers(), singleConsumer(), singleConcurrentConsumerThread()); + addParameterPair(parameters, multipleProducers(), singleConsumer(), + multipleConcurrentConsumerThreads()); + addParameterPair(parameters, multipleProducers(), multipleConsumers(), + singleConcurrentConsumerThread()); + addParameterPair(parameters, multipleProducers(), multipleConsumers(), + multipleConcurrentConsumerThreads()); + + return parameters; + } + + private static void addParameterPair(final List parameters, final int producers, + final int consumers, final int parallelConsumerThreads) { + final String multipleConsumerOption = (consumers > 1 ? "multipleConsumers=true" : ""); + final String concurrentConsumerOptions = (parallelConsumerThreads > 1 ? "concurrentConsumers=" + + parallelConsumerThreads : ""); + final String sizeOption = SIZE_PARAMETER_VALUE > 0 ? "size=" + SIZE_PARAMETER_VALUE : ""; + final String sizeOptionSeda = SIZE_PARAMETER_VALUE > 0 ? "&blockWhenFull=true" : ""; + + String options = ""; + if (!multipleConsumerOption.isEmpty()) { + if (!options.isEmpty()) { + options += "&"; + } + options += multipleConsumerOption; + } + if (!concurrentConsumerOptions.isEmpty()) { + if (!options.isEmpty()) { + options += "&"; + } + options += concurrentConsumerOptions; + } + if (!sizeOption.isEmpty()) { + if (!options.isEmpty()) { + options += "&"; + } + options += sizeOption; + } + + if (!options.isEmpty()) { + options = "?" + options; + } + + final String sedaOptions = sizeOptionSeda.isEmpty() ? options : options + sizeOptionSeda; + // Using { ... } because there is a bug in JUnit 4.11 and Eclipse: https://bugs.eclipse.org/bugs/show_bug.cgi?id=102512 + final String testDescription = " { P=" + producers + ", C=" + consumers + ", CCT=" + + parallelConsumerThreads + ", SIZE=" + SIZE_PARAMETER_VALUE + " }"; + parameters.add(new Object[] {"SEDA" + testDescription, "seda:speedtest" + sedaOptions, producers, + consumers, parallelConsumerThreads, SEDA_SIZE_HISTOGRAM_BOUNDS}); + parameters + .add(new Object[] {"Disruptor" + testDescription, "disruptor:speedtest" + options, producers, + consumers, parallelConsumerThreads, DISRUPTOR_SIZE_HISTOGRAM_BOUNDS}); + } + + @Test + public void speedTestDisruptor() throws InterruptedException { + + System.out.println("Warming up for test of: " + componentName); + + performTest(true); + System.out.println("Starting real test of: " + componentName); + + forceGC(); + Thread.sleep(1000); + + performTest(false); + } + + private void forceGC() { + // unfortunately there is no nice API that forces the Garbage collector to run, but it may consider our request + // more seriously if we ask it twice :) + System.gc(); + System.gc(); + } + + private void resetExchangeAwaiters() { + for (final ExchangeAwaiter exchangeAwaiter : exchangeAwaiters) { + exchangeAwaiter.reset(); + } + } + + private void awaitExchangeAwaiters() throws InterruptedException { + for (final ExchangeAwaiter exchangeAwaiter : exchangeAwaiters) { + while (!exchangeAwaiter.awaitMessagesReceived(10, TimeUnit.SECONDS)) { + System.err.println( + "Processing takes longer then expected: " + componentName + " " + exchangeAwaiter + .getStatus()); + } + } + } + + private void outputExchangeAwaitersResult(final long start) throws InterruptedException { + for (final ExchangeAwaiter exchangeAwaiter : exchangeAwaiters) { + final long stop = exchangeAwaiter.getCountDownReachedTime(); + final Histogram histogram = exchangeAwaiter.getLatencyHistogram(); + + System.out.printf("%-45s time spent = %5d ms. Latency (ms): %s %n", componentName, (stop - start), + histogram.toString()); + } + } + + private void performTest(final boolean warmup) throws InterruptedException { + resetExchangeAwaiters(); + + final ProducerThread[] producerThread = new ProducerThread[amountProducers]; + for (int i = 0; i < producerThread.length; ++i) { + producerThread[i] = new ProducerThread(SPEED_TEST_EXCHANGE_COUNT / amountProducers); + } + + ExecutorService monitoring = null; + if (!warmup) { + monitoring = installSizeMonitoring(context.getEndpoint(endpointUri)); + } + final long start = System.currentTimeMillis(); + + for (int i = 0; i < producerThread.length; ++i) { + producerThread[i].start(); + } + + awaitExchangeAwaiters(); + + if (!warmup) { + outputExchangeAwaitersResult(start); + uninstallSizeMonitoring(monitoring); + } + } + + private ExecutorService installSizeMonitoring(final Endpoint endpoint) { + final ScheduledExecutorService service = context.getExecutorServiceManager() + .newScheduledThreadPool(this, "SizeMonitoringThread", 1); + endpointSizeQueue.clear(); + final Runnable monitoring = new Runnable() { + @Override + public void run() { + if (endpoint instanceof SedaEndpoint) { + final SedaEndpoint sedaEndpoint = (SedaEndpoint)endpoint; + endpointSizeQueue.offer(sedaEndpoint.getCurrentQueueSize()); + } else if (endpoint instanceof DisruptorEndpoint) { + final DisruptorEndpoint disruptorEndpoint = (DisruptorEndpoint)endpoint; + + long remainingCapacity = 0; + try { + remainingCapacity = disruptorEndpoint.getRemainingCapacity(); + } catch (DisruptorNotStartedException e) { + //ignore + } + endpointSizeQueue.offer((int)(disruptorEndpoint.getBufferSize() - remainingCapacity)); + } + } + }; + service.scheduleAtFixedRate(monitoring, 0, 100, TimeUnit.MILLISECONDS); + return service; + } + + private void uninstallSizeMonitoring(final ExecutorService monitoring) { + if (monitoring != null) { + monitoring.shutdownNow(); + } + final Histogram histogram = new Histogram(sizeHistogramBounds); + for (final int observation : endpointSizeQueue) { + histogram.addObservation(observation); + } + System.out.printf("%82s %s%n", "Endpoint size (# exchanges pending):", histogram.toString()); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + for (final ExchangeAwaiter exchangeAwaiter : exchangeAwaiters) { + from(endpointUri).process(exchangeAwaiter); + } + } + }; + } + + private static final class ExchangeAwaiter implements Processor { + + private CountDownLatch latch; + private final int count; + private long countDownReachedTime = 0; + + private Queue latencyQueue = new ConcurrentLinkedQueue(); + + public ExchangeAwaiter(final int count) { + this.count = count; + } + + public void reset() { + latencyQueue = new ConcurrentLinkedQueue(); + latch = new CountDownLatch(count); + countDownReachedTime = 0; + } + + public boolean awaitMessagesReceived(final long timeout, final TimeUnit unit) + throws InterruptedException { + return latch.await(timeout, unit); + } + + public String getStatus() { + final StringBuilder sb = new StringBuilder(100); + sb.append("processed "); + sb.append(count - latch.getCount()); + sb.append('/'); + sb.append(count); + sb.append(" messages"); + + return sb.toString(); + } + + @Override + public void process(final Exchange exchange) throws Exception { + final long sentTimeNs = exchange.getIn().getBody(Long.class); + latencyQueue.offer(Long.valueOf(System.nanoTime() - sentTimeNs)); + + countDownReachedTime = System.currentTimeMillis(); + latch.countDown(); + } + + public long getCountDownReachedTime() { + // Make sure we wait until all exchanges have been processed. Otherwise the time value doesn't make sense. + try { + latch.await(); + } catch (InterruptedException e) { + countDownReachedTime = 0; + } + return countDownReachedTime; + } + + public Histogram getLatencyHistogram() { + final Histogram histogram = new Histogram(LATENCY_HISTOGRAM_BOUNDS); + for (final Long latencyValue : latencyQueue) { + histogram.addObservation(latencyValue / 1000000); + } + return histogram; + } + } + + private final class ProducerThread extends Thread { + + private final int totalMessageCount; + private int producedMessageCount = 0; + + public ProducerThread(final int totalMessageCount) { + super("TestDataProducerThread"); + this.totalMessageCount = totalMessageCount; + } + + public void run() { + final Endpoint endpoint = context().getEndpoint(endpointUri); + while (producedMessageCount++ < totalMessageCount) { + producerTemplate.sendBody(endpoint, ExchangePattern.InOnly, System.nanoTime()); + } + } + + public String getStatus() { + final StringBuilder sb = new StringBuilder(100); + sb.append("produced "); + sb.append(producedMessageCount - 1); + sb.append('/'); + sb.append(totalMessageCount); + sb.append(" messages"); + + return sb.toString(); + } + } +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmComponentReferenceEndpointTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmComponentReferenceEndpointTest.java new file mode 100644 index 0000000000000..ff1c15d659f40 --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmComponentReferenceEndpointTest.java @@ -0,0 +1,89 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor.vm; + +import org.apache.camel.component.disruptor.DisruptorReference; +import org.apache.camel.ContextTestSupport; +import org.apache.camel.builder.RouteBuilder; + +import java.util.Iterator; + +/** + * + */ +public class DisruptorVmComponentReferenceEndpointTest extends ContextTestSupport { + + public void testDisruptorVmComponentReference() throws Exception { + DisruptorVmComponent vm = context.getComponent("disruptor-vm", DisruptorVmComponent.class); + + String key = vm.getDisruptorKey("disruptor-vm://foo"); + assertEquals(1, vm.getDisruptors().get(key).getEndpointCount()); + assertEquals(2, numberOfReferences(vm)); + + // add a second consumer on the endpoint + context.addRoutes(new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor-vm:foo?blockWhenFull=true").routeId("foo2").to("mock:foo2"); + } + }); + + assertEquals(2, vm.getDisruptors().get(key).getEndpointCount()); + assertEquals(3, numberOfReferences(vm)); + + // remove the 1st route + context.stopRoute("foo"); + context.removeRoute("foo"); + + assertEquals(1, vm.getDisruptors().get(key).getEndpointCount()); + assertEquals(2, numberOfReferences(vm)); + + // remove the 2nd route + context.stopRoute("foo2"); + context.removeRoute("foo2"); + + // and there is no longer queues for the foo key + assertNull(vm.getDisruptors().get(key)); + + // there should still be a bar + assertEquals(1, numberOfReferences(vm)); + key = vm.getDisruptorKey("disruptor-vm://bar"); + assertEquals(1, vm.getDisruptors().get(key).getEndpointCount()); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor-vm:foo").routeId("foo").to("mock:foo"); + + from("disruptor-vm:bar").routeId("bar").to("mock:bar"); + } + }; + } + + private int numberOfReferences(DisruptorVmComponent vm) { + int num = 0; + Iterator it = vm.getDisruptors().values().iterator(); + while (it.hasNext()) { + num += it.next().getEndpointCount(); + } + return num; + } + +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmConcurrentConsumersTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmConcurrentConsumersTest.java new file mode 100644 index 0000000000000..ac5ccea9048a1 --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmConcurrentConsumersTest.java @@ -0,0 +1,46 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor.vm; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.component.vm.AbstractVmTestSupport; + +/** + * @version + */ +public class DisruptorVmConcurrentConsumersTest extends AbstractVmTestSupport { + + public void testSendToSeda() throws Exception { + MockEndpoint mock = getMockEndpoint("mock:result"); + mock.expectedBodiesReceived("Hello World"); + + template2.sendBody("disruptor-vm:foo?concurrentConsumers=5", "Hello World"); + + assertMockEndpointsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor-vm:foo?concurrentConsumers=5").to("mock:result"); + } + }; + } +} \ No newline at end of file diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmDifferentOptionsOnConsumerAndProducerTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmDifferentOptionsOnConsumerAndProducerTest.java new file mode 100644 index 0000000000000..75db3875c378f --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmDifferentOptionsOnConsumerAndProducerTest.java @@ -0,0 +1,64 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor.vm; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.component.vm.AbstractVmTestSupport; +import org.junit.Test; + +/** + * @version + */ +public class DisruptorVmDifferentOptionsOnConsumerAndProducerTest extends AbstractVmTestSupport { + + @Test + public void testSendToDisruptorVm() throws Exception { + MockEndpoint result = getMockEndpoint("mock:result"); + result.expectedBodiesReceived("Hello World"); + + + template2.sendBody("direct:start", "Hello World"); + + assertMockEndpointsSatisfied(); + + // check the camel context of the exchange + assertEquals("Get a wrong context. ", context, result.getExchanges().get(0).getContext()); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor-vm:foo?concurrentConsumers=5") + .to("mock:result"); + } + }; + } + + @Override + protected RouteBuilder createRouteBuilderForSecondContext() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:start") + .to("disruptor-vm:foo"); + } + }; + } +} \ No newline at end of file diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmInOnlyChainedTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmInOnlyChainedTest.java new file mode 100644 index 0000000000000..fd4b320193d4b --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmInOnlyChainedTest.java @@ -0,0 +1,60 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor.vm; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.component.vm.AbstractVmTestSupport; + +/** + * @version + */ +public class DisruptorVmInOnlyChainedTest extends AbstractVmTestSupport { + + public void testInOnlyDisruptorVmChained() throws Exception { + getMockEndpoint("mock:a").expectedBodiesReceived("start"); + resolveMandatoryEndpoint(context2, "mock:b", MockEndpoint.class).expectedBodiesReceived("start-a"); + getMockEndpoint("mock:c").expectedBodiesReceived("start-a-b"); + + template.sendBody("disruptor-vm:a", "start"); + + assertMockEndpointsSatisfied(); + MockEndpoint.assertIsSatisfied(context2); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor-vm:a").to("mock:a").setBody(simple("${body}-a")).to("disruptor-vm:b"); + + from("disruptor-vm:c").to("mock:c").setBody(simple("${body}-c")); + } + }; + } + + @Override + protected RouteBuilder createRouteBuilderForSecondContext() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor-vm:b").to("mock:b").setBody(simple("${body}-b")).to("disruptor-vm:c"); + } + }; + } +} \ No newline at end of file diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmInOnlyTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmInOnlyTest.java new file mode 100644 index 0000000000000..7e8e2301932d5 --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmInOnlyTest.java @@ -0,0 +1,54 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor.vm; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.vm.AbstractVmTestSupport; + +/** + * @version + */ +public class DisruptorVmInOnlyTest extends AbstractVmTestSupport { + + public void testInOnly() throws Exception { + getMockEndpoint("mock:result").expectedBodiesReceived("Hello World"); + + template2.sendBody("direct:start", "Hello World"); + + assertMockEndpointsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor-vm:foo").to("mock:result"); + } + }; + } + + @Override + protected RouteBuilder createRouteBuilderForSecondContext() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:start").to("disruptor-vm:foo"); + } + }; + } +} \ No newline at end of file diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmInOutChainedTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmInOutChainedTest.java new file mode 100644 index 0000000000000..6535b26cc890d --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmInOutChainedTest.java @@ -0,0 +1,60 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor.vm; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.component.vm.AbstractVmTestSupport; + +/** + * @version + */ +public class DisruptorVmInOutChainedTest extends AbstractVmTestSupport { + + public void testInOutDisruptorVmChained() throws Exception { + getMockEndpoint("mock:a").expectedBodiesReceived("start"); + resolveMandatoryEndpoint(context2, "mock:b", MockEndpoint.class).expectedBodiesReceived("start-a"); + getMockEndpoint("mock:c").expectedBodiesReceived("start-a-b"); + + String reply = template2.requestBody("disruptor-vm:a", "start", String.class); + assertEquals("start-a-b-c", reply); + + assertMockEndpointsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor-vm:a").to("mock:a").transform(simple("${body}-a")).to("disruptor-vm:b"); + + from("disruptor-vm:c").to("mock:c").transform(simple("${body}-c")); + } + }; + } + + @Override + protected RouteBuilder createRouteBuilderForSecondContext() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor-vm:b").to("mock:b").transform(simple("${body}-b")).to("disruptor-vm:c"); + } + }; + } +} \ No newline at end of file diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmInOutChainedTimeoutTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmInOutChainedTimeoutTest.java new file mode 100644 index 0000000000000..aacca8377580f --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmInOutChainedTimeoutTest.java @@ -0,0 +1,76 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor.vm; + +import org.apache.camel.CamelExecutionException; +import org.apache.camel.ExchangeTimedOutException; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.vm.AbstractVmTestSupport; +import org.apache.camel.util.StopWatch; + +/** + * @version + */ +public class DisruptorVmInOutChainedTimeoutTest extends AbstractVmTestSupport { + + public void testDisruptorVmInOutChainedTimeout() throws Exception { + StopWatch watch = new StopWatch(); + + try { + template2.requestBody("disruptor-vm:a?timeout=1000", "Hello World"); + fail("Should have thrown an exception"); + } catch (CamelExecutionException e) { + // the chained vm caused the timeout + ExchangeTimedOutException cause = assertIsInstanceOf(ExchangeTimedOutException.class, + e.getCause()); + assertEquals(200, cause.getTimeout()); + } + + long delta = watch.stop(); + + assertTrue("Should be faster than 1 sec, was: " + delta, delta < 1100); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor-vm:b") + .to("mock:b") + .delay(500) + .transform().constant("Bye World"); + } + }; + } + + @Override + protected RouteBuilder createRouteBuilderForSecondContext() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + errorHandler(noErrorHandler()); + + from("disruptor-vm:a") + .to("mock:a") + // this timeout will trigger an exception to occur + .to("disruptor-vm:b?timeout=200") + .to("mock:a2"); + } + }; + } +} \ No newline at end of file diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmInOutTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmInOutTest.java new file mode 100644 index 0000000000000..4e5aa652cf0ee --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmInOutTest.java @@ -0,0 +1,55 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor.vm; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.vm.AbstractVmTestSupport; + +/** + * @version + */ +public class DisruptorVmInOutTest extends AbstractVmTestSupport { + + public void testInOut() throws Exception { + getMockEndpoint("mock:result").expectedBodiesReceived("Bye World"); + + String out = template2.requestBody("direct:start", "Hello World", String.class); + assertEquals("Bye World", out); + + assertMockEndpointsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor-vm:foo").transform(constant("Bye World")).to("mock:result"); + } + }; + } + + @Override + protected RouteBuilder createRouteBuilderForSecondContext() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:start").to("disruptor-vm:foo"); + } + }; + } +} \ No newline at end of file diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmInOutWithErrorTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmInOutWithErrorTest.java new file mode 100644 index 0000000000000..1bd0bcd69dfef --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmInOutWithErrorTest.java @@ -0,0 +1,63 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor.vm; + +import org.apache.camel.CamelExecutionException; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.vm.AbstractVmTestSupport; + +/** + * @version + */ +public class DisruptorVmInOutWithErrorTest extends AbstractVmTestSupport { + + public void testInOutWithError() throws Exception { + getMockEndpoint("mock:result").expectedMessageCount(0); + + try { + template2.requestBody("direct:start", "Hello World", String.class); + fail("Should have thrown an exception"); + } catch (CamelExecutionException e) { + assertIsInstanceOf(IllegalArgumentException.class, e.getCause()); + assertEquals("Damn I cannot do this", e.getCause().getMessage()); + } + + assertMockEndpointsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor-vm:foo").transform(constant("Bye World")) + .throwException(new IllegalArgumentException("Damn I cannot do this")) + .to("mock:result"); + } + }; + } + + @Override + protected RouteBuilder createRouteBuilderForSecondContext() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:start").to("disruptor-vm:foo"); + } + }; + } +} \ No newline at end of file diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmMultipleConsumersIssueTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmMultipleConsumersIssueTest.java new file mode 100644 index 0000000000000..2c8b2289e698c --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmMultipleConsumersIssueTest.java @@ -0,0 +1,72 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor.vm; + +import org.apache.camel.ContextTestSupport; +import org.apache.camel.ExchangePattern; +import org.apache.camel.builder.RouteBuilder; + +/** + * + */ +public class DisruptorVmMultipleConsumersIssueTest extends ContextTestSupport { + + public void testDisruptorVmMultipleConsumersIssue() throws Exception { + getMockEndpoint("mock:a").expectedBodiesReceived("Hello World"); + getMockEndpoint("mock:b").expectedBodiesReceived("Hello World"); + getMockEndpoint("mock:c").expectedBodiesReceived("Hello World"); + getMockEndpoint("mock:d").expectedBodiesReceived("Hello World"); + getMockEndpoint("mock:e").expectedBodiesReceived("Hello World"); + getMockEndpoint("mock:done").expectedBodiesReceived("Hello World"); + + template.sendBody("direct:inbox", "Hello World"); + + assertMockEndpointsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:inbox") + .to(ExchangePattern.InOut, "disruptor-vm:foo?timeout=5000") + .to("mock:done"); + + from("disruptor-vm:foo?multipleConsumers=true") + .to("log:a") + .to("mock:a"); + + from("disruptor-vm:foo?multipleConsumers=true") + .to("log:b") + .to("mock:b"); + + from("disruptor-vm:foo?multipleConsumers=true") + .to("log:c") + .to("mock:c"); + + from("disruptor-vm:foo?multipleConsumers=true") + .to("log:d") + .to("mock:d"); + + from("disruptor-vm:foo?multipleConsumers=true") + .to("log:e") + .to("mock:e"); + } + }; + } +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmMultipleContextsStartStopTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmMultipleContextsStartStopTest.java new file mode 100644 index 0000000000000..d4a2c163af439 --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmMultipleContextsStartStopTest.java @@ -0,0 +1,67 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor.vm; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.component.vm.AbstractVmTestSupport; + +/** + * @version + */ +public class DisruptorVmMultipleContextsStartStopTest extends AbstractVmTestSupport { + + public void testStartStop() throws Exception { + /* Check that contexts are communicated */ + MockEndpoint mock = context2.getEndpoint("mock:result", MockEndpoint.class); + mock.expectedMessageCount(1); + template.requestBody("direct:test", "Hello world!"); + mock.assertIsSatisfied(); + mock.reset(); + + /* Restart the consumer Camel Context */ + context2.stop(); + context2.start(); + + /* Send a message again and assert that it's received */ + template.requestBody("direct:test", "Hello world!"); + mock.assertIsSatisfied(); + + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:test").to("disruptor-vm:foo"); + } + }; + } + + @Override + protected RouteBuilder createRouteBuilderForSecondContext() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor-vm:foo").to("mock:result"); + } + }; + + } + +} \ No newline at end of file diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmQueueTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmQueueTest.java new file mode 100644 index 0000000000000..d36755c31e867 --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmQueueTest.java @@ -0,0 +1,57 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor.vm; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.component.vm.AbstractVmTestSupport; + +/** + * @version + */ +public class DisruptorVmQueueTest extends AbstractVmTestSupport { + + public void testQueue() throws Exception { + MockEndpoint mock = getMockEndpoint("mock:result"); + mock.expectedBodiesReceivedInAnyOrder("Hello World", "Bye World", "Goodday World", "Bar"); + + template2.sendBody("disruptor-vm:foo", "Hello World"); + template2.sendBody("disruptor-vm:foo?size=20", "Bye World"); + template2.sendBody("disruptor-vm:foo?concurrentConsumers=5", "Goodday World"); + template.sendBody("disruptor-vm:bar", "Bar"); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor-vm:bar").to("mock:result"); + } + }; + } + + @Override + protected RouteBuilder createRouteBuilderForSecondContext() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor-vm:foo?size=20&concurrentConsumers=2").to("mock:result"); + } + }; + } +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmShouldNotUseSameThreadTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmShouldNotUseSameThreadTest.java new file mode 100644 index 0000000000000..a7bbbf9c308f3 --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmShouldNotUseSameThreadTest.java @@ -0,0 +1,69 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor.vm; + +import org.apache.camel.Exchange; +import org.apache.camel.Processor; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.component.vm.AbstractVmTestSupport; + +/** + * Unit test to verify continuing using NOT same thread on the consumer side. + */ +public class DisruptorVmShouldNotUseSameThreadTest extends AbstractVmTestSupport { + + private static long id; + private final ThreadLocal local = new ThreadLocal(); + + public void testNotUseSameThread() throws Exception { + MockEndpoint mock = getMockEndpoint("mock:result"); + mock.expectedBodiesReceived("Hello World"); + + template2.sendBody("direct:start", "Hello World"); + + assertMockEndpointsSatisfied(); + } + + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + public void configure() throws Exception { + from("disruptor-vm:foo").process(new Processor() { + public void process(Exchange exchange) throws Exception { + assertNull(local.get()); + assertNotSame("Thread is should not be same", id, Thread.currentThread().getId()); + } + }).to("mock:result"); + } + }; + } + + @Override + protected RouteBuilder createRouteBuilderForSecondContext() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:start").process(new Processor() { + public void process(Exchange exchange) throws Exception { + local.set("Hello"); + id = Thread.currentThread().getId(); + } + }).to("disruptor-vm:foo"); + } + }; + } +} \ No newline at end of file diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmSplitterTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmSplitterTest.java new file mode 100644 index 0000000000000..4810f88b40ba5 --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmSplitterTest.java @@ -0,0 +1,81 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor.vm; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.component.vm.AbstractVmTestSupport; +import org.apache.camel.impl.JndiRegistry; + +import java.util.Arrays; +import java.util.List; + +public class DisruptorVmSplitterTest extends AbstractVmTestSupport { + + protected JndiRegistry createRegistry() throws Exception { + JndiRegistry jndi = super.createRegistry(); + jndi.bind("splitterBean", new SplitWordsBean()); + return jndi; + } + + + public void testSplitUsingMethodCall() throws Exception { + MockEndpoint resultEndpoint = getMockEndpoint("mock:result"); + resultEndpoint.expectedBodiesReceived("Claus", "James", "Willem"); + + template2.sendBody("direct:start", "Claus@James@Willem"); + + assertMockEndpointsSatisfied(); + } + + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor-vm:server").split().method("splitterBean", "splitWords").to("mock:result"); + } + }; + } + + @Override + protected RouteBuilder createRouteBuilderForSecondContext() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:start").to("disruptor-vm:server"); + } + }; + } + + public static final class SplitWordsBean { + private SplitWordsBean() { + // Helper Class + } + + public static List splitWords(String body) { + // here we split the payload using java code + // we have the true power of Java to do the splitting + // as we like. As this is based on a unit test we just do it easy + return Arrays.asList(body.split("@")); + } + + } + +} diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmTimeoutIssueTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmTimeoutIssueTest.java new file mode 100644 index 0000000000000..62fdaa32fbc09 --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmTimeoutIssueTest.java @@ -0,0 +1,88 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor.vm; + +import org.apache.camel.CamelExecutionException; +import org.apache.camel.Exchange; +import org.apache.camel.ExchangeTimedOutException; +import org.apache.camel.Processor; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.vm.AbstractVmTestSupport; + +/** + * @version + */ +public class DisruptorVmTimeoutIssueTest extends AbstractVmTestSupport { + + public void testDisruptorVmTimeoutWithAnotherDisruptorVm() throws Exception { + try { + template2.requestBody("disruptor-vm:start1?timeout=4000", "Hello"); + fail("Should have thrown an exception"); + } catch (CamelExecutionException e) { + ExchangeTimedOutException cause = assertIsInstanceOf(ExchangeTimedOutException.class, + e.getCause()); + assertEquals(2000, cause.getTimeout()); + } + } + + public void testDisruptorVmTimeoutWithProcessor() throws Exception { + try { + template2.requestBody("disruptor-vm:start2?timeout=4000", "Hello"); + fail("Should have thrown an exception"); + } catch (CamelExecutionException e) { + ExchangeTimedOutException cause = assertIsInstanceOf(ExchangeTimedOutException.class, + e.getCause()); + assertEquals(2000, cause.getTimeout()); + } + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor-vm:end") + .delay(3000).transform().constant("Bye World"); + } + }; + } + + @Override + protected RouteBuilder createRouteBuilderForSecondContext() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + errorHandler(noErrorHandler()); + + from("disruptor-vm:start1?timeout=4000") + .to("log:AFTER_START1") + .to("disruptor-vm:end?timeout=2000") + .to("log:AFTER_END"); + + from("disruptor-vm:start2?timeout=4000") + .to("log:AFTER_START2") + .process(new Processor() { + public void process(Exchange exchange) throws Exception { + // this exception will trigger to stop asap + throw new ExchangeTimedOutException(exchange, 2000); + } + }) + .to("log:AFTER_PROCESSOR"); + } + }; + } +} \ No newline at end of file diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmUseSameQueueTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmUseSameQueueTest.java new file mode 100644 index 0000000000000..17b9d84b04298 --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmUseSameQueueTest.java @@ -0,0 +1,55 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor.vm; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.vm.AbstractVmTestSupport; + +/** + * @version + */ +public class DisruptorVmUseSameQueueTest extends AbstractVmTestSupport { + + public void testDisruptorVmUseSameQueue() throws Exception { + getMockEndpoint("mock:result").expectedMessageCount(2); + + template2.sendBody("direct:start", "Hello World"); + template2.sendBody("direct:start", "Bye World"); + + assertMockEndpointsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor-vm:foo?size=500").to("mock:result"); + } + }; + } + + @Override + protected RouteBuilder createRouteBuilderForSecondContext() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:start").to("disruptor-vm:foo"); + } + }; + } +} \ No newline at end of file diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmWaitForTaskCompleteTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmWaitForTaskCompleteTest.java new file mode 100644 index 0000000000000..afec25082baab --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmWaitForTaskCompleteTest.java @@ -0,0 +1,75 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor.vm; + +import org.apache.camel.Exchange; +import org.apache.camel.ExchangePattern; +import org.apache.camel.Processor; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.vm.AbstractVmTestSupport; + +/** + * @version + */ +public class DisruptorVmWaitForTaskCompleteTest extends AbstractVmTestSupport { + + public void testInOut() throws Exception { + getMockEndpoint("mock:result").expectedBodiesReceived("Bye World"); + + String out = template2.requestBody("direct:start", "Hello World", String.class); + assertEquals("Bye World", out); + + assertMockEndpointsSatisfied(); + } + + public void testInOnly() throws Exception { + getMockEndpoint("mock:result").expectedBodiesReceived("Bye World"); + + // we send an in only but we use Always to wait for it to complete + // and since the route changes the payload we can get the response anyway + Exchange out = template2.send("direct:start", new Processor() { + public void process(Exchange exchange) throws Exception { + exchange.getIn().setBody("Hello World"); + exchange.setPattern(ExchangePattern.InOnly); + } + }); + assertEquals("Bye World", out.getIn().getBody()); + + assertMockEndpointsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor-vm:foo?waitForTaskToComplete=Always").transform(constant("Bye World")) + .to("mock:result"); + } + }; + } + + @Override + protected RouteBuilder createRouteBuilderForSecondContext() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:start").to("disruptor-vm:foo?waitForTaskToComplete=Always"); + } + }; + } +} \ No newline at end of file diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmWaitForTaskIfReplyExpectedTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmWaitForTaskIfReplyExpectedTest.java new file mode 100644 index 0000000000000..9ef6932f9bdc6 --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmWaitForTaskIfReplyExpectedTest.java @@ -0,0 +1,75 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor.vm; + +import org.apache.camel.Exchange; +import org.apache.camel.ExchangePattern; +import org.apache.camel.Processor; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.vm.AbstractVmTestSupport; + +/** + * @version + */ +public class DisruptorVmWaitForTaskIfReplyExpectedTest extends AbstractVmTestSupport { + + public void testInOut() throws Exception { + getMockEndpoint("mock:result").expectedBodiesReceived("Bye World"); + + String out = template2.requestBody("direct:start", "Hello World", String.class); + assertEquals("Bye World", out); + + assertMockEndpointsSatisfied(); + } + + public void testInOnly() throws Exception { + getMockEndpoint("mock:result").expectedBodiesReceived("Bye World"); + + Exchange out = template2.send("direct:start", new Processor() { + public void process(Exchange exchange) throws Exception { + exchange.getIn().setBody("Hello World"); + exchange.setPattern(ExchangePattern.InOnly); + } + }); + // we do not expecy a reply and thus do no wait so we just get our own input back + assertEquals("Hello World", out.getIn().getBody()); + assertNull(out.getOut().getBody()); + + assertMockEndpointsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor-vm:foo?waitForTaskToComplete=IfReplyExpected") + .transform(constant("Bye World")).to("mock:result"); + } + }; + } + + @Override + protected RouteBuilder createRouteBuilderForSecondContext() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:start").to("disruptor-vm:foo?waitForTaskToComplete=IfReplyExpected"); + } + }; + } +} \ No newline at end of file diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmWaitForTaskNewerTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmWaitForTaskNewerTest.java new file mode 100644 index 0000000000000..72a112f6985fe --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmWaitForTaskNewerTest.java @@ -0,0 +1,76 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor.vm; + +import org.apache.camel.Exchange; +import org.apache.camel.ExchangePattern; +import org.apache.camel.Processor; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.vm.AbstractVmTestSupport; + +/** + * @version + */ +public class DisruptorVmWaitForTaskNewerTest extends AbstractVmTestSupport { + + public void testInOut() throws Exception { + getMockEndpoint("mock:result").expectedBodiesReceived("Bye World"); + + String out = template2.requestBody("direct:start", "Hello World", String.class); + // we do not wait for the response so we just get our own input back + assertEquals("Hello World", out); + + assertMockEndpointsSatisfied(); + } + + public void testInOnly() throws Exception { + getMockEndpoint("mock:result").expectedBodiesReceived("Bye World"); + + Exchange out = template2.send("direct:start", new Processor() { + public void process(Exchange exchange) throws Exception { + exchange.getIn().setBody("Hello World"); + exchange.setPattern(ExchangePattern.InOnly); + } + }); + // we do not wait for the response so we just get our own input back + assertEquals("Hello World", out.getIn().getBody()); + assertNull(out.getOut().getBody()); + + assertMockEndpointsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor-vm:foo?waitForTaskToComplete=Never").transform(constant("Bye World")) + .to("mock:result"); + } + }; + } + + @Override + protected RouteBuilder createRouteBuilderForSecondContext() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:start").to("disruptor-vm:foo?waitForTaskToComplete=Never"); + } + }; + } +} \ No newline at end of file diff --git a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/SameDisruptorVmQueueSizeAndNoSizeTest.java b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/SameDisruptorVmQueueSizeAndNoSizeTest.java new file mode 100644 index 0000000000000..dcf152bafa056 --- /dev/null +++ b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/SameDisruptorVmQueueSizeAndNoSizeTest.java @@ -0,0 +1,79 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.disruptor.vm; + +import org.apache.camel.CamelExecutionException; +import org.apache.camel.ContextTestSupport; +import org.apache.camel.ResolveEndpointFailedException; +import org.apache.camel.builder.RouteBuilder; + +/** + * + */ +public class SameDisruptorVmQueueSizeAndNoSizeTest extends ContextTestSupport { + + public void testSameQueue() throws Exception { + for (int i = 0; i < 128; i++) { + template.sendBody("disruptor-vm:foo?blockWhenFull=false", "" + i); + } + + try { + template.sendBody("disruptor-vm:foo?blockWhenFull=false", "Should be full now"); + fail("Should fail"); + } catch (CamelExecutionException e) { + IllegalStateException ise = assertIsInstanceOf(IllegalStateException.class, e.getCause()); + assertEquals("Disruptors ringbuffer was full", ise.getMessage()); + } + } + + public void testSameQueueDifferentSize() throws Exception { + try { + template.sendBody("disruptor-vm:foo?size=256", "Should fail"); + fail("Should fail"); + } catch (ResolveEndpointFailedException e) { + IllegalArgumentException ise = assertIsInstanceOf(IllegalArgumentException.class, e.getCause()); + assertEquals( + "Cannot use existing queue disruptor-vm://foo as the existing queue size 128 does not match given queue size 256", + ise.getMessage()); + } + } + + public void testSameQueueDifferentSizeBar() throws Exception { + try { + template.sendBody("disruptor-vm:bar?size=256", "Should fail"); + fail("Should fail"); + } catch (ResolveEndpointFailedException e) { + IllegalArgumentException ise = assertIsInstanceOf(IllegalArgumentException.class, e.getCause()); + assertEquals("Cannot use existing queue disruptor-vm://bar as the existing queue size " + 1024 + + " does not match given queue size 256", ise.getMessage()); + } + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor-vm:foo?size=128&blockWhenFull=false").routeId("foo").noAutoStartup() + .to("mock:foo"); + + from("disruptor-vm:bar").routeId("bar").noAutoStartup() + .to("mock:bar"); + } + }; + } +} diff --git a/components/camel-disruptor/src/test/resources/log4j.properties b/components/camel-disruptor/src/test/resources/log4j.properties new file mode 100644 index 0000000000000..19deb2aeb4569 --- /dev/null +++ b/components/camel-disruptor/src/test/resources/log4j.properties @@ -0,0 +1,23 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# + +log4j.rootLogger = WARN, console +log4j.appender.console=org.apache.log4j.ConsoleAppender +log4j.appender.console.layout=org.apache.log4j.PatternLayout +log4j.appender.console.layout.conversionPattern=%d{HH:mm:ss,SSS} - %-5p - %16.16t - %50.50c # %m%n + +#log4j.logger.org.apache.camel.component.disruptor = DEBUG \ No newline at end of file diff --git a/components/pom.xml b/components/pom.xml index 2a0cf693188d2..997fdc3a81c91 100644 --- a/components/pom.xml +++ b/components/pom.xml @@ -74,6 +74,7 @@ camel-couchdb camel-crypto camel-csv + camel-disruptor camel-dns camel-dozer camel-eclipse diff --git a/parent/pom.xml b/parent/pom.xml index 53d2c2c87a209..5b50891952e15 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -107,6 +107,7 @@ 2.6.0 0.3-incubating 10.9.1.0 + 3.0.1 2.1.1 2.1.1_1 1.6.1_5 @@ -597,6 +598,11 @@ camel-cxf-transport ${project.version} + + org.apache.camel + camel-disruptor + ${project.version} + org.apache.camel camel-dns @@ -1593,6 +1599,13 @@ ${castor-bundle-version} + + + com.lmax + disruptor + ${disruptor.version} + + net.sf.dozer diff --git a/tests/camel-itest-osgi/pom.xml b/tests/camel-itest-osgi/pom.xml index fc5fa2045c659..9307e7c3843a9 100644 --- a/tests/camel-itest-osgi/pom.xml +++ b/tests/camel-itest-osgi/pom.xml @@ -395,6 +395,11 @@ camel-aws test + + org.apache.camel + camel-disruptor + test + org.apache.derby derby diff --git a/tests/camel-itest-osgi/src/test/java/org/apache/camel/itest/osgi/disruptor/DisruptorTest.java b/tests/camel-itest-osgi/src/test/java/org/apache/camel/itest/osgi/disruptor/DisruptorTest.java new file mode 100644 index 0000000000000..aa11f4ff711eb --- /dev/null +++ b/tests/camel-itest-osgi/src/test/java/org/apache/camel/itest/osgi/disruptor/DisruptorTest.java @@ -0,0 +1,66 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.itest.osgi.disruptor; + +import org.apache.camel.CamelContext; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.itest.osgi.OSGiIntegrationTestSupport; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.ops4j.pax.exam.junit.JUnit4TestRunner; + +/** + * @version + */ +@RunWith(JUnit4TestRunner.class) +public class DisruptorTest extends OSGiIntegrationTestSupport { + + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + public void configure() { + from("disruptor:foo").to("mock:bar"); + } + }; + } + + @Test + public void testSendMessage() throws Exception { + MockEndpoint mock = getMandatoryEndpoint("mock:bar", MockEndpoint.class); + assertNotNull("The mock endpoint should not be null", mock); + + mock.expectedBodiesReceived("Hello World"); + template.sendBody("disruptor:foo", "Hello World"); + assertMockEndpointsSatisfied(); + } + + @Test + public void testCamelContextName() throws Exception { + // should get the context name with osgi bundle id + String name1 = context.getName(); + + CamelContext context2 = createCamelContext(); + String name2 = context2.getName(); + + assertNotSame(name1, name2); + + String id = "" + bundleContext.getBundle().getBundleId(); + assertTrue(name1.startsWith(id)); + assertTrue(name2.startsWith(id)); + } + +} \ No newline at end of file diff --git a/tests/camel-itest-osgi/src/test/java/org/apache/camel/itest/osgi/disruptor/vm/DisruptorVmTest.java b/tests/camel-itest-osgi/src/test/java/org/apache/camel/itest/osgi/disruptor/vm/DisruptorVmTest.java new file mode 100644 index 0000000000000..36b0a844091e4 --- /dev/null +++ b/tests/camel-itest-osgi/src/test/java/org/apache/camel/itest/osgi/disruptor/vm/DisruptorVmTest.java @@ -0,0 +1,60 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.itest.osgi.disruptor.vm; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.itest.osgi.OSGiIntegrationTestSupport; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.ops4j.pax.exam.junit.JUnit4TestRunner; + +/** + * @version + */ +@RunWith(JUnit4TestRunner.class) +public class DisruptorVmTest extends OSGiIntegrationTestSupport { + + @Override + protected RouteBuilder[] createRouteBuilders() throws Exception { + RouteBuilder[] routeBuilders = new RouteBuilder[2]; + routeBuilders[0] = new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:start") + .to("disruptor-vm:foo"); + } + }; + routeBuilders[1] = new RouteBuilder() { + @Override + public void configure() throws Exception { + from("disruptor-vm:foo?concurrentConsumers=5") + .to("mock:result"); + } + }; + + return routeBuilders; + } + + @Test + public void testSendMessage() throws Exception { + getMockEndpoint("mock:result").expectedBodiesReceived("Hello World"); + + template.sendBody("direct:start", "Hello World"); + + assertMockEndpointsSatisfied(); + } +} \ No newline at end of file