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.Iterator; 020 021import org.apache.camel.AsyncCallback; 022import org.apache.camel.AsyncProcessor; 023import org.apache.camel.AsyncProducerCallback; 024import org.apache.camel.CamelContext; 025import org.apache.camel.Endpoint; 026import org.apache.camel.ErrorHandlerFactory; 027import org.apache.camel.Exchange; 028import org.apache.camel.ExchangePattern; 029import org.apache.camel.Expression; 030import org.apache.camel.FailedToCreateProducerException; 031import org.apache.camel.Message; 032import org.apache.camel.Processor; 033import org.apache.camel.Producer; 034import org.apache.camel.Traceable; 035import org.apache.camel.builder.ExpressionBuilder; 036import org.apache.camel.impl.DefaultExchange; 037import org.apache.camel.impl.EmptyProducerCache; 038import org.apache.camel.impl.ProducerCache; 039import org.apache.camel.spi.EndpointUtilizationStatistics; 040import org.apache.camel.spi.IdAware; 041import org.apache.camel.spi.RouteContext; 042import org.apache.camel.support.ServiceSupport; 043import org.apache.camel.util.AsyncProcessorHelper; 044import org.apache.camel.util.ExchangeHelper; 045import org.apache.camel.util.MessageHelper; 046import org.apache.camel.util.ObjectHelper; 047import org.apache.camel.util.ServiceHelper; 048import org.slf4j.Logger; 049import org.slf4j.LoggerFactory; 050 051import static org.apache.camel.processor.PipelineHelper.continueProcessing; 052import static org.apache.camel.util.ObjectHelper.notNull; 053 054/** 055 * Implements a <a href="http://camel.apache.org/routing-slip.html">Routing Slip</a> 056 * pattern where the list of actual endpoints to send a message exchange to are 057 * dependent on the value of a message header. 058 * <p/> 059 * This implementation mirrors the logic from the {@link org.apache.camel.processor.Pipeline} in the async variation 060 * as the failover load balancer is a specialized pipeline. So the trick is to keep doing the same as the 061 * pipeline to ensure it works the same and the async routing engine is flawless. 062 */ 063public class RoutingSlip extends ServiceSupport implements AsyncProcessor, Traceable, IdAware { 064 protected final Logger log = LoggerFactory.getLogger(getClass()); 065 protected String id; 066 protected ProducerCache producerCache; 067 protected int cacheSize; 068 protected boolean ignoreInvalidEndpoints; 069 protected String header; 070 protected Expression expression; 071 protected String uriDelimiter; 072 protected final CamelContext camelContext; 073 074 /** 075 * The iterator to be used for retrieving the next routing slip(s) to be used. 076 */ 077 protected interface RoutingSlipIterator { 078 079 /** 080 * Are the more routing slip(s)? 081 * 082 * @param exchange the current exchange 083 * @return <tt>true</tt> if more slips, <tt>false</tt> otherwise. 084 */ 085 boolean hasNext(Exchange exchange); 086 087 /** 088 * Returns the next routing slip(s). 089 * 090 * @param exchange the current exchange 091 * @return the slip(s). 092 */ 093 Object next(Exchange exchange); 094 095 } 096 097 public RoutingSlip(CamelContext camelContext) { 098 notNull(camelContext, "camelContext"); 099 this.camelContext = camelContext; 100 } 101 102 public RoutingSlip(CamelContext camelContext, Expression expression, String uriDelimiter) { 103 notNull(camelContext, "camelContext"); 104 notNull(expression, "expression"); 105 106 this.camelContext = camelContext; 107 this.expression = expression; 108 this.uriDelimiter = uriDelimiter; 109 this.header = null; 110 } 111 112 public String getId() { 113 return id; 114 } 115 116 public void setId(String id) { 117 this.id = id; 118 } 119 120 public Expression getExpression() { 121 return expression; 122 } 123 124 public String getUriDelimiter() { 125 return uriDelimiter; 126 } 127 128 public void setDelimiter(String delimiter) { 129 this.uriDelimiter = delimiter; 130 } 131 132 public boolean isIgnoreInvalidEndpoints() { 133 return ignoreInvalidEndpoints; 134 } 135 136 public void setIgnoreInvalidEndpoints(boolean ignoreInvalidEndpoints) { 137 this.ignoreInvalidEndpoints = ignoreInvalidEndpoints; 138 } 139 140 public int getCacheSize() { 141 return cacheSize; 142 } 143 144 public void setCacheSize(int cacheSize) { 145 this.cacheSize = cacheSize; 146 } 147 148 @Override 149 public String toString() { 150 return "RoutingSlip[expression=" + expression + " uriDelimiter=" + uriDelimiter + "]"; 151 } 152 153 public String getTraceLabel() { 154 return "routingSlip[" + expression + "]"; 155 } 156 157 public void process(Exchange exchange) throws Exception { 158 AsyncProcessorHelper.process(this, exchange); 159 } 160 161 public boolean process(Exchange exchange, AsyncCallback callback) { 162 if (!isStarted()) { 163 exchange.setException(new IllegalStateException("RoutingSlip has not been started: " + this)); 164 callback.done(true); 165 return true; 166 } 167 168 return doRoutingSlipWithExpression(exchange, this.expression, callback); 169 } 170 171 public boolean doRoutingSlip(Exchange exchange, Object routingSlip, AsyncCallback callback) { 172 if (routingSlip instanceof Expression) { 173 return doRoutingSlipWithExpression(exchange, (Expression) routingSlip, callback); 174 } else { 175 return doRoutingSlipWithExpression(exchange, ExpressionBuilder.constantExpression(routingSlip), callback); 176 } 177 } 178 179 /** 180 * Creates the route slip iterator to be used. 181 * 182 * @param exchange the exchange 183 * @param expression the expression 184 * @return the iterator, should never be <tt>null</tt> 185 */ 186 protected RoutingSlipIterator createRoutingSlipIterator(final Exchange exchange, final Expression expression) throws Exception { 187 Object slip = expression.evaluate(exchange, Object.class); 188 if (exchange.getException() != null) { 189 // force any exceptions occurred during evaluation to be thrown 190 throw exchange.getException(); 191 } 192 193 final Iterator<Object> delegate = ObjectHelper.createIterator(slip, uriDelimiter); 194 195 return new RoutingSlipIterator() { 196 public boolean hasNext(Exchange exchange) { 197 return delegate.hasNext(); 198 } 199 200 public Object next(Exchange exchange) { 201 return delegate.next(); 202 } 203 }; 204 } 205 206 private boolean doRoutingSlipWithExpression(final Exchange exchange, final Expression expression, final AsyncCallback originalCallback) { 207 Exchange current = exchange; 208 RoutingSlipIterator iter; 209 try { 210 iter = createRoutingSlipIterator(exchange, expression); 211 } catch (Exception e) { 212 exchange.setException(e); 213 originalCallback.done(true); 214 return true; 215 } 216 217 // ensure the slip is empty when we start 218 if (current.hasProperties()) { 219 current.setProperty(Exchange.SLIP_ENDPOINT, null); 220 } 221 222 while (iter.hasNext(current)) { 223 Endpoint endpoint; 224 try { 225 endpoint = resolveEndpoint(iter, exchange); 226 // if no endpoint was resolved then try the next 227 if (endpoint == null) { 228 continue; 229 } 230 } catch (Exception e) { 231 // error resolving endpoint so we should break out 232 current.setException(e); 233 break; 234 } 235 236 //process and prepare the routing slip 237 boolean sync = processExchange(endpoint, current, exchange, originalCallback, iter); 238 current = prepareExchangeForRoutingSlip(current, endpoint); 239 240 if (!sync) { 241 log.trace("Processing exchangeId: {} is continued being processed asynchronously", exchange.getExchangeId()); 242 // the remainder of the routing slip will be completed async 243 // so we break out now, then the callback will be invoked which then continue routing from where we left here 244 return false; 245 } 246 247 log.trace("Processing exchangeId: {} is continued being processed synchronously", exchange.getExchangeId()); 248 249 // we ignore some kind of exceptions and allow us to continue 250 if (isIgnoreInvalidEndpoints()) { 251 FailedToCreateProducerException e = current.getException(FailedToCreateProducerException.class); 252 if (e != null) { 253 if (log.isDebugEnabled()) { 254 log.debug("Endpoint uri is invalid: " + endpoint + ". This exception will be ignored.", e); 255 } 256 current.setException(null); 257 } 258 } 259 260 // Decide whether to continue with the recipients or not; similar logic to the Pipeline 261 // check for error if so we should break out 262 if (!continueProcessing(current, "so breaking out of the routing slip", log)) { 263 break; 264 } 265 } 266 267 // logging nextExchange as it contains the exchange that might have altered the payload and since 268 // we are logging the completion if will be confusing if we log the original instead 269 // we could also consider logging the original and the nextExchange then we have *before* and *after* snapshots 270 log.trace("Processing complete for exchangeId: {} >>> {}", exchange.getExchangeId(), current); 271 272 // copy results back to the original exchange 273 ExchangeHelper.copyResults(exchange, current); 274 275 // okay we are completely done with the routing slip 276 // so we need to signal done on the original callback so it can continue 277 originalCallback.done(true); 278 return true; 279 } 280 281 protected Endpoint resolveEndpoint(RoutingSlipIterator iter, Exchange exchange) throws Exception { 282 Object nextRecipient = iter.next(exchange); 283 Endpoint endpoint = null; 284 try { 285 endpoint = ExchangeHelper.resolveEndpoint(exchange, nextRecipient); 286 } catch (Exception e) { 287 if (isIgnoreInvalidEndpoints()) { 288 log.info("Endpoint uri is invalid: " + nextRecipient + ". This exception will be ignored.", e); 289 } else { 290 throw e; 291 } 292 } 293 return endpoint; 294 } 295 296 protected Exchange prepareExchangeForRoutingSlip(Exchange current, Endpoint endpoint) { 297 Exchange copy = new DefaultExchange(current); 298 // we must use the same id as this is a snapshot strategy where Camel copies a snapshot 299 // before processing the next step in the pipeline, so we have a snapshot of the exchange 300 // just before. This snapshot is used if Camel should do redeliveries (re try) using 301 // DeadLetterChannel. That is why it's important the id is the same, as it is the *same* 302 // exchange being routed. 303 copy.setExchangeId(current.getExchangeId()); 304 copyOutToIn(copy, current); 305 306 // ensure stream caching is reset 307 MessageHelper.resetStreamCache(copy.getIn()); 308 309 return copy; 310 } 311 312 protected AsyncProcessor createErrorHandler(RouteContext routeContext, Exchange exchange, AsyncProcessor processor, Endpoint endpoint) { 313 AsyncProcessor answer = processor; 314 315 boolean tryBlock = exchange.getProperty(Exchange.TRY_ROUTE_BLOCK, false, boolean.class); 316 317 // do not wrap in error handler if we are inside a try block 318 if (!tryBlock && routeContext != null) { 319 // wrap the producer in error handler so we have fine grained error handling on 320 // the output side instead of the input side 321 // this is needed to support redelivery on that output alone and not doing redelivery 322 // for the entire routingslip/dynamic-router block again which will start from scratch again 323 324 log.trace("Creating error handler for: {}", processor); 325 ErrorHandlerFactory builder = routeContext.getRoute().getErrorHandlerBuilder(); 326 // create error handler (create error handler directly to keep it light weight, 327 // instead of using ProcessorDefinition.wrapInErrorHandler) 328 try { 329 answer = (AsyncProcessor) builder.createErrorHandler(routeContext, processor); 330 331 // must start the error handler 332 ServiceHelper.startServices(answer); 333 334 } catch (Exception e) { 335 throw ObjectHelper.wrapRuntimeCamelException(e); 336 } 337 } 338 339 return answer; 340 } 341 342 protected boolean processExchange(final Endpoint endpoint, final Exchange exchange, final Exchange original, 343 final AsyncCallback originalCallback, final RoutingSlipIterator iter) { 344 345 // this does the actual processing so log at trace level 346 log.trace("Processing exchangeId: {} >>> {}", exchange.getExchangeId(), exchange); 347 348 // routing slip callback which are used when 349 // - routing slip was routed asynchronously 350 // - and we are completely done with the routing slip 351 // so we need to signal done on the original callback so it can continue 352 AsyncCallback callback = new AsyncCallback() { 353 @Override 354 public void done(boolean doneSync) { 355 if (!doneSync) { 356 originalCallback.done(false); 357 } 358 } 359 }; 360 boolean sync = producerCache.doInAsyncProducer(endpoint, exchange, null, callback, new AsyncProducerCallback() { 361 public boolean doInAsyncProducer(Producer producer, AsyncProcessor asyncProducer, final Exchange exchange, 362 ExchangePattern exchangePattern, final AsyncCallback callback) { 363 364 // rework error handling to support fine grained error handling 365 RouteContext routeContext = exchange.getUnitOfWork() != null ? exchange.getUnitOfWork().getRouteContext() : null; 366 AsyncProcessor target = createErrorHandler(routeContext, exchange, asyncProducer, endpoint); 367 368 // set property which endpoint we send to 369 exchange.setProperty(Exchange.TO_ENDPOINT, endpoint.getEndpointUri()); 370 exchange.setProperty(Exchange.SLIP_ENDPOINT, endpoint.getEndpointUri()); 371 372 boolean answer = target.process(exchange, new AsyncCallback() { 373 public void done(boolean doneSync) { 374 // we only have to handle async completion of the routing slip 375 if (doneSync) { 376 callback.done(true); 377 return; 378 } 379 380 try { 381 // continue processing the routing slip asynchronously 382 Exchange current = prepareExchangeForRoutingSlip(exchange, endpoint); 383 384 while (iter.hasNext(current)) { 385 386 // we ignore some kind of exceptions and allow us to continue 387 if (isIgnoreInvalidEndpoints()) { 388 FailedToCreateProducerException e = current.getException(FailedToCreateProducerException.class); 389 if (e != null) { 390 if (log.isDebugEnabled()) { 391 log.debug("Endpoint uri is invalid: " + endpoint + ". This exception will be ignored.", e); 392 } 393 current.setException(null); 394 } 395 } 396 397 // Decide whether to continue with the recipients or not; similar logic to the Pipeline 398 // check for error if so we should break out 399 if (!continueProcessing(current, "so breaking out of the routing slip", log)) { 400 break; 401 } 402 403 Endpoint endpoint; 404 try { 405 endpoint = resolveEndpoint(iter, exchange); 406 // if no endpoint was resolved then try the next 407 if (endpoint == null) { 408 continue; 409 } 410 } catch (Exception e) { 411 // error resolving endpoint so we should break out 412 exchange.setException(e); 413 break; 414 } 415 416 // prepare and process the routing slip 417 boolean sync = processExchange(endpoint, current, original, callback, iter); 418 current = prepareExchangeForRoutingSlip(current, endpoint); 419 420 if (!sync) { 421 log.trace("Processing exchangeId: {} is continued being processed asynchronously", original.getExchangeId()); 422 return; 423 } 424 } 425 426 // logging nextExchange as it contains the exchange that might have altered the payload and since 427 // we are logging the completion if will be confusing if we log the original instead 428 // we could also consider logging the original and the nextExchange then we have *before* and *after* snapshots 429 log.trace("Processing complete for exchangeId: {} >>> {}", original.getExchangeId(), current); 430 431 // copy results back to the original exchange 432 ExchangeHelper.copyResults(original, current); 433 434 if (target instanceof DeadLetterChannel) { 435 Processor deadLetter = ((DeadLetterChannel) target).getDeadLetter(); 436 try { 437 ServiceHelper.stopService(deadLetter); 438 } catch (Exception e) { 439 log.warn("Error stopping DeadLetterChannel error handler on routing slip. This exception is ignored.", e); 440 } 441 } 442 } catch (Throwable e) { 443 exchange.setException(e); 444 } 445 446 // okay we are completely done with the routing slip 447 // so we need to signal done on the original callback so it can continue 448 originalCallback.done(false); 449 } 450 }); 451 452 // stop error handler if we completed synchronously 453 if (answer) { 454 if (target instanceof DeadLetterChannel) { 455 Processor deadLetter = ((DeadLetterChannel) target).getDeadLetter(); 456 try { 457 ServiceHelper.stopService(deadLetter); 458 } catch (Exception e) { 459 log.warn("Error stopping DeadLetterChannel error handler on routing slip. This exception is ignored.", e); 460 } 461 } 462 } 463 464 return answer; 465 } 466 }); 467 468 return sync; 469 } 470 471 protected void doStart() throws Exception { 472 if (producerCache == null) { 473 if (cacheSize < 0) { 474 producerCache = new EmptyProducerCache(this, camelContext); 475 log.debug("RoutingSlip {} is not using ProducerCache", this); 476 } else if (cacheSize == 0) { 477 producerCache = new ProducerCache(this, camelContext); 478 log.debug("RoutingSlip {} using ProducerCache with default cache size", this); 479 } else { 480 producerCache = new ProducerCache(this, camelContext, cacheSize); 481 log.debug("RoutingSlip {} using ProducerCache with cacheSize={}", this, cacheSize); 482 } 483 } 484 ServiceHelper.startService(producerCache); 485 } 486 487 protected void doStop() throws Exception { 488 ServiceHelper.stopServices(producerCache); 489 } 490 491 protected void doShutdown() throws Exception { 492 ServiceHelper.stopAndShutdownServices(producerCache); 493 } 494 495 public EndpointUtilizationStatistics getEndpointUtilizationStatistics() { 496 return producerCache.getEndpointUtilizationStatistics(); 497 } 498 499 /** 500 * Returns the outbound message if available. Otherwise return the inbound message. 501 */ 502 private Message getResultMessage(Exchange exchange) { 503 if (exchange.hasOut()) { 504 return exchange.getOut(); 505 } else { 506 // if this endpoint had no out (like a mock endpoint) just take the in 507 return exchange.getIn(); 508 } 509 } 510 511 /** 512 * Copy the outbound data in 'source' to the inbound data in 'result'. 513 */ 514 private void copyOutToIn(Exchange result, Exchange source) { 515 result.setException(source.getException()); 516 result.setIn(getResultMessage(source)); 517 518 result.getProperties().clear(); 519 result.getProperties().putAll(source.getProperties()); 520 } 521}