/** * 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.Suspendable; import org.apache.camel.spi.ExceptionHandler; import org.apache.camel.spi.ShutdownAware; import org.apache.camel.spi.Synchronization; import org.apache.camel.support.LoggingExceptionHandler; import org.apache.camel.support.ServiceSupport; import org.apache.camel.util.AsyncProcessorConverterHelper; 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, Suspendable, ShutdownAware { private static final Logger LOGGER = LoggerFactory.getLogger(DisruptorConsumer.class); private static final AsyncCallback NOOP_ASYNC_CALLBACK = new AsyncCallback() { @Override public void done(boolean doneSync) { //Noop } }; 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(endpoint.getCamelContext(), 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<LifecycleAwareExchangeEventHandler> createEventHandlers(final int concurrentConsumers) { final Set<LifecycleAwareExchangeEventHandler> eventHandlers = new HashSet<LifecycleAwareExchangeEventHandler>(); 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(boolean suspendOnly, 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 SynchronizedExchange synchronizedExchange) { try { Exchange exchange = synchronizedExchange.getExchange(); final boolean ignore = exchange.hasProperties() && exchange .getProperties().containsKey(DisruptorEndpoint.DISRUPTOR_IGNORE_EXCHANGE); 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); // We need to be notified when the exchange processing is complete to synchronize the original exchange // This is however the last part of the processing of this exchange and as such can't be done // in the AsyncCallback as that is called *AFTER* processing is considered to be done // (see org.apache.camel.processor.CamelInternalProcessor.InternalCallback#done). // To solve this problem, a new synchronization is set on the exchange that is to be // processed result.addOnCompletion(new Synchronization() { @Override public void onComplete(Exchange exchange) { synchronizedExchange.consumed(result); } @Override public void onFailure(Exchange exchange) { synchronizedExchange.consumed(result); } }); // As the necessary post-processing of the exchange is done by the registered Synchronization, // we can suffice with a no-op AsyncCallback processor.process(result, NOOP_ASYNC_CALLBACK); } catch (Exception e) { Exchange exchange = synchronizedExchange.getExchange(); if (exchange != null) { getExceptionHandler().handleException("Error processing exchange", exchange, e); } else { getExceptionHandler().handleException(e); } } } /** * 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; 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) { process(event.getSynchronizedExchange()); } } } }