/* * Copyright 2012 Riccardo Sirchia * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.camel.component.disruptor; import com.lmax.disruptor.InsufficientCapacityException; import org.apache.camel.*; import org.apache.camel.api.management.ManagedAttribute; import org.apache.camel.impl.DefaultEndpoint; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; import java.util.concurrent.CopyOnWriteArraySet; /** * An implementation of the <a href="https://github.com/sirchia/camel-disruptor">Disruptor component</a> * for asynchronous SEDA exchanges on an * <a href="https://github.com/LMAX-Exchange/disruptor">LMAX Disruptor</a> 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<DisruptorProducer> producers = new CopyOnWriteArraySet<DisruptorProducer>(); private final Set<DisruptorConsumer> consumers = new CopyOnWriteArraySet<DisruptorConsumer>(); 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<DisruptorConsumer> getConsumers() { return Collections.unmodifiableSet(consumers); } /** * Returns the current active producers on this endpoint */ public Set<DisruptorProducer> 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); } Collection<LifecycleAwareExchangeEventHandler> createConsumerEventHandlers() { final List<LifecycleAwareExchangeEventHandler> eventHandlers = new ArrayList<LifecycleAwareExchangeEventHandler>(); for (final DisruptorConsumer consumer : consumers) { eventHandlers.addAll(consumer.createEventHandlers(concurrentConsumers)); } return eventHandlers; } /** * 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()); } }