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.List; 020import java.util.concurrent.BlockingQueue; 021import java.util.concurrent.CountDownLatch; 022import java.util.concurrent.ExecutorService; 023import java.util.concurrent.TimeUnit; 024import java.util.concurrent.atomic.AtomicInteger; 025 026import org.apache.camel.AsyncCallback; 027import org.apache.camel.AsyncProcessor; 028import org.apache.camel.Consumer; 029import org.apache.camel.Endpoint; 030import org.apache.camel.Exchange; 031import org.apache.camel.Processor; 032import org.apache.camel.ShutdownRunningTask; 033import org.apache.camel.Suspendable; 034import org.apache.camel.processor.MulticastProcessor; 035import org.apache.camel.spi.ExceptionHandler; 036import org.apache.camel.spi.ShutdownAware; 037import org.apache.camel.spi.Synchronization; 038import org.apache.camel.support.EmptyAsyncCallback; 039import org.apache.camel.support.LoggingExceptionHandler; 040import org.apache.camel.support.ServiceSupport; 041import org.apache.camel.util.AsyncProcessorConverterHelper; 042import org.apache.camel.util.ExchangeHelper; 043import org.apache.camel.util.ObjectHelper; 044import org.apache.camel.util.UnitOfWorkHelper; 045import org.slf4j.Logger; 046import org.slf4j.LoggerFactory; 047 048/** 049 * A Consumer for the SEDA component. 050 * <p/> 051 * In this implementation there is a little <i>slack period</i> when you suspend/stop the consumer, by which 052 * the consumer may pickup a newly arrived messages and process it. That period is up till 1 second. 053 * 054 * @version 055 */ 056public class SedaConsumer extends ServiceSupport implements Consumer, Runnable, ShutdownAware, Suspendable { 057 private static final Logger LOG = LoggerFactory.getLogger(SedaConsumer.class); 058 059 private final AtomicInteger taskCount = new AtomicInteger(); 060 private volatile CountDownLatch latch; 061 private volatile boolean shutdownPending; 062 private volatile boolean forceShutdown; 063 private SedaEndpoint endpoint; 064 private AsyncProcessor processor; 065 private ExecutorService executor; 066 private ExceptionHandler exceptionHandler; 067 private final int pollTimeout; 068 069 public SedaConsumer(SedaEndpoint endpoint, Processor processor) { 070 this.endpoint = endpoint; 071 this.processor = AsyncProcessorConverterHelper.convert(processor); 072 this.pollTimeout = endpoint.getPollTimeout(); 073 this.exceptionHandler = new LoggingExceptionHandler(endpoint.getCamelContext(), getClass()); 074 } 075 076 @Override 077 public String toString() { 078 return "SedaConsumer[" + endpoint + "]"; 079 } 080 081 public Endpoint getEndpoint() { 082 return endpoint; 083 } 084 085 public ExceptionHandler getExceptionHandler() { 086 return exceptionHandler; 087 } 088 089 public void setExceptionHandler(ExceptionHandler exceptionHandler) { 090 this.exceptionHandler = exceptionHandler; 091 } 092 093 public Processor getProcessor() { 094 return processor; 095 } 096 097 public boolean deferShutdown(ShutdownRunningTask shutdownRunningTask) { 098 // deny stopping on shutdown as we want seda consumers to run in case some other queues 099 // depend on this consumer to run, so it can complete its exchanges 100 return true; 101 } 102 103 public int getPendingExchangesSize() { 104 // the route is shutting down, so either we should purge the queue, 105 // or return how many exchanges are still on the queue 106 if (endpoint.isPurgeWhenStopping()) { 107 endpoint.purgeQueue(); 108 } 109 return endpoint.getQueue().size(); 110 } 111 112 @Override 113 public void prepareShutdown(boolean suspendOnly, boolean forced) { 114 // if we are suspending then we want to keep the thread running but just not route the exchange 115 // this logic is only when we stop or shutdown the consumer 116 if (suspendOnly) { 117 LOG.debug("Skip preparing to shutdown as consumer is being suspended"); 118 return; 119 } 120 121 // signal we want to shutdown 122 shutdownPending = true; 123 forceShutdown = forced; 124 125 if (latch != null) { 126 LOG.debug("Preparing to shutdown, waiting for {} consumer threads to complete.", latch.getCount()); 127 128 // wait for all threads to end 129 try { 130 latch.await(); 131 } catch (InterruptedException e) { 132 // ignore 133 } 134 } 135 } 136 137 @Override 138 public boolean isRunAllowed() { 139 // if we force shutdown then do not allow running anymore 140 if (forceShutdown) { 141 return false; 142 } 143 144 if (isSuspending() || isSuspended()) { 145 // allow to run even if we are suspended as we want to 146 // keep the thread task running 147 return true; 148 } 149 return super.isRunAllowed(); 150 } 151 152 public void run() { 153 taskCount.incrementAndGet(); 154 try { 155 doRun(); 156 } finally { 157 taskCount.decrementAndGet(); 158 latch.countDown(); 159 LOG.debug("Ending this polling consumer thread, there are still {} consumer threads left.", latch.getCount()); 160 } 161 } 162 163 protected void doRun() { 164 BlockingQueue<Exchange> queue = endpoint.getQueue(); 165 // loop while we are allowed, or if we are stopping loop until the queue is empty 166 while (queue != null && isRunAllowed()) { 167 168 // do not poll during CamelContext is starting, as we should only poll when CamelContext is fully started 169 if (getEndpoint().getCamelContext().getStatus().isStarting()) { 170 LOG.trace("CamelContext is starting so skip polling"); 171 try { 172 // sleep at most 1 sec 173 Thread.sleep(Math.min(pollTimeout, 1000)); 174 } catch (InterruptedException e) { 175 LOG.debug("Sleep interrupted, are we stopping? {}", isStopping() || isStopped()); 176 } 177 continue; 178 } 179 180 // do not poll if we are suspended or starting again after resuming 181 if (isSuspending() || isSuspended() || isStarting()) { 182 if (shutdownPending && queue.isEmpty()) { 183 LOG.trace("Consumer is suspended and shutdown is pending, so this consumer thread is breaking out because the task queue is empty."); 184 // we want to shutdown so break out if there queue is empty 185 break; 186 } else { 187 LOG.trace("Consumer is suspended so skip polling"); 188 try { 189 // sleep at most 1 sec 190 Thread.sleep(Math.min(pollTimeout, 1000)); 191 } catch (InterruptedException e) { 192 LOG.debug("Sleep interrupted, are we stopping? {}", isStopping() || isStopped()); 193 } 194 continue; 195 } 196 } 197 198 Exchange exchange = null; 199 try { 200 // use the end user configured poll timeout 201 exchange = queue.poll(pollTimeout, TimeUnit.MILLISECONDS); 202 if (LOG.isTraceEnabled()) { 203 LOG.trace("Polled queue {} with timeout {} ms. -> {}", new Object[]{ObjectHelper.getIdentityHashCode(queue), pollTimeout, exchange}); 204 } 205 if (exchange != null) { 206 try { 207 // send a new copied exchange with new camel context 208 Exchange newExchange = prepareExchange(exchange); 209 // process the exchange 210 sendToConsumers(newExchange); 211 // copy the message back 212 if (newExchange.hasOut()) { 213 exchange.setOut(newExchange.getOut().copy()); 214 } else { 215 exchange.setIn(newExchange.getIn()); 216 } 217 // log exception if an exception occurred and was not handled 218 if (newExchange.getException() != null) { 219 exchange.setException(newExchange.getException()); 220 getExceptionHandler().handleException("Error processing exchange", exchange, exchange.getException()); 221 } 222 } catch (Exception e) { 223 getExceptionHandler().handleException("Error processing exchange", exchange, e); 224 } 225 } else if (shutdownPending && queue.isEmpty()) { 226 LOG.trace("Shutdown is pending, so this consumer thread is breaking out because the task queue is empty."); 227 // we want to shutdown so break out if there queue is empty 228 break; 229 } 230 } catch (InterruptedException e) { 231 LOG.debug("Sleep interrupted, are we stopping? {}", isStopping() || isStopped()); 232 continue; 233 } catch (Throwable e) { 234 if (exchange != null) { 235 getExceptionHandler().handleException("Error processing exchange", exchange, e); 236 } else { 237 getExceptionHandler().handleException(e); 238 } 239 } 240 } 241 } 242 243 /** 244 * Strategy to prepare exchange for being processed by this consumer 245 * 246 * @param exchange the exchange 247 * @return the exchange to process by this consumer. 248 */ 249 protected Exchange prepareExchange(Exchange exchange) { 250 // send a new copied exchange with new camel context 251 Exchange newExchange = ExchangeHelper.copyExchangeAndSetCamelContext(exchange, endpoint.getCamelContext()); 252 // set the from endpoint 253 newExchange.setFromEndpoint(endpoint); 254 return newExchange; 255 } 256 257 /** 258 * Send the given {@link Exchange} to the consumer(s). 259 * <p/> 260 * If multiple consumers then they will each receive a copy of the Exchange. 261 * A multicast processor will send the exchange in parallel to the multiple consumers. 262 * <p/> 263 * If there is only a single consumer then its dispatched directly to it using same thread. 264 * 265 * @param exchange the exchange 266 * @throws Exception can be thrown if processing of the exchange failed 267 */ 268 protected void sendToConsumers(final Exchange exchange) throws Exception { 269 // validate multiple consumers has been enabled 270 int size = endpoint.getConsumers().size(); 271 if (size > 1 && !endpoint.isMultipleConsumersSupported()) { 272 throw new IllegalStateException("Multiple consumers for the same endpoint is not allowed: " + endpoint); 273 } 274 275 // if there are multiple consumers then multicast to them 276 if (endpoint.isMultipleConsumersSupported()) { 277 278 if (LOG.isTraceEnabled()) { 279 LOG.trace("Multicasting to {} consumers for Exchange: {}", size, exchange); 280 } 281 282 // handover completions, as we need to done this when the multicast is done 283 final List<Synchronization> completions = exchange.handoverCompletions(); 284 285 // use a multicast processor to process it 286 MulticastProcessor mp = endpoint.getConsumerMulticastProcessor(); 287 ObjectHelper.notNull(mp, "ConsumerMulticastProcessor", this); 288 289 // and use the asynchronous routing engine to support it 290 mp.process(exchange, new AsyncCallback() { 291 public void done(boolean doneSync) { 292 // done the uow on the completions 293 UnitOfWorkHelper.doneSynchronizations(exchange, completions, LOG); 294 } 295 }); 296 } else { 297 // use the regular processor and use the asynchronous routing engine to support it 298 processor.process(exchange, EmptyAsyncCallback.get()); 299 } 300 } 301 302 protected void doStart() throws Exception { 303 latch = new CountDownLatch(endpoint.getConcurrentConsumers()); 304 shutdownPending = false; 305 forceShutdown = false; 306 307 setupTasks(); 308 endpoint.onStarted(this); 309 } 310 311 @Override 312 protected void doSuspend() throws Exception { 313 endpoint.onStopped(this); 314 } 315 316 @Override 317 protected void doResume() throws Exception { 318 endpoint.onStarted(this); 319 } 320 321 protected void doStop() throws Exception { 322 // ensure queue is purged if we stop the consumer 323 if (endpoint.isPurgeWhenStopping()) { 324 endpoint.purgeQueue(); 325 } 326 327 endpoint.onStopped(this); 328 329 shutdownExecutor(); 330 } 331 332 @Override 333 protected void doShutdown() throws Exception { 334 shutdownExecutor(); 335 } 336 337 private void shutdownExecutor() { 338 if (executor != null) { 339 endpoint.getCamelContext().getExecutorServiceManager().shutdownNow(executor); 340 executor = null; 341 } 342 } 343 344 /** 345 * Setup the thread pool and ensures tasks gets executed (if needed) 346 */ 347 private void setupTasks() { 348 int poolSize = endpoint.getConcurrentConsumers(); 349 350 // create thread pool if needed 351 if (executor == null) { 352 executor = endpoint.getCamelContext().getExecutorServiceManager().newFixedThreadPool(this, endpoint.getEndpointUri(), poolSize); 353 } 354 355 // submit needed number of tasks 356 int tasks = poolSize - taskCount.get(); 357 LOG.debug("Creating {} consumer tasks with poll timeout {} ms.", tasks, pollTimeout); 358 for (int i = 0; i < tasks; i++) { 359 executor.execute(this); 360 } 361 } 362 363}