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 }