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