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 }