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}