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 */
017 package org.apache.camel.model;
018
019 import java.lang.reflect.Method;
020 import java.util.Map;
021 import javax.xml.bind.annotation.XmlAccessType;
022 import javax.xml.bind.annotation.XmlAccessorType;
023 import javax.xml.bind.annotation.XmlAttribute;
024 import javax.xml.bind.annotation.XmlRootElement;
025 import javax.xml.bind.annotation.XmlTransient;
026
027 import org.apache.camel.NoSuchBeanException;
028 import org.apache.camel.Processor;
029 import org.apache.camel.RuntimeCamelException;
030 import org.apache.camel.processor.WrapProcessor;
031 import org.apache.camel.spi.Policy;
032 import org.apache.camel.spi.RouteContext;
033 import org.apache.camel.spi.TransactedPolicy;
034 import org.apache.camel.util.CamelContextHelper;
035 import org.apache.camel.util.ObjectHelper;
036 import org.slf4j.Logger;
037 import org.slf4j.LoggerFactory;
038
039 /**
040 * Represents an XML <transacted/> element
041 *
042 * @version
043 */
044 @XmlRootElement(name = "transacted")
045 @XmlAccessorType(XmlAccessType.FIELD)
046 public class TransactedDefinition extends OutputDefinition<TransactedDefinition> {
047
048 // TODO: Align this code with PolicyDefinition
049
050 // JAXB does not support changing the ref attribute from required to optional
051 // if we extend PolicyDefinition so we must make a copy of the class
052 @XmlTransient
053 public static final String PROPAGATION_REQUIRED = "PROPAGATION_REQUIRED";
054
055 private static final transient Logger LOG = LoggerFactory.getLogger(TransactedDefinition.class);
056
057 @XmlTransient
058 protected Class<? extends Policy> type = TransactedPolicy.class;
059 @XmlAttribute
060 protected String ref;
061 @XmlTransient
062 private Policy policy;
063
064 public TransactedDefinition() {
065 }
066
067 public TransactedDefinition(Policy policy) {
068 this.policy = policy;
069 }
070
071 @Override
072 public String toString() {
073 return "Transacted[" + description() + "]";
074 }
075
076 protected String description() {
077 if (ref != null) {
078 return "ref:" + ref;
079 } else if (policy != null) {
080 return policy.toString();
081 } else {
082 return "";
083 }
084 }
085
086 @Override
087 public String getShortName() {
088 return "transacted";
089 }
090
091 @Override
092 public String getLabel() {
093 return "transacted[" + description() + "]";
094 }
095
096 @Override
097 public boolean isAbstract() {
098 return true;
099 }
100
101 public String getRef() {
102 return ref;
103 }
104
105 public void setRef(String ref) {
106 this.ref = ref;
107 }
108
109 /**
110 * Sets a policy type that this definition should scope within.
111 * <p/>
112 * Is used for convention over configuration situations where the policy
113 * should be automatic looked up in the registry and it should be based
114 * on this type. For instance a {@link org.apache.camel.spi.TransactedPolicy}
115 * can be set as type for easy transaction configuration.
116 * <p/>
117 * Will by default scope to the wide {@link Policy}
118 *
119 * @param type the policy type
120 */
121 public void setType(Class<? extends Policy> type) {
122 this.type = type;
123 }
124
125 /**
126 * Sets a reference to use for lookup the policy in the registry.
127 *
128 * @param ref the reference
129 * @return the builder
130 */
131 public TransactedDefinition ref(String ref) {
132 setRef(ref);
133 return this;
134 }
135
136 @Override
137 public Processor createProcessor(RouteContext routeContext) throws Exception {
138 Policy policy = resolvePolicy(routeContext);
139 ObjectHelper.notNull(policy, "policy", this);
140
141 // before wrap
142 policy.beforeWrap(routeContext, this);
143
144 // create processor after the before wrap
145 Processor childProcessor = this.createChildProcessor(routeContext, true);
146
147 // wrap
148 Processor target = policy.wrap(routeContext, childProcessor);
149
150 // wrap the target so it becomes a service and we can manage its lifecycle
151 WrapProcessor wrap = new WrapProcessor(target, childProcessor);
152 return wrap;
153 }
154
155 protected Policy resolvePolicy(RouteContext routeContext) {
156 if (policy != null) {
157 return policy;
158 }
159 return doResolvePolicy(routeContext, getRef(), type);
160 }
161
162 protected static Policy doResolvePolicy(RouteContext routeContext, String ref, Class<? extends Policy> type) {
163 // explicit ref given so lookup by it
164 if (ObjectHelper.isNotEmpty(ref)) {
165 return CamelContextHelper.mandatoryLookup(routeContext.getCamelContext(), ref, Policy.class);
166 }
167
168 // no explicit reference given from user so we can use some convention over configuration here
169
170 // try to lookup by scoped type
171 Policy answer = null;
172 if (type != null) {
173 // try find by type, note that this method is not supported by all registry
174 Map<String, ?> types = routeContext.lookupByType(type);
175 if (types.size() == 1) {
176 // only one policy defined so use it
177 Object found = types.values().iterator().next();
178 if (type.isInstance(found)) {
179 return type.cast(found);
180 }
181 }
182 }
183
184 // for transacted routing try the default REQUIRED name
185 if (type == TransactedPolicy.class) {
186 // still not found try with the default name PROPAGATION_REQUIRED
187 answer = routeContext.lookup(PROPAGATION_REQUIRED, TransactedPolicy.class);
188 }
189
190 // this logic only applies if we are a transacted policy
191 // still no policy found then try lookup the platform transaction manager and use it as policy
192 if (answer == null && type == TransactedPolicy.class) {
193 Class<?> tmClazz = routeContext.getCamelContext().getClassResolver().resolveClass("org.springframework.transaction.PlatformTransactionManager");
194 if (tmClazz != null) {
195 // see if we can find the platform transaction manager in the registry
196 Map<String, ?> maps = routeContext.lookupByType(tmClazz);
197 if (maps.size() == 1) {
198 // only one platform manager then use it as default and create a transacted
199 // policy with it and default to required
200
201 // as we do not want dependency on spring jars in the camel-core we use
202 // reflection to lookup classes and create new objects and call methods
203 // as this is only done during route building it does not matter that we
204 // use reflection as performance is no a concern during route building
205 Object transactionManager = maps.values().iterator().next();
206 LOG.debug("One instance of PlatformTransactionManager found in registry: {}", transactionManager);
207 Class<?> txClazz = routeContext.getCamelContext().getClassResolver().resolveClass("org.apache.camel.spring.spi.SpringTransactionPolicy");
208 if (txClazz != null) {
209 LOG.debug("Creating a new temporary SpringTransactionPolicy using the PlatformTransactionManager: {}", transactionManager);
210 TransactedPolicy txPolicy = ObjectHelper.newInstance(txClazz, TransactedPolicy.class);
211 Method method;
212 try {
213 method = txClazz.getMethod("setTransactionManager", tmClazz);
214 } catch (NoSuchMethodException e) {
215 throw new RuntimeCamelException("Cannot get method setTransactionManager(PlatformTransactionManager) on class: " + txClazz);
216 }
217 ObjectHelper.invokeMethod(method, txPolicy, transactionManager);
218 return txPolicy;
219 } else {
220 // camel-spring is missing on the classpath
221 throw new RuntimeCamelException("Cannot create a transacted policy as camel-spring.jar is not on the classpath!");
222 }
223 } else {
224 if (maps.isEmpty()) {
225 throw new NoSuchBeanException(null, "PlatformTransactionManager");
226 } else {
227 throw new IllegalArgumentException("Found " + maps.size() + " PlatformTransactionManager in registry. "
228 + "Cannot determine which one to use. Please configure a TransactionTemplate on the transacted policy.");
229 }
230 }
231 }
232 }
233
234 return answer;
235 }
236
237 }