| /** |
| * 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; |
| |
| 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) { |
| process(event.getSynchronizedExchange()); |
| } |
| } |
| |
| } |
| } |