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.CamelExchangeException; 022import org.apache.camel.Endpoint; 023import org.apache.camel.EndpointAware; 024import org.apache.camel.Exchange; 025import org.apache.camel.ExchangePattern; 026import org.apache.camel.Producer; 027import org.apache.camel.impl.DefaultExchange; 028import org.apache.camel.processor.aggregate.AggregationStrategy; 029import org.apache.camel.support.ServiceSupport; 030import org.apache.camel.util.AsyncProcessorConverterHelper; 031import org.apache.camel.util.AsyncProcessorHelper; 032import org.apache.camel.util.EventHelper; 033import org.apache.camel.util.ExchangeHelper; 034import org.apache.camel.util.ServiceHelper; 035import org.apache.camel.util.StopWatch; 036import org.slf4j.Logger; 037import org.slf4j.LoggerFactory; 038import static org.apache.camel.util.ExchangeHelper.copyResultsPreservePattern; 039 040/** 041 * A content enricher that enriches input data by first obtaining additional 042 * data from a <i>resource</i> represented by an endpoint <code>producer</code> 043 * and second by aggregating input data and additional data. Aggregation of 044 * input data and additional data is delegated to an {@link AggregationStrategy} 045 * object. 046 * <p/> 047 * Uses a {@link org.apache.camel.Producer} to obtain the additional data as opposed to {@link PollEnricher} 048 * that uses a {@link org.apache.camel.PollingConsumer}. 049 * 050 * @see PollEnricher 051 */ 052public class Enricher extends ServiceSupport implements AsyncProcessor, EndpointAware { 053 054 private static final Logger LOG = LoggerFactory.getLogger(Enricher.class); 055 private AggregationStrategy aggregationStrategy; 056 private Producer producer; 057 private boolean aggregateOnException; 058 059 /** 060 * Creates a new {@link Enricher}. The default aggregation strategy is to 061 * copy the additional data obtained from the enricher's resource over the 062 * input data. When using the copy aggregation strategy the enricher 063 * degenerates to a normal transformer. 064 * 065 * @param producer producer to resource endpoint. 066 */ 067 public Enricher(Producer producer) { 068 this(defaultAggregationStrategy(), producer); 069 } 070 071 /** 072 * Creates a new {@link Enricher}. 073 * 074 * @param aggregationStrategy aggregation strategy to aggregate input data and additional data. 075 * @param producer producer to resource endpoint. 076 */ 077 public Enricher(AggregationStrategy aggregationStrategy, Producer producer) { 078 this.aggregationStrategy = aggregationStrategy; 079 this.producer = producer; 080 } 081 082 /** 083 * Sets the aggregation strategy for this enricher. 084 * 085 * @param aggregationStrategy the aggregationStrategy to set 086 */ 087 public void setAggregationStrategy(AggregationStrategy aggregationStrategy) { 088 this.aggregationStrategy = aggregationStrategy; 089 } 090 091 public AggregationStrategy getAggregationStrategy() { 092 return aggregationStrategy; 093 } 094 095 public boolean isAggregateOnException() { 096 return aggregateOnException; 097 } 098 099 /** 100 * Whether to call {@link org.apache.camel.processor.aggregate.AggregationStrategy#aggregate(org.apache.camel.Exchange, org.apache.camel.Exchange)} if 101 * an exception was thrown. 102 */ 103 public void setAggregateOnException(boolean aggregateOnException) { 104 this.aggregateOnException = aggregateOnException; 105 } 106 107 /** 108 * Sets the default aggregation strategy for this enricher. 109 */ 110 public void setDefaultAggregationStrategy() { 111 this.aggregationStrategy = defaultAggregationStrategy(); 112 } 113 114 public Endpoint getEndpoint() { 115 return producer.getEndpoint(); 116 } 117 118 public void process(Exchange exchange) throws Exception { 119 AsyncProcessorHelper.process(this, exchange); 120 } 121 122 /** 123 * Enriches the input data (<code>exchange</code>) by first obtaining 124 * additional data from an endpoint represented by an endpoint 125 * <code>producer</code> and second by aggregating input data and additional 126 * data. Aggregation of input data and additional data is delegated to an 127 * {@link AggregationStrategy} object set at construction time. If the 128 * message exchange with the resource endpoint fails then no aggregation 129 * will be done and the failed exchange content is copied over to the 130 * original message exchange. 131 * 132 * @param exchange input data. 133 */ 134 public boolean process(final Exchange exchange, final AsyncCallback callback) { 135 final Exchange resourceExchange = createResourceExchange(exchange, ExchangePattern.InOut); 136 final Endpoint destination = producer.getEndpoint(); 137 138 EventHelper.notifyExchangeSending(exchange.getContext(), resourceExchange, destination); 139 // record timing for sending the exchange using the producer 140 final StopWatch watch = new StopWatch(); 141 AsyncProcessor ap = AsyncProcessorConverterHelper.convert(producer); 142 boolean sync = ap.process(resourceExchange, new AsyncCallback() { 143 public void done(boolean doneSync) { 144 // we only have to handle async completion of the routing slip 145 if (doneSync) { 146 return; 147 } 148 149 // emit event that the exchange was sent to the endpoint 150 long timeTaken = watch.stop(); 151 EventHelper.notifyExchangeSent(resourceExchange.getContext(), resourceExchange, destination, timeTaken); 152 153 if (!isAggregateOnException() && resourceExchange.isFailed()) { 154 // copy resource exchange onto original exchange (preserving pattern) 155 copyResultsPreservePattern(exchange, resourceExchange); 156 } else { 157 prepareResult(exchange); 158 try { 159 // prepare the exchanges for aggregation 160 ExchangeHelper.prepareAggregation(exchange, resourceExchange); 161 162 Exchange aggregatedExchange = aggregationStrategy.aggregate(exchange, resourceExchange); 163 if (aggregatedExchange != null) { 164 // copy aggregation result onto original exchange (preserving pattern) 165 copyResultsPreservePattern(exchange, aggregatedExchange); 166 } 167 } catch (Throwable e) { 168 // if the aggregationStrategy threw an exception, set it on the original exchange 169 exchange.setException(new CamelExchangeException("Error occurred during aggregation", exchange, e)); 170 callback.done(false); 171 // we failed so break out now 172 return; 173 } 174 } 175 176 // set property with the uri of the endpoint enriched so we can use that for tracing etc 177 exchange.setProperty(Exchange.TO_ENDPOINT, producer.getEndpoint().getEndpointUri()); 178 179 callback.done(false); 180 } 181 }); 182 183 if (!sync) { 184 LOG.trace("Processing exchangeId: {} is continued being processed asynchronously", exchange.getExchangeId()); 185 // the remainder of the routing slip will be completed async 186 // so we break out now, then the callback will be invoked which then continue routing from where we left here 187 return false; 188 } 189 190 LOG.trace("Processing exchangeId: {} is continued being processed synchronously", exchange.getExchangeId()); 191 192 // emit event that the exchange was sent to the endpoint 193 long timeTaken = watch.stop(); 194 EventHelper.notifyExchangeSent(resourceExchange.getContext(), resourceExchange, destination, timeTaken); 195 196 if (!isAggregateOnException() && resourceExchange.isFailed()) { 197 // copy resource exchange onto original exchange (preserving pattern) 198 copyResultsPreservePattern(exchange, resourceExchange); 199 } else { 200 prepareResult(exchange); 201 202 try { 203 // prepare the exchanges for aggregation 204 ExchangeHelper.prepareAggregation(exchange, resourceExchange); 205 206 Exchange aggregatedExchange = aggregationStrategy.aggregate(exchange, resourceExchange); 207 if (aggregatedExchange != null) { 208 // copy aggregation result onto original exchange (preserving pattern) 209 copyResultsPreservePattern(exchange, aggregatedExchange); 210 } 211 } catch (Throwable e) { 212 // if the aggregationStrategy threw an exception, set it on the original exchange 213 exchange.setException(new CamelExchangeException("Error occurred during aggregation", exchange, e)); 214 callback.done(true); 215 // we failed so break out now 216 return true; 217 } 218 } 219 220 // set property with the uri of the endpoint enriched so we can use that for tracing etc 221 exchange.setProperty(Exchange.TO_ENDPOINT, producer.getEndpoint().getEndpointUri()); 222 223 callback.done(true); 224 return true; 225 } 226 227 /** 228 * Creates a new {@link DefaultExchange} instance from the given 229 * <code>exchange</code>. The resulting exchange's pattern is defined by 230 * <code>pattern</code>. 231 * 232 * @param source exchange to copy from. 233 * @param pattern exchange pattern to set. 234 * @return created exchange. 235 */ 236 protected Exchange createResourceExchange(Exchange source, ExchangePattern pattern) { 237 // copy exchange, and do not share the unit of work 238 Exchange target = ExchangeHelper.createCorrelatedCopy(source, false); 239 target.setPattern(pattern); 240 return target; 241 } 242 243 private static void prepareResult(Exchange exchange) { 244 if (exchange.getPattern().isOutCapable()) { 245 exchange.getOut().copyFrom(exchange.getIn()); 246 } 247 } 248 249 private static AggregationStrategy defaultAggregationStrategy() { 250 return new CopyAggregationStrategy(); 251 } 252 253 @Override 254 public String toString() { 255 return "Enrich[" + producer.getEndpoint() + "]"; 256 } 257 258 protected void doStart() throws Exception { 259 ServiceHelper.startServices(aggregationStrategy, producer); 260 } 261 262 protected void doStop() throws Exception { 263 ServiceHelper.stopServices(producer, aggregationStrategy); 264 } 265 266 private static class CopyAggregationStrategy implements AggregationStrategy { 267 268 public Exchange aggregate(Exchange oldExchange, Exchange newExchange) { 269 if (newExchange != null) { 270 copyResultsPreservePattern(oldExchange, newExchange); 271 } 272 return oldExchange; 273 } 274 275 } 276 277}