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}