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