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