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