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