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 java.util.ArrayList;
020 import java.util.List;
021
022 import org.apache.camel.CamelExchangeException;
023 import org.apache.camel.Exchange;
024 import org.apache.camel.PollingConsumer;
025 import org.apache.camel.Processor;
026 import org.apache.camel.processor.aggregate.AggregationStrategy;
027 import org.apache.camel.support.ServiceSupport;
028 import org.apache.camel.util.ExchangeHelper;
029 import org.apache.camel.util.ServiceHelper;
030 import org.slf4j.Logger;
031 import org.slf4j.LoggerFactory;
032
033 import static org.apache.camel.util.ExchangeHelper.copyResultsPreservePattern;
034
035 /**
036 * A content enricher that enriches input data by first obtaining additional
037 * data from a <i>resource</i> represented by an endpoint <code>producer</code>
038 * and second by aggregating input data and additional data. Aggregation of
039 * input data and additional data is delegated to an {@link org.apache.camel.processor.aggregate.AggregationStrategy}
040 * object.
041 * <p/>
042 * Uses a {@link org.apache.camel.PollingConsumer} to obtain the additional data as opposed to {@link Enricher}
043 * that uses a {@link org.apache.camel.Producer}.
044 *
045 * @see Enricher
046 */
047 public class PollEnricher extends ServiceSupport implements Processor {
048
049 private static final transient Logger LOG = LoggerFactory.getLogger(PollEnricher.class);
050 private AggregationStrategy aggregationStrategy;
051 private PollingConsumer consumer;
052 private long timeout;
053 private Boolean pollMultiple;
054
055 /**
056 * Creates a new {@link PollEnricher}. 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 consumer consumer to resource endpoint.
062 */
063 public PollEnricher(PollingConsumer consumer) {
064 this(defaultAggregationStrategy(), consumer, 0, false);
065 }
066
067 /**
068 * Creates a new {@link PollEnricher}.
069 *
070 * @param aggregationStrategy aggregation strategy to aggregate input data and additional data.
071 * @param consumer consumer to resource endpoint.
072 * @param timeout timeout in millis
073 */
074 public PollEnricher(AggregationStrategy aggregationStrategy, PollingConsumer consumer, long timeout) {
075 this.aggregationStrategy = aggregationStrategy;
076 this.consumer = consumer;
077 this.timeout = timeout;
078 }
079
080 /**
081 * Creates a new {@link PollEnricher}.
082 *
083 * @param aggregationStrategy aggregation strategy to aggregate input data and additional data.
084 * @param consumer consumer to resource endpoint.
085 * @param timeout timeout in millis
086 * @param pollMultiple enabled building a List of multiple exchanges
087 */
088 public PollEnricher(AggregationStrategy aggregationStrategy, PollingConsumer consumer, long timeout, Boolean pollMultiple) {
089 this.aggregationStrategy = aggregationStrategy;
090 this.consumer = consumer;
091 this.timeout = timeout;
092 this.pollMultiple = pollMultiple;
093 }
094
095 /**
096 * Sets the aggregation strategy for this poll enricher.
097 *
098 * @param aggregationStrategy the aggregationStrategy to set
099 */
100 public void setAggregationStrategy(AggregationStrategy aggregationStrategy) {
101 this.aggregationStrategy = aggregationStrategy;
102 }
103
104 /**
105 * Sets the default aggregation strategy for this poll enricher.
106 */
107 public void setDefaultAggregationStrategy() {
108 this.aggregationStrategy = defaultAggregationStrategy();
109 }
110
111 /**
112 * Sets the timeout to use when polling.
113 * <p/>
114 * Use 0 or negative to not use timeout and block until data is available.
115 *
116 * @param timeout timeout in millis.
117 */
118 public void setTimeout(long timeout) {
119 this.timeout = timeout;
120 }
121
122 public void setPollMultiple(Boolean value) {
123 this.pollMultiple = value;
124 }
125
126 /**
127 * Enriches the input data (<code>exchange</code>) by first obtaining
128 * additional data from an endpoint represented by an endpoint
129 * <code>producer</code> and second by aggregating input data and additional
130 * data. Aggregation of input data and additional data is delegated to an
131 * {@link org.apache.camel.processor.aggregate.AggregationStrategy} object set at construction time. If the
132 * message exchange with the resource endpoint fails then no aggregation
133 * will be done and the failed exchange content is copied over to the
134 * original message exchange.
135 *
136 * @param exchange input data.
137 */
138 public void process(Exchange exchange) throws Exception {
139
140 preCheckPoll(exchange);
141
142 if (pollMultiple != null && pollMultiple) {
143
144 List<Exchange> exchangeList = new ArrayList<Exchange>();
145 Exchange receivedExchange;
146 while (true) {
147 if (timeout == 0) {
148 LOG.debug("Polling Consumer receiveNoWait: {}", consumer);
149 receivedExchange = consumer.receiveNoWait();
150 } else {
151 LOG.debug("Polling Consumer receive with timeout: {} ms. {}", timeout, consumer);
152 receivedExchange = consumer.receive(timeout);
153 }
154
155 if (receivedExchange == null) {
156 break;
157 }
158 exchangeList.add(receivedExchange);
159 }
160 exchange.getIn().setBody(exchangeList);
161 } else {
162
163 Exchange resourceExchange;
164 if (timeout < 0) {
165 LOG.debug("Consumer receive: {}", consumer);
166 resourceExchange = consumer.receive();
167 } else if (timeout == 0) {
168 LOG.debug("Consumer receiveNoWait: {}", consumer);
169 resourceExchange = consumer.receiveNoWait();
170 } else {
171 LOG.debug("Consumer receive with timeout: {} ms. {}", timeout, consumer);
172 resourceExchange = consumer.receive(timeout);
173 }
174
175 if (resourceExchange == null) {
176 LOG.debug("Consumer received no exchange");
177 } else {
178 LOG.debug("Consumer received: {}", resourceExchange);
179 }
180
181 if (resourceExchange != null && resourceExchange.isFailed()) {
182 // copy resource exchange onto original exchange (preserving pattern)
183 copyResultsPreservePattern(exchange, resourceExchange);
184 } else {
185 prepareResult(exchange);
186
187 // prepare the exchanges for aggregation
188 ExchangeHelper.prepareAggregation(exchange, resourceExchange);
189 // must catch any exception from aggregation
190 Exchange aggregatedExchange;
191 try {
192 aggregatedExchange = aggregationStrategy.aggregate(exchange, resourceExchange);
193 } catch (Throwable e) {
194 throw new CamelExchangeException("Error occurred during aggregation", exchange, e);
195 }
196 if (aggregatedExchange != null) {
197 // copy aggregation result onto original exchange (preserving pattern)
198 copyResultsPreservePattern(exchange, aggregatedExchange);
199 // handover any synchronization
200 if (resourceExchange != null) {
201 resourceExchange.handoverCompletions(exchange);
202 }
203 }
204 }
205 }
206
207 // set header with the uri of the endpoint enriched so we can use that for tracing etc
208 if (exchange.hasOut()) {
209 exchange.getOut().setHeader(Exchange.TO_ENDPOINT, consumer.getEndpoint().getEndpointUri());
210 } else {
211 exchange.getIn().setHeader(Exchange.TO_ENDPOINT, consumer.getEndpoint().getEndpointUri());
212 }
213 }
214
215 /**
216 * Strategy to pre check polling.
217 * <p/>
218 * Is currently used to prevent doing poll enrich from a file based endpoint when the current route also
219 * started from a file based endpoint as that is not currently supported.
220 *
221 * @param exchange the current exchange
222 */
223 protected void preCheckPoll(Exchange exchange) throws Exception {
224 // noop
225 }
226
227 private static void prepareResult(Exchange exchange) {
228 if (exchange.getPattern().isOutCapable()) {
229 exchange.getOut().copyFrom(exchange.getIn());
230 }
231 }
232
233 private static AggregationStrategy defaultAggregationStrategy() {
234 return new CopyAggregationStrategy();
235 }
236
237 @Override
238 public String toString() {
239 return "PollEnrich[" + consumer + "]";
240 }
241
242 protected void doStart() throws Exception {
243 ServiceHelper.startService(consumer);
244 }
245
246 protected void doStop() throws Exception {
247 ServiceHelper.stopService(consumer);
248 }
249
250 private static class CopyAggregationStrategy implements AggregationStrategy {
251
252 public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
253 if (newExchange != null) {
254 copyResultsPreservePattern(oldExchange, newExchange);
255 } else {
256 // if no newExchange then there was no message from the external resource
257 // and therefore we should set an empty body to indicate this fact
258 // but keep headers/attachments as we want to propagate those
259 oldExchange.getIn().setBody(null);
260 oldExchange.setOut(null);
261 }
262 return oldExchange;
263 }
264
265 }
266
267 }