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.util.ArrayList; 020import java.util.List; 021import java.util.concurrent.TimeUnit; 022import java.util.concurrent.locks.Condition; 023import java.util.concurrent.locks.Lock; 024import java.util.concurrent.locks.ReentrantLock; 025 026import org.apache.camel.AsyncCallback; 027import org.apache.camel.AsyncProcessor; 028import org.apache.camel.CamelContext; 029import org.apache.camel.CamelExchangeException; 030import org.apache.camel.Exchange; 031import org.apache.camel.Navigate; 032import org.apache.camel.Processor; 033import org.apache.camel.Traceable; 034import org.apache.camel.impl.LoggingExceptionHandler; 035import org.apache.camel.processor.resequencer.ResequencerEngine; 036import org.apache.camel.processor.resequencer.SequenceElementComparator; 037import org.apache.camel.processor.resequencer.SequenceSender; 038import org.apache.camel.spi.ExceptionHandler; 039import org.apache.camel.support.ServiceSupport; 040import org.apache.camel.util.AsyncProcessorHelper; 041import org.apache.camel.util.ObjectHelper; 042import org.apache.camel.util.ServiceHelper; 043import org.slf4j.Logger; 044import org.slf4j.LoggerFactory; 045 046/** 047 * A resequencer that re-orders a (continuous) stream of {@link Exchange}s. The 048 * algorithm implemented by {@link ResequencerEngine} is based on the detection 049 * of gaps in a message stream rather than on a fixed batch size. Gap detection 050 * in combination with timeouts removes the constraint of having to know the 051 * number of messages of a sequence (i.e. the batch size) in advance. 052 * <p> 053 * Messages must contain a unique sequence number for which a predecessor and a 054 * successor is known. For example a message with the sequence number 3 has a 055 * predecessor message with the sequence number 2 and a successor message with 056 * the sequence number 4. The message sequence 2,3,5 has a gap because the 057 * successor of 3 is missing. The resequencer therefore has to retain message 5 058 * until message 4 arrives (or a timeout occurs). 059 * <p> 060 * Instances of this class poll for {@link Exchange}s from a given 061 * <code>endpoint</code>. Resequencing work and the delivery of messages to 062 * the next <code>processor</code> is done within the single polling thread. 063 * 064 * @version 065 * 066 * @see ResequencerEngine 067 */ 068public class StreamResequencer extends ServiceSupport implements SequenceSender<Exchange>, AsyncProcessor, Navigate<Processor>, Traceable { 069 070 private static final long DELIVERY_ATTEMPT_INTERVAL = 1000L; 071 private static final Logger LOG = LoggerFactory.getLogger(StreamResequencer.class); 072 073 private final CamelContext camelContext; 074 private final ExceptionHandler exceptionHandler; 075 private final ResequencerEngine<Exchange> engine; 076 private final Processor processor; 077 private Delivery delivery; 078 private int capacity; 079 private boolean ignoreInvalidExchanges; 080 081 /** 082 * Creates a new {@link StreamResequencer} instance. 083 * 084 * @param processor next processor that processes re-ordered exchanges. 085 * @param comparator a sequence element comparator for exchanges. 086 */ 087 public StreamResequencer(CamelContext camelContext, Processor processor, SequenceElementComparator<Exchange> comparator) { 088 ObjectHelper.notNull(camelContext, "CamelContext"); 089 this.camelContext = camelContext; 090 this.engine = new ResequencerEngine<Exchange>(comparator); 091 this.engine.setSequenceSender(this); 092 this.processor = processor; 093 this.exceptionHandler = new LoggingExceptionHandler(camelContext, getClass()); 094 } 095 096 /** 097 * Returns this resequencer's exception handler. 098 */ 099 public ExceptionHandler getExceptionHandler() { 100 return exceptionHandler; 101 } 102 103 /** 104 * Returns the next processor. 105 */ 106 public Processor getProcessor() { 107 return processor; 108 } 109 110 /** 111 * Returns this resequencer's capacity. The capacity is the maximum number 112 * of exchanges that can be managed by this resequencer at a given point in 113 * time. If the capacity if reached, polling from the endpoint will be 114 * skipped for <code>timeout</code> milliseconds giving exchanges the 115 * possibility to time out and to be delivered after the waiting period. 116 * 117 * @return this resequencer's capacity. 118 */ 119 public int getCapacity() { 120 return capacity; 121 } 122 123 /** 124 * Returns this resequencer's timeout. This sets the resequencer engine's 125 * timeout via {@link ResequencerEngine#setTimeout(long)}. This value is 126 * also used to define the polling timeout from the endpoint. 127 * 128 * @return this resequencer's timeout. (Processor) 129 * @see ResequencerEngine#setTimeout(long) 130 */ 131 public long getTimeout() { 132 return engine.getTimeout(); 133 } 134 135 public void setCapacity(int capacity) { 136 this.capacity = capacity; 137 } 138 139 public void setTimeout(long timeout) { 140 engine.setTimeout(timeout); 141 } 142 143 public boolean isIgnoreInvalidExchanges() { 144 return ignoreInvalidExchanges; 145 } 146 147 public void setRejectOld(Boolean rejectOld) { 148 engine.setRejectOld(rejectOld); 149 } 150 151 /** 152 * Sets whether to ignore invalid exchanges which cannot be used by this stream resequencer. 153 * <p/> 154 * Default is <tt>false</tt>, by which an {@link CamelExchangeException} is thrown if the {@link Exchange} 155 * is invalid. 156 */ 157 public void setIgnoreInvalidExchanges(boolean ignoreInvalidExchanges) { 158 this.ignoreInvalidExchanges = ignoreInvalidExchanges; 159 } 160 161 @Override 162 public String toString() { 163 return "StreamResequencer[to: " + processor + "]"; 164 } 165 166 public String getTraceLabel() { 167 return "streamResequence"; 168 } 169 170 @Override 171 protected void doStart() throws Exception { 172 ServiceHelper.startServices(processor); 173 delivery = new Delivery(); 174 engine.start(); 175 delivery.start(); 176 } 177 178 @Override 179 protected void doStop() throws Exception { 180 // let's stop everything in the reverse order 181 // no need to stop the worker thread -- it will stop automatically when this service is stopped 182 engine.stop(); 183 ServiceHelper.stopServices(processor); 184 } 185 186 /** 187 * Sends the <code>exchange</code> to the next <code>processor</code>. 188 * 189 * @param exchange exchange to send. 190 */ 191 public void sendElement(Exchange exchange) throws Exception { 192 processor.process(exchange); 193 } 194 195 public void process(Exchange exchange) throws Exception { 196 AsyncProcessorHelper.process(this, exchange); 197 } 198 199 public boolean process(Exchange exchange, AsyncCallback callback) { 200 while (engine.size() >= capacity) { 201 try { 202 Thread.sleep(getTimeout()); 203 } catch (InterruptedException e) { 204 // we was interrupted so break out 205 exchange.setException(e); 206 callback.done(true); 207 return true; 208 } 209 } 210 211 try { 212 engine.insert(exchange); 213 delivery.request(); 214 } catch (Exception e) { 215 if (isIgnoreInvalidExchanges()) { 216 LOG.debug("Invalid Exchange. This Exchange will be ignored: {}", exchange); 217 } else { 218 exchange.setException(new CamelExchangeException("Error processing Exchange in StreamResequencer", exchange, e)); 219 } 220 } 221 222 callback.done(true); 223 return true; 224 } 225 226 public boolean hasNext() { 227 return processor != null; 228 } 229 230 public List<Processor> next() { 231 if (!hasNext()) { 232 return null; 233 } 234 List<Processor> answer = new ArrayList<Processor>(1); 235 answer.add(processor); 236 return answer; 237 } 238 239 class Delivery extends Thread { 240 241 private Lock deliveryRequestLock = new ReentrantLock(); 242 private Condition deliveryRequestCondition = deliveryRequestLock.newCondition(); 243 244 public Delivery() { 245 super(camelContext.getExecutorServiceManager().resolveThreadName("Resequencer Delivery")); 246 } 247 248 @Override 249 public void run() { 250 while (isRunAllowed()) { 251 try { 252 deliveryRequestLock.lock(); 253 try { 254 deliveryRequestCondition.await(DELIVERY_ATTEMPT_INTERVAL, TimeUnit.MILLISECONDS); 255 } finally { 256 deliveryRequestLock.unlock(); 257 } 258 } catch (InterruptedException e) { 259 break; 260 } 261 try { 262 engine.deliver(); 263 } catch (Throwable t) { 264 // a fail safe to handle all exceptions being thrown 265 getExceptionHandler().handleException(t); 266 } 267 } 268 } 269 270 public void cancel() { 271 interrupt(); 272 } 273 274 public void request() { 275 deliveryRequestLock.lock(); 276 try { 277 deliveryRequestCondition.signal(); 278 } finally { 279 deliveryRequestLock.unlock(); 280 } 281 } 282 283 } 284 285}