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 java.net.URISyntaxException;
020import java.util.HashMap;
021
022import org.apache.camel.AsyncCallback;
023import org.apache.camel.AsyncProcessor;
024import org.apache.camel.AsyncProducerCallback;
025import org.apache.camel.CamelContext;
026import org.apache.camel.Endpoint;
027import org.apache.camel.EndpointAware;
028import org.apache.camel.Exchange;
029import org.apache.camel.ExchangePattern;
030import org.apache.camel.Producer;
031import org.apache.camel.ServicePoolAware;
032import org.apache.camel.Traceable;
033import org.apache.camel.impl.InterceptSendToEndpoint;
034import org.apache.camel.impl.ProducerCache;
035import org.apache.camel.support.ServiceSupport;
036import org.apache.camel.util.AsyncProcessorConverterHelper;
037import org.apache.camel.util.AsyncProcessorHelper;
038import org.apache.camel.util.EndpointHelper;
039import org.apache.camel.util.EventHelper;
040import org.apache.camel.util.ObjectHelper;
041import org.apache.camel.util.ServiceHelper;
042import org.apache.camel.util.StopWatch;
043import org.apache.camel.util.URISupport;
044import org.slf4j.Logger;
045import org.slf4j.LoggerFactory;
046
047/**
048 * Processor for forwarding exchanges to an endpoint destination.
049 *
050 * @version 
051 */
052public class SendProcessor extends ServiceSupport implements AsyncProcessor, Traceable, EndpointAware {
053    protected static final Logger LOG = LoggerFactory.getLogger(SendProcessor.class);
054    protected final CamelContext camelContext;
055    protected final ExchangePattern pattern;
056    protected ProducerCache producerCache;
057    protected AsyncProcessor producer;
058    protected Endpoint destination;
059    protected ExchangePattern destinationExchangePattern;
060
061    public SendProcessor(Endpoint destination) {
062        this(destination, null);
063    }
064
065    public SendProcessor(Endpoint destination, ExchangePattern pattern) {
066        ObjectHelper.notNull(destination, "destination");
067        this.destination = destination;
068        this.camelContext = destination.getCamelContext();
069        this.pattern = pattern;
070        try {
071            this.destinationExchangePattern = null;
072            this.destinationExchangePattern = EndpointHelper.resolveExchangePatternFromUrl(destination.getEndpointUri());
073        } catch (URISyntaxException e) {
074            throw ObjectHelper.wrapRuntimeCamelException(e);
075        }
076        ObjectHelper.notNull(this.camelContext, "camelContext");
077    }
078
079    @Override
080    public String toString() {
081        return "sendTo(" + destination + (pattern != null ? " " + pattern : "") + ")";
082    }
083
084    /**
085     * @deprecated not longer supported.
086     */
087    @Deprecated
088    public void setDestination(Endpoint destination) {
089    }
090
091    public String getTraceLabel() {
092        return URISupport.sanitizeUri(destination.getEndpointUri());
093    }
094
095    @Override
096    public Endpoint getEndpoint() {
097        return destination;
098    }
099
100    public void process(final Exchange exchange) throws Exception {
101        AsyncProcessorHelper.process(this, exchange);
102    }
103
104    public boolean process(Exchange exchange, final AsyncCallback callback) {
105        if (!isStarted()) {
106            exchange.setException(new IllegalStateException("SendProcessor has not been started: " + this));
107            callback.done(true);
108            return true;
109        }
110
111
112        // we should preserve existing MEP so remember old MEP
113        // if you want to permanently to change the MEP then use .setExchangePattern in the DSL
114        final ExchangePattern existingPattern = exchange.getPattern();
115
116        // if we have a producer then use that as its optimized
117        if (producer != null) {
118
119            // record timing for sending the exchange using the producer
120            final StopWatch watch = new StopWatch();
121
122            final Exchange target = configureExchange(exchange, pattern);
123
124            EventHelper.notifyExchangeSending(exchange.getContext(), target, destination);
125            LOG.debug(">>>> {} {}", destination, exchange);
126
127            boolean sync = true;
128            try {
129                sync = producer.process(exchange, new AsyncCallback() {
130                    @Override
131                    public void done(boolean doneSync) {
132                        try {
133                            // restore previous MEP
134                            target.setPattern(existingPattern);
135                            // emit event that the exchange was sent to the endpoint
136                            long timeTaken = watch.stop();
137                            EventHelper.notifyExchangeSent(target.getContext(), target, destination, timeTaken);
138                        } finally {
139                            callback.done(doneSync);
140                        }
141                    }
142                });
143            } catch (Throwable throwable) {
144                exchange.setException(throwable);
145                callback.done(sync);
146            }
147
148            return sync;
149        }
150
151        // send the exchange to the destination using the producer cache for the non optimized producers
152        return producerCache.doInAsyncProducer(destination, exchange, pattern, callback, new AsyncProducerCallback() {
153            public boolean doInAsyncProducer(Producer producer, AsyncProcessor asyncProducer, final Exchange exchange,
154                                             ExchangePattern pattern, final AsyncCallback callback) {
155                final Exchange target = configureExchange(exchange, pattern);
156                LOG.debug(">>>> {} {}", destination, exchange);
157                return asyncProducer.process(target, new AsyncCallback() {
158                    public void done(boolean doneSync) {
159                        // restore previous MEP
160                        target.setPattern(existingPattern);
161                        // signal we are done
162                        callback.done(doneSync);
163                    }
164                });
165            }
166        });
167    }
168    
169    public Endpoint getDestination() {
170        return destination;
171    }
172
173    public ExchangePattern getPattern() {
174        return pattern;
175    }
176
177    protected Exchange configureExchange(Exchange exchange, ExchangePattern pattern) {
178        // destination exchange pattern overrides pattern
179        if (destinationExchangePattern != null) {
180            exchange.setPattern(destinationExchangePattern);
181        } else if (pattern != null) {
182            exchange.setPattern(pattern);
183        }
184        // set property which endpoint we send to
185        exchange.setProperty(Exchange.TO_ENDPOINT, destination.getEndpointUri());
186        return exchange;
187    }
188
189    protected void doStart() throws Exception {
190        if (producerCache == null) {
191            // use a single producer cache as we need to only hold reference for one destination
192            // and use a regular HashMap as we do not want a soft reference store that may get re-claimed when low on memory
193            // as we want to ensure the producer is kept around, to ensure its lifecycle is fully managed,
194            // eg stopping the producer when we stop etc.
195            producerCache = new ProducerCache(this, camelContext, new HashMap<String, Producer>(1));
196            // do not add as service as we do not want to manage the producer cache
197        }
198        ServiceHelper.startService(producerCache);
199
200        // the destination could since have been intercepted by a interceptSendToEndpoint so we got to
201        // lookup this before we can use the destination
202        Endpoint lookup = camelContext.hasEndpoint(destination.getEndpointKey());
203        if (lookup instanceof InterceptSendToEndpoint) {
204            if (LOG.isDebugEnabled()) {
205                LOG.debug("Intercepted sending to {} -> {}",
206                        URISupport.sanitizeUri(destination.getEndpointUri()), URISupport.sanitizeUri(lookup.getEndpointUri()));
207            }
208            destination = lookup;
209        }
210        // warm up the producer by starting it so we can fail fast if there was a problem
211        // however must start endpoint first
212        ServiceHelper.startService(destination);
213
214        // this SendProcessor is used a lot in Camel (eg every .to in the route DSL) and therefore we
215        // want to optimize for regular producers, by using the producer directly instead of the ProducerCache
216        // Only for pooled and non singleton producers we have to use the ProducerCache as it supports these
217        // kind of producer better (though these kind of producer should be rare)
218
219        Producer producer = producerCache.acquireProducer(destination);
220        if (producer instanceof ServicePoolAware || !producer.isSingleton()) {
221            // no we cannot optimize it - so release the producer back to the producer cache
222            // and use the producer cache for sending
223            producerCache.releaseProducer(destination, producer);
224        } else {
225            // yes we can optimize and use the producer directly for sending
226            this.producer = AsyncProcessorConverterHelper.convert(producer);
227        }
228    }
229
230    protected void doStop() throws Exception {
231        ServiceHelper.stopServices(producerCache, producer);
232    }
233
234    protected void doShutdown() throws Exception {
235        ServiceHelper.stopAndShutdownServices(producerCache, producer);
236    }
237}