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