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}