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