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 org.apache.camel.AsyncCallback; 020import org.apache.camel.AsyncProcessor; 021import org.apache.camel.CamelContext; 022import org.apache.camel.CamelContextAware; 023import org.apache.camel.CamelExchangeException; 024import org.apache.camel.Endpoint; 025import org.apache.camel.Exchange; 026import org.apache.camel.ExchangePattern; 027import org.apache.camel.Expression; 028import org.apache.camel.Producer; 029import org.apache.camel.impl.DefaultExchange; 030import org.apache.camel.impl.EmptyProducerCache; 031import org.apache.camel.impl.ProducerCache; 032import org.apache.camel.processor.aggregate.AggregationStrategy; 033import org.apache.camel.spi.EndpointUtilizationStatistics; 034import org.apache.camel.spi.IdAware; 035import org.apache.camel.support.ServiceSupport; 036import org.apache.camel.util.AsyncProcessorConverterHelper; 037import org.apache.camel.util.AsyncProcessorHelper; 038import org.apache.camel.util.EventHelper; 039import org.apache.camel.util.ExchangeHelper; 040import org.apache.camel.util.ServiceHelper; 041import org.apache.camel.util.StopWatch; 042import org.slf4j.Logger; 043import org.slf4j.LoggerFactory; 044 045import static org.apache.camel.util.ExchangeHelper.copyResultsPreservePattern; 046 047/** 048 * A content enricher that enriches input data by first obtaining additional 049 * data from a <i>resource</i> represented by an endpoint <code>producer</code> 050 * and second by aggregating input data and additional data. Aggregation of 051 * input data and additional data is delegated to an {@link AggregationStrategy} 052 * object. 053 * <p/> 054 * Uses a {@link org.apache.camel.Producer} to obtain the additional data as opposed to {@link PollEnricher} 055 * that uses a {@link org.apache.camel.PollingConsumer}. 056 * 057 * @see PollEnricher 058 */ 059public class Enricher extends ServiceSupport implements AsyncProcessor, IdAware, CamelContextAware { 060 061 private static final Logger LOG = LoggerFactory.getLogger(Enricher.class); 062 private CamelContext camelContext; 063 private String id; 064 private ProducerCache producerCache; 065 private final Expression expression; 066 private AggregationStrategy aggregationStrategy; 067 private boolean aggregateOnException; 068 private boolean shareUnitOfWork; 069 private int cacheSize; 070 private boolean ignoreInvalidEndpoint; 071 072 public Enricher(Expression expression) { 073 this.expression = expression; 074 } 075 076 public CamelContext getCamelContext() { 077 return camelContext; 078 } 079 080 public void setCamelContext(CamelContext camelContext) { 081 this.camelContext = camelContext; 082 } 083 084 public String getId() { 085 return id; 086 } 087 088 public void setId(String id) { 089 this.id = id; 090 } 091 092 public Expression getExpression() { 093 return expression; 094 } 095 096 public EndpointUtilizationStatistics getEndpointUtilizationStatistics() { 097 return producerCache.getEndpointUtilizationStatistics(); 098 } 099 100 public void setAggregationStrategy(AggregationStrategy aggregationStrategy) { 101 this.aggregationStrategy = aggregationStrategy; 102 } 103 104 public AggregationStrategy getAggregationStrategy() { 105 return aggregationStrategy; 106 } 107 108 public boolean isAggregateOnException() { 109 return aggregateOnException; 110 } 111 112 public void setAggregateOnException(boolean aggregateOnException) { 113 this.aggregateOnException = aggregateOnException; 114 } 115 116 public boolean isShareUnitOfWork() { 117 return shareUnitOfWork; 118 } 119 120 public void setShareUnitOfWork(boolean shareUnitOfWork) { 121 this.shareUnitOfWork = shareUnitOfWork; 122 } 123 124 public int getCacheSize() { 125 return cacheSize; 126 } 127 128 public void setCacheSize(int cacheSize) { 129 this.cacheSize = cacheSize; 130 } 131 132 public boolean isIgnoreInvalidEndpoint() { 133 return ignoreInvalidEndpoint; 134 } 135 136 public void setIgnoreInvalidEndpoint(boolean ignoreInvalidEndpoint) { 137 this.ignoreInvalidEndpoint = ignoreInvalidEndpoint; 138 } 139 140 public void process(Exchange exchange) throws Exception { 141 AsyncProcessorHelper.process(this, exchange); 142 } 143 144 /** 145 * Enriches the input data (<code>exchange</code>) by first obtaining 146 * additional data from an endpoint represented by an endpoint 147 * <code>producer</code> and second by aggregating input data and additional 148 * data. Aggregation of input data and additional data is delegated to an 149 * {@link AggregationStrategy} object set at construction time. If the 150 * message exchange with the resource endpoint fails then no aggregation 151 * will be done and the failed exchange content is copied over to the 152 * original message exchange. 153 * 154 * @param exchange input data. 155 */ 156 public boolean process(final Exchange exchange, final AsyncCallback callback) { 157 // which producer to use 158 final Producer producer; 159 final Endpoint endpoint; 160 161 // use dynamic endpoint so calculate the endpoint to use 162 Object recipient = null; 163 try { 164 recipient = expression.evaluate(exchange, Object.class); 165 endpoint = resolveEndpoint(exchange, recipient); 166 // acquire the consumer from the cache 167 producer = producerCache.acquireProducer(endpoint); 168 } catch (Throwable e) { 169 if (isIgnoreInvalidEndpoint()) { 170 if (LOG.isDebugEnabled()) { 171 LOG.debug("Endpoint uri is invalid: " + recipient + ". This exception will be ignored.", e); 172 } 173 } else { 174 exchange.setException(e); 175 } 176 callback.done(true); 177 return true; 178 } 179 180 final Exchange resourceExchange = createResourceExchange(exchange, ExchangePattern.InOut); 181 final Endpoint destination = producer.getEndpoint(); 182 183 StopWatch sw = null; 184 boolean sending = EventHelper.notifyExchangeSending(exchange.getContext(), resourceExchange, destination); 185 if (sending) { 186 sw = new StopWatch(); 187 } 188 // record timing for sending the exchange using the producer 189 final StopWatch watch = sw; 190 AsyncProcessor ap = AsyncProcessorConverterHelper.convert(producer); 191 boolean sync = ap.process(resourceExchange, new AsyncCallback() { 192 public void done(boolean doneSync) { 193 // we only have to handle async completion of the routing slip 194 if (doneSync) { 195 return; 196 } 197 198 // emit event that the exchange was sent to the endpoint 199 if (watch != null) { 200 long timeTaken = watch.taken(); 201 EventHelper.notifyExchangeSent(resourceExchange.getContext(), resourceExchange, destination, timeTaken); 202 } 203 204 if (!isAggregateOnException() && resourceExchange.isFailed()) { 205 // copy resource exchange onto original exchange (preserving pattern) 206 copyResultsPreservePattern(exchange, resourceExchange); 207 } else { 208 prepareResult(exchange); 209 try { 210 // prepare the exchanges for aggregation 211 ExchangeHelper.prepareAggregation(exchange, resourceExchange); 212 213 Exchange aggregatedExchange = aggregationStrategy.aggregate(exchange, resourceExchange); 214 if (aggregatedExchange != null) { 215 // copy aggregation result onto original exchange (preserving pattern) 216 copyResultsPreservePattern(exchange, aggregatedExchange); 217 } 218 } catch (Throwable e) { 219 // if the aggregationStrategy threw an exception, set it on the original exchange 220 exchange.setException(new CamelExchangeException("Error occurred during aggregation", exchange, e)); 221 callback.done(false); 222 // we failed so break out now 223 return; 224 } 225 } 226 227 // set property with the uri of the endpoint enriched so we can use that for tracing etc 228 exchange.setProperty(Exchange.TO_ENDPOINT, producer.getEndpoint().getEndpointUri()); 229 230 // return the producer back to the cache 231 try { 232 producerCache.releaseProducer(endpoint, producer); 233 } catch (Exception e) { 234 // ignore 235 } 236 237 callback.done(false); 238 } 239 }); 240 241 if (!sync) { 242 LOG.trace("Processing exchangeId: {} is continued being processed asynchronously", exchange.getExchangeId()); 243 // the remainder of the routing slip will be completed async 244 // so we break out now, then the callback will be invoked which then continue routing from where we left here 245 return false; 246 } 247 248 LOG.trace("Processing exchangeId: {} is continued being processed synchronously", exchange.getExchangeId()); 249 250 if (watch != null) { 251 // emit event that the exchange was sent to the endpoint 252 long timeTaken = watch.taken(); 253 EventHelper.notifyExchangeSent(resourceExchange.getContext(), resourceExchange, destination, timeTaken); 254 } 255 256 if (!isAggregateOnException() && resourceExchange.isFailed()) { 257 // copy resource exchange onto original exchange (preserving pattern) 258 copyResultsPreservePattern(exchange, resourceExchange); 259 } else { 260 prepareResult(exchange); 261 262 try { 263 // prepare the exchanges for aggregation 264 ExchangeHelper.prepareAggregation(exchange, resourceExchange); 265 266 Exchange aggregatedExchange = aggregationStrategy.aggregate(exchange, resourceExchange); 267 if (aggregatedExchange != null) { 268 // copy aggregation result onto original exchange (preserving pattern) 269 copyResultsPreservePattern(exchange, aggregatedExchange); 270 } 271 } catch (Throwable e) { 272 // if the aggregationStrategy threw an exception, set it on the original exchange 273 exchange.setException(new CamelExchangeException("Error occurred during aggregation", exchange, e)); 274 callback.done(true); 275 // we failed so break out now 276 return true; 277 } 278 } 279 280 // set property with the uri of the endpoint enriched so we can use that for tracing etc 281 exchange.setProperty(Exchange.TO_ENDPOINT, producer.getEndpoint().getEndpointUri()); 282 283 // return the producer back to the cache 284 try { 285 producerCache.releaseProducer(endpoint, producer); 286 } catch (Exception e) { 287 // ignore 288 } 289 290 callback.done(true); 291 return true; 292 } 293 294 protected Endpoint resolveEndpoint(Exchange exchange, Object recipient) { 295 // trim strings as end users might have added spaces between separators 296 if (recipient instanceof String) { 297 recipient = ((String)recipient).trim(); 298 } 299 return ExchangeHelper.resolveEndpoint(exchange, recipient); 300 } 301 302 /** 303 * Creates a new {@link DefaultExchange} instance from the given 304 * <code>exchange</code>. The resulting exchange's pattern is defined by 305 * <code>pattern</code>. 306 * 307 * @param source exchange to copy from. 308 * @param pattern exchange pattern to set. 309 * @return created exchange. 310 */ 311 protected Exchange createResourceExchange(Exchange source, ExchangePattern pattern) { 312 // copy exchange, and do not share the unit of work 313 Exchange target = ExchangeHelper.createCorrelatedCopy(source, false); 314 target.setPattern(pattern); 315 316 // if we share unit of work, we need to prepare the resource exchange 317 if (isShareUnitOfWork()) { 318 target.setProperty(Exchange.PARENT_UNIT_OF_WORK, source.getUnitOfWork()); 319 // and then share the unit of work 320 target.setUnitOfWork(source.getUnitOfWork()); 321 } 322 return target; 323 } 324 325 private static void prepareResult(Exchange exchange) { 326 if (exchange.getPattern().isOutCapable()) { 327 exchange.getOut().copyFrom(exchange.getIn()); 328 } 329 } 330 331 private static AggregationStrategy defaultAggregationStrategy() { 332 return new CopyAggregationStrategy(); 333 } 334 335 @Override 336 public String toString() { 337 return "Enrich[" + expression + "]"; 338 } 339 340 protected void doStart() throws Exception { 341 if (aggregationStrategy == null) { 342 aggregationStrategy = defaultAggregationStrategy(); 343 } 344 345 if (producerCache == null) { 346 if (cacheSize < 0) { 347 producerCache = new EmptyProducerCache(this, camelContext); 348 LOG.debug("Enricher {} is not using ProducerCache", this); 349 } else if (cacheSize == 0) { 350 producerCache = new ProducerCache(this, camelContext); 351 LOG.debug("Enricher {} using ProducerCache with default cache size", this); 352 } else { 353 producerCache = new ProducerCache(this, camelContext, cacheSize); 354 LOG.debug("Enricher {} using ProducerCache with cacheSize={}", this, cacheSize); 355 } 356 } 357 358 ServiceHelper.startServices(producerCache, aggregationStrategy); 359 } 360 361 protected void doStop() throws Exception { 362 ServiceHelper.stopServices(aggregationStrategy, producerCache); 363 } 364 365 private static class CopyAggregationStrategy implements AggregationStrategy { 366 367 public Exchange aggregate(Exchange oldExchange, Exchange newExchange) { 368 if (newExchange != null) { 369 copyResultsPreservePattern(oldExchange, newExchange); 370 } 371 return oldExchange; 372 } 373 374 } 375 376}