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