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