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.processor; 018 019import java.util.concurrent.Callable; 020import java.util.concurrent.ExecutorService; 021 022import org.apache.camel.AsyncCallback; 023import org.apache.camel.AsyncProcessor; 024import org.apache.camel.CamelContext; 025import org.apache.camel.Exchange; 026import org.apache.camel.ExchangePattern; 027import org.apache.camel.Message; 028import org.apache.camel.Ordered; 029import org.apache.camel.Predicate; 030import org.apache.camel.Processor; 031import org.apache.camel.Route; 032import org.apache.camel.Traceable; 033import org.apache.camel.spi.IdAware; 034import org.apache.camel.support.ServiceSupport; 035import org.apache.camel.support.SynchronizationAdapter; 036import org.apache.camel.util.AsyncProcessorHelper; 037import org.apache.camel.util.ExchangeHelper; 038import org.apache.camel.util.ServiceHelper; 039import org.slf4j.Logger; 040import org.slf4j.LoggerFactory; 041 042import static org.apache.camel.util.ObjectHelper.notNull; 043 044/** 045 * Processor implementing <a href="http://camel.apache.org/oncompletion.html">onCompletion</a>. 046 * 047 * @version 048 */ 049public class OnCompletionProcessor extends ServiceSupport implements AsyncProcessor, Traceable, IdAware { 050 051 private static final Logger LOG = LoggerFactory.getLogger(OnCompletionProcessor.class); 052 private final CamelContext camelContext; 053 private String id; 054 private final Processor processor; 055 private final ExecutorService executorService; 056 private final boolean shutdownExecutorService; 057 private final boolean onCompleteOnly; 058 private final boolean onFailureOnly; 059 private final Predicate onWhen; 060 private final boolean useOriginalBody; 061 private final boolean afterConsumer; 062 063 public OnCompletionProcessor(CamelContext camelContext, Processor processor, ExecutorService executorService, boolean shutdownExecutorService, 064 boolean onCompleteOnly, boolean onFailureOnly, Predicate onWhen, boolean useOriginalBody, boolean afterConsumer) { 065 notNull(camelContext, "camelContext"); 066 notNull(processor, "processor"); 067 this.camelContext = camelContext; 068 this.processor = processor; 069 this.executorService = executorService; 070 this.shutdownExecutorService = shutdownExecutorService; 071 this.onCompleteOnly = onCompleteOnly; 072 this.onFailureOnly = onFailureOnly; 073 this.onWhen = onWhen; 074 this.useOriginalBody = useOriginalBody; 075 this.afterConsumer = afterConsumer; 076 } 077 078 @Override 079 protected void doStart() throws Exception { 080 ServiceHelper.startService(processor); 081 } 082 083 @Override 084 protected void doStop() throws Exception { 085 ServiceHelper.stopService(processor); 086 } 087 088 @Override 089 protected void doShutdown() throws Exception { 090 ServiceHelper.stopAndShutdownService(processor); 091 if (shutdownExecutorService) { 092 getCamelContext().getExecutorServiceManager().shutdownNow(executorService); 093 } 094 } 095 096 public CamelContext getCamelContext() { 097 return camelContext; 098 } 099 100 public String getId() { 101 return id; 102 } 103 104 public void setId(String id) { 105 this.id = id; 106 } 107 108 public void process(Exchange exchange) throws Exception { 109 AsyncProcessorHelper.process(this, exchange); 110 } 111 112 public boolean process(Exchange exchange, AsyncCallback callback) { 113 if (processor != null) { 114 // register callback 115 if (afterConsumer) { 116 exchange.getUnitOfWork().addSynchronization(new OnCompletionSynchronizationAfterConsumer()); 117 } else { 118 exchange.getUnitOfWork().addSynchronization(new OnCompletionSynchronizationBeforeConsumer()); 119 } 120 } 121 122 callback.done(true); 123 return true; 124 } 125 126 protected boolean isCreateCopy() { 127 // we need to create a correlated copy if we run in parallel mode or is in after consumer mode (as the UoW would be done on the original exchange otherwise) 128 return executorService != null || afterConsumer; 129 } 130 131 /** 132 * Processes the exchange by the processors 133 * 134 * @param processor the processor 135 * @param exchange the exchange 136 */ 137 protected static void doProcess(Processor processor, Exchange exchange) { 138 // must remember some properties which we cannot use during onCompletion processing 139 // as otherwise we may cause issues 140 // but keep the caused exception stored as a property (Exchange.EXCEPTION_CAUGHT) on the exchange 141 Object stop = exchange.removeProperty(Exchange.ROUTE_STOP); 142 Object failureHandled = exchange.removeProperty(Exchange.FAILURE_HANDLED); 143 Object errorhandlerHandled = exchange.removeProperty(Exchange.ERRORHANDLER_HANDLED); 144 Object rollbackOnly = exchange.removeProperty(Exchange.ROLLBACK_ONLY); 145 Object rollbackOnlyLast = exchange.removeProperty(Exchange.ROLLBACK_ONLY_LAST); 146 // and we should not be regarded as exhausted as we are in a onCompletion block 147 Object exhausted = exchange.removeProperty(Exchange.REDELIVERY_EXHAUSTED); 148 149 Exception cause = exchange.getException(); 150 exchange.setException(null); 151 152 try { 153 processor.process(exchange); 154 } catch (Exception e) { 155 exchange.setException(e); 156 } finally { 157 // restore the options 158 if (stop != null) { 159 exchange.setProperty(Exchange.ROUTE_STOP, stop); 160 } 161 if (failureHandled != null) { 162 exchange.setProperty(Exchange.FAILURE_HANDLED, failureHandled); 163 } 164 if (errorhandlerHandled != null) { 165 exchange.setProperty(Exchange.ERRORHANDLER_HANDLED, errorhandlerHandled); 166 } 167 if (rollbackOnly != null) { 168 exchange.setProperty(Exchange.ROLLBACK_ONLY, rollbackOnly); 169 } 170 if (rollbackOnlyLast != null) { 171 exchange.setProperty(Exchange.ROLLBACK_ONLY_LAST, rollbackOnlyLast); 172 } 173 if (exhausted != null) { 174 exchange.setProperty(Exchange.REDELIVERY_EXHAUSTED, exhausted); 175 } 176 if (cause != null) { 177 exchange.setException(cause); 178 } 179 } 180 } 181 182 /** 183 * Prepares the {@link Exchange} to send as onCompletion. 184 * 185 * @param exchange the current exchange 186 * @return the exchange to be routed in onComplete 187 */ 188 protected Exchange prepareExchange(Exchange exchange) { 189 Exchange answer; 190 191 if (isCreateCopy()) { 192 // for asynchronous routing we must use a copy as we dont want it 193 // to cause side effects of the original exchange 194 // (the original thread will run in parallel) 195 answer = ExchangeHelper.createCorrelatedCopy(exchange, false); 196 if (answer.hasOut()) { 197 // move OUT to IN (pipes and filters) 198 answer.setIn(answer.getOut()); 199 answer.setOut(null); 200 } 201 // set MEP to InOnly as this onCompletion is a fire and forget 202 answer.setPattern(ExchangePattern.InOnly); 203 } else { 204 // use the exchange as-is 205 answer = exchange; 206 } 207 208 if (useOriginalBody) { 209 LOG.trace("Using the original IN message instead of current"); 210 211 Message original = ExchangeHelper.getOriginalInMessage(exchange); 212 answer.setIn(original); 213 } 214 215 // add a header flag to indicate its a on completion exchange 216 answer.setProperty(Exchange.ON_COMPLETION, Boolean.TRUE); 217 218 return answer; 219 } 220 221 private final class OnCompletionSynchronizationAfterConsumer extends SynchronizationAdapter implements Ordered { 222 223 public int getOrder() { 224 // we want to be last 225 return Ordered.LOWEST; 226 } 227 228 @Override 229 public void onComplete(final Exchange exchange) { 230 if (onFailureOnly) { 231 return; 232 } 233 234 if (onWhen != null && !onWhen.matches(exchange)) { 235 // predicate did not match so do not route the onComplete 236 return; 237 } 238 239 // must use a copy as we dont want it to cause side effects of the original exchange 240 final Exchange copy = prepareExchange(exchange); 241 242 if (executorService != null) { 243 executorService.submit(new Callable<Exchange>() { 244 public Exchange call() throws Exception { 245 LOG.debug("Processing onComplete: {}", copy); 246 doProcess(processor, copy); 247 return copy; 248 } 249 }); 250 } else { 251 // run without thread-pool 252 LOG.debug("Processing onComplete: {}", copy); 253 doProcess(processor, copy); 254 } 255 } 256 257 public void onFailure(final Exchange exchange) { 258 if (onCompleteOnly) { 259 return; 260 } 261 262 if (onWhen != null && !onWhen.matches(exchange)) { 263 // predicate did not match so do not route the onComplete 264 return; 265 } 266 267 268 // must use a copy as we dont want it to cause side effects of the original exchange 269 final Exchange copy = prepareExchange(exchange); 270 final Exception original = copy.getException(); 271 final boolean originalFault = copy.hasOut() ? copy.getOut().isFault() : copy.getIn().isFault(); 272 // must remove exception otherwise onFailure routing will fail as well 273 // the caused exception is stored as a property (Exchange.EXCEPTION_CAUGHT) on the exchange 274 copy.setException(null); 275 // must clear fault otherwise onFailure routing will fail as well 276 if (copy.hasOut()) { 277 copy.getOut().setFault(false); 278 } else { 279 copy.getIn().setFault(false); 280 } 281 282 if (executorService != null) { 283 executorService.submit(new Callable<Exchange>() { 284 public Exchange call() throws Exception { 285 LOG.debug("Processing onFailure: {}", copy); 286 doProcess(processor, copy); 287 // restore exception after processing 288 copy.setException(original); 289 return null; 290 } 291 }); 292 } else { 293 // run without thread-pool 294 LOG.debug("Processing onFailure: {}", copy); 295 doProcess(processor, copy); 296 // restore exception after processing 297 copy.setException(original); 298 // restore fault after processing 299 if (copy.hasOut()) { 300 copy.getOut().setFault(originalFault); 301 } else { 302 copy.getIn().setFault(originalFault); 303 } 304 } 305 } 306 307 @Override 308 public String toString() { 309 if (!onCompleteOnly && !onFailureOnly) { 310 return "onCompleteOrFailure"; 311 } else if (onCompleteOnly) { 312 return "onCompleteOnly"; 313 } else { 314 return "onFailureOnly"; 315 } 316 } 317 } 318 319 private final class OnCompletionSynchronizationBeforeConsumer extends SynchronizationAdapter implements Ordered { 320 321 public int getOrder() { 322 // we want to be last 323 return Ordered.LOWEST; 324 } 325 326 @Override 327 public void onAfterRoute(Route route, Exchange exchange) { 328 if (exchange.isFailed() && onCompleteOnly) { 329 return; 330 } 331 332 if (!exchange.isFailed() && onFailureOnly) { 333 return; 334 } 335 336 if (onWhen != null && !onWhen.matches(exchange)) { 337 // predicate did not match so do not route the onComplete 338 return; 339 } 340 341 // must use a copy as we dont want it to cause side effects of the original exchange 342 final Exchange copy = prepareExchange(exchange); 343 344 if (executorService != null) { 345 executorService.submit(new Callable<Exchange>() { 346 public Exchange call() throws Exception { 347 LOG.debug("Processing onAfterRoute: {}", copy); 348 doProcess(processor, copy); 349 return copy; 350 } 351 }); 352 } else { 353 // run without thread-pool 354 LOG.debug("Processing onAfterRoute: {}", copy); 355 doProcess(processor, copy); 356 } 357 } 358 359 @Override 360 public String toString() { 361 return "onAfterRoute"; 362 } 363 } 364 365 @Override 366 public String toString() { 367 return "OnCompletionProcessor[" + processor + "]"; 368 } 369 370 public String getTraceLabel() { 371 return "onCompletion"; 372 } 373}