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    
028    import org.w3c.dom.Element;
029    import org.w3c.dom.Node;
030    import org.w3c.dom.NodeList;
031    
032    import org.apache.camel.builder.xml.Namespaces;
033    import org.apache.camel.model.dataformat.ArtixDSDataFormat;
034    import org.apache.camel.model.dataformat.JaxbDataFormat;
035    import org.apache.camel.model.dataformat.SerializationDataFormat;
036    import org.apache.camel.model.dataformat.XMLBeansDataFormat;
037    import org.apache.camel.model.loadbalancer.RandomLoadBalanceStrategy;
038    import org.apache.camel.model.loadbalancer.RoundRobinLoadBalanceStrategy;
039    import org.apache.camel.model.loadbalancer.StickyLoadBalanceStrategy;
040    import org.apache.camel.model.loadbalancer.TopicLoadBalanceStrategy;
041    import org.apache.camel.spi.NamespaceAware;
042    import org.apache.camel.spring.CamelBeanPostProcessor;
043    import org.apache.camel.spring.CamelContextFactoryBean;
044    import org.apache.camel.spring.CamelJMXAgentType;
045    import org.apache.camel.spring.CamelTemplateFactoryBean;
046    import org.apache.camel.spring.EndpointFactoryBean;
047    import org.apache.camel.spring.remoting.CamelProxyFactoryBean;
048    import org.apache.camel.spring.remoting.CamelServiceExporter;
049    import org.apache.camel.util.ObjectHelper;
050    import org.springframework.beans.factory.BeanDefinitionStoreException;
051    import org.springframework.beans.factory.config.BeanDefinition;
052    import org.springframework.beans.factory.config.RuntimeBeanReference;
053    import org.springframework.beans.factory.parsing.BeanComponentDefinition;
054    import org.springframework.beans.factory.support.BeanDefinitionBuilder;
055    import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
056    import org.springframework.beans.factory.xml.ParserContext;
057    
058    
059    /**
060     * Camel namespace for the spring XML configuration file.
061     */
062    public class CamelNamespaceHandler extends NamespaceHandlerSupport {
063    
064        protected BeanDefinitionParser endpointParser = new BeanDefinitionParser(EndpointFactoryBean.class);
065        protected BeanDefinitionParser beanPostProcessorParser = new BeanDefinitionParser(CamelBeanPostProcessor.class);
066        protected Set<String> parserElementNames = new HashSet<String>();
067        private JAXBContext jaxbContext;
068        private Map<String, BeanDefinitionParser> parserMap = new HashMap<String, BeanDefinitionParser>();
069        private Binder<Node> binder;
070    
071        public void init() {
072            // remoting
073            addBeanDefinitionParser("proxy", CamelProxyFactoryBean.class);
074            addBeanDefinitionParser("template", CamelTemplateFactoryBean.class);
075            addBeanDefinitionParser("export", CamelServiceExporter.class);
076    
077            // data types
078            addBeanDefinitionParser("artixDS", ArtixDSDataFormat.class);
079            addBeanDefinitionParser("jaxb", JaxbDataFormat.class);
080            addBeanDefinitionParser("serialization", SerializationDataFormat.class);
081            addBeanDefinitionParser("xmlBeans", XMLBeansDataFormat.class);
082    
083            // load balancers
084            addBeanDefinitionParser("roundRobin", RoundRobinLoadBalanceStrategy.class);
085            addBeanDefinitionParser("random", RandomLoadBalanceStrategy.class);
086            addBeanDefinitionParser("sticky", StickyLoadBalanceStrategy.class);
087            addBeanDefinitionParser("topic", TopicLoadBalanceStrategy.class);
088    
089            // jmx agent
090            addBeanDefinitionParser("jmxAgent", CamelJMXAgentType.class);
091    
092            // TODO switch to use the above mechanism?
093            registerParser("endpoint", endpointParser);
094    
095            Class cl = CamelContextFactoryBean.class;
096            try {
097                cl = Class.forName("org.apache.camel.osgi.CamelContextFactoryBean");
098            } catch (Throwable t) {
099            }
100            registerParser("camelContext", new CamelContextBeanDefinitionParser(cl));
101        }
102    
103        private void addBeanDefinitionParser(String elementName, Class<?> type) {
104            BeanDefinitionParser parser = new BeanDefinitionParser(type);
105            registerParser(elementName, parser);
106            parserMap.put(elementName, parser);
107        }
108    
109        protected void createBeanPostProcessor(ParserContext parserContext, String contextId, Element childElement, BeanDefinitionBuilder parentBuilder) {
110            String beanPostProcessorId = contextId + ":beanPostProcessor";
111            childElement.setAttribute("id", beanPostProcessorId);
112            BeanDefinition definition = beanPostProcessorParser.parse(childElement, parserContext);
113            definition.getPropertyValues().addPropertyValue("camelContext", new RuntimeBeanReference(contextId));
114            parentBuilder.addPropertyReference("beanPostProcessor", beanPostProcessorId);
115        }
116    
117        protected void registerScriptParser(String elementName, String engineName) {
118            registerParser(elementName, new ScriptDefinitionParser(engineName));
119        }
120    
121        protected void registerParser(String name,
122                                      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                /*
136                 * Unmarshaller unmarshaller =
137                 * getJaxbContext().createUnmarshaller(); return
138                 * unmarshaller.unmarshal(element);
139                 */
140            } catch (JAXBException e) {
141                throw new BeanDefinitionStoreException("Failed to parse JAXB element: " + e, e);
142            }
143        }
144    
145        protected JAXBContext getJaxbContext() throws JAXBException {
146            if (jaxbContext == null) {
147                jaxbContext = createJaxbContext();
148            }
149            return jaxbContext;
150        }
151    
152        protected JAXBContext createJaxbContext() throws JAXBException {
153            StringBuilder packages = new StringBuilder();
154            for (Class cl : getJaxbPackages()) {
155                if (packages.length() > 0) {
156                    packages.append(":");
157                }
158                packages.append(cl.getName().substring(0, cl.getName().lastIndexOf('.')));
159            }
160            return JAXBContext.newInstance(packages.toString(), getClass().getClassLoader());
161        }
162    
163        protected Set<Class> getJaxbPackages() {
164            Set<Class> classes = new HashSet<Class>();
165            classes.add(org.apache.camel.spring.CamelContextFactoryBean.class);
166            classes.add(org.apache.camel.model.RouteType.class);
167            classes.add(org.apache.camel.model.config.StreamResequencerConfig.class);
168            classes.add(org.apache.camel.model.dataformat.DataFormatType.class);
169            classes.add(org.apache.camel.model.language.ExpressionType.class);
170            classes.add(org.apache.camel.model.loadbalancer.LoadBalancerType.class);
171            return classes;
172        }
173    
174        protected class CamelContextBeanDefinitionParser extends BeanDefinitionParser {
175            public CamelContextBeanDefinitionParser(Class type) {
176                super(type);
177            }
178    
179            @Override
180            protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
181                super.doParse(element, parserContext, builder);
182    
183                String contextId = element.getAttribute("id");
184    
185                // lets avoid folks having to explicitly give an ID to a camel
186                // context
187                if (ObjectHelper.isNullOrBlank(contextId)) {
188                    contextId = "camelContext";
189                    element.setAttribute("id", contextId);
190                }
191    
192                // now lets parse the routes
193                Object value = parseUsingJaxb(element, parserContext);
194                if (value instanceof CamelContextFactoryBean) {
195                    CamelContextFactoryBean factoryBean = (CamelContextFactoryBean)value;
196                    builder.addPropertyValue("id", contextId);
197                    builder.addPropertyValue("routes", factoryBean.getRoutes());
198                    builder.addPropertyValue("builderRefs", factoryBean.getBuilderRefs());
199    
200                    if (factoryBean.getPackages().length > 0) {
201                        builder.addPropertyValue("packages", factoryBean.getPackages());
202                    }
203                }
204    
205                boolean createdBeanPostProcessor = false;
206                NodeList list = element.getChildNodes();
207                int size = list.getLength();
208                for (int i = 0; i < size; i++) {
209                    Node child = list.item(i);
210                    if (child instanceof Element) {
211                        Element childElement = (Element)child;
212                        String localName = child.getLocalName();
213                        if (localName.equals("beanPostProcessor")) {
214                            createBeanPostProcessor(parserContext, contextId, childElement, builder);
215                            createdBeanPostProcessor = true;
216                        } else if (localName.equals("endpoint")) {
217                            BeanDefinition definition = endpointParser.parse(childElement, parserContext);
218                            String id = childElement.getAttribute("id");
219                            if (ObjectHelper.isNotNullAndNonEmpty(id)) {
220                                // TODO we can zap this?
221                                definition.getPropertyValues()
222                                    .addPropertyValue("camelContext", new RuntimeBeanReference(contextId));
223                                // definition.getPropertyValues().addPropertyValue("context",
224                                // builder.getBeanDefinition());
225                                parserContext.registerComponent(new BeanComponentDefinition(definition, id));
226                            }
227                        } else {
228                            BeanDefinitionParser parser = parserMap.get(localName);
229                            if (parser != null) {
230                                BeanDefinition definition = parser.parse(childElement, parserContext);
231                                String id = childElement.getAttribute("id");
232                                if (ObjectHelper.isNotNullAndNonEmpty(id)) {
233                                    parserContext.registerComponent(new BeanComponentDefinition(definition, id));
234                                    if (localName.equals("jmxAgent")) {
235                                        builder.addPropertyReference("camelJMXAgent", id);
236                                    }
237                                }
238                            }
239    
240                        }
241                    }
242                }
243                // lets inject the namespaces into any namespace aware POJOs
244                injectNamespaces(element);
245                if (!createdBeanPostProcessor) {
246                    // no bean processor element so lets create it by ourself
247                    Element childElement = element.getOwnerDocument().createElement("beanPostProcessor");
248                    element.appendChild(childElement);
249                    createBeanPostProcessor(parserContext, contextId, childElement, builder);
250                }
251            }
252        }
253    
254        protected void injectNamespaces(Element element) {
255            NodeList list = element.getChildNodes();
256            Namespaces namespaces = null;
257            int size = list.getLength();
258            for (int i = 0; i < size; i++) {
259                Node child = list.item(i);
260                if (child instanceof Element) {
261                    Element childElement = (Element)child;
262                    Object object = binder.getJAXBNode(child);
263                    if (object instanceof NamespaceAware) {
264                        NamespaceAware namespaceAware = (NamespaceAware)object;
265                        if (namespaces == null) {
266                            namespaces = new Namespaces(element);
267                        }
268                        namespaces.configure(namespaceAware);
269                    }
270                    injectNamespaces(childElement);
271                }
272            }
273        }
274    }