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.io.IOException;
020import java.util.ArrayList;
021import java.util.List;
022import java.util.concurrent.Callable;
023import java.util.concurrent.ExecutorService;
024
025import org.apache.camel.AsyncCallback;
026import org.apache.camel.AsyncProcessor;
027import org.apache.camel.Endpoint;
028import org.apache.camel.EndpointAware;
029import org.apache.camel.Exchange;
030import org.apache.camel.ExchangePattern;
031import org.apache.camel.Expression;
032import org.apache.camel.Message;
033import org.apache.camel.Processor;
034import org.apache.camel.StreamCache;
035import org.apache.camel.Traceable;
036import org.apache.camel.impl.DefaultExchange;
037import org.apache.camel.support.ServiceSupport;
038import org.apache.camel.util.AsyncProcessorHelper;
039import org.apache.camel.util.EventHelper;
040import org.apache.camel.util.ExchangeHelper;
041import org.apache.camel.util.ObjectHelper;
042import org.apache.camel.util.ServiceHelper;
043import org.apache.camel.util.StopWatch;
044import org.slf4j.Logger;
045import org.slf4j.LoggerFactory;
046
047/**
048 * Processor for wire tapping exchanges to an endpoint destination.
049 *
050 * @version 
051 */
052public class WireTapProcessor extends ServiceSupport implements AsyncProcessor, Traceable, EndpointAware {
053    private static final Logger LOG = LoggerFactory.getLogger(WireTapProcessor.class);
054    private final Endpoint destination;
055    private final Processor processor;
056    private final ExchangePattern exchangePattern;
057    private final ExecutorService executorService;
058    private volatile boolean shutdownExecutorService;
059
060    // expression or processor used for populating a new exchange to send
061    // as opposed to traditional wiretap that sends a copy of the original exchange
062    private Expression newExchangeExpression;
063    private List<Processor> newExchangeProcessors;
064    private boolean copy;
065    private Processor onPrepare;
066
067    public WireTapProcessor(Endpoint destination, Processor processor, ExchangePattern exchangePattern,
068                            ExecutorService executorService, boolean shutdownExecutorService) {
069        this.destination = destination;
070        this.processor = processor;
071        this.exchangePattern = exchangePattern;
072        ObjectHelper.notNull(executorService, "executorService");
073        this.executorService = executorService;
074        this.shutdownExecutorService = shutdownExecutorService;
075    }
076
077    @Override
078    public String toString() {
079        return "WireTap[" + destination + "]";
080    }
081
082    @Override
083    public String getTraceLabel() {
084        return "wireTap(" + destination + ")";
085    }
086
087    public Endpoint getEndpoint() {
088        return destination;
089    }
090
091    public void process(Exchange exchange) throws Exception {
092        AsyncProcessorHelper.process(this, exchange);
093    }
094
095    public boolean process(final Exchange exchange, final AsyncCallback callback) {
096        if (!isStarted()) {
097            throw new IllegalStateException("WireTapProcessor has not been started: " + this);
098        }
099
100        // must configure the wire tap beforehand
101        Exchange target;
102        try {
103            target = configureExchange(exchange, exchangePattern);
104        } catch (Exception e) {
105            exchange.setException(e);
106            callback.done(true);
107            return true;
108        }
109
110        final Exchange wireTapExchange = target;
111
112        // send the exchange to the destination using an executor service
113        executorService.submit(new Callable<Exchange>() {
114            public Exchange call() throws Exception {
115                final StopWatch watch = new StopWatch();
116                try {
117                    EventHelper.notifyExchangeSending(wireTapExchange.getContext(), wireTapExchange, destination);
118                    LOG.debug(">>>> (wiretap) {} {}", destination, wireTapExchange);
119                    processor.process(wireTapExchange);
120                } catch (Throwable e) {
121                    LOG.warn("Error occurred during processing " + wireTapExchange + " wiretap to " + destination + ". This exception will be ignored.", e);
122                } finally {
123                    // emit event that the exchange was sent to the endpoint
124                    long timeTaken = watch.stop();
125                    EventHelper.notifyExchangeSent(wireTapExchange.getContext(), wireTapExchange, destination, timeTaken);
126                }
127                return wireTapExchange;
128            }
129        });
130
131        // continue routing this synchronously
132        callback.done(true);
133        return true;
134    }
135
136
137    protected Exchange configureExchange(Exchange exchange, ExchangePattern pattern) throws IOException {
138        Exchange answer;
139        if (copy) {
140            // use a copy of the original exchange
141            answer = configureCopyExchange(exchange);
142        } else {
143            // use a new exchange
144            answer = configureNewExchange(exchange);
145        }
146
147        // set property which endpoint we send to
148        answer.setProperty(Exchange.TO_ENDPOINT, destination.getEndpointUri());
149
150        // prepare the exchange
151        if (newExchangeExpression != null) {
152            Object body = newExchangeExpression.evaluate(answer, Object.class);
153            if (body != null) {
154                answer.getIn().setBody(body);
155            }
156        }
157
158        if (newExchangeProcessors != null) {
159            for (Processor processor : newExchangeProcessors) {
160                try {
161                    processor.process(answer);
162                } catch (Exception e) {
163                    throw ObjectHelper.wrapRuntimeCamelException(e);
164                }
165            }
166        }
167
168        // if the body is a stream cache we must use a copy of the stream in the wire tapped exchange
169        Message msg = answer.hasOut() ? answer.getOut() : answer.getIn();
170        if (msg.getBody() instanceof StreamCache) {
171            // in parallel processing case, the stream must be copied, therefore get the stream
172            StreamCache cache = (StreamCache) msg.getBody();
173            StreamCache copied = cache.copy();
174            if (copied != null) {
175                msg.setBody(copied);
176            }
177        }
178
179        // invoke on prepare on the exchange if specified
180        if (onPrepare != null) {
181            try {
182                onPrepare.process(answer);
183            } catch (Exception e) {
184                throw ObjectHelper.wrapRuntimeCamelException(e);
185            }
186        }
187
188        return answer;
189    }
190
191    private Exchange configureCopyExchange(Exchange exchange) {
192        // must use a copy as we dont want it to cause side effects of the original exchange
193        Exchange copy = ExchangeHelper.createCorrelatedCopy(exchange, false);
194        // set MEP to InOnly as this wire tap is a fire and forget
195        copy.setPattern(ExchangePattern.InOnly);
196        return copy;
197    }
198
199    private Exchange configureNewExchange(Exchange exchange) {
200        return new DefaultExchange(exchange.getFromEndpoint(), ExchangePattern.InOnly);
201    }
202
203    public List<Processor> getNewExchangeProcessors() {
204        return newExchangeProcessors;
205    }
206
207    public void setNewExchangeProcessors(List<Processor> newExchangeProcessors) {
208        this.newExchangeProcessors = newExchangeProcessors;
209    }
210
211    public Expression getNewExchangeExpression() {
212        return newExchangeExpression;
213    }
214
215    public void setNewExchangeExpression(Expression newExchangeExpression) {
216        this.newExchangeExpression = newExchangeExpression;
217    }
218
219    public void addNewExchangeProcessor(Processor processor) {
220        if (newExchangeProcessors == null) {
221            newExchangeProcessors = new ArrayList<Processor>();
222        }
223        newExchangeProcessors.add(processor);
224    }
225
226    public boolean isCopy() {
227        return copy;
228    }
229
230    public void setCopy(boolean copy) {
231        this.copy = copy;
232    }
233
234    public Processor getOnPrepare() {
235        return onPrepare;
236    }
237
238    public void setOnPrepare(Processor onPrepare) {
239        this.onPrepare = onPrepare;
240    }
241
242    @Override
243    protected void doStart() throws Exception {
244        ServiceHelper.startService(processor);
245    }
246
247    @Override
248    protected void doStop() throws Exception {
249        ServiceHelper.stopService(processor);
250    }
251
252    @Override
253    protected void doShutdown() throws Exception {
254        ServiceHelper.stopAndShutdownService(processor);
255        if (shutdownExecutorService) {
256            destination.getCamelContext().getExecutorServiceManager().shutdownNow(executorService);
257        }
258    }
259}