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