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}