org.apache.camel.processor.resequencer.ResequencerEngine Maven / Gradle / Ivy
Show all versions of camel-core Show documentation
/**
* 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));
}
}