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 */
017package org.apache.camel.model;
018
019import java.time.Duration;
020import java.util.List;
021
022import javax.xml.bind.annotation.XmlAccessType;
023import javax.xml.bind.annotation.XmlAccessorType;
024import javax.xml.bind.annotation.XmlElement;
025import javax.xml.bind.annotation.XmlElementRef;
026import javax.xml.bind.annotation.XmlElements;
027import javax.xml.bind.annotation.XmlRootElement;
028import javax.xml.bind.annotation.XmlTransient;
029
030import org.apache.camel.Expression;
031import org.apache.camel.model.config.BatchResequencerConfig;
032import org.apache.camel.model.config.ResequencerConfig;
033import org.apache.camel.model.config.StreamResequencerConfig;
034import org.apache.camel.model.language.ExpressionDefinition;
035import org.apache.camel.processor.resequencer.ExpressionResultComparator;
036import org.apache.camel.spi.Metadata;
037import org.apache.camel.util.TimeUtils;
038
039/**
040 * Resequences (re-order) messages based on an expression
041 */
042@Metadata(label = "eip,routing")
043@XmlRootElement(name = "resequence")
044@XmlAccessorType(XmlAccessType.FIELD)
045public class ResequenceDefinition extends OutputDefinition<ResequenceDefinition> {
046    @Metadata(required = false)
047    @XmlElements({@XmlElement(name = "batch-config", type = BatchResequencerConfig.class), @XmlElement(name = "stream-config", type = StreamResequencerConfig.class)})
048    private ResequencerConfig resequencerConfig;
049    @XmlTransient
050    private BatchResequencerConfig batchConfig;
051    @XmlTransient
052    private StreamResequencerConfig streamConfig;
053    @XmlElementRef
054    @Metadata(required = true)
055    private ExpressionDefinition expression;
056
057    public ResequenceDefinition() {
058    }
059
060    public ResequenceDefinition(Expression expression) {
061        if (expression != null) {
062            setExpression(ExpressionNodeHelper.toExpressionDefinition(expression));
063        }
064    }
065
066    @Override
067    public List<ProcessorDefinition<?>> getOutputs() {
068        return outputs;
069    }
070
071    @XmlElementRef
072    @Override
073    public void setOutputs(List<ProcessorDefinition<?>> outputs) {
074        super.setOutputs(outputs);
075    }
076
077    // Fluent API
078    // -------------------------------------------------------------------------
079    /**
080     * Configures the stream-based resequencing algorithm using the default
081     * configuration.
082     *
083     * @return the builder
084     */
085    public ResequenceDefinition stream() {
086        return stream(StreamResequencerConfig.getDefault());
087    }
088
089    /**
090     * Configures the batch-based resequencing algorithm using the default
091     * configuration.
092     *
093     * @return the builder
094     */
095    public ResequenceDefinition batch() {
096        return batch(BatchResequencerConfig.getDefault());
097    }
098
099    /**
100     * Configures the stream-based resequencing algorithm using the given
101     * {@link StreamResequencerConfig}.
102     *
103     * @param config the config
104     * @return the builder
105     */
106    public ResequenceDefinition stream(StreamResequencerConfig config) {
107        this.streamConfig = config;
108        this.batchConfig = null;
109        return this;
110    }
111
112    /**
113     * Configures the batch-based resequencing algorithm using the given
114     * {@link BatchResequencerConfig}.
115     *
116     * @param config the config
117     * @return the builder
118     */
119    public ResequenceDefinition batch(BatchResequencerConfig config) {
120        this.batchConfig = config;
121        this.streamConfig = null;
122        return this;
123    }
124
125    /**
126     * Sets the timeout
127     *
128     * @param timeout timeout in millis
129     * @return the builder
130     */
131    public ResequenceDefinition timeout(long timeout) {
132        return timeout(Duration.ofMillis(timeout));
133    }
134
135    /**
136     * Sets the timeout
137     *
138     * @param timeout timeout
139     * @return the builder
140     */
141    public ResequenceDefinition timeout(Duration timeout) {
142        return timeout(TimeUtils.printDuration(timeout));
143    }
144
145    /**
146     * Sets the timeout
147     *
148     * @param timeout timeout
149     * @return the builder
150     */
151    public ResequenceDefinition timeout(String timeout) {
152        if (streamConfig != null) {
153            streamConfig.setTimeout(timeout);
154        } else {
155            // initialize batch mode as its default mode
156            if (batchConfig == null) {
157                batch();
158            }
159            batchConfig.setBatchTimeout(timeout);
160        }
161        return this;
162    }
163
164    /**
165     * Sets the interval in milli seconds the stream resequencer will at most
166     * wait while waiting for condition of being able to deliver.
167     *
168     * @param deliveryAttemptInterval interval in millis
169     * @return the builder
170     */
171    public ResequenceDefinition deliveryAttemptInterval(long deliveryAttemptInterval) {
172        if (streamConfig == null) {
173            throw new IllegalStateException("deliveryAttemptInterval() only supported for stream resequencer");
174        }
175        streamConfig.setDeliveryAttemptInterval(Long.toString(deliveryAttemptInterval));
176        return this;
177    }
178
179    /**
180     * Sets the rejectOld flag to throw an error when a message older than the
181     * last delivered message is processed
182     * 
183     * @return the builder
184     */
185    public ResequenceDefinition rejectOld() {
186        if (streamConfig == null) {
187            throw new IllegalStateException("rejectOld() only supported for stream resequencer");
188        }
189        streamConfig.setRejectOld(Boolean.toString(true));
190        return this;
191    }
192
193    /**
194     * Sets the in batch size for number of exchanges received
195     * 
196     * @param batchSize the batch size
197     * @return the builder
198     */
199    public ResequenceDefinition size(int batchSize) {
200        if (streamConfig != null) {
201            throw new IllegalStateException("size() only supported for batch resequencer");
202        }
203        // initialize batch mode as its default mode
204        if (batchConfig == null) {
205            batch();
206        }
207        batchConfig.setBatchSize(Integer.toString(batchSize));
208        return this;
209    }
210
211    /**
212     * Sets the capacity for the stream resequencer
213     *
214     * @param capacity the capacity
215     * @return the builder
216     */
217    public ResequenceDefinition capacity(int capacity) {
218        if (streamConfig == null) {
219            throw new IllegalStateException("capacity() only supported for stream resequencer");
220        }
221        streamConfig.setCapacity(Integer.toString(capacity));
222        return this;
223
224    }
225
226    /**
227     * Enables duplicates for the batch resequencer mode
228     * 
229     * @return the builder
230     */
231    public ResequenceDefinition allowDuplicates() {
232        if (streamConfig != null) {
233            throw new IllegalStateException("allowDuplicates() only supported for batch resequencer");
234        }
235        // initialize batch mode as its default mode
236        if (batchConfig == null) {
237            batch();
238        }
239        batchConfig.setAllowDuplicates(Boolean.toString(true));
240        return this;
241    }
242
243    /**
244     * Enables reverse mode for the batch resequencer mode.
245     * <p/>
246     * This means the expression for determine the sequence order will be
247     * reversed. Can be used for Z..A or 9..0 ordering.
248     *
249     * @return the builder
250     */
251    public ResequenceDefinition reverse() {
252        if (streamConfig != null) {
253            throw new IllegalStateException("reverse() only supported for batch resequencer");
254        }
255        // initialize batch mode as its default mode
256        if (batchConfig == null) {
257            batch();
258        }
259        batchConfig.setReverse(Boolean.toString(true));
260        return this;
261    }
262
263    /**
264     * If an incoming {@link org.apache.camel.Exchange} is invalid, then it will
265     * be ignored.
266     *
267     * @return builder
268     */
269    public ResequenceDefinition ignoreInvalidExchanges() {
270        if (streamConfig != null) {
271            streamConfig.setIgnoreInvalidExchanges(Boolean.toString(true));
272        } else {
273            // initialize batch mode as its default mode
274            if (batchConfig == null) {
275                batch();
276            }
277            batchConfig.setIgnoreInvalidExchanges(Boolean.toString(true));
278        }
279        return this;
280    }
281
282    /**
283     * Sets the comparator to use for stream resequencer
284     *
285     * @param comparator the comparator
286     * @return the builder
287     */
288    public ResequenceDefinition comparator(ExpressionResultComparator comparator) {
289        if (streamConfig == null) {
290            throw new IllegalStateException("comparator() only supported for stream resequencer");
291        }
292        streamConfig.setComparator(comparator);
293        return this;
294    }
295
296    @Override
297    public String toString() {
298        return "Resequencer[" + getExpression() + " -> " + getOutputs() + "]";
299    }
300
301    @Override
302    public String getShortName() {
303        return "resequence";
304    }
305
306    @Override
307    public String getLabel() {
308        return "resequencer[" + (getExpression() != null ? getExpression().getLabel() : "") + "]";
309    }
310
311    public ResequencerConfig getResequencerConfig() {
312        return resequencerConfig;
313    }
314
315    /**
316     * To configure the resequencer in using either batch or stream
317     * configuration. Will by default use batch configuration.
318     */
319    public void setResequencerConfig(ResequencerConfig resequencerConfig) {
320        this.resequencerConfig = resequencerConfig;
321    }
322
323    public BatchResequencerConfig getBatchConfig() {
324        if (batchConfig == null && resequencerConfig != null && resequencerConfig instanceof BatchResequencerConfig) {
325            return (BatchResequencerConfig)resequencerConfig;
326        }
327        return batchConfig;
328    }
329
330    public StreamResequencerConfig getStreamConfig() {
331        if (streamConfig == null && resequencerConfig != null && resequencerConfig instanceof StreamResequencerConfig) {
332            return (StreamResequencerConfig)resequencerConfig;
333        }
334        return streamConfig;
335    }
336
337    public void setBatchConfig(BatchResequencerConfig batchConfig) {
338        this.batchConfig = batchConfig;
339    }
340
341    public void setStreamConfig(StreamResequencerConfig streamConfig) {
342        this.streamConfig = streamConfig;
343    }
344
345    public ExpressionDefinition getExpression() {
346        return expression;
347    }
348
349    /**
350     * Expression to use for re-ordering the messages, such as a header with a
351     * sequence number
352     */
353    public void setExpression(ExpressionDefinition expression) {
354        this.expression = expression;
355    }
356
357    /**
358     * Expression to use for re-ordering the messages, such as a header with a
359     * sequence number
360     */
361    public void setExpression(Expression expression) {
362        setExpression(new ExpressionDefinition(expression));
363    }
364
365}