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