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