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