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