All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.camel.processor.resequencer.ResequencerEngine Maven / Gradle / Ivy

There is a newer version: 4.6.0
Show newest version
/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.camel.processor.resequencer;

import java.util.Timer;

import org.apache.camel.util.concurrent.ThreadHelper;

/**
 * Resequences elements based on a given {@link SequenceElementComparator}.
 * This resequencer is designed for resequencing element streams. Stream-based
 * resequencing has the advantage that the number of elements to be resequenced
 * need not be known in advance. Resequenced elements are delivered via a
 * {@link SequenceSender}.
 * 

* The resequencer's behaviour for a given comparator is controlled by the * timeout property. This is the timeout (in milliseconds) for a * given element managed by this resequencer. An out-of-sequence element can * only be marked as ready-for-delivery if it either times out or if it * has an immediate predecessor (in that case it is in-sequence). If an * immediate predecessor of a waiting element arrives the timeout task for the * waiting element will be cancelled (which marks it as ready-for-delivery). *

* If the maximum out-of-sequence time difference between elements within a * stream is known, the timeout value should be set to this * value. In this case it is guaranteed that all elements of a stream will be * delivered in sequence via the {@link SequenceSender}. The lower the * timeout value is compared to the out-of-sequence time * difference between elements within a stream the higher the probability is for * out-of-sequence elements delivered by this resequencer. Delivery of elements * must be explicitly triggered by applications using the {@link #deliver()} or * {@link #deliverNext()} methods. Only elements that are ready-for-delivery * are delivered by these methods. The longer an application waits to trigger a * delivery the more elements may become ready-for-delivery. *

* The resequencer remembers the last-delivered element. If an element arrives * which is the immediate successor of the last-delivered element it is * ready-for-delivery immediately. After delivery the last-delivered * element is adjusted accordingly. If the last-delivered element is * null i.e. the resequencer was newly created the first arriving * element needs timeout milliseconds in any case for becoming * ready-for-delivery. *

* * @version */ public class ResequencerEngine { /** * The element that most recently hash been delivered or null * if no element has been delivered yet. */ private Element lastDelivered; /** * Minimum amount of time to wait for out-of-sequence elements. */ private long timeout; /** * A sequence of elements for sorting purposes. */ private Sequence> sequence; /** * A timer for scheduling timeout notifications. */ private Timer timer; /** * A strategy for sending sequence elements. */ private SequenceSender sequenceSender; /** * Indicates whether an error should be thrown if message older (based on Comparator) than the last delivered message is received. */ private Boolean rejectOld; /** * Creates a new resequencer instance with a default timeout of 2000 * milliseconds. * * @param comparator a sequence element comparator. */ public ResequencerEngine(SequenceElementComparator comparator) { this.sequence = createSequence(comparator); this.timeout = 2000L; this.lastDelivered = null; } public void start() { timer = new Timer(ThreadHelper.resolveThreadName("Camel Thread ${counter} - ${name}", "Stream Resequencer Timer"), true); } /** * Stops this resequencer (i.e. this resequencer's {@link Timer} instance). */ public void stop() { timer.cancel(); } /** * Returns the number of elements currently maintained by this resequencer. * * @return the number of elements currently maintained by this resequencer. */ public synchronized int size() { return sequence.size(); } /** * Returns this resequencer's timeout value. * * @return the timeout in milliseconds. */ public long getTimeout() { return timeout; } /** * Sets this sequencer's timeout value. * * @param timeout the timeout in milliseconds. */ public void setTimeout(long timeout) { this.timeout = timeout; } public Boolean getRejectOld() { return rejectOld; } public void setRejectOld(Boolean rejectOld) { this.rejectOld = rejectOld; } /** * Returns the sequence sender. * * @return the sequence sender. */ public SequenceSender getSequenceSender() { return sequenceSender; } /** * Sets the sequence sender. * * @param sequenceSender a sequence element sender. */ public void setSequenceSender(SequenceSender sequenceSender) { this.sequenceSender = sequenceSender; } /** * Returns the last delivered element. * * @return the last delivered element or null if no delivery * has been made yet. */ E getLastDelivered() { if (lastDelivered == null) { return null; } return lastDelivered.getObject(); } /** * Sets the last delivered element. This is for testing purposes only. * * @param o an element. */ void setLastDelivered(E o) { lastDelivered = new Element(o); } /** * Inserts the given element into this resequencer. If the element is not * ready for immediate delivery and has no immediate presecessor then it is * scheduled for timing out. After being timed out it is ready for delivery. * * @param o an element. * @throws IllegalArgumentException if the element cannot be used with this resequencer engine */ public synchronized void insert(E o) { // wrap object into internal element Element element = new Element(o); // validate the exchange has no problem if (!sequence.comparator().isValid(element)) { throw new IllegalArgumentException("Element cannot be used in comparator: " + sequence.comparator()); } // validate the exchange shouldn't be 'rejected' (if applicable) if (rejectOld != null && rejectOld.booleanValue() && beforeLastDelivered(element)) { throw new MessageRejectedException("rejecting message [" + element.getObject() + "], it should have been sent before the last delivered message [" + lastDelivered.getObject() + "]"); } // add element to sequence in proper order sequence.add(element); Element successor = sequence.successor(element); // check if there is an immediate successor and cancel // timer task (no need to wait any more for timeout) if (successor != null) { successor.cancel(); } // start delivery if current element is successor of last delivered element if (successorOfLastDelivered(element)) { // nothing to schedule } else if (sequence.predecessor(element) != null) { // nothing to schedule } else { element.schedule(defineTimeout()); } } /** * Delivers all elements which are currently ready to deliver. * * @throws Exception thrown by {@link SequenceSender#sendElement(Object)}. * * @see ResequencerEngine#deliverNext() */ public synchronized void deliver() throws Exception { while (deliverNext()) { // do nothing here } } /** * Attempts to deliver a single element from the head of the resequencer * queue (sequence). Only elements which have not been scheduled for timing * out or which already timed out can be delivered. Elements are delivered via * {@link SequenceSender#sendElement(Object)}. * * @return true if the element has been delivered * false otherwise. * * @throws Exception thrown by {@link SequenceSender#sendElement(Object)}. * */ public boolean deliverNext() throws Exception { if (sequence.size() == 0) { return false; } // inspect element with lowest sequence value Element element = sequence.first(); // if element is scheduled do not deliver and return if (element.scheduled()) { return false; } // remove deliverable element from sequence sequence.remove(element); // set the delivered element to last delivered element lastDelivered = element; // deliver the sequence element sequenceSender.sendElement(element.getObject()); // element has been delivered return true; } /** * Returns true if the given element is the immediate * successor of the last delivered element. * * @param element an element. * @return true if the given element is the immediate * successor of the last delivered element. */ private boolean successorOfLastDelivered(Element element) { if (lastDelivered == null) { return false; } if (sequence.comparator().successor(element, lastDelivered)) { return true; } return false; } /** * Retuns true if the given element is before the last delivered element. * * @param element an element. * @return true if the given element is before the last delivered element. */ private boolean beforeLastDelivered(Element element) { if (lastDelivered == null) { return false; } if (sequence.comparator().compare(element, lastDelivered) < 0) { return true; } return false; } /** * Creates a timeout task based on the timeout setting of this resequencer. * * @return a new timeout task. */ private Timeout defineTimeout() { return new Timeout(timer, timeout); } private static Sequence> createSequence(SequenceElementComparator comparator) { return new Sequence>(new ElementComparator(comparator)); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy