/*
* 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 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 <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 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<String, DisruptorReference> disruptors = new HashMap<String, DisruptorReference>();
@Override
protected Endpoint createEndpoint(final String uri, final String remaining, final Map<String, Object> 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<String, DisruptorReference> 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);
}
}
}