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.lang.reflect.Method;
020 import java.util.ArrayList;
021 import java.util.HashMap;
022 import java.util.HashSet;
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.builder.xml.Namespaces;
036 import org.apache.camel.core.xml.CamelJMXAgentDefinition;
037 import org.apache.camel.core.xml.CamelPropertyPlaceholderDefinition;
038 import org.apache.camel.impl.DefaultCamelContextNameStrategy;
039 import org.apache.camel.model.FromDefinition;
040 import org.apache.camel.model.SendDefinition;
041 import org.apache.camel.spi.NamespaceAware;
042 import org.apache.camel.spring.CamelBeanPostProcessor;
043 import org.apache.camel.spring.CamelConsumerTemplateFactoryBean;
044 import org.apache.camel.spring.CamelContextFactoryBean;
045 import org.apache.camel.spring.CamelEndpointFactoryBean;
046 import org.apache.camel.spring.CamelProducerTemplateFactoryBean;
047 import org.apache.camel.spring.CamelRouteContextFactoryBean;
048 import org.apache.camel.spring.CamelThreadPoolFactoryBean;
049 import org.apache.camel.spring.remoting.CamelProxyFactoryBean;
050 import org.apache.camel.spring.remoting.CamelServiceExporter;
051 import org.apache.camel.util.ObjectHelper;
052 import org.apache.camel.view.ModelFileGenerator;
053 import org.apache.commons.logging.Log;
054 import org.apache.commons.logging.LogFactory;
055 import org.springframework.beans.factory.BeanCreationException;
056 import org.springframework.beans.factory.BeanDefinitionStoreException;
057 import org.springframework.beans.factory.config.BeanDefinition;
058 import org.springframework.beans.factory.config.RuntimeBeanReference;
059 import org.springframework.beans.factory.parsing.BeanComponentDefinition;
060 import org.springframework.beans.factory.support.BeanDefinitionBuilder;
061 import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
062 import org.springframework.beans.factory.xml.ParserContext;
063
064 /**
065 * Camel namespace for the spring XML configuration file.
066 */
067 public class CamelNamespaceHandler extends NamespaceHandlerSupport {
068 private static final String SPRING_NS = "http://camel.apache.org/schema/spring";
069 private static final Log LOG = LogFactory.getLog(CamelNamespaceHandler.class);
070 protected BeanDefinitionParser endpointParser = new BeanDefinitionParser(CamelEndpointFactoryBean.class);
071 protected BeanDefinitionParser beanPostProcessorParser = new BeanDefinitionParser(CamelBeanPostProcessor.class);
072 protected Set<String> parserElementNames = new HashSet<String>();
073 private JAXBContext jaxbContext;
074 private Map<String, BeanDefinitionParser> parserMap = new HashMap<String, BeanDefinitionParser>();
075 private Map<String, BeanDefinition> autoRegisterMap = new HashMap<String, BeanDefinition>();
076
077 public static void renameNamespaceRecursive(Node node) {
078 if (node.getNodeType() == Node.ELEMENT_NODE) {
079 Document doc = node.getOwnerDocument();
080 if (node.getNamespaceURI().startsWith(SPRING_NS + "/v")) {
081 doc.renameNode(node, SPRING_NS, node.getNodeName());
082 }
083 }
084 NodeList list = node.getChildNodes();
085 for (int i = 0; i < list.getLength(); ++i) {
086 renameNamespaceRecursive(list.item(i));
087 }
088 }
089
090 public ModelFileGenerator createModelFileGenerator() throws JAXBException {
091 return new ModelFileGenerator(getJaxbContext());
092 }
093
094 public void init() {
095 // register routeContext parser
096 registerParser("routeContext", new RouteContextDefinitionParser());
097
098 addBeanDefinitionParser("proxy", CamelProxyFactoryBean.class, true);
099 addBeanDefinitionParser("template", CamelProducerTemplateFactoryBean.class, true);
100 addBeanDefinitionParser("consumerTemplate", CamelConsumerTemplateFactoryBean.class, true);
101 addBeanDefinitionParser("export", CamelServiceExporter.class, true);
102 addBeanDefinitionParser("endpoint", CamelEndpointFactoryBean.class, true);
103 addBeanDefinitionParser("threadPool", CamelThreadPoolFactoryBean.class, true);
104
105 // jmx agent and property placeholder cannot be used outside of the camel context
106 addBeanDefinitionParser("jmxAgent", CamelJMXAgentDefinition.class, false);
107 addBeanDefinitionParser("propertyPlaceholder", CamelPropertyPlaceholderDefinition.class, false);
108
109 // errorhandler could be the sub element of camelContext or defined outside camelContext
110 BeanDefinitionParser errorHandlerParser = new ErrorHandlerDefinitionParser();
111 registerParser("errorHandler", errorHandlerParser);
112 parserMap.put("errorHandler", errorHandlerParser);
113
114 // camel context
115 boolean osgi = false;
116 Class cl = CamelContextFactoryBean.class;
117 try {
118 Class<?> c = Class.forName("org.apache.camel.osgi.Activator");
119 Method mth = c.getDeclaredMethod("getBundle");
120 Object bundle = mth.invoke(null);
121 if (bundle != null) {
122 cl = Class.forName("org.apache.camel.osgi.CamelContextFactoryBean");
123 osgi = true;
124 }
125 } catch (Throwable t) {
126 // not running with camel-osgi so we fallback to the regular factory bean
127 LOG.trace("Cannot find class so assuming not running in OSGi container: " + t.getMessage());
128 }
129 if (osgi) {
130 LOG.info("OSGi environment detected.");
131 } else {
132 LOG.info("OSGi environment not detected.");
133 }
134 if (LOG.isDebugEnabled()) {
135 LOG.debug("Using " + cl.getCanonicalName() + " as CamelContextBeanDefinitionParser");
136 }
137 registerParser("camelContext", new CamelContextBeanDefinitionParser(cl));
138 }
139
140 private void addBeanDefinitionParser(String elementName, Class<?> type, boolean register) {
141 BeanDefinitionParser parser = new BeanDefinitionParser(type);
142 if (register) {
143 registerParser(elementName, parser);
144 }
145 parserMap.put(elementName, parser);
146 }
147
148 protected void createBeanPostProcessor(ParserContext parserContext, String contextId, Element childElement, BeanDefinitionBuilder parentBuilder) {
149 String beanPostProcessorId = contextId + ":beanPostProcessor";
150 childElement.setAttribute("id", beanPostProcessorId);
151 BeanDefinition definition = beanPostProcessorParser.parse(childElement, parserContext);
152 // only register to camel context id as a String. Then we can look it up later
153 // otherwise we get a circular reference in spring and it will not allow custom bean post processing
154 // see more at CAMEL-1663
155 definition.getPropertyValues().addPropertyValue("camelId", contextId);
156 parentBuilder.addPropertyReference("beanPostProcessor", beanPostProcessorId);
157 }
158
159 protected void registerParser(String name, org.springframework.beans.factory.xml.BeanDefinitionParser parser) {
160 parserElementNames.add(name);
161 registerBeanDefinitionParser(name, parser);
162 }
163
164 protected Object parseUsingJaxb(Element element, ParserContext parserContext, Binder<Node> binder) {
165 try {
166 return binder.unmarshal(element);
167 } catch (JAXBException e) {
168 throw new BeanDefinitionStoreException("Failed to parse JAXB element", e);
169 }
170 }
171
172 public JAXBContext getJaxbContext() throws JAXBException {
173 if (jaxbContext == null) {
174 jaxbContext = createJaxbContext();
175 }
176 return jaxbContext;
177 }
178
179 protected JAXBContext createJaxbContext() throws JAXBException {
180 StringBuilder packages = new StringBuilder();
181 for (Class cl : getJaxbPackages()) {
182 if (packages.length() > 0) {
183 packages.append(":");
184 }
185 packages.append(cl.getName().substring(0, cl.getName().lastIndexOf('.')));
186 }
187 return JAXBContext.newInstance(packages.toString(), getClass().getClassLoader());
188 }
189
190 protected Set<Class> getJaxbPackages() {
191 Set<Class> classes = new HashSet<Class>();
192 classes.add(org.apache.camel.spring.CamelContextFactoryBean.class);
193 classes.add(CamelJMXAgentDefinition.class);
194 classes.add(org.apache.camel.ExchangePattern.class);
195 classes.add(org.apache.camel.model.RouteDefinition.class);
196 classes.add(org.apache.camel.model.config.StreamResequencerConfig.class);
197 classes.add(org.apache.camel.model.dataformat.DataFormatsDefinition.class);
198 classes.add(org.apache.camel.model.language.ExpressionDefinition.class);
199 classes.add(org.apache.camel.model.loadbalancer.RoundRobinLoadBalancerDefinition.class);
200 return classes;
201 }
202
203 protected class RouteContextDefinitionParser extends BeanDefinitionParser {
204
205 public RouteContextDefinitionParser() {
206 super(CamelRouteContextFactoryBean.class);
207 }
208
209 @Override
210 protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
211 renameNamespaceRecursive(element);
212 super.doParse(element, parserContext, builder);
213
214 // now lets parse the routes with JAXB
215 Binder<Node> binder;
216 try {
217 binder = getJaxbContext().createBinder();
218 } catch (JAXBException e) {
219 throw new BeanDefinitionStoreException("Failed to create the JAXB binder", e);
220 }
221 Object value = parseUsingJaxb(element, parserContext, binder);
222
223 if (value instanceof CamelRouteContextFactoryBean) {
224 CamelRouteContextFactoryBean factoryBean = (CamelRouteContextFactoryBean) value;
225 builder.addPropertyValue("routes", factoryBean.getRoutes());
226 }
227 }
228 }
229
230
231 protected class CamelContextBeanDefinitionParser extends BeanDefinitionParser {
232
233 public CamelContextBeanDefinitionParser(Class type) {
234 super(type);
235 }
236
237 @Override
238 protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
239 renameNamespaceRecursive(element);
240 super.doParse(element, parserContext, builder);
241
242 String contextId = element.getAttribute("id");
243 boolean implicitId = false;
244
245 // lets avoid folks having to explicitly give an ID to a camel context
246 if (ObjectHelper.isEmpty(contextId)) {
247 // if no explicit id was set then use a default auto generated name
248 contextId = DefaultCamelContextNameStrategy.getNextName();
249 element.setAttribute("id", contextId);
250 implicitId = true;
251 }
252
253 // now lets parse the routes with JAXB
254 Binder<Node> binder;
255 try {
256 binder = getJaxbContext().createBinder();
257 } catch (JAXBException e) {
258 throw new BeanDefinitionStoreException("Failed to create the JAXB binder", e);
259 }
260 Object value = parseUsingJaxb(element, parserContext, binder);
261
262 if (value instanceof CamelContextFactoryBean) {
263 // set the property value with the JAXB parsed value
264 CamelContextFactoryBean factoryBean = (CamelContextFactoryBean) value;
265 builder.addPropertyValue("id", contextId);
266 builder.addPropertyValue("implicitId", implicitId);
267 builder.addPropertyValue("routes", factoryBean.getRoutes());
268 builder.addPropertyValue("intercepts", factoryBean.getIntercepts());
269 builder.addPropertyValue("interceptFroms", factoryBean.getInterceptFroms());
270 builder.addPropertyValue("interceptSendToEndpoints", factoryBean.getInterceptSendToEndpoints());
271 builder.addPropertyValue("dataFormats", factoryBean.getDataFormats());
272 builder.addPropertyValue("onCompletions", factoryBean.getOnCompletions());
273 builder.addPropertyValue("onExceptions", factoryBean.getOnExceptions());
274 builder.addPropertyValue("builderRefs", factoryBean.getBuilderRefs());
275 builder.addPropertyValue("routeRefs", factoryBean.getRouteRefs());
276 builder.addPropertyValue("properties", factoryBean.getProperties());
277 builder.addPropertyValue("packageScan", factoryBean.getPackageScan());
278 builder.addPropertyValue("contextScan", factoryBean.getContextScan());
279 if (factoryBean.getPackages().length > 0) {
280 builder.addPropertyValue("packages", factoryBean.getPackages());
281 }
282 builder.addPropertyValue("camelPropertyPlaceholder", factoryBean.getCamelPropertyPlaceholder());
283 builder.addPropertyValue("camelJMXAgent", factoryBean.getCamelJMXAgent());
284 builder.addPropertyValue("threadPoolProfiles", factoryBean.getThreadPoolProfiles());
285 // add any depends-on
286 addDependsOn(factoryBean, builder);
287 }
288
289 boolean createdBeanPostProcessor = false;
290 NodeList list = element.getChildNodes();
291 int size = list.getLength();
292 for (int i = 0; i < size; i++) {
293 Node child = list.item(i);
294 if (child instanceof Element) {
295 Element childElement = (Element) child;
296 String localName = child.getLocalName();
297 if (localName.equals("beanPostProcessor")) {
298 createBeanPostProcessor(parserContext, contextId, childElement, builder);
299 createdBeanPostProcessor = true;
300 } else if (localName.equals("endpoint")) {
301 registerEndpoint(childElement, parserContext, contextId);
302 } else if (localName.equals("routeBuilder")) {
303 addDependsOnToRouteBuilder(childElement, parserContext, contextId);
304 } else {
305 BeanDefinitionParser parser = parserMap.get(localName);
306 if (parser != null) {
307 BeanDefinition definition = parser.parse(childElement, parserContext);
308 String id = childElement.getAttribute("id");
309 if (ObjectHelper.isNotEmpty(id)) {
310 parserContext.registerComponent(new BeanComponentDefinition(definition, id));
311 // set the templates with the camel context
312 if (localName.equals("template") || localName.equals("consumerTemplate")
313 || localName.equals("proxy") || localName.equals("export")) {
314 // set the camel context
315 definition.getPropertyValues().addPropertyValue("camelContext", new RuntimeBeanReference(contextId));
316 }
317 }
318 }
319 }
320 }
321 }
322
323 // register as endpoint defined indirectly in the routes by from/to types having id explicit set
324 registerEndpointsWithIdsDefinedInFromOrToTypes(element, parserContext, contextId, binder);
325
326 // register templates if not already defined
327 registerTemplates(element, parserContext, contextId);
328
329 // lets inject the namespaces into any namespace aware POJOs
330 injectNamespaces(element, binder);
331 if (!createdBeanPostProcessor) {
332 // no bean processor element so lets create it by our self
333 Element childElement = element.getOwnerDocument().createElement("beanPostProcessor");
334 element.appendChild(childElement);
335 createBeanPostProcessor(parserContext, contextId, childElement, builder);
336 }
337 }
338 }
339
340 protected void addDependsOn(CamelContextFactoryBean factoryBean, BeanDefinitionBuilder builder) {
341 String dependsOn = factoryBean.getDependsOn();
342 if (ObjectHelper.isNotEmpty(dependsOn)) {
343 // comma, whitespace and semi colon is valid separators in Spring depends-on
344 String[] depends = dependsOn.split(",|;|\\s");
345 if (depends == null) {
346 throw new IllegalArgumentException("Cannot separate depends-on, was: " + dependsOn);
347 } else {
348 for (String depend : depends) {
349 depend = depend.trim();
350 if (LOG.isDebugEnabled()) {
351 LOG.debug("Adding dependsOn " + depend + " to CamelContext(" + factoryBean.getId() + ")");
352 }
353 builder.addDependsOn(depend);
354 }
355 }
356 }
357 }
358
359 private void addDependsOnToRouteBuilder(Element childElement, ParserContext parserContext, String contextId) {
360 // setting the depends-on explicitly is required since Spring 3.0
361 String routeBuilderName = childElement.getAttribute("ref");
362 if (ObjectHelper.isNotEmpty(routeBuilderName)) {
363 // set depends-on to the context for a routeBuilder bean
364 try {
365 BeanDefinition definition = parserContext.getRegistry().getBeanDefinition(routeBuilderName);
366 Method getDependsOn = definition.getClass().getMethod("getDependsOn", new Class[]{});
367 String[] dependsOn = (String[])getDependsOn.invoke(definition, new Object[]{});
368 if (dependsOn == null || dependsOn.length == 0) {
369 dependsOn = new String[]{contextId};
370 } else {
371 String[] temp = new String[dependsOn.length + 1];
372 System.arraycopy(dependsOn, 0, temp, 0, dependsOn.length);
373 temp[dependsOn.length] = contextId;
374 dependsOn = temp;
375 }
376 Method method = definition.getClass().getMethod("setDependsOn", String[].class);
377 method.invoke(definition, (Object)dependsOn);
378 } catch (Exception e) {
379 // Do nothing here
380 }
381 }
382 }
383
384 protected void injectNamespaces(Element element, Binder<Node> binder) {
385 NodeList list = element.getChildNodes();
386 Namespaces namespaces = null;
387 int size = list.getLength();
388 for (int i = 0; i < size; i++) {
389 Node child = list.item(i);
390 if (child instanceof Element) {
391 Element childElement = (Element) child;
392 Object object = binder.getJAXBNode(child);
393 if (object instanceof NamespaceAware) {
394 NamespaceAware namespaceAware = (NamespaceAware) object;
395 if (namespaces == null) {
396 namespaces = new Namespaces(element);
397 }
398 namespaces.configure(namespaceAware);
399 }
400 injectNamespaces(childElement, binder);
401 }
402 }
403 }
404
405 /**
406 * Used for auto registering endpoints from the <tt>from</tt> or <tt>to</tt> DSL if they have an id attribute set
407 */
408 protected void registerEndpointsWithIdsDefinedInFromOrToTypes(Element element, ParserContext parserContext, String contextId, Binder<Node> binder) {
409 NodeList list = element.getChildNodes();
410 int size = list.getLength();
411 for (int i = 0; i < size; i++) {
412 Node child = list.item(i);
413 if (child instanceof Element) {
414 Element childElement = (Element) child;
415 Object object = binder.getJAXBNode(child);
416 // we only want from/to types to be registered as endpoints
417 if (object instanceof FromDefinition || object instanceof SendDefinition) {
418 registerEndpoint(childElement, parserContext, contextId);
419 }
420 // recursive
421 registerEndpointsWithIdsDefinedInFromOrToTypes(childElement, parserContext, contextId, binder);
422 }
423 }
424 }
425
426 /**
427 * Used for auto registering producer and consumer templates if not already defined in XML.
428 */
429 protected void registerTemplates(Element element, ParserContext parserContext, String contextId) {
430 boolean template = false;
431 boolean consumerTemplate = false;
432
433 NodeList list = element.getChildNodes();
434 int size = list.getLength();
435 for (int i = 0; i < size; i++) {
436 Node child = list.item(i);
437 if (child instanceof Element) {
438 Element childElement = (Element) child;
439 String localName = childElement.getLocalName();
440 if ("template".equals(localName)) {
441 template = true;
442 } else if ("consumerTemplate".equals(localName)) {
443 consumerTemplate = true;
444 }
445 }
446 }
447
448 if (!template) {
449 // either we have not used template before or we have auto registered it already and therefore we
450 // need it to allow to do it so it can remove the existing auto registered as there is now a clash id
451 // since we have multiple camel contexts
452 boolean existing = autoRegisterMap.get("template") != null;
453 boolean inUse = false;
454 try {
455 inUse = parserContext.getRegistry().isBeanNameInUse("template");
456 } catch (BeanCreationException e) {
457 // Spring Eclipse Tooling may throw an exception when you edit the Spring XML online in Eclipse
458 // when the isBeanNameInUse method is invoked, so ignore this and continue (CAMEL-2739)
459 LOG.debug("Error checking isBeanNameInUse(template). This exception will be ignored", e);
460 }
461 if (!inUse || existing) {
462 String id = "template";
463 // auto create a template
464 Element templateElement = element.getOwnerDocument().createElement("template");
465 templateElement.setAttribute("id", id);
466 BeanDefinitionParser parser = parserMap.get("template");
467 BeanDefinition definition = parser.parse(templateElement, parserContext);
468
469 // auto register it
470 autoRegisterBeanDefinition(id, definition, parserContext, contextId);
471 }
472 }
473
474 if (!consumerTemplate) {
475 // either we have not used template before or we have auto registered it already and therefore we
476 // need it to allow to do it so it can remove the existing auto registered as there is now a clash id
477 // since we have multiple camel contexts
478 boolean existing = autoRegisterMap.get("consumerTemplate") != null;
479 boolean inUse = false;
480 try {
481 inUse = parserContext.getRegistry().isBeanNameInUse("consumerTemplate");
482 } catch (BeanCreationException e) {
483 // Spring Eclipse Tooling may throw an exception when you edit the Spring XML online in Eclipse
484 // when the isBeanNameInUse method is invoked, so ignore this and continue (CAMEL-2739)
485 LOG.debug("Error checking isBeanNameInUse(consumerTemplate). This exception will be ignored", e);
486 }
487 if (!inUse || existing) {
488 String id = "consumerTemplate";
489 // auto create a template
490 Element templateElement = element.getOwnerDocument().createElement("consumerTemplate");
491 templateElement.setAttribute("id", id);
492 BeanDefinitionParser parser = parserMap.get("consumerTemplate");
493 BeanDefinition definition = parser.parse(templateElement, parserContext);
494
495 // auto register it
496 autoRegisterBeanDefinition(id, definition, parserContext, contextId);
497 }
498 }
499
500 }
501
502 private void autoRegisterBeanDefinition(String id, BeanDefinition definition, ParserContext parserContext, String contextId) {
503 // it is a bit cumbersome to work with the spring bean definition parser
504 // as we kinda need to eagerly register the bean definition on the parser context
505 // and then later we might find out that we should not have done that in case we have multiple camel contexts
506 // that would have a id clash by auto registering the same bean definition with the same id such as a producer template
507
508 // see if we have already auto registered this id
509 BeanDefinition existing = autoRegisterMap.get(id);
510 if (existing == null) {
511 // no then add it to the map and register it
512 autoRegisterMap.put(id, definition);
513 parserContext.registerComponent(new BeanComponentDefinition(definition, id));
514 if (LOG.isDebugEnabled()) {
515 LOG.debug("Registered default: " + definition.getBeanClassName() + " with id: " + id + " on camel context: " + contextId);
516 }
517 } else {
518 // ups we have already registered it before with same id, but on another camel context
519 // this is not good so we need to remove all traces of this auto registering.
520 // end user must manually add the needed XML elements and provide unique ids access all camel context himself.
521 if (LOG.isDebugEnabled()) {
522 LOG.debug("Unregistered default: " + definition.getBeanClassName() + " with id: " + id
523 + " as we have multiple camel contexts and they must use unique ids."
524 + " You must define the definition in the XML file manually to avoid id clashes when using multiple camel contexts");
525 }
526
527 parserContext.getRegistry().removeBeanDefinition(id);
528 }
529 }
530
531 private void registerEndpoint(Element childElement, ParserContext parserContext, String contextId) {
532 String id = childElement.getAttribute("id");
533 // must have an id to be registered
534 if (ObjectHelper.isNotEmpty(id)) {
535 BeanDefinition definition = endpointParser.parse(childElement, parserContext);
536 definition.getPropertyValues().addPropertyValue("camelContext", new RuntimeBeanReference(contextId));
537 // Need to add this dependency of CamelContext for Spring 3.0
538 try {
539 Method method = definition.getClass().getMethod("setDependsOn", String[].class);
540 method.invoke(definition, (Object) new String[]{contextId});
541 } catch (Exception e) {
542 // Do nothing here
543 }
544 parserContext.registerBeanComponent(new BeanComponentDefinition(definition, id));
545 }
546 }
547
548 }