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.ArrayList;
021import java.util.List;
022import java.util.concurrent.TimeUnit;
023
024import javax.xml.bind.annotation.XmlAccessType;
025import javax.xml.bind.annotation.XmlAccessorType;
026import javax.xml.bind.annotation.XmlAttribute;
027import javax.xml.bind.annotation.XmlElement;
028import javax.xml.bind.annotation.XmlElementRef;
029import javax.xml.bind.annotation.XmlRootElement;
030import javax.xml.bind.annotation.XmlTransient;
031
032import org.apache.camel.Expression;
033import org.apache.camel.saga.CamelSagaService;
034import org.apache.camel.spi.Metadata;
035import org.apache.camel.util.ObjectHelper;
036import org.apache.camel.util.TimeUtils;
037
038/**
039 * Enables sagas on the route
040 */
041@Metadata(label = "eip,routing")
042@XmlRootElement(name = "saga")
043@XmlAccessorType(XmlAccessType.FIELD)
044public class SagaDefinition extends OutputDefinition<SagaDefinition> {
045
046    @XmlAttribute
047    @Metadata(javaType = "org.apache.camel.model.SagaPropagation", defaultValue = "REQUIRED", enums = "REQUIRED,REQUIRES_NEW,MANDATORY,SUPPORTS,NOT_SUPPORTED,NEVER")
048    private String propagation;
049
050    @XmlAttribute
051    @Metadata(javaType = "org.apache.camel.model.SagaCompletionMode", defaultValue = "AUTO", enums = "AUTO,MANUAL")
052    private String completionMode;
053
054    @XmlAttribute
055    @Metadata(javaType = "java.lang.Long", deprecationNote = "Use timeout instead")
056    @Deprecated
057    private String timeoutInMilliseconds;
058
059    @XmlAttribute
060    @Metadata(javaType = "java.time.Duration")
061    private String timeout;
062
063    @XmlElement
064    private SagaActionUriDefinition compensation;
065
066    @XmlElement
067    private SagaActionUriDefinition completion;
068
069    @XmlElement(name = "option")
070    private List<SagaOptionDefinition> options;
071
072    @XmlAttribute
073    private String sagaServiceRef;
074    @XmlTransient
075    private CamelSagaService sagaService;
076
077    public SagaDefinition() {
078    }
079
080    @XmlElementRef
081    @Override
082    public void setOutputs(List<ProcessorDefinition<?>> outputs) {
083        super.setOutputs(outputs);
084    }
085
086    @Override
087    public boolean isAbstract() {
088        return true;
089    }
090
091    @Override
092    public boolean isTopLevelOnly() {
093        return true;
094    }
095
096    @Override
097    public boolean isWrappingEntireOutput() {
098        return true;
099    }
100
101    @Override
102    public String getLabel() {
103        String desc = description();
104        if (ObjectHelper.isEmpty(desc)) {
105            return "saga";
106        } else {
107            return "saga[" + desc + "]";
108        }
109    }
110
111    @Override
112    public String toString() {
113        String desc = description();
114        if (ObjectHelper.isEmpty(desc)) {
115            return "Saga -> [" + outputs + "]";
116        } else {
117            return "Saga[" + desc + "] -> [" + outputs + "]";
118        }
119    }
120
121    // Properties
122
123    public SagaActionUriDefinition getCompensation() {
124        return compensation;
125    }
126
127    /**
128     * The compensation endpoint URI that must be called to compensate all
129     * changes done in the route. The route corresponding to the compensation
130     * URI must perform compensation and complete without error. If errors occur
131     * during compensation, the saga service may call again the compensation URI
132     * to retry.
133     */
134    public void setCompensation(SagaActionUriDefinition compensation) {
135        this.compensation = compensation;
136    }
137
138    public SagaActionUriDefinition getCompletion() {
139        return completion;
140    }
141
142    /**
143     * The completion endpoint URI that will be called when the Saga is
144     * completed successfully. The route corresponding to the completion URI
145     * must perform completion tasks and terminate without error. If errors
146     * occur during completion, the saga service may call again the completion
147     * URI to retry.
148     */
149    public void setCompletion(SagaActionUriDefinition completion) {
150        this.completion = completion;
151    }
152
153    public String getPropagation() {
154        return propagation;
155    }
156
157    /**
158     * Set the Saga propagation mode (REQUIRED, REQUIRES_NEW, MANDATORY,
159     * SUPPORTS, NOT_SUPPORTED, NEVER).
160     */
161    public void setPropagation(String propagation) {
162        this.propagation = propagation;
163    }
164
165    public String getCompletionMode() {
166        return completionMode;
167    }
168
169    /**
170     * Determine how the saga should be considered complete. When set to AUTO,
171     * the saga is completed when the exchange that initiates the saga is
172     * processed successfully, or compensated when it completes exceptionally.
173     * When set to MANUAL, the user must complete or compensate the saga using
174     * the "saga:complete" or "saga:compensate" endpoints.
175     */
176    public void setCompletionMode(String completionMode) {
177        this.completionMode = completionMode;
178    }
179
180    public CamelSagaService getSagaService() {
181        return sagaService;
182    }
183
184    public void setSagaService(CamelSagaService sagaService) {
185        this.sagaService = sagaService;
186    }
187
188    public String getSagaServiceRef() {
189        return sagaServiceRef;
190    }
191
192    /**
193     * Refers to the id to lookup in the registry for the specific CamelSagaService to use.
194     */
195    public void setSagaServiceRef(String sagaServiceRef) {
196        this.sagaServiceRef = sagaServiceRef;
197    }
198
199    public List<SagaOptionDefinition> getOptions() {
200        return options;
201    }
202
203    /**
204     * Allows to save properties of the current exchange in order to re-use them
205     * in a compensation/completion callback route. Options are usually helpful
206     * e.g. to store and retrieve identifiers of objects that should be deleted
207     * in compensating actions. Option values will be transformed into input
208     * headers of the compensation/completion exchange.
209     */
210    public void setOptions(List<SagaOptionDefinition> options) {
211        this.options = options;
212    }
213
214    public String getTimeout() {
215        return timeout;
216    }
217
218    /**
219     * Set the maximum amount of time for the Saga. After the timeout is
220     * expired, the saga will be compensated automatically (unless a different
221     * decision has been taken in the meantime).
222     */
223    public void setTimeout(String timeout) {
224        this.timeout = timeout;
225    }
226
227    public String getTimeoutInMilliseconds() {
228        return timeoutInMilliseconds;
229    }
230
231    /**
232     * Set the maximum amount of time for the Saga. After the timeout is
233     * expired, the saga will be compensated automatically (unless a different
234     * decision has been taken in the meantime).
235     */
236    public void setTimeoutInMilliseconds(String timeoutInMilliseconds) {
237        this.timeoutInMilliseconds = timeoutInMilliseconds;
238    }
239
240    private void addOption(String option, Expression expression) {
241        if (this.options == null) {
242            this.options = new ArrayList<>();
243        }
244        this.options.add(new SagaOptionDefinition(option, expression));
245    }
246
247    // Builders
248
249    public SagaDefinition compensation(String compensation) {
250        if (this.compensation != null) {
251            throw new IllegalStateException("Compensation has already been set");
252        }
253        this.compensation = new SagaActionUriDefinition(compensation);
254        return this;
255    }
256
257    public SagaDefinition completion(String completion) {
258        if (this.completion != null) {
259            throw new IllegalStateException("Completion has already been set");
260        }
261        this.completion = new SagaActionUriDefinition(completion);
262        return this;
263    }
264
265    public SagaDefinition propagation(String propagation) {
266        return propagation(propagation);
267    }
268
269    public SagaDefinition propagation(SagaPropagation propagation) {
270        setPropagation(propagation.name());
271        return this;
272    }
273
274    public SagaDefinition sagaService(CamelSagaService sagaService) {
275        setSagaService(sagaService);
276        return this;
277    }
278
279    public SagaDefinition sagaServiceRef(String sagaServiceRef) {
280        setSagaServiceRef(sagaServiceRef);
281        return this;
282    }
283
284    public SagaDefinition completionMode(SagaCompletionMode completionMode) {
285        return completionMode(completionMode.name());
286    }
287
288    public SagaDefinition completionMode(String completionMode) {
289        setCompletionMode(completionMode);
290        return this;
291    }
292
293    public SagaDefinition option(String option, Expression expression) {
294        addOption(option, expression);
295        return this;
296    }
297
298    public SagaDefinition timeout(Duration duration) {
299        return timeout(TimeUtils.printDuration(duration));
300    }
301
302    public SagaDefinition timeout(long timeout, TimeUnit unit) {
303        return timeout(Duration.ofMillis(unit.toMillis(timeout)));
304    }
305
306    public SagaDefinition timeout(String duration) {
307        setTimeout(duration);
308        return this;
309    }
310
311    // Utils
312
313    protected String description() {
314        StringBuilder desc = new StringBuilder();
315        addField(desc, "compensation", compensation);
316        addField(desc, "completion", completion);
317        addField(desc, "propagation", propagation);
318        return desc.toString();
319    }
320
321    private void addField(StringBuilder builder, String key, Object value) {
322        if (value == null) {
323            return;
324        }
325        if (builder.length() > 0) {
326            builder.append(',');
327        }
328        builder.append(key).append(':').append(value);
329    }
330
331}