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.spring.handler;
018    
019    import java.util.HashMap;
020    import java.util.HashSet;
021    import java.util.Map;
022    import java.util.Set;
023    
024    import javax.xml.bind.Binder;
025    import javax.xml.bind.JAXBContext;
026    import javax.xml.bind.JAXBException;
027    import org.w3c.dom.Element;
028    import org.w3c.dom.Node;
029    import org.w3c.dom.NodeList;
030    
031    import org.apache.camel.builder.xml.Namespaces;
032    import org.apache.camel.model.FromDefinition;
033    import org.apache.camel.model.SendDefinition;
034    import org.apache.camel.model.dataformat.ArtixDSDataFormat;
035    import org.apache.camel.model.dataformat.JaxbDataFormat;
036    import org.apache.camel.model.dataformat.SerializationDataFormat;
037    import org.apache.camel.model.dataformat.XMLBeansDataFormat;
038    import org.apache.camel.model.loadbalancer.FailoverLoadBalancerDefinition;
039    import org.apache.camel.model.loadbalancer.RandomLoadBalancerDefinition;
040    import org.apache.camel.model.loadbalancer.RoundRobinLoadBalancerDefinition;
041    import org.apache.camel.model.loadbalancer.StickyLoadBalancerDefinition;
042    import org.apache.camel.model.loadbalancer.TopicLoadBalancerDefinition;
043    import org.apache.camel.spi.NamespaceAware;
044    import org.apache.camel.spring.CamelBeanPostProcessor;
045    import org.apache.camel.spring.CamelConsumerTemplateFactoryBean;
046    import org.apache.camel.spring.CamelContextFactoryBean;
047    import org.apache.camel.spring.CamelEndpointFactoryBean;
048    import org.apache.camel.spring.CamelJMXAgentDefinition;
049    import org.apache.camel.spring.CamelProducerTemplateFactoryBean;
050    import org.apache.camel.spring.remoting.CamelProxyFactoryBean;
051    import org.apache.camel.spring.remoting.CamelServiceExporter;
052    import org.apache.camel.util.ObjectHelper;
053    import org.apache.camel.view.ModelFileGenerator;
054    import org.springframework.beans.factory.BeanDefinitionStoreException;
055    import org.springframework.beans.factory.config.BeanDefinition;
056    import org.springframework.beans.factory.config.RuntimeBeanReference;
057    import org.springframework.beans.factory.parsing.BeanComponentDefinition;
058    import org.springframework.beans.factory.support.BeanDefinitionBuilder;
059    import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
060    import org.springframework.beans.factory.xml.ParserContext;
061    
062    /**
063     * Camel namespace for the spring XML configuration file.
064     */
065    public class CamelNamespaceHandler extends NamespaceHandlerSupport {
066    
067        protected BeanDefinitionParser endpointParser = new BeanDefinitionParser(CamelEndpointFactoryBean.class);
068        protected BeanDefinitionParser beanPostProcessorParser = new BeanDefinitionParser(CamelBeanPostProcessor.class);
069        protected Set<String> parserElementNames = new HashSet<String>();
070        protected Binder<Node> binder;
071        private JAXBContext jaxbContext;
072        private Map<String, BeanDefinitionParser> parserMap = new HashMap<String, BeanDefinitionParser>();
073    
074        public ModelFileGenerator createModelFileGenerator() throws JAXBException {
075            return new ModelFileGenerator(getJaxbContext());
076        }
077    
078        public void init() {
079            // remoting
080            addBeanDefinitionParser("proxy", CamelProxyFactoryBean.class);
081            addBeanDefinitionParser("template", CamelProducerTemplateFactoryBean.class);
082            addBeanDefinitionParser("consumerTemplate", CamelConsumerTemplateFactoryBean.class);
083            addBeanDefinitionParser("export", CamelServiceExporter.class);
084           
085            // jmx agent
086            addBeanDefinitionParser("jmxAgent", CamelJMXAgentDefinition.class);
087    
088            // endpoint
089            addBeanDefinitionParser("endpoint", CamelEndpointFactoryBean.class);
090    
091            // camel context
092            Class cl = CamelContextFactoryBean.class;
093            try {
094                cl = Class.forName("org.apache.camel.osgi.CamelContextFactoryBean");
095            } catch (Throwable t) {
096                // not running with camel-osgi so we fallback to the regular factory bean
097            }
098            registerParser("camelContext", new CamelContextBeanDefinitionParser(cl));
099        }
100    
101        private void addBeanDefinitionParser(String elementName, Class<?> type) {
102            BeanDefinitionParser parser = new BeanDefinitionParser(type);
103            registerParser(elementName, parser);
104            parserMap.put(elementName, parser);
105        }
106    
107        protected void createBeanPostProcessor(ParserContext parserContext, String contextId, Element childElement, BeanDefinitionBuilder parentBuilder) {
108            String beanPostProcessorId = contextId + ":beanPostProcessor";
109            childElement.setAttribute("id", beanPostProcessorId);
110            BeanDefinition definition = beanPostProcessorParser.parse(childElement, parserContext);
111            // only register to camel context id as a String. Then we can look it up later
112            // otherwise we get a circular reference in spring and it will not allow custom bean post processing
113            // see more at CAMEL-1663
114            definition.getPropertyValues().addPropertyValue("camelId", contextId);
115            parentBuilder.addPropertyReference("beanPostProcessor", beanPostProcessorId);
116        }
117    
118        protected void registerScriptParser(String elementName, String engineName) {
119            registerParser(elementName, new ScriptDefinitionParser(engineName));
120        }
121    
122        protected void registerParser(String name, org.springframework.beans.factory.xml.BeanDefinitionParser parser) {
123            parserElementNames.add(name);
124            registerBeanDefinitionParser(name, parser);
125        }
126    
127        public Set<String> getParserElementNames() {
128            return parserElementNames;
129        }
130    
131        protected Object parseUsingJaxb(Element element, ParserContext parserContext) {
132            try {
133                binder = getJaxbContext().createBinder();
134                return binder.unmarshal(element);
135            } catch (JAXBException e) {
136                throw new BeanDefinitionStoreException("Failed to parse JAXB element: " + e, e);
137            }
138        }
139    
140        public JAXBContext getJaxbContext() throws JAXBException {
141            if (jaxbContext == null) {
142                jaxbContext = createJaxbContext();
143            }
144            return jaxbContext;
145        }
146    
147        protected JAXBContext createJaxbContext() throws JAXBException {
148            StringBuilder packages = new StringBuilder();
149            for (Class cl : getJaxbPackages()) {
150                if (packages.length() > 0) {
151                    packages.append(":");
152                }
153                packages.append(cl.getName().substring(0, cl.getName().lastIndexOf('.')));
154            }
155            return JAXBContext.newInstance(packages.toString(), getClass().getClassLoader());
156        }
157    
158        protected Set<Class> getJaxbPackages() {
159            Set<Class> classes = new HashSet<Class>();
160            classes.add(org.apache.camel.spring.CamelContextFactoryBean.class);
161            classes.add(org.apache.camel.ExchangePattern.class);
162            classes.add(org.apache.camel.model.RouteDefinition.class);
163            classes.add(org.apache.camel.model.config.StreamResequencerConfig.class);     
164            classes.add(org.apache.camel.model.dataformat.DataFormatsDefinition.class);
165            classes.add(org.apache.camel.model.language.ExpressionDefinition.class);
166            classes.add(org.apache.camel.model.loadbalancer.RoundRobinLoadBalancerDefinition.class);
167            return classes;
168        }
169    
170        protected class CamelContextBeanDefinitionParser extends BeanDefinitionParser {
171            public CamelContextBeanDefinitionParser(Class type) {
172                super(type);
173            }
174    
175            @Override
176            protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
177                super.doParse(element, parserContext, builder);
178    
179                String contextId = element.getAttribute("id");
180    
181                // lets avoid folks having to explicitly give an ID to a camel context
182                if (ObjectHelper.isEmpty(contextId)) {
183                    contextId = "camelContext";
184                    element.setAttribute("id", contextId);
185                }
186    
187                // now lets parse the routes with JAXB
188                Object value = parseUsingJaxb(element, parserContext);
189                
190                if (value instanceof CamelContextFactoryBean) {
191                    // set the property value with the JAXB parsed value
192                    CamelContextFactoryBean factoryBean = (CamelContextFactoryBean)value;
193                    builder.addPropertyValue("id", contextId);
194                    builder.addPropertyValue("routes", factoryBean.getRoutes());
195                    builder.addPropertyValue("intercepts", factoryBean.getIntercepts());
196                    builder.addPropertyValue("interceptFroms", factoryBean.getInterceptFroms());
197                    builder.addPropertyValue("interceptSendToEndpoints", factoryBean.getInterceptSendToEndpoints());
198                    builder.addPropertyValue("dataFormats", factoryBean.getDataFormats());
199                    builder.addPropertyValue("onCompletions", factoryBean.getOnCompletions());
200                    builder.addPropertyValue("onExceptions", factoryBean.getOnExceptions());
201                    builder.addPropertyValue("builderRefs", factoryBean.getBuilderRefs());
202                    builder.addPropertyValue("properties", factoryBean.getProperties());
203                    if (factoryBean.getPackages().length > 0) {
204                        builder.addPropertyValue("packages", factoryBean.getPackages());
205                    }
206                }
207    
208                boolean createdBeanPostProcessor = false;
209                NodeList list = element.getChildNodes();
210                int size = list.getLength();
211                for (int i = 0; i < size; i++) {
212                    Node child = list.item(i);
213                    if (child instanceof Element) {
214                        Element childElement = (Element)child;
215                        String localName = child.getLocalName();
216                        if (localName.equals("beanPostProcessor")) {
217                            createBeanPostProcessor(parserContext, contextId, childElement, builder);
218                            createdBeanPostProcessor = true;
219                        } else if (localName.equals("endpoint")) {
220                            registerEndpoint(childElement, parserContext, contextId);
221                        } else {
222                            BeanDefinitionParser parser = parserMap.get(localName);
223                            if (parser != null) {
224                                BeanDefinition definition = parser.parse(childElement, parserContext);
225                                String id = childElement.getAttribute("id");
226                                if (ObjectHelper.isNotEmpty(id)) {
227                                    parserContext.registerComponent(new BeanComponentDefinition(definition, id));
228                                    if (localName.equals("jmxAgent")) {
229                                        builder.addPropertyReference("camelJMXAgent", id);
230                                    }
231                                }
232                            }
233    
234                        }
235                    }
236                }
237    
238                // register as endpoint defined indirectly in the routes by from/to types having id explict set
239                registerEndpointsWithIdsDefinedInFromOrToTypes(element, parserContext, contextId);
240    
241                // lets inject the namespaces into any namespace aware POJOs
242                injectNamespaces(element);
243                if (!createdBeanPostProcessor) {
244                    // no bean processor element so lets create it by ourself
245                    Element childElement = element.getOwnerDocument().createElement("beanPostProcessor");
246                    element.appendChild(childElement);
247                    createBeanPostProcessor(parserContext, contextId, childElement, builder);
248                }
249            }
250        }
251    
252        protected void injectNamespaces(Element element) {
253            NodeList list = element.getChildNodes();
254            Namespaces namespaces = null;
255            int size = list.getLength();
256            for (int i = 0; i < size; i++) {
257                Node child = list.item(i);
258                if (child instanceof Element) {
259                    Element childElement = (Element)child;
260                    Object object = binder.getJAXBNode(child);
261                    if (object instanceof NamespaceAware) {
262                        NamespaceAware namespaceAware = (NamespaceAware)object;
263                        if (namespaces == null) {
264                            namespaces = new Namespaces(element);
265                        }
266                        namespaces.configure(namespaceAware);
267                    }
268                    injectNamespaces(childElement);
269                }
270            }
271        }
272    
273        /**
274         * Uses for auto registering endpoints from the <tt>from</tt> or <tt>to</tt> DSL if they have an id attribute set
275         */
276        protected void registerEndpointsWithIdsDefinedInFromOrToTypes(Element element, ParserContext parserContext, String contextId) {
277            NodeList list = element.getChildNodes();
278            int size = list.getLength();
279            for (int i = 0; i < size; i++) {
280                Node child = list.item(i);
281                if (child instanceof Element) {
282                    Element childElement = (Element)child;
283                    Object object = binder.getJAXBNode(child);
284                    // we only want from/to types to be registered as endpoints
285                    if (object instanceof FromDefinition || object instanceof SendDefinition) {
286                        registerEndpoint(childElement, parserContext, contextId);
287                    }
288                    // recursive
289                    registerEndpointsWithIdsDefinedInFromOrToTypes(childElement, parserContext, contextId);
290                }
291            }
292        }
293    
294        private void registerEndpoint(Element childElement, ParserContext parserContext, String contextId) {
295            String id = childElement.getAttribute("id");
296            // must have an id to be registered
297            if (ObjectHelper.isNotEmpty(id)) {
298                BeanDefinition definition = endpointParser.parse(childElement, parserContext);
299                definition.getPropertyValues().addPropertyValue("camelContext", new RuntimeBeanReference(contextId));
300                parserContext.registerComponent(new BeanComponentDefinition(definition, id));
301            }
302        }
303        
304    }