001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.camel.component.seda;
018
019import java.util.HashMap;
020import java.util.Map;
021import java.util.concurrent.BlockingQueue;
022
023import org.apache.camel.Component;
024import org.apache.camel.Endpoint;
025import org.apache.camel.Exchange;
026import org.apache.camel.impl.UriEndpointComponent;
027import org.apache.camel.spi.Metadata;
028import org.slf4j.Logger;
029import org.slf4j.LoggerFactory;
030
031/**
032 * The <a href="http://camel.apache.org/seda.html">SEDA Component</a> is for asynchronous SEDA exchanges on a {@link BlockingQueue} within a CamelContext
033 *
034 * @version 
035 */
036public class SedaComponent extends UriEndpointComponent {
037    protected final Logger log = LoggerFactory.getLogger(getClass());
038    protected final int maxConcurrentConsumers = 500;
039
040    @Metadata(label = "consumer", defaultValue = "1")
041    protected int concurrentConsumers = 1;
042    @Metadata(label = "advanced")
043    protected int queueSize;
044    @Metadata(label = "advanced")
045    protected BlockingQueueFactory<Exchange> defaultQueueFactory = new LinkedBlockingQueueFactory<Exchange>();
046
047    private final Map<String, QueueReference> queues = new HashMap<String, QueueReference>();
048
049    public SedaComponent() {
050        super(SedaEndpoint.class);
051    }
052
053    public SedaComponent(Class<? extends Endpoint> endpointClass) {
054        super(endpointClass);
055    }
056
057    /**
058     * Sets the default maximum capacity of the SEDA queue (i.e., the number of messages it can hold).
059     */
060    public void setQueueSize(int size) {
061        queueSize = size;
062    }
063    
064    public int getQueueSize() {
065        return queueSize;
066    }
067
068    /**
069     * Sets the default number of concurrent threads processing exchanges.
070     */
071    public void setConcurrentConsumers(int size) {
072        concurrentConsumers = size;
073    }
074    
075    public int getConcurrentConsumers() {
076        return concurrentConsumers;
077    }
078
079    public BlockingQueueFactory<Exchange> getDefaultQueueFactory() {
080        return defaultQueueFactory;
081    }
082
083    /**
084     * Sets the default queue factory.
085     */
086    public void setDefaultQueueFactory(BlockingQueueFactory<Exchange> defaultQueueFactory) {
087        this.defaultQueueFactory = defaultQueueFactory;
088    }
089
090    /**
091     * @deprecated use
092     */
093    @Deprecated
094    public synchronized QueueReference getOrCreateQueue(SedaEndpoint endpoint, Integer size) {
095        return getOrCreateQueue(endpoint, size, null);
096    }
097
098    /**
099     * @deprecated use {@link #getOrCreateQueue(SedaEndpoint, Integer, Boolean, BlockingQueueFactory)}
100     */
101    @Deprecated
102    public synchronized QueueReference getOrCreateQueue(SedaEndpoint endpoint, Integer size, Boolean multipleConsumers) {
103        return getOrCreateQueue(endpoint, size, multipleConsumers, null);
104    }
105
106    public synchronized QueueReference getOrCreateQueue(SedaEndpoint endpoint, Integer size, Boolean multipleConsumers, BlockingQueueFactory<Exchange> customQueueFactory) {
107        String key = getQueueKey(endpoint.getEndpointUri());
108
109        QueueReference ref = getQueues().get(key);
110        if (ref != null) {
111
112            // if the given size is not provided, we just use the existing queue as is
113            if (size != null && !size.equals(ref.getSize())) {
114                // there is already a queue, so make sure the size matches
115                throw new IllegalArgumentException("Cannot use existing queue " + key + " as the existing queue size "
116                        + (ref.getSize() != null ? ref.getSize() : Integer.MAX_VALUE) + " does not match given queue size " + size);
117            }
118            // add the reference before returning queue
119            ref.addReference(endpoint);
120
121            if (log.isDebugEnabled()) {
122                log.debug("Reusing existing queue {} with size {} and reference count {}", new Object[]{key, size, ref.getCount()});
123            }
124            return ref;
125        }
126
127        // create queue
128        BlockingQueue<Exchange> queue;
129        BlockingQueueFactory<Exchange> queueFactory = customQueueFactory == null ? defaultQueueFactory : customQueueFactory;
130        if (size != null && size > 0) {
131            queue = queueFactory.create(size);
132        } else {
133            if (getQueueSize() > 0) {
134                size = getQueueSize();
135                queue = queueFactory.create(getQueueSize());
136            } else {
137                queue = queueFactory.create();
138            }
139        }
140        log.debug("Created queue {} with size {}", key, size);
141
142        // create and add a new reference queue
143        ref = new QueueReference(queue, size, multipleConsumers);
144        ref.addReference(endpoint);
145        getQueues().put(key, ref);
146
147        return ref;
148    }
149
150    public synchronized QueueReference registerQueue(SedaEndpoint endpoint, BlockingQueue<Exchange> queue) {
151        String key = getQueueKey(endpoint.getEndpointUri());
152
153        QueueReference ref = getQueues().get(key);
154        if (ref == null) {
155            ref = new QueueReference(queue, endpoint.getSize(), endpoint.isMultipleConsumers());
156            ref.addReference(endpoint);
157            getQueues().put(key, ref);
158        }
159
160        return ref;
161    }
162
163    public Map<String, QueueReference> getQueues() {
164        return queues;
165    }
166
167    public QueueReference getQueueReference(String key) {
168        return queues.get(key);
169    }
170
171    @Override
172    @SuppressWarnings("unchecked")
173    protected Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception {
174        int consumers = getAndRemoveOrResolveReferenceParameter(parameters, "concurrentConsumers", Integer.class, concurrentConsumers);
175        boolean limitConcurrentConsumers = getAndRemoveOrResolveReferenceParameter(parameters, "limitConcurrentConsumers", Boolean.class, true);
176        if (limitConcurrentConsumers && consumers >  maxConcurrentConsumers) {
177            throw new IllegalArgumentException("The limitConcurrentConsumers flag in set to true. ConcurrentConsumers cannot be set at a value greater than "
178                    + maxConcurrentConsumers + " was " + consumers);
179        }
180
181        // Resolve queue reference
182        BlockingQueue<Exchange> queue = resolveAndRemoveReferenceParameter(parameters, "queue", BlockingQueue.class);
183        SedaEndpoint answer;
184        // Resolve queue factory when no queue specified
185        if (queue == null) {
186            BlockingQueueFactory<Exchange> queueFactory = resolveAndRemoveReferenceParameter(parameters, "queueFactory", BlockingQueueFactory.class);
187            // defer creating queue till endpoint is started, so we pass the queue factory
188            answer = createEndpoint(uri, this, queueFactory, consumers);
189        } else {
190            answer = createEndpoint(uri, this, queue, consumers);
191        }
192        answer.configureProperties(parameters);
193        answer.setConcurrentConsumers(consumers);
194        answer.setLimitConcurrentConsumers(limitConcurrentConsumers);
195        return answer;
196    }
197
198    protected SedaEndpoint createEndpoint(String endpointUri, Component component, BlockingQueueFactory<Exchange> queueFactory, int concurrentConsumers) {
199        return new SedaEndpoint(endpointUri, component, queueFactory, concurrentConsumers);
200    }
201
202    protected SedaEndpoint createEndpoint(String endpointUri, Component component, BlockingQueue<Exchange> queue, int concurrentConsumers) {
203        return new SedaEndpoint(endpointUri, component, queue, concurrentConsumers);
204    }
205
206    public String getQueueKey(String uri) {
207        if (uri.contains("?")) {
208            // strip parameters
209            uri = uri.substring(0, uri.indexOf('?'));
210        }
211        return uri;
212    }
213
214    @Override
215    protected void doStop() throws Exception {
216        getQueues().clear();
217        super.doStop();
218    }
219
220    /**
221     * On shutting down the endpoint
222     * 
223     * @param endpoint the endpoint
224     */
225    void onShutdownEndpoint(SedaEndpoint endpoint) {
226        // we need to remove the endpoint from the reference counter
227        String key = getQueueKey(endpoint.getEndpointUri());
228        QueueReference ref = getQueues().get(key);
229        if (ref != null && endpoint.getConsumers().size() == 0) {
230            // only remove the endpoint when the consumers are removed
231            ref.removeReference(endpoint);
232            if (ref.getCount() <= 0) {
233                // reference no longer needed so remove from queues
234                getQueues().remove(key);
235            }
236        }
237    }
238
239}