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