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.Document;
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.FromDefinition;
034 import org.apache.camel.model.SendDefinition;
035 import org.apache.camel.spi.NamespaceAware;
036 import org.apache.camel.spring.CamelBeanPostProcessor;
037 import org.apache.camel.spring.CamelConsumerTemplateFactoryBean;
038 import org.apache.camel.spring.CamelContextFactoryBean;
039 import org.apache.camel.spring.CamelEndpointFactoryBean;
040 import org.apache.camel.spring.CamelJMXAgentDefinition;
041 import org.apache.camel.spring.CamelProducerTemplateFactoryBean;
042 import org.apache.camel.spring.remoting.CamelProxyFactoryBean;
043 import org.apache.camel.spring.remoting.CamelServiceExporter;
044 import org.apache.camel.util.ObjectHelper;
045 import org.apache.camel.view.ModelFileGenerator;
046 import org.apache.commons.logging.Log;
047 import org.apache.commons.logging.LogFactory;
048 import org.springframework.beans.factory.BeanDefinitionStoreException;
049 import org.springframework.beans.factory.config.BeanDefinition;
050 import org.springframework.beans.factory.config.RuntimeBeanReference;
051 import org.springframework.beans.factory.parsing.BeanComponentDefinition;
052 import org.springframework.beans.factory.support.BeanDefinitionBuilder;
053 import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
054 import org.springframework.beans.factory.xml.ParserContext;
055
056 /**
057 * Camel namespace for the spring XML configuration file.
058 */
059 public class CamelNamespaceHandler extends NamespaceHandlerSupport {
060 private static final String SPRING_NS = "http://camel.apache.org/schema/spring";
061 private static final Log LOG = LogFactory.getLog(CamelNamespaceHandler.class);
062 protected BeanDefinitionParser endpointParser = new BeanDefinitionParser(CamelEndpointFactoryBean.class);
063 protected BeanDefinitionParser beanPostProcessorParser = new BeanDefinitionParser(CamelBeanPostProcessor.class);
064 protected Set<String> parserElementNames = new HashSet<String>();
065 private JAXBContext jaxbContext;
066 private Map<String, BeanDefinitionParser> parserMap = new HashMap<String, BeanDefinitionParser>();
067 private Map<String, BeanDefinition> autoRegisterMap = new HashMap<String, BeanDefinition>();
068
069
070 public static void renameNamespaceRecursive(Node node) {
071 if (node.getNodeType() == Node.ELEMENT_NODE) {
072 Document doc = node.getOwnerDocument();
073 if (((Element) node).getNamespaceURI().startsWith(SPRING_NS + "/v")) {
074 doc.renameNode(node, SPRING_NS, node.getNodeName());
075 }
076 }
077 NodeList list = node.getChildNodes();
078 for (int i = 0; i < list.getLength(); ++i) {
079 renameNamespaceRecursive(list.item(i));
080 }
081 }
082
083 public ModelFileGenerator createModelFileGenerator() throws JAXBException {
084 return new ModelFileGenerator(getJaxbContext());
085 }
086
087 public void init() {
088 // These elements parser should be used inside the camel context
089 addBeanDefinitionParser("proxy", CamelProxyFactoryBean.class, false);
090 addBeanDefinitionParser("template", CamelProducerTemplateFactoryBean.class, false);
091 addBeanDefinitionParser("consumerTemplate", CamelConsumerTemplateFactoryBean.class, false);
092 addBeanDefinitionParser("export", CamelServiceExporter.class, false);
093
094 // jmx agent cannot be used outside of the camel context
095 addBeanDefinitionParser("jmxAgent", CamelJMXAgentDefinition.class, false);
096
097 // endpoint
098 // This element cannot be used out side of camel context
099 addBeanDefinitionParser("endpoint", CamelEndpointFactoryBean.class, false);
100
101 // camel context
102 boolean osgi = false;
103 Class cl = CamelContextFactoryBean.class;
104 try {
105 cl = Class.forName("org.apache.camel.osgi.CamelContextFactoryBean");
106 osgi = true;
107 } catch (Throwable t) {
108 // not running with camel-osgi so we fallback to the regular factory bean
109 LOG.trace("Cannot find class so assuming not running in OSGI container: " + t.getMessage());
110 }
111
112 if (osgi) {
113 LOG.info("camel-osgi.jar detected in classpath");
114 } else {
115 LOG.info("camel-osgi.jar not detected in classpath");
116 }
117
118 if (LOG.isDebugEnabled()) {
119 LOG.debug("Using " + cl.getCanonicalName() + " as CamelContextBeanDefinitionParser");
120 }
121 registerParser("camelContext", new CamelContextBeanDefinitionParser(cl));
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 registerParser(String name, org.springframework.beans.factory.xml.BeanDefinitionParser parser) {
144 parserElementNames.add(name);
145 registerBeanDefinitionParser(name, parser);
146 }
147
148 protected Object parseUsingJaxb(Element element, ParserContext parserContext, Binder<Node> binder) {
149 try {
150 return binder.unmarshal(element);
151 } catch (JAXBException e) {
152 throw new BeanDefinitionStoreException("Failed to parse JAXB element: " + e, e);
153 }
154 }
155
156 public JAXBContext getJaxbContext() throws JAXBException {
157 if (jaxbContext == null) {
158 jaxbContext = createJaxbContext();
159 }
160 return jaxbContext;
161 }
162
163 protected JAXBContext createJaxbContext() throws JAXBException {
164 StringBuilder packages = new StringBuilder();
165 for (Class cl : getJaxbPackages()) {
166 if (packages.length() > 0) {
167 packages.append(":");
168 }
169 packages.append(cl.getName().substring(0, cl.getName().lastIndexOf('.')));
170 }
171 return JAXBContext.newInstance(packages.toString(), getClass().getClassLoader());
172 }
173
174 protected Set<Class> getJaxbPackages() {
175 Set<Class> classes = new HashSet<Class>();
176 classes.add(org.apache.camel.spring.CamelContextFactoryBean.class);
177 classes.add(org.apache.camel.ExchangePattern.class);
178 classes.add(org.apache.camel.model.RouteDefinition.class);
179 classes.add(org.apache.camel.model.config.StreamResequencerConfig.class);
180 classes.add(org.apache.camel.model.dataformat.DataFormatsDefinition.class);
181 classes.add(org.apache.camel.model.language.ExpressionDefinition.class);
182 classes.add(org.apache.camel.model.loadbalancer.RoundRobinLoadBalancerDefinition.class);
183 return classes;
184 }
185
186 protected class CamelContextBeanDefinitionParser extends BeanDefinitionParser {
187 public CamelContextBeanDefinitionParser(Class type) {
188 super(type);
189 }
190
191 @Override
192 protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
193 renameNamespaceRecursive(element);
194 super.doParse(element, parserContext, builder);
195
196 String contextId = element.getAttribute("id");
197
198 // lets avoid folks having to explicitly give an ID to a camel context
199 if (ObjectHelper.isEmpty(contextId)) {
200 contextId = "camelContext";
201 element.setAttribute("id", contextId);
202 }
203
204 // now lets parse the routes with JAXB
205 Binder<Node> binder = null;
206 try {
207 binder = getJaxbContext().createBinder();
208 } catch (JAXBException e) {
209 throw new BeanDefinitionStoreException("Failed to create the JAXB binder : " + e, e);
210 }
211 Object value = parseUsingJaxb(element, parserContext, binder);
212
213 if (value instanceof CamelContextFactoryBean) {
214 // set the property value with the JAXB parsed value
215 CamelContextFactoryBean factoryBean = (CamelContextFactoryBean)value;
216 builder.addPropertyValue("id", contextId);
217 builder.addPropertyValue("routes", factoryBean.getRoutes());
218 builder.addPropertyValue("intercepts", factoryBean.getIntercepts());
219 builder.addPropertyValue("interceptFroms", factoryBean.getInterceptFroms());
220 builder.addPropertyValue("interceptSendToEndpoints", factoryBean.getInterceptSendToEndpoints());
221 builder.addPropertyValue("dataFormats", factoryBean.getDataFormats());
222 builder.addPropertyValue("onCompletions", factoryBean.getOnCompletions());
223 builder.addPropertyValue("onExceptions", factoryBean.getOnExceptions());
224 builder.addPropertyValue("builderRefs", factoryBean.getBuilderRefs());
225 builder.addPropertyValue("properties", factoryBean.getProperties());
226 builder.addPropertyValue("packageScan", factoryBean.getPackageScan());
227 if (factoryBean.getPackages().length > 0) {
228 builder.addPropertyValue("packages", factoryBean.getPackages());
229 }
230 }
231
232 boolean createdBeanPostProcessor = false;
233 NodeList list = element.getChildNodes();
234 int size = list.getLength();
235 for (int i = 0; i < size; i++) {
236 Node child = list.item(i);
237 if (child instanceof Element) {
238 Element childElement = (Element)child;
239 String localName = child.getLocalName();
240 if (localName.equals("beanPostProcessor")) {
241 createBeanPostProcessor(parserContext, contextId, childElement, builder);
242 createdBeanPostProcessor = true;
243 } else if (localName.equals("endpoint")) {
244 registerEndpoint(childElement, parserContext, contextId);
245 } else {
246 BeanDefinitionParser parser = parserMap.get(localName);
247 if (parser != null) {
248 BeanDefinition definition = parser.parse(childElement, parserContext);
249 String id = childElement.getAttribute("id");
250 if (ObjectHelper.isNotEmpty(id)) {
251 parserContext.registerComponent(new BeanComponentDefinition(definition, id));
252 if (localName.equals("jmxAgent")) {
253 builder.addPropertyReference("camelJMXAgent", id);
254 }
255 // set the templates with the camel context
256 if (localName.equals("template") || localName.equals("consumerTemplate")
257 || localName.equals("proxy") || localName.equals("export")) {
258 // set the camel context
259 definition.getPropertyValues().addPropertyValue("camelContext", new RuntimeBeanReference(contextId));
260 }
261 }
262 }
263 }
264 }
265 }
266
267
268 // register as endpoint defined indirectly in the routes by from/to types having id explict set
269 registerEndpointsWithIdsDefinedInFromOrToTypes(element, parserContext, contextId, binder);
270
271 // register templates if not already defined
272 registerTemplates(element, parserContext, contextId);
273
274 // lets inject the namespaces into any namespace aware POJOs
275 injectNamespaces(element, binder);
276 if (!createdBeanPostProcessor) {
277 // no bean processor element so lets create it by ourself
278 Element childElement = element.getOwnerDocument().createElement("beanPostProcessor");
279 element.appendChild(childElement);
280 createBeanPostProcessor(parserContext, contextId, childElement, builder);
281 }
282 }
283
284 }
285
286 protected void injectNamespaces(Element element, Binder<Node> binder) {
287 NodeList list = element.getChildNodes();
288 Namespaces namespaces = null;
289 int size = list.getLength();
290 for (int i = 0; i < size; i++) {
291 Node child = list.item(i);
292 if (child instanceof Element) {
293 Element childElement = (Element)child;
294 Object object = binder.getJAXBNode(child);
295 if (object instanceof NamespaceAware) {
296 NamespaceAware namespaceAware = (NamespaceAware)object;
297 if (namespaces == null) {
298 namespaces = new Namespaces(element);
299 }
300 namespaces.configure(namespaceAware);
301 }
302 injectNamespaces(childElement, binder);
303 }
304 }
305 }
306
307 /**
308 * Used for auto registering endpoints from the <tt>from</tt> or <tt>to</tt> DSL if they have an id attribute set
309 */
310 protected void registerEndpointsWithIdsDefinedInFromOrToTypes(Element element, ParserContext parserContext, String contextId, Binder<Node> binder) {
311 NodeList list = element.getChildNodes();
312 int size = list.getLength();
313 for (int i = 0; i < size; i++) {
314 Node child = list.item(i);
315 if (child instanceof Element) {
316 Element childElement = (Element)child;
317 Object object = binder.getJAXBNode(child);
318 // we only want from/to types to be registered as endpoints
319 if (object instanceof FromDefinition || object instanceof SendDefinition) {
320 registerEndpoint(childElement, parserContext, contextId);
321 }
322 // recursive
323 registerEndpointsWithIdsDefinedInFromOrToTypes(childElement, parserContext, contextId, binder);
324 }
325 }
326 }
327
328 /**
329 * Used for auto registering producer and consumer templates if not already defined in XML.
330 */
331 protected void registerTemplates(Element element, ParserContext parserContext, String contextId) {
332 boolean template = false;
333 boolean consumerTemplate = false;
334
335 NodeList list = element.getChildNodes();
336 int size = list.getLength();
337 for (int i = 0; i < size; i++) {
338 Node child = list.item(i);
339 if (child instanceof Element) {
340 Element childElement = (Element)child;
341 String localName = childElement.getLocalName();
342 if ("template".equals(localName)) {
343 template = true;
344 } else if ("consumerTemplate".equals(localName)) {
345 consumerTemplate = true;
346 }
347 }
348 }
349
350 // either we have not used templat before or we have auto registered it already and therefore we
351 // need it to allow to do it so it can remove the existing auto registered as there is now a clash id
352 // since we have multiple camel contexts
353 boolean canDoTemplate = autoRegisterMap.get("template") != null
354 || !parserContext.getRegistry().isBeanNameInUse("template");
355 if (!template && canDoTemplate) {
356 String id = "template";
357 // auto create a template
358 Element templateElement = element.getOwnerDocument().createElement("template");
359 templateElement.setAttribute("id", id);
360 BeanDefinitionParser parser = parserMap.get("template");
361 BeanDefinition definition = parser.parse(templateElement, parserContext);
362
363 // auto register it
364 autoRegisterBeanDefinition(id, definition, parserContext, contextId);
365 }
366
367 // either we have not used templat before or we have auto registered it already and therefore we
368 // need it to allow to do it so it can remove the existing auto registered as there is now a clash id
369 // since we have multiple camel contexts
370 boolean canDoConsumerTemplate = autoRegisterMap.get("consumerTemplate") != null
371 || !parserContext.getRegistry().isBeanNameInUse("consumerTemplate");
372 if (!consumerTemplate && canDoConsumerTemplate) {
373 String id = "consumerTemplate";
374 // auto create a template
375 Element templateElement = element.getOwnerDocument().createElement("consumerTemplate");
376 templateElement.setAttribute("id", id);
377 BeanDefinitionParser parser = parserMap.get("consumerTemplate");
378 BeanDefinition definition = parser.parse(templateElement, parserContext);
379
380 // auto register it
381 autoRegisterBeanDefinition(id, definition, parserContext, contextId);
382 }
383
384 }
385
386 private void autoRegisterBeanDefinition(String id, BeanDefinition definition, ParserContext parserContext, String contextId) {
387 // it is a bit cumbersome to work with the spring bean definition parser
388 // as we kinda need to eagerly register the bean definition on the parser context
389 // and then later we might find out that we should not have done that in case we have multiple camel contexts
390 // that would have a id clash by auto regsitering the same bean definition with the same id such as a producer template
391
392 // see if we have already auto registered this id
393 BeanDefinition existing = autoRegisterMap.get(id);
394 if (existing == null) {
395 // no then add it to the map and register it
396 autoRegisterMap.put(id, definition);
397 parserContext.registerComponent(new BeanComponentDefinition(definition, id));
398 if (LOG.isDebugEnabled()) {
399 LOG.debug("Registered default: " + definition.getBeanClassName() + " with id: " + id + " on camel context: " + contextId);
400 }
401 } else {
402 // ups we have already registered it before with same id, but on another camel context
403 // this is not good so we need to remove all traces of this auto registering.
404 // end user must manually add the needed XML elements and provide unique ids access all camel context himself.
405 if (LOG.isDebugEnabled()) {
406 LOG.debug("Unregistered default: " + definition.getBeanClassName() + " with id: " + id
407 + " as we have multiple camel contexts and they must use unique ids."
408 + " You must define the definition in the XML file manually to avoid id clashes when using multiple camel contexts");
409 }
410
411 parserContext.getRegistry().removeBeanDefinition(id);
412 }
413 }
414
415 private void registerEndpoint(Element childElement, ParserContext parserContext, String contextId) {
416 String id = childElement.getAttribute("id");
417 // must have an id to be registered
418 if (ObjectHelper.isNotEmpty(id)) {
419 BeanDefinition definition = endpointParser.parse(childElement, parserContext);
420 definition.getPropertyValues().addPropertyValue("camelContext", new RuntimeBeanReference(contextId));
421 parserContext.registerComponent(new BeanComponentDefinition(definition, id));
422 }
423 }
424
425 }