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.resequencer; 018 019 import java.util.Queue; 020 import java.util.Timer; 021 022 import org.apache.commons.logging.Log; 023 import org.apache.commons.logging.LogFactory; 024 025 /** 026 * Resequences elements based on a given {@link SequenceElementComparator}. 027 * This resequencer is designed for resequencing element streams. Resequenced 028 * elements are added to an output {@link Queue}. The resequencer is configured 029 * via the <code>timeout</code> and <code>capacity</code> properties. 030 * 031 * <ul> 032 * <li><code>timeout</code>. Defines the timeout (in milliseconds) for a 033 * given element managed by this resequencer. An out-of-sequence element can 034 * only be marked as <i>ready-for-delivery</i> if it either times out or if it 035 * has an immediate predecessor (in that case it is in-sequence). If an 036 * immediate predecessor of a waiting element arrives the timeout task for the 037 * waiting element will be cancelled (which marks it as <i>ready-for-delivery</i>). 038 * <p> 039 * If the maximum out-of-sequence time between elements within a stream is 040 * known, the <code>timeout</code> value should be set to this value. In this 041 * case it is guaranteed that all elements of a stream will be delivered in 042 * sequence to the output queue. However, large <code>timeout</code> values 043 * might require a very high resequencer <code>capacity</code> which might be 044 * in conflict with available memory resources. The lower the 045 * <code>timeout</code> value is compared to the out-of-sequence time between 046 * elements within a stream the higher the probability is for out-of-sequence 047 * elements delivered by this resequencer.</li> 048 * <li><code>capacity</code>. The capacity of this resequencer.</li> 049 * </ul> 050 * 051 * Whenever a timeout for a certain element occurs or an element has been added 052 * to this resequencer a delivery attempt is started. If a (sub)sequence of 053 * elements is <i>ready-for-delivery</i> then they are added to output queue. 054 * <p> 055 * The resequencer remembers the last-delivered element. If an element arrives 056 * which is the immediate successor of the last-delivered element it will be 057 * delivered immediately and the last-delivered element is adjusted accordingly. 058 * If the last-delivered element is <code>null</code> i.e. the resequencer was 059 * newly created the first arriving element will wait <code>timeout</code> 060 * milliseconds for being delivered to the output queue. 061 * 062 * @author Martin Krasser 063 * 064 * @version $Revision 065 */ 066 public class ResequencerEngine<E> implements TimeoutHandler { 067 068 private static final Log LOG = LogFactory.getLog(ResequencerEngine.class); 069 070 private long timeout; 071 072 private int capacity; 073 074 private Queue<E> outQueue; 075 076 private Element<E> lastDelivered; 077 078 /** 079 * A sequence of elements for sorting purposes. 080 */ 081 private Sequence<Element<E>> sequence; 082 083 /** 084 * A timer for scheduling timeout notifications. 085 */ 086 private Timer timer; 087 088 /** 089 * Creates a new resequencer instance with a default timeout of 2000 090 * milliseconds. The capacity is set to {@link Integer#MAX_VALUE}. 091 * 092 * @param comparator a sequence element comparator. 093 */ 094 public ResequencerEngine(SequenceElementComparator<E> comparator) { 095 this(comparator, Integer.MAX_VALUE); 096 } 097 098 /** 099 * Creates a new resequencer instance with a default timeout of 2000 100 * milliseconds. 101 * 102 * @param comparator a sequence element comparator. 103 * @param capacity the capacity of this resequencer. 104 */ 105 public ResequencerEngine(SequenceElementComparator<E> comparator, int capacity) { 106 this.timer = new Timer("Resequencer Timer"); 107 this.sequence = createSequence(comparator); 108 this.capacity = capacity; 109 this.timeout = 2000L; 110 this.lastDelivered = null; 111 } 112 113 /** 114 * Stops this resequencer (i.e. this resequencer's {@link Timer} instance). 115 */ 116 public void stop() { 117 this.timer.cancel(); 118 } 119 120 /** 121 * Returns the output queue. 122 * 123 * @return the output queue. 124 */ 125 public Queue<E> getOutQueue() { 126 return outQueue; 127 } 128 129 /** 130 * Sets the output queue. 131 * 132 * @param outQueue output queue. 133 */ 134 public void setOutQueue(Queue<E> outQueue) { 135 this.outQueue = outQueue; 136 } 137 138 /** 139 * Returns this resequencer's timeout value. 140 * 141 * @return the timeout in milliseconds. 142 */ 143 public long getTimeout() { 144 return timeout; 145 } 146 147 /** 148 * Sets this sequencer's timeout value. 149 * 150 * @param timeout the timeout in milliseconds. 151 */ 152 public void setTimeout(long timeout) { 153 this.timeout = timeout; 154 } 155 156 /** 157 * Handles a timeout notification by starting a delivery attempt. 158 * 159 * @param timout timeout task that caused the notification. 160 */ 161 public synchronized void timeout(Timeout timout) { 162 try { 163 while (deliver()) { 164 // work done in deliver() 165 } 166 } catch (RuntimeException e) { 167 LOG.error("error during delivery", e); 168 } 169 } 170 171 /** 172 * Adds an element to this resequencer throwing an exception if the maximum 173 * capacity is reached. 174 * 175 * @param o element to be resequenced. 176 * @throws IllegalStateException if the element cannot be added at this time 177 * due to capacity restrictions. 178 */ 179 public synchronized void add(E o) { 180 if (sequence.size() >= capacity) { 181 throw new IllegalStateException("maximum capacity is reached"); 182 } 183 insert(o); 184 } 185 186 /** 187 * Adds an element to this resequencer waiting, if necessary, until capacity 188 * becomes available. 189 * 190 * @param o element to be resequenced. 191 * @throws InterruptedException if interrupted while waiting. 192 */ 193 public synchronized void put(E o) throws InterruptedException { 194 if (sequence.size() >= capacity) { 195 wait(); 196 } 197 insert(o); 198 } 199 200 /** 201 * Returns the last delivered element. 202 * 203 * @return the last delivered element or <code>null</code> if no delivery 204 * has been made yet. 205 */ 206 E getLastDelivered() { 207 if (lastDelivered == null) { 208 return null; 209 } 210 return lastDelivered.getObject(); 211 } 212 213 /** 214 * Sets the last delivered element. This is for testing purposes only. 215 * 216 * @param o an element. 217 */ 218 void setLastDelivered(E o) { 219 lastDelivered = new Element<E>(o); 220 } 221 222 /** 223 * Inserts the given element into this resequencing queue (sequence). If the 224 * element is not ready for immediate delivery and has no immediate 225 * presecessor then it is scheduled for timing out. After being timed out it 226 * is ready for delivery. 227 * 228 * @param o an element. 229 */ 230 private void insert(E o) { 231 // wrap object into internal element 232 Element<E> element = new Element<E>(o); 233 // add element to sequence in proper order 234 sequence.add(element); 235 236 Element<E> successor = sequence.successor(element); 237 238 // check if there is an immediate successor and cancel 239 // timer task (no need to wait any more for timeout) 240 if (successor != null) { 241 successor.cancel(); 242 } 243 244 // start delivery if current element is successor of last delivered element 245 if (successorOfLastDelivered(element)) { 246 // nothing to schedule 247 } else if (sequence.predecessor(element) != null) { 248 // nothing to schedule 249 } else { 250 Timeout t = defineTimeout(); 251 element.schedule(t); 252 } 253 254 // start delivery 255 while (deliver()) { 256 // work done in deliver() 257 } 258 } 259 260 /** 261 * Attempts to deliver a single element from the head of the resequencer 262 * queue (sequence). Only elements which have not been scheduled for timing 263 * out or which already timed out can be delivered. 264 * 265 * @return <code>true</code> if the element has been delivered 266 * <code>false</code> otherwise. 267 */ 268 private boolean deliver() { 269 if (sequence.size() == 0) { 270 return false; 271 } 272 // inspect element with lowest sequence value 273 Element<E> element = sequence.first(); 274 275 // if element is scheduled do not deliver and return 276 if (element.scheduled()) { 277 return false; 278 } 279 280 // remove deliverable element from sequence 281 sequence.remove(element); 282 283 // set the delivered element to last delivered element 284 lastDelivered = element; 285 286 // notify a waiting thread that capacity is available 287 notify(); 288 289 // add element to output queue 290 outQueue.add(element.getObject()); 291 292 // element has been delivered 293 return true; 294 } 295 296 /** 297 * Returns <code>true</code> if the given element is the immediate 298 * successor of the last delivered element. 299 * 300 * @param element an element. 301 * @return <code>true</code> if the given element is the immediate 302 * successor of the last delivered element. 303 */ 304 private boolean successorOfLastDelivered(Element<E> element) { 305 if (lastDelivered == null) { 306 return false; 307 } 308 if (sequence.comparator().successor(element, lastDelivered)) { 309 return true; 310 } 311 return false; 312 } 313 314 /** 315 * Creates a timeout task based on the timeout setting of this resequencer. 316 * 317 * @return a new timeout task. 318 */ 319 private Timeout defineTimeout() { 320 Timeout result = new Timeout(timer, timeout); 321 result.addTimeoutHandler(this); 322 return result; 323 } 324 325 private static <E> Sequence<Element<E>> createSequence(SequenceElementComparator<E> comparator) { 326 return new Sequence<Element<E>>(new ElementComparator<E>(comparator)); 327 } 328 329 }