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