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.ArrayList;
020import java.util.HashSet;
021import java.util.List;
022import java.util.Set;
023import java.util.concurrent.BlockingQueue;
024import java.util.concurrent.CopyOnWriteArraySet;
025import java.util.concurrent.ExecutorService;
026
027import org.apache.camel.AsyncEndpoint;
028import org.apache.camel.Component;
029import org.apache.camel.Consumer;
030import org.apache.camel.Exchange;
031import org.apache.camel.MultipleConsumersSupport;
032import org.apache.camel.PollingConsumer;
033import org.apache.camel.Processor;
034import org.apache.camel.Producer;
035import org.apache.camel.WaitForTaskToComplete;
036import org.apache.camel.api.management.ManagedAttribute;
037import org.apache.camel.api.management.ManagedOperation;
038import org.apache.camel.api.management.ManagedResource;
039import org.apache.camel.impl.DefaultEndpoint;
040import org.apache.camel.processor.MulticastProcessor;
041import org.apache.camel.spi.BrowsableEndpoint;
042import org.apache.camel.spi.Metadata;
043import org.apache.camel.spi.UriEndpoint;
044import org.apache.camel.spi.UriParam;
045import org.apache.camel.spi.UriPath;
046import org.apache.camel.util.ServiceHelper;
047import org.apache.camel.util.URISupport;
048import org.slf4j.Logger;
049import org.slf4j.LoggerFactory;
050
051/**
052 * The seda component provides asynchronous call to another endpoint from any CamelContext in the same JVM.
053 */
054@ManagedResource(description = "Managed SedaEndpoint")
055@UriEndpoint(firstVersion = "1.1.0", scheme = "seda", title = "SEDA", syntax = "seda:name", consumerClass = SedaConsumer.class, label = "core,endpoint")
056public class SedaEndpoint extends DefaultEndpoint implements AsyncEndpoint, BrowsableEndpoint, MultipleConsumersSupport {
057    private static final Logger LOG = LoggerFactory.getLogger(SedaEndpoint.class);
058    private final Set<SedaProducer> producers = new CopyOnWriteArraySet<SedaProducer>();
059    private final Set<SedaConsumer> consumers = new CopyOnWriteArraySet<SedaConsumer>();
060    private volatile MulticastProcessor consumerMulticastProcessor;
061    private volatile boolean multicastStarted;
062    private volatile ExecutorService multicastExecutor;
063
064    @UriPath(description = "Name of queue") @Metadata(required = "true")
065    private String name;
066    @UriParam(label = "advanced", description = "Define the queue instance which will be used by the endpoint")
067    private BlockingQueue queue;
068    @UriParam(defaultValue = "" + Integer.MAX_VALUE)
069    private int size = Integer.MAX_VALUE;
070
071    @UriParam(label = "consumer", defaultValue = "1")
072    private int concurrentConsumers = 1;
073    @UriParam(label = "consumer,advanced", defaultValue = "true")
074    private boolean limitConcurrentConsumers = true;
075    @UriParam(label = "consumer,advanced")
076    private boolean multipleConsumers;
077    @UriParam(label = "consumer,advanced")
078    private boolean purgeWhenStopping;
079    @UriParam(label = "consumer,advanced", defaultValue = "1000")
080    private int pollTimeout = 1000;
081
082    @UriParam(label = "producer", defaultValue = "IfReplyExpected")
083    private WaitForTaskToComplete waitForTaskToComplete = WaitForTaskToComplete.IfReplyExpected;
084    @UriParam(label = "producer", defaultValue = "30000")
085    private long timeout = 30000;
086    @UriParam(label = "producer")
087    private boolean blockWhenFull;
088    @UriParam(label = "producer")
089    private boolean failIfNoConsumers;
090    @UriParam(label = "producer")
091    private boolean discardIfNoConsumers;
092
093    private BlockingQueueFactory<Exchange> queueFactory;
094
095    public SedaEndpoint() {
096        queueFactory = new LinkedBlockingQueueFactory<Exchange>();
097    }
098
099    public SedaEndpoint(String endpointUri, Component component, BlockingQueue<Exchange> queue) {
100        this(endpointUri, component, queue, 1);
101    }
102
103    public SedaEndpoint(String endpointUri, Component component, BlockingQueue<Exchange> queue, int concurrentConsumers) {
104        this(endpointUri, component, concurrentConsumers);
105        this.queue = queue;
106        if (queue != null) {
107            this.size = queue.remainingCapacity();
108        }
109        queueFactory = new LinkedBlockingQueueFactory<Exchange>();
110        getComponent().registerQueue(this, queue);
111    }
112
113    public SedaEndpoint(String endpointUri, Component component, BlockingQueueFactory<Exchange> queueFactory, int concurrentConsumers) {
114        this(endpointUri, component, concurrentConsumers);
115        this.queueFactory = queueFactory;
116    }
117
118    private SedaEndpoint(String endpointUri, Component component, int concurrentConsumers) {
119        super(endpointUri, component);
120        this.concurrentConsumers = concurrentConsumers;
121    }
122
123    @Override
124    public SedaComponent getComponent() {
125        return (SedaComponent) super.getComponent();
126    }
127
128    public Producer createProducer() throws Exception {
129        return new SedaProducer(this, getWaitForTaskToComplete(), getTimeout(), isBlockWhenFull());
130    }
131
132    public Consumer createConsumer(Processor processor) throws Exception {
133        if (getComponent() != null) {
134            // all consumers must match having the same multipleConsumers options
135            String key = getComponent().getQueueKey(getEndpointUri());
136            QueueReference ref = getComponent().getQueueReference(key);
137            if (ref != null && ref.getMultipleConsumers() != isMultipleConsumers()) {
138                // there is already a multiple consumers, so make sure they matches
139                throw new IllegalArgumentException("Cannot use existing queue " + key + " as the existing queue multiple consumers "
140                        + ref.getMultipleConsumers() + " does not match given multiple consumers " + multipleConsumers);
141            }
142        }
143
144        Consumer answer = createNewConsumer(processor);
145        configureConsumer(answer);
146        return answer;
147    }
148
149    protected SedaConsumer createNewConsumer(Processor processor) {
150        return new SedaConsumer(this, processor);
151    }
152
153    @Override
154    public PollingConsumer createPollingConsumer() throws Exception {
155        SedaPollingConsumer answer = new SedaPollingConsumer(this);
156        configureConsumer(answer);
157        return answer;
158    }
159
160    public synchronized BlockingQueue<Exchange> getQueue() {
161        if (queue == null) {
162            // prefer to lookup queue from component, so if this endpoint is re-created or re-started
163            // then the existing queue from the component can be used, so new producers and consumers
164            // can use the already existing queue referenced from the component
165            if (getComponent() != null) {
166                // use null to indicate default size (= use what the existing queue has been configured with)
167                Integer size = getSize() == Integer.MAX_VALUE ? null : getSize();
168                QueueReference ref = getComponent().getOrCreateQueue(this, size, isMultipleConsumers(), queueFactory);
169                queue = ref.getQueue();
170                String key = getComponent().getQueueKey(getEndpointUri());
171                LOG.info("Endpoint {} is using shared queue: {} with size: {}", new Object[]{this, key, ref.getSize() !=  null ? ref.getSize() : Integer.MAX_VALUE});
172                // and set the size we are using
173                if (ref.getSize() != null) {
174                    setSize(ref.getSize());
175                }
176            } else {
177                // fallback and create queue (as this endpoint has no component)
178                queue = createQueue();
179                LOG.info("Endpoint {} is using queue: {} with size: {}", new Object[]{this, getEndpointUri(), getSize()});
180            }
181        }
182        return queue;
183    }
184
185    protected BlockingQueue<Exchange> createQueue() {
186        if (size > 0) {
187            return queueFactory.create(size);
188        } else {
189            return queueFactory.create();
190        }
191    }
192
193    /**
194     * Get's the {@link QueueReference} for the this endpoint.
195     * @return the reference, or <tt>null</tt> if no queue reference exists.
196     */
197    public synchronized QueueReference getQueueReference() {
198        String key = getComponent().getQueueKey(getEndpointUri());
199        QueueReference ref = getComponent().getQueueReference(key);
200        return ref;
201    }
202
203    protected synchronized MulticastProcessor getConsumerMulticastProcessor() throws Exception {
204        if (!multicastStarted && consumerMulticastProcessor != null) {
205            // only start it on-demand to avoid starting it during stopping
206            ServiceHelper.startService(consumerMulticastProcessor);
207            multicastStarted = true;
208        }
209        return consumerMulticastProcessor;
210    }
211
212    protected synchronized void updateMulticastProcessor() throws Exception {
213        // only needed if we support multiple consumers
214        if (!isMultipleConsumersSupported()) {
215            return;
216        }
217
218        // stop old before we create a new
219        if (consumerMulticastProcessor != null) {
220            ServiceHelper.stopService(consumerMulticastProcessor);
221            consumerMulticastProcessor = null;
222        }
223
224        int size = getConsumers().size();
225        if (size >= 1) {
226            if (multicastExecutor == null) {
227                // create multicast executor as we need it when we have more than 1 processor
228                multicastExecutor = getCamelContext().getExecutorServiceManager().newDefaultThreadPool(this, URISupport.sanitizeUri(getEndpointUri()) + "(multicast)");
229            }
230            // create list of consumers to multicast to
231            List<Processor> processors = new ArrayList<Processor>(size);
232            for (SedaConsumer consumer : getConsumers()) {
233                processors.add(consumer.getProcessor());
234            }
235            // create multicast processor
236            multicastStarted = false;
237            consumerMulticastProcessor = new MulticastProcessor(getCamelContext(), processors, null,
238                                                                true, multicastExecutor, false, false, false, 
239                                                                0, null, false, false);
240        }
241    }
242
243    /**
244     * Define the queue instance which will be used by the endpoint.
245     * <p/>
246     * This option is only for rare use-cases where you want to use a custom queue instance.
247     */
248    public void setQueue(BlockingQueue<Exchange> queue) {
249        this.queue = queue;
250        this.size = queue.remainingCapacity();
251    }
252
253    @ManagedAttribute(description = "Queue max capacity")
254    public int getSize() {
255        return size;
256    }
257
258    /**
259     * The maximum capacity of the SEDA queue (i.e., the number of messages it can hold).
260     */
261    public void setSize(int size) {
262        this.size = size;
263    }
264
265    @ManagedAttribute(description = "Current queue size")
266    public int getCurrentQueueSize() {
267        return queue.size();
268    }
269
270    /**
271     * Whether a thread that sends messages to a full SEDA queue will block until the queue's capacity is no longer exhausted.
272     * By default, an exception will be thrown stating that the queue is full.
273     * By enabling this option, the calling thread will instead block and wait until the message can be accepted.
274     */
275    public void setBlockWhenFull(boolean blockWhenFull) {
276        this.blockWhenFull = blockWhenFull;
277    }
278
279    @ManagedAttribute(description = "Whether the caller will block sending to a full queue")
280    public boolean isBlockWhenFull() {
281        return blockWhenFull;
282    }
283
284    /**
285     * Number of concurrent threads processing exchanges.
286     */
287    public void setConcurrentConsumers(int concurrentConsumers) {
288        this.concurrentConsumers = concurrentConsumers;
289    }
290
291    @ManagedAttribute(description = "Number of concurrent consumers")
292    public int getConcurrentConsumers() {
293        return concurrentConsumers;
294    }
295
296    @ManagedAttribute
297    public boolean isLimitConcurrentConsumers() {
298        return limitConcurrentConsumers;
299    }
300
301    /**
302     * Whether to limit the number of concurrentConsumers to the maximum of 500.
303     * By default, an exception will be thrown if an endpoint is configured with a greater number. You can disable that check by turning this option off.
304     */
305    public void setLimitConcurrentConsumers(boolean limitConcurrentConsumers) {
306        this.limitConcurrentConsumers = limitConcurrentConsumers;
307    }
308
309    public WaitForTaskToComplete getWaitForTaskToComplete() {
310        return waitForTaskToComplete;
311    }
312
313    /**
314     * Option to specify whether the caller should wait for the async task to complete or not before continuing.
315     * The following three options are supported: Always, Never or IfReplyExpected.
316     * The first two values are self-explanatory.
317     * The last value, IfReplyExpected, will only wait if the message is Request Reply based.
318     * The default option is IfReplyExpected.
319     */
320    public void setWaitForTaskToComplete(WaitForTaskToComplete waitForTaskToComplete) {
321        this.waitForTaskToComplete = waitForTaskToComplete;
322    }
323
324    @ManagedAttribute
325    public long getTimeout() {
326        return timeout;
327    }
328
329    /**
330     * Timeout (in milliseconds) before a SEDA producer will stop waiting for an asynchronous task to complete.
331     * You can disable timeout by using 0 or a negative value.
332     */
333    public void setTimeout(long timeout) {
334        this.timeout = timeout;
335    }
336
337    @ManagedAttribute
338    public boolean isFailIfNoConsumers() {
339        return failIfNoConsumers;
340    }
341
342    /**
343     * Whether the producer should fail by throwing an exception, when sending to a queue with no active consumers.
344     * <p/>
345     * Only one of the options <tt>discardIfNoConsumers</tt> and <tt>failIfNoConsumers</tt> can be enabled at the same time.
346     */
347    public void setFailIfNoConsumers(boolean failIfNoConsumers) {
348        this.failIfNoConsumers = failIfNoConsumers;
349    }
350
351    @ManagedAttribute
352    public boolean isDiscardIfNoConsumers() {
353        return discardIfNoConsumers;
354    }
355
356    /**
357     * Whether the producer should discard the message (do not add the message to the queue), when sending to a queue with no active consumers.
358     * <p/>
359     * Only one of the options <tt>discardIfNoConsumers</tt> and <tt>failIfNoConsumers</tt> can be enabled at the same time.
360     */
361    public void setDiscardIfNoConsumers(boolean discardIfNoConsumers) {
362        this.discardIfNoConsumers = discardIfNoConsumers;
363    }
364
365    @ManagedAttribute
366    public boolean isMultipleConsumers() {
367        return multipleConsumers;
368    }
369
370    /**
371     * Specifies whether multiple consumers are allowed. If enabled, you can use SEDA for Publish-Subscribe messaging.
372     * That is, you can send a message to the SEDA queue and have each consumer receive a copy of the message.
373     * When enabled, this option should be specified on every consumer endpoint.
374     */
375    public void setMultipleConsumers(boolean multipleConsumers) {
376        this.multipleConsumers = multipleConsumers;
377    }
378
379    @ManagedAttribute
380    public int getPollTimeout() {
381        return pollTimeout;
382    }
383
384    /**
385     * The timeout used when polling. When a timeout occurs, the consumer can check whether it is allowed to continue running.
386     * Setting a lower value allows the consumer to react more quickly upon shutdown.
387     */
388    public void setPollTimeout(int pollTimeout) {
389        this.pollTimeout = pollTimeout;
390    }
391
392    @ManagedAttribute
393    public boolean isPurgeWhenStopping() {
394        return purgeWhenStopping;
395    }
396
397    /**
398     * Whether to purge the task queue when stopping the consumer/route.
399     * This allows to stop faster, as any pending messages on the queue is discarded.
400     */
401    public void setPurgeWhenStopping(boolean purgeWhenStopping) {
402        this.purgeWhenStopping = purgeWhenStopping;
403    }
404
405    public boolean isSingleton() {
406        return true;
407    }
408
409    /**
410     * Returns the current pending exchanges
411     */
412    public List<Exchange> getExchanges() {
413        return new ArrayList<Exchange>(getQueue());
414    }
415
416    @ManagedAttribute
417    public boolean isMultipleConsumersSupported() {
418        return isMultipleConsumers();
419    }
420
421    /**
422     * Purges the queue
423     */
424    @ManagedOperation(description = "Purges the seda queue")
425    public void purgeQueue() {
426        LOG.debug("Purging queue with {} exchanges", queue.size());
427        queue.clear();
428    }
429
430    /**
431     * Returns the current active consumers on this endpoint
432     */
433    public Set<SedaConsumer> getConsumers() {
434        return consumers;
435    }
436
437    /**
438     * Returns the current active producers on this endpoint
439     */
440    public Set<SedaProducer> getProducers() {
441        return new HashSet<SedaProducer>(producers);
442    }
443
444    void onStarted(SedaProducer producer) {
445        producers.add(producer);
446    }
447
448    void onStopped(SedaProducer producer) {
449        producers.remove(producer);
450    }
451
452    void onStarted(SedaConsumer consumer) throws Exception {
453        consumers.add(consumer);
454        if (isMultipleConsumers()) {
455            updateMulticastProcessor();
456        }
457    }
458
459    void onStopped(SedaConsumer consumer) throws Exception {
460        consumers.remove(consumer);
461        if (isMultipleConsumers()) {
462            updateMulticastProcessor();
463        }
464    }
465
466    public boolean hasConsumers() {
467        return this.consumers.size() > 0;
468    }
469
470    @Override
471    protected void doStart() throws Exception {
472        super.doStart();
473
474        // force creating queue when starting
475        if (queue == null) {
476            queue = getQueue();
477        }
478
479        // special for unit testing where we can set a system property to make seda poll faster
480        // and therefore also react faster upon shutdown, which makes overall testing faster of the Camel project
481        String override = System.getProperty("CamelSedaPollTimeout", "" + getPollTimeout());
482        setPollTimeout(Integer.valueOf(override));
483    }
484
485    @Override
486    public void stop() throws Exception {
487        if (getConsumers().isEmpty()) {
488            super.stop();
489        } else {
490            LOG.debug("There is still active consumers.");
491        }
492    }
493
494    @Override
495    public void shutdown() throws Exception {
496        if (shutdown.get()) {
497            LOG.trace("Service already shut down");
498            return;
499        }
500
501        // notify component we are shutting down this endpoint
502        if (getComponent() != null) {
503            getComponent().onShutdownEndpoint(this);
504        }
505
506        if (getConsumers().isEmpty()) {
507            super.shutdown();
508        } else {
509            LOG.debug("There is still active consumers.");
510        }
511    }
512
513    @Override
514    protected void doShutdown() throws Exception {
515        // shutdown thread pool if it was in use
516        if (multicastExecutor != null) {
517            getCamelContext().getExecutorServiceManager().shutdownNow(multicastExecutor);
518            multicastExecutor = null;
519        }
520
521        // clear queue, as we are shutdown, so if re-created then the queue must be updated
522        queue = null;
523    }
524
525}