/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.camel.blueprint.handler;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import javax.xml.bind.Binder;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.apache.aries.blueprint.BeanProcessor;
import org.apache.aries.blueprint.ComponentDefinitionRegistry;
import org.apache.aries.blueprint.ComponentDefinitionRegistryProcessor;
import org.apache.aries.blueprint.NamespaceHandler;
import org.apache.aries.blueprint.ParserContext;
import org.apache.aries.blueprint.PassThroughMetadata;
import org.apache.aries.blueprint.mutable.MutableBeanMetadata;
import org.apache.aries.blueprint.mutable.MutablePassThroughMetadata;
import org.apache.aries.blueprint.mutable.MutableRefMetadata;
import org.apache.aries.blueprint.mutable.MutableReferenceMetadata;
import org.apache.camel.BeanInject;
import org.apache.camel.CamelContext;
import org.apache.camel.Endpoint;
import org.apache.camel.EndpointInject;
import org.apache.camel.Produce;
import org.apache.camel.PropertyInject;
import org.apache.camel.blueprint.BlueprintCamelContext;
import org.apache.camel.blueprint.BlueprintModelJAXBContextFactory;
import org.apache.camel.blueprint.CamelContextFactoryBean;
import org.apache.camel.blueprint.CamelEndpointFactoryBean;
import org.apache.camel.blueprint.CamelRestContextFactoryBean;
import org.apache.camel.blueprint.CamelRouteContextFactoryBean;
import org.apache.camel.builder.xml.Namespaces;
import org.apache.camel.component.properties.PropertiesComponent;
import org.apache.camel.core.xml.AbstractCamelFactoryBean;
import org.apache.camel.impl.CamelPostProcessorHelper;
import org.apache.camel.impl.DefaultCamelContextNameStrategy;
import org.apache.camel.model.AggregateDefinition;
import org.apache.camel.model.CatchDefinition;
import org.apache.camel.model.DataFormatDefinition;
import org.apache.camel.model.ExpressionNode;
import org.apache.camel.model.ExpressionSubElementDefinition;
import org.apache.camel.model.FromDefinition;
import org.apache.camel.model.MarshalDefinition;
import org.apache.camel.model.OnExceptionDefinition;
import org.apache.camel.model.ProcessorDefinition;
import org.apache.camel.model.ResequenceDefinition;
import org.apache.camel.model.RouteDefinition;
import org.apache.camel.model.SendDefinition;
import org.apache.camel.model.SortDefinition;
import org.apache.camel.model.ToDefinition;
import org.apache.camel.model.ToDynamicDefinition;
import org.apache.camel.model.UnmarshalDefinition;
import org.apache.camel.model.WireTapDefinition;
import org.apache.camel.model.language.ExpressionDefinition;
import org.apache.camel.model.rest.RestBindingMode;
import org.apache.camel.model.rest.RestDefinition;
import org.apache.camel.model.rest.VerbDefinition;
import org.apache.camel.spi.CamelContextNameStrategy;
import org.apache.camel.spi.ComponentResolver;
import org.apache.camel.spi.DataFormatResolver;
import org.apache.camel.spi.LanguageResolver;
import org.apache.camel.spi.NamespaceAware;
import org.apache.camel.util.ObjectHelper;
import org.apache.camel.util.URISupport;
import org.apache.camel.util.blueprint.KeyStoreParametersFactoryBean;
import org.apache.camel.util.blueprint.SSLContextParametersFactoryBean;
import org.apache.camel.util.blueprint.SecureRandomParametersFactoryBean;
import org.apache.camel.util.jsse.KeyStoreParameters;
import org.apache.camel.util.jsse.SSLContextParameters;
import org.apache.camel.util.jsse.SecureRandomParameters;
import org.osgi.framework.Bundle;
import org.osgi.service.blueprint.container.BlueprintContainer;
import org.osgi.service.blueprint.container.ComponentDefinitionException;
import org.osgi.service.blueprint.reflect.BeanMetadata;
import org.osgi.service.blueprint.reflect.ComponentMetadata;
import org.osgi.service.blueprint.reflect.Metadata;
import org.osgi.service.blueprint.reflect.RefMetadata;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.osgi.service.blueprint.reflect.ComponentMetadata.ACTIVATION_LAZY;
import static org.osgi.service.blueprint.reflect.ServiceReferenceMetadata.AVAILABILITY_MANDATORY;
import static org.osgi.service.blueprint.reflect.ServiceReferenceMetadata.AVAILABILITY_OPTIONAL;
/**
* Camel {@link NamespaceHandler} to parse the Camel related namespaces.
*/
public class CamelNamespaceHandler implements NamespaceHandler {
public static final String BLUEPRINT_NS = "http://camel.apache.org/schema/blueprint";
public static final String SPRING_NS = "http://camel.apache.org/schema/spring";
private static final String CAMEL_CONTEXT = "camelContext";
private static final String ROUTE_CONTEXT = "routeContext";
private static final String REST_CONTEXT = "restContext";
private static final String ENDPOINT = "endpoint";
private static final String KEY_STORE_PARAMETERS = "keyStoreParameters";
private static final String SECURE_RANDOM_PARAMETERS = "secureRandomParameters";
private static final String SSL_CONTEXT_PARAMETERS = "sslContextParameters";
private static final Logger LOG = LoggerFactory.getLogger(CamelNamespaceHandler.class);
private JAXBContext jaxbContext;
/**
* Prepares the nodes before parsing.
*/
public static void doBeforeParse(Node node, String fromNamespace, String toNamespace) {
if (node.getNodeType() == Node.ELEMENT_NODE) {
Document doc = node.getOwnerDocument();
if (node.getNamespaceURI().equals(fromNamespace)) {
doc.renameNode(node, toNamespace, node.getLocalName());
}
// remove whitespace noise from uri, xxxUri attributes, eg new lines, and tabs etc, which allows end users to format
// their Camel routes in more human readable format, but at runtime those attributes must be trimmed
// the parser removes most of the noise, but keeps double spaces in the attribute values
NamedNodeMap map = node.getAttributes();
for (int i = 0; i < map.getLength(); i++) {
Node att = map.item(i);
if (att.getNodeName().equals("uri") || att.getNodeName().endsWith("Uri")) {
final String value = att.getNodeValue();
String before = ObjectHelper.before(value, "?");
String after = ObjectHelper.after(value, "?");
if (before != null && after != null) {
// remove all double spaces in the uri parameters
String changed = after.replaceAll("\\s{2,}", "");
if (!after.equals(changed)) {
String newAtr = before.trim() + "?" + changed.trim();
LOG.debug("Removed whitespace noise from attribute {} -> {}", value, newAtr);
att.setNodeValue(newAtr);
}
}
}
}
}
NodeList list = node.getChildNodes();
for (int i = 0; i < list.getLength(); ++i) {
doBeforeParse(list.item(i), fromNamespace, toNamespace);
}
}
public URL getSchemaLocation(String namespace) {
if (BLUEPRINT_NS.equals(namespace)) {
return getClass().getClassLoader().getResource("camel-blueprint.xsd");
}
return null;
}
@SuppressWarnings({"rawtypes"})
public Set<Class> getManagedClasses() {
return new HashSet<Class>(Arrays.asList(BlueprintCamelContext.class));
}
public Metadata parse(Element element, ParserContext context) {
LOG.trace("Parsing element {}", element);
try {
// as the camel-core model namespace is Spring we need to rename from blueprint to spring
doBeforeParse(element, BLUEPRINT_NS, SPRING_NS);
if (element.getLocalName().equals(CAMEL_CONTEXT)) {
return parseCamelContextNode(element, context);
}
if (element.getLocalName().equals(ROUTE_CONTEXT)) {
return parseRouteContextNode(element, context);
}
if (element.getLocalName().equals(REST_CONTEXT)) {
return parseRestContextNode(element, context);
}
if (element.getLocalName().equals(ENDPOINT)) {
return parseEndpointNode(element, context);
}
if (element.getLocalName().equals(KEY_STORE_PARAMETERS)) {
return parseKeyStoreParametersNode(element, context);
}
if (element.getLocalName().equals(SECURE_RANDOM_PARAMETERS)) {
return parseSecureRandomParametersNode(element, context);
}
if (element.getLocalName().equals(SSL_CONTEXT_PARAMETERS)) {
return parseSSLContextParametersNode(element, context);
}
} finally {
// make sure to rename back so we leave the DOM as-is
doBeforeParse(element, SPRING_NS, BLUEPRINT_NS);
}
return null;
}
private Metadata parseCamelContextNode(Element element, ParserContext context) {
LOG.trace("Parsing CamelContext {}", element);
// Find the id, generate one if needed
String contextId = element.getAttribute("id");
boolean implicitId = false;
// let's avoid folks having to explicitly give an ID to a camel context
if (ObjectHelper.isEmpty(contextId)) {
// if no explicit id was set then use a default auto generated name
CamelContextNameStrategy strategy = new DefaultCamelContextNameStrategy();
contextId = strategy.getName();
element.setAttributeNS(null, "id", contextId);
implicitId = true;
}
// now let's parse the routes with JAXB
Binder<Node> binder;
try {
binder = getJaxbContext().createBinder();
} catch (JAXBException e) {
throw new ComponentDefinitionException("Failed to create the JAXB binder : " + e, e);
}
Object value = parseUsingJaxb(element, context, binder);
if (!(value instanceof CamelContextFactoryBean)) {
throw new ComponentDefinitionException("Expected an instance of " + CamelContextFactoryBean.class);
}
CamelContextFactoryBean ccfb = (CamelContextFactoryBean) value;
ccfb.setImplicitId(implicitId);
// The properties component is always used / created by the CamelContextFactoryBean
// so we need to ensure that the resolver is ready to use
ComponentMetadata propertiesComponentResolver = getComponentResolverReference(context, "properties");
MutablePassThroughMetadata factory = context.createMetadata(MutablePassThroughMetadata.class);
factory.setId(".camelBlueprint.passThrough." + contextId);
factory.setObject(new PassThroughCallable<Object>(value));
MutableBeanMetadata factory2 = context.createMetadata(MutableBeanMetadata.class);
factory2.setId(".camelBlueprint.factory." + contextId);
factory2.setFactoryComponent(factory);
factory2.setFactoryMethod("call");
factory2.setInitMethod("afterPropertiesSet");
factory2.setDestroyMethod("destroy");
factory2.addProperty("blueprintContainer", createRef(context, "blueprintContainer"));
factory2.addProperty("bundleContext", createRef(context, "blueprintBundleContext"));
factory2.addDependsOn(propertiesComponentResolver.getId());
// We need to add other components which the camel context dependsOn
if (ObjectHelper.isNotEmpty(ccfb.getDependsOn())) {
factory2.setDependsOn(Arrays.asList(ccfb.getDependsOn().split(" |,")));
}
context.getComponentDefinitionRegistry().registerComponentDefinition(factory2);
MutableBeanMetadata ctx = context.createMetadata(MutableBeanMetadata.class);
ctx.setId(contextId);
ctx.setRuntimeClass(BlueprintCamelContext.class);
ctx.setFactoryComponent(factory2);
ctx.setFactoryMethod("getContext");
ctx.setInitMethod("init");
ctx.setDestroyMethod("destroy");
// Register factory beans
registerBeans(context, contextId, ccfb.getThreadPools());
registerBeans(context, contextId, ccfb.getEndpoints());
registerBeans(context, contextId, ccfb.getRedeliveryPolicies());
registerBeans(context, contextId, ccfb.getBeansFactory());
// Register processors
MutablePassThroughMetadata beanProcessorFactory = context.createMetadata(MutablePassThroughMetadata.class);
beanProcessorFactory.setId(".camelBlueprint.processor.bean.passThrough." + contextId);
beanProcessorFactory.setObject(new PassThroughCallable<Object>(new CamelInjector(contextId)));
MutableBeanMetadata beanProcessor = context.createMetadata(MutableBeanMetadata.class);
beanProcessor.setId(".camelBlueprint.processor.bean." + contextId);
beanProcessor.setRuntimeClass(CamelInjector.class);
beanProcessor.setFactoryComponent(beanProcessorFactory);
beanProcessor.setFactoryMethod("call");
beanProcessor.setProcessor(true);
beanProcessor.addProperty("blueprintContainer", createRef(context, "blueprintContainer"));
context.getComponentDefinitionRegistry().registerComponentDefinition(beanProcessor);
MutablePassThroughMetadata regProcessorFactory = context.createMetadata(MutablePassThroughMetadata.class);
regProcessorFactory.setId(".camelBlueprint.processor.registry.passThrough." + contextId);
regProcessorFactory.setObject(new PassThroughCallable<Object>(new CamelDependenciesFinder(contextId, context)));
MutableBeanMetadata regProcessor = context.createMetadata(MutableBeanMetadata.class);
regProcessor.setId(".camelBlueprint.processor.registry." + contextId);
regProcessor.setRuntimeClass(CamelDependenciesFinder.class);
regProcessor.setFactoryComponent(regProcessorFactory);
regProcessor.setFactoryMethod("call");
regProcessor.setProcessor(true);
regProcessor.addDependsOn(".camelBlueprint.processor.bean." + contextId);
regProcessor.addProperty("blueprintContainer", createRef(context, "blueprintContainer"));
context.getComponentDefinitionRegistry().registerComponentDefinition(regProcessor);
// lets inject the namespaces into any namespace aware POJOs
injectNamespaces(element, binder);
LOG.trace("Parsing CamelContext done, returning {}", ctx);
return ctx;
}
protected void injectNamespaces(Element element, Binder<Node> binder) {
NodeList list = element.getChildNodes();
Namespaces namespaces = null;
int size = list.getLength();
for (int i = 0; i < size; i++) {
Node child = list.item(i);
if (child instanceof Element) {
Element childElement = (Element) child;
Object object = binder.getJAXBNode(child);
if (object instanceof NamespaceAware) {
NamespaceAware namespaceAware = (NamespaceAware) object;
if (namespaces == null) {
namespaces = new Namespaces(element);
}
namespaces.configure(namespaceAware);
}
injectNamespaces(childElement, binder);
}
}
}
private Metadata parseRouteContextNode(Element element, ParserContext context) {
LOG.trace("Parsing RouteContext {}", element);
// now parse the routes with JAXB
Binder<Node> binder;
try {
binder = getJaxbContext().createBinder();
} catch (JAXBException e) {
throw new ComponentDefinitionException("Failed to create the JAXB binder : " + e, e);
}
Object value = parseUsingJaxb(element, context, binder);
if (!(value instanceof CamelRouteContextFactoryBean)) {
throw new ComponentDefinitionException("Expected an instance of " + CamelRouteContextFactoryBean.class);
}
CamelRouteContextFactoryBean rcfb = (CamelRouteContextFactoryBean) value;
String id = rcfb.getId();
MutablePassThroughMetadata factory = context.createMetadata(MutablePassThroughMetadata.class);
factory.setId(".camelBlueprint.passThrough." + id);
factory.setObject(new PassThroughCallable<Object>(rcfb));
MutableBeanMetadata factory2 = context.createMetadata(MutableBeanMetadata.class);
factory2.setId(".camelBlueprint.factory." + id);
factory2.setFactoryComponent(factory);
factory2.setFactoryMethod("call");
MutableBeanMetadata ctx = context.createMetadata(MutableBeanMetadata.class);
ctx.setId(id);
ctx.setRuntimeClass(List.class);
ctx.setFactoryComponent(factory2);
ctx.setFactoryMethod("getRoutes");
// must be lazy as we want CamelContext to be activated first
ctx.setActivation(ACTIVATION_LAZY);
// lets inject the namespaces into any namespace aware POJOs
injectNamespaces(element, binder);
LOG.trace("Parsing RouteContext done, returning {}", element, ctx);
return ctx;
}
private Metadata parseRestContextNode(Element element, ParserContext context) {
LOG.trace("Parsing RestContext {}", element);
// now parse the rests with JAXB
Binder<Node> binder;
try {
binder = getJaxbContext().createBinder();
} catch (JAXBException e) {
throw new ComponentDefinitionException("Failed to create the JAXB binder : " + e, e);
}
Object value = parseUsingJaxb(element, context, binder);
if (!(value instanceof CamelRestContextFactoryBean)) {
throw new ComponentDefinitionException("Expected an instance of " + CamelRestContextFactoryBean.class);
}
CamelRestContextFactoryBean rcfb = (CamelRestContextFactoryBean) value;
String id = rcfb.getId();
MutablePassThroughMetadata factory = context.createMetadata(MutablePassThroughMetadata.class);
factory.setId(".camelBlueprint.passThrough." + id);
factory.setObject(new PassThroughCallable<Object>(rcfb));
MutableBeanMetadata factory2 = context.createMetadata(MutableBeanMetadata.class);
factory2.setId(".camelBlueprint.factory." + id);
factory2.setFactoryComponent(factory);
factory2.setFactoryMethod("call");
MutableBeanMetadata ctx = context.createMetadata(MutableBeanMetadata.class);
ctx.setId(id);
ctx.setRuntimeClass(List.class);
ctx.setFactoryComponent(factory2);
ctx.setFactoryMethod("getRests");
// must be lazy as we want CamelContext to be activated first
ctx.setActivation(ACTIVATION_LAZY);
// lets inject the namespaces into any namespace aware POJOs
injectNamespaces(element, binder);
LOG.trace("Parsing RestContext done, returning {}", element, ctx);
return ctx;
}
private Metadata parseEndpointNode(Element element, ParserContext context) {
LOG.trace("Parsing Endpoint {}", element);
// now parse the rests with JAXB
Binder<Node> binder;
try {
binder = getJaxbContext().createBinder();
} catch (JAXBException e) {
throw new ComponentDefinitionException("Failed to create the JAXB binder : " + e, e);
}
Object value = parseUsingJaxb(element, context, binder);
if (!(value instanceof CamelEndpointFactoryBean)) {
throw new ComponentDefinitionException("Expected an instance of " + CamelEndpointFactoryBean.class);
}
CamelEndpointFactoryBean rcfb = (CamelEndpointFactoryBean) value;
String id = rcfb.getId();
MutablePassThroughMetadata factory = context.createMetadata(MutablePassThroughMetadata.class);
factory.setId(".camelBlueprint.passThrough." + id);
factory.setObject(new PassThroughCallable<Object>(rcfb));
MutableBeanMetadata factory2 = context.createMetadata(MutableBeanMetadata.class);
factory2.setId(".camelBlueprint.factory." + id);
factory2.setFactoryComponent(factory);
factory2.setFactoryMethod("call");
factory2.setInitMethod("afterPropertiesSet");
factory2.setDestroyMethod("destroy");
factory2.addProperty("blueprintContainer", createRef(context, "blueprintContainer"));
MutableBeanMetadata ctx = context.createMetadata(MutableBeanMetadata.class);
ctx.setId(id);
ctx.setRuntimeClass(Endpoint.class);
ctx.setFactoryComponent(factory2);
ctx.setFactoryMethod("getObject");
// must be lazy as we want CamelContext to be activated first
ctx.setActivation(ACTIVATION_LAZY);
LOG.trace("Parsing endpoint done, returning {}", element, ctx);
return ctx;
}
private Metadata parseKeyStoreParametersNode(Element element, ParserContext context) {
LOG.trace("Parsing KeyStoreParameters {}", element);
// now parse the key store parameters with JAXB
Binder<Node> binder;
try {
binder = getJaxbContext().createBinder();
} catch (JAXBException e) {
throw new ComponentDefinitionException("Failed to create the JAXB binder : " + e, e);
}
Object value = parseUsingJaxb(element, context, binder);
if (!(value instanceof KeyStoreParametersFactoryBean)) {
throw new ComponentDefinitionException("Expected an instance of " + KeyStoreParametersFactoryBean.class);
}
KeyStoreParametersFactoryBean kspfb = (KeyStoreParametersFactoryBean) value;
String id = kspfb.getId();
MutablePassThroughMetadata factory = context.createMetadata(MutablePassThroughMetadata.class);
factory.setId(".camelBlueprint.passThrough." + id);
factory.setObject(new PassThroughCallable<Object>(kspfb));
MutableBeanMetadata factory2 = context.createMetadata(MutableBeanMetadata.class);
factory2.setId(".camelBlueprint.factory." + id);
factory2.setFactoryComponent(factory);
factory2.setFactoryMethod("call");
factory2.setInitMethod("afterPropertiesSet");
factory2.setDestroyMethod("destroy");
factory2.addProperty("blueprintContainer", createRef(context, "blueprintContainer"));
MutableBeanMetadata ctx = context.createMetadata(MutableBeanMetadata.class);
ctx.setId(id);
ctx.setRuntimeClass(KeyStoreParameters.class);
ctx.setFactoryComponent(factory2);
ctx.setFactoryMethod("getObject");
// must be lazy as we want CamelContext to be activated first
ctx.setActivation(ACTIVATION_LAZY);
LOG.trace("Parsing KeyStoreParameters done, returning {}", ctx);
return ctx;
}
private Metadata parseSecureRandomParametersNode(Element element, ParserContext context) {
LOG.trace("Parsing SecureRandomParameters {}", element);
// now parse the key store parameters with JAXB
Binder<Node> binder;
try {
binder = getJaxbContext().createBinder();
} catch (JAXBException e) {
throw new ComponentDefinitionException("Failed to create the JAXB binder : " + e, e);
}
Object value = parseUsingJaxb(element, context, binder);
if (!(value instanceof SecureRandomParametersFactoryBean)) {
throw new ComponentDefinitionException("Expected an instance of " + SecureRandomParametersFactoryBean.class);
}
SecureRandomParametersFactoryBean srfb = (SecureRandomParametersFactoryBean) value;
String id = srfb.getId();
MutablePassThroughMetadata factory = context.createMetadata(MutablePassThroughMetadata.class);
factory.setId(".camelBlueprint.passThrough." + id);
factory.setObject(new PassThroughCallable<Object>(srfb));
MutableBeanMetadata factory2 = context.createMetadata(MutableBeanMetadata.class);
factory2.setId(".camelBlueprint.factory." + id);
factory2.setFactoryComponent(factory);
factory2.setFactoryMethod("call");
factory2.setInitMethod("afterPropertiesSet");
factory2.setDestroyMethod("destroy");
factory2.addProperty("blueprintContainer", createRef(context, "blueprintContainer"));
MutableBeanMetadata ctx = context.createMetadata(MutableBeanMetadata.class);
ctx.setId(id);
ctx.setRuntimeClass(SecureRandomParameters.class);
ctx.setFactoryComponent(factory2);
ctx.setFactoryMethod("getObject");
// must be lazy as we want CamelContext to be activated first
ctx.setActivation(ACTIVATION_LAZY);
LOG.trace("Parsing SecureRandomParameters done, returning {}", ctx);
return ctx;
}
private Metadata parseSSLContextParametersNode(Element element, ParserContext context) {
LOG.trace("Parsing SSLContextParameters {}", element);
// now parse the key store parameters with JAXB
Binder<Node> binder;
try {
binder = getJaxbContext().createBinder();
} catch (JAXBException e) {
throw new ComponentDefinitionException("Failed to create the JAXB binder : " + e, e);
}
Object value = parseUsingJaxb(element, context, binder);
if (!(value instanceof SSLContextParametersFactoryBean)) {
throw new ComponentDefinitionException("Expected an instance of " + SSLContextParametersFactoryBean.class);
}
SSLContextParametersFactoryBean scpfb = (SSLContextParametersFactoryBean) value;
String id = scpfb.getId();
MutablePassThroughMetadata factory = context.createMetadata(MutablePassThroughMetadata.class);
factory.setId(".camelBlueprint.passThrough." + id);
factory.setObject(new PassThroughCallable<Object>(scpfb));
MutableBeanMetadata factory2 = context.createMetadata(MutableBeanMetadata.class);
factory2.setId(".camelBlueprint.factory." + id);
factory2.setFactoryComponent(factory);
factory2.setFactoryMethod("call");
factory2.setInitMethod("afterPropertiesSet");
factory2.setDestroyMethod("destroy");
factory2.addProperty("blueprintContainer", createRef(context, "blueprintContainer"));
MutableBeanMetadata ctx = context.createMetadata(MutableBeanMetadata.class);
ctx.setId(id);
ctx.setRuntimeClass(SSLContextParameters.class);
ctx.setFactoryComponent(factory2);
ctx.setFactoryMethod("getObject");
// must be lazy as we want CamelContext to be activated first
ctx.setActivation(ACTIVATION_LAZY);
LOG.trace("Parsing SSLContextParameters done, returning {}", ctx);
return ctx;
}
private void registerBeans(ParserContext context, String contextId, List<?> beans) {
if (beans != null) {
for (Object bean : beans) {
if (bean instanceof AbstractCamelFactoryBean) {
registerBean(context, contextId, (AbstractCamelFactoryBean<?>) bean);
}
}
}
}
protected void registerBean(ParserContext context, String contextId, AbstractCamelFactoryBean<?> fact) {
String id = fact.getId();
fact.setCamelContextId(contextId);
MutablePassThroughMetadata eff = context.createMetadata(MutablePassThroughMetadata.class);
eff.setId(".camelBlueprint.bean.passthrough." + id);
eff.setObject(new PassThroughCallable<Object>(fact));
MutableBeanMetadata ef = context.createMetadata(MutableBeanMetadata.class);
ef.setId(".camelBlueprint.bean.factory." + id);
ef.setFactoryComponent(eff);
ef.setFactoryMethod("call");
ef.addProperty("blueprintContainer", createRef(context, "blueprintContainer"));
ef.setInitMethod("afterPropertiesSet");
ef.setDestroyMethod("destroy");
MutableBeanMetadata e = context.createMetadata(MutableBeanMetadata.class);
e.setId(id);
e.setRuntimeClass(fact.getObjectType());
e.setFactoryComponent(ef);
e.setFactoryMethod("getObject");
e.addDependsOn(".camelBlueprint.processor.bean." + contextId);
context.getComponentDefinitionRegistry().registerComponentDefinition(e);
}
protected BlueprintContainer getBlueprintContainer(ParserContext context) {
PassThroughMetadata ptm = (PassThroughMetadata) context.getComponentDefinitionRegistry().getComponentDefinition("blueprintContainer");
return (BlueprintContainer) ptm.getObject();
}
public ComponentMetadata decorate(Node node, ComponentMetadata component, ParserContext context) {
return null;
}
protected Object parseUsingJaxb(Element element, ParserContext parserContext, Binder<Node> binder) {
try {
return binder.unmarshal(element);
} catch (JAXBException e) {
throw new ComponentDefinitionException("Failed to parse JAXB element: " + e, e);
}
}
public JAXBContext getJaxbContext() throws JAXBException {
if (jaxbContext == null) {
jaxbContext = new BlueprintModelJAXBContextFactory(getClass().getClassLoader()).newJAXBContext();
}
return jaxbContext;
}
private RefMetadata createRef(ParserContext context, String value) {
MutableRefMetadata r = context.createMetadata(MutableRefMetadata.class);
r.setComponentId(value);
return r;
}
private static ComponentMetadata getDataformatResolverReference(ParserContext context, String dataformat) {
// we cannot resolve dataformat names using property placeholders at this point in time
if (dataformat.startsWith(PropertiesComponent.DEFAULT_PREFIX_TOKEN)) {
return null;
}
ComponentDefinitionRegistry componentDefinitionRegistry = context.getComponentDefinitionRegistry();
ComponentMetadata cm = componentDefinitionRegistry.getComponentDefinition(".camelBlueprint.dataformatResolver." + dataformat);
if (cm == null) {
MutableReferenceMetadata svc = context.createMetadata(MutableReferenceMetadata.class);
svc.setId(".camelBlueprint.dataformatResolver." + dataformat);
svc.setFilter("(dataformat=" + dataformat + ")");
svc.setAvailability(componentDefinitionRegistry.containsComponentDefinition(dataformat) ? AVAILABILITY_OPTIONAL : AVAILABILITY_MANDATORY);
try {
// Try to set the runtime interface (only with aries blueprint > 0.1
svc.getClass().getMethod("setRuntimeInterface", Class.class).invoke(svc, DataFormatResolver.class);
} catch (Throwable t) {
// Check if the bundle can see the class
try {
PassThroughMetadata ptm = (PassThroughMetadata) componentDefinitionRegistry.getComponentDefinition("blueprintBundle");
Bundle b = (Bundle) ptm.getObject();
if (b.loadClass(DataFormatResolver.class.getName()) != DataFormatResolver.class) {
throw new UnsupportedOperationException();
}
svc.setInterface(DataFormatResolver.class.getName());
} catch (Throwable t2) {
throw new UnsupportedOperationException();
}
}
componentDefinitionRegistry.registerComponentDefinition(svc);
cm = svc;
}
return cm;
}
private static ComponentMetadata getLanguageResolverReference(ParserContext context, String language) {
// we cannot resolve language names using property placeholders at this point in time
if (language.startsWith(PropertiesComponent.DEFAULT_PREFIX_TOKEN)) {
return null;
}
ComponentDefinitionRegistry componentDefinitionRegistry = context.getComponentDefinitionRegistry();
ComponentMetadata cm = componentDefinitionRegistry.getComponentDefinition(".camelBlueprint.languageResolver." + language);
if (cm == null) {
MutableReferenceMetadata svc = context.createMetadata(MutableReferenceMetadata.class);
svc.setId(".camelBlueprint.languageResolver." + language);
svc.setFilter("(language=" + language + ")");
svc.setAvailability(componentDefinitionRegistry.containsComponentDefinition(language) ? AVAILABILITY_OPTIONAL : AVAILABILITY_MANDATORY);
try {
// Try to set the runtime interface (only with aries blueprint > 0.1
svc.getClass().getMethod("setRuntimeInterface", Class.class).invoke(svc, LanguageResolver.class);
} catch (Throwable t) {
// Check if the bundle can see the class
try {
PassThroughMetadata ptm = (PassThroughMetadata) componentDefinitionRegistry.getComponentDefinition("blueprintBundle");
Bundle b = (Bundle) ptm.getObject();
if (b.loadClass(LanguageResolver.class.getName()) != LanguageResolver.class) {
throw new UnsupportedOperationException();
}
svc.setInterface(LanguageResolver.class.getName());
} catch (Throwable t2) {
throw new UnsupportedOperationException();
}
}
componentDefinitionRegistry.registerComponentDefinition(svc);
cm = svc;
}
return cm;
}
private static ComponentMetadata getComponentResolverReference(ParserContext context, String component) {
// we cannot resolve component names using property placeholders at this point in time
if (component.startsWith(PropertiesComponent.DEFAULT_PREFIX_TOKEN)) {
return null;
}
ComponentDefinitionRegistry componentDefinitionRegistry = context.getComponentDefinitionRegistry();
ComponentMetadata cm = componentDefinitionRegistry.getComponentDefinition(".camelBlueprint.componentResolver." + component);
if (cm == null) {
MutableReferenceMetadata svc = context.createMetadata(MutableReferenceMetadata.class);
svc.setId(".camelBlueprint.componentResolver." + component);
svc.setFilter("(component=" + component + ")");
svc.setAvailability(componentDefinitionRegistry.containsComponentDefinition(component) ? AVAILABILITY_OPTIONAL : AVAILABILITY_MANDATORY);
try {
// Try to set the runtime interface (only with aries blueprint > 0.1
svc.getClass().getMethod("setRuntimeInterface", Class.class).invoke(svc, ComponentResolver.class);
} catch (Throwable t) {
// Check if the bundle can see the class
try {
PassThroughMetadata ptm = (PassThroughMetadata) componentDefinitionRegistry.getComponentDefinition("blueprintBundle");
Bundle b = (Bundle) ptm.getObject();
if (b.loadClass(ComponentResolver.class.getName()) != ComponentResolver.class) {
throw new UnsupportedOperationException();
}
svc.setInterface(ComponentResolver.class.getName());
} catch (Throwable t2) {
throw new UnsupportedOperationException();
}
}
componentDefinitionRegistry.registerComponentDefinition(svc);
cm = svc;
}
return cm;
}
public static class PassThroughCallable<T> implements Callable<T> {
private T value;
public PassThroughCallable(T value) {
this.value = value;
}
public T call() throws Exception {
return value;
}
}
public static class CamelInjector extends CamelPostProcessorHelper implements BeanProcessor {
private final String camelContextName;
private BlueprintContainer blueprintContainer;
public CamelInjector(String camelContextName) {
this.camelContextName = camelContextName;
}
public void setBlueprintContainer(BlueprintContainer blueprintContainer) {
this.blueprintContainer = blueprintContainer;
}
@Override
public CamelContext getCamelContext() {
if (blueprintContainer != null) {
CamelContext answer = (CamelContext) blueprintContainer.getComponentInstance(camelContextName);
return answer;
}
return null;
}
public Object beforeInit(Object bean, String beanName, BeanCreator beanCreator, BeanMetadata beanMetadata) {
LOG.trace("Before init of bean: {} -> {}", beanName, bean);
// prefer to inject later in afterInit
return bean;
}
/**
* A strategy method to allow implementations to perform some custom JBI
* based injection of the POJO
*
* @param bean the bean to be injected
*/
protected void injectFields(final Object bean, final String beanName) {
Class<?> clazz = bean.getClass();
do {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
PropertyInject propertyInject = field.getAnnotation(PropertyInject.class);
if (propertyInject != null && matchContext(propertyInject.context())) {
injectFieldProperty(field, propertyInject.value(), propertyInject.defaultValue(), bean, beanName);
}
BeanInject beanInject = field.getAnnotation(BeanInject.class);
if (beanInject != null && matchContext(beanInject.context())) {
injectFieldBean(field, beanInject.value(), bean, beanName);
}
EndpointInject endpointInject = field.getAnnotation(EndpointInject.class);
if (endpointInject != null && matchContext(endpointInject.context())) {
injectField(field, endpointInject.uri(), endpointInject.ref(), endpointInject.property(), bean, beanName);
}
Produce produce = field.getAnnotation(Produce.class);
if (produce != null && matchContext(produce.context())) {
injectField(field, produce.uri(), produce.ref(), produce.property(), bean, beanName);
}
}
clazz = clazz.getSuperclass();
} while (clazz != null && clazz != Object.class);
}
protected void injectField(Field field, String endpointUri, String endpointRef, String endpointProperty, Object bean, String beanName) {
setField(field, bean, getInjectionValue(field.getType(), endpointUri, endpointRef, endpointProperty, field.getName(), bean, beanName));
}
protected void injectFieldProperty(Field field, String propertyName, String propertyDefaultValue, Object bean, String beanName) {
setField(field, bean, getInjectionPropertyValue(field.getType(), propertyName, propertyDefaultValue, field.getName(), bean, beanName));
}
public void injectFieldBean(Field field, String name, Object bean, String beanName) {
setField(field, bean, getInjectionBeanValue(field.getType(), name));
}
protected static void setField(Field field, Object instance, Object value) {
try {
boolean oldAccessible = field.isAccessible();
boolean shouldSetAccessible = !Modifier.isPublic(field.getModifiers()) && !oldAccessible;
if (shouldSetAccessible) {
field.setAccessible(true);
}
field.set(instance, value);
if (shouldSetAccessible) {
field.setAccessible(oldAccessible);
}
} catch (IllegalArgumentException ex) {
throw new UnsupportedOperationException("Cannot inject value of class: " + value.getClass() + " into: " + field);
} catch (IllegalAccessException ex) {
throw new IllegalStateException("Could not access method: " + ex.getMessage());
}
}
protected void injectMethods(final Object bean, final String beanName) {
Class<?> clazz = bean.getClass();
do {
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
setterInjection(method, bean, beanName);
consumerInjection(method, bean, beanName);
}
clazz = clazz.getSuperclass();
} while (clazz != null && clazz != Object.class);
}
protected void setterInjection(Method method, Object bean, String beanName) {
PropertyInject propertyInject = method.getAnnotation(PropertyInject.class);
if (propertyInject != null && matchContext(propertyInject.context())) {
setterPropertyInjection(method, propertyInject.value(), propertyInject.defaultValue(), bean, beanName);
}
BeanInject beanInject = method.getAnnotation(BeanInject.class);
if (beanInject != null && matchContext(beanInject.context())) {
setterBeanInjection(method, beanInject.value(), bean, beanName);
}
EndpointInject endpointInject = method.getAnnotation(EndpointInject.class);
if (endpointInject != null && matchContext(endpointInject.context())) {
setterInjection(method, bean, beanName, endpointInject.uri(), endpointInject.ref(), endpointInject.property());
}
Produce produce = method.getAnnotation(Produce.class);
if (produce != null && matchContext(produce.context())) {
setterInjection(method, bean, beanName, produce.uri(), produce.ref(), produce.property());
}
}
protected void setterPropertyInjection(Method method, String propertyValue, String propertyDefaultValue, Object bean, String beanName) {
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes != null) {
if (parameterTypes.length != 1) {
LOG.warn("Ignoring badly annotated method for injection due to incorrect number of parameters: " + method);
} else {
String propertyName = ObjectHelper.getPropertyName(method);
Object value = getInjectionPropertyValue(parameterTypes[0], propertyValue, propertyDefaultValue, propertyName, bean, beanName);
ObjectHelper.invokeMethod(method, bean, value);
}
}
}
protected void setterBeanInjection(Method method, String name, Object bean, String beanName) {
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes != null) {
if (parameterTypes.length != 1) {
LOG.warn("Ignoring badly annotated method for injection due to incorrect number of parameters: " + method);
} else {
Object value = getInjectionBeanValue(parameterTypes[0], name);
ObjectHelper.invokeMethod(method, bean, value);
}
}
}
protected void setterInjection(Method method, Object bean, String beanName, String endpointUri, String endpointRef, String endpointProperty) {
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes != null) {
if (parameterTypes.length != 1) {
LOG.warn("Ignoring badly annotated method for injection due to incorrect number of parameters: " + method);
} else {
String propertyName = ObjectHelper.getPropertyName(method);
Object value = getInjectionValue(parameterTypes[0], endpointUri, endpointRef, endpointProperty, propertyName, bean, beanName);
ObjectHelper.invokeMethod(method, bean, value);
}
}
}
public Object afterInit(Object bean, String beanName, BeanCreator beanCreator, BeanMetadata beanMetadata) {
LOG.trace("After init of bean: {} -> {}", beanName, bean);
// we cannot inject CamelContextAware beans as the CamelContext may not be ready
injectFields(bean, beanName);
injectMethods(bean, beanName);
return bean;
}
public void beforeDestroy(Object bean, String beanName) {
}
public void afterDestroy(Object bean, String beanName) {
}
@Override
protected boolean isSingleton(Object bean, String beanName) {
if (beanName != null) {
ComponentMetadata meta = blueprintContainer.getComponentMetadata(beanName);
if (meta != null && meta instanceof BeanMetadata) {
String scope = ((BeanMetadata) meta).getScope();
if (scope != null) {
return BeanMetadata.SCOPE_SINGLETON.equals(scope);
}
}
}
// fallback to super, which will assume singleton
// for beans not implementing Camel's IsSingleton interface
return super.isSingleton(bean, beanName);
}
}
public static class CamelDependenciesFinder implements ComponentDefinitionRegistryProcessor {
private final String camelContextName;
private final ParserContext context;
private BlueprintContainer blueprintContainer;
public CamelDependenciesFinder(String camelContextName, ParserContext context) {
this.camelContextName = camelContextName;
this.context = context;
}
public void setBlueprintContainer(BlueprintContainer blueprintContainer) {
this.blueprintContainer = blueprintContainer;
}
public void process(ComponentDefinitionRegistry componentDefinitionRegistry) {
CamelContextFactoryBean ccfb = (CamelContextFactoryBean) blueprintContainer.getComponentInstance(".camelBlueprint.factory." + camelContextName);
CamelContext camelContext = ccfb.getContext();
Set<String> components = new HashSet<>();
Set<String> languages = new HashSet<>();
Set<String> dataformats = new HashSet<>();
// regular camel routes
for (RouteDefinition rd : camelContext.getRouteDefinitions()) {
findInputComponents(rd.getInputs(), components, languages, dataformats);
findOutputComponents(rd.getOutputs(), components, languages, dataformats);
}
// rest services can have embedded routes or a singular to
for (RestDefinition rd : camelContext.getRestDefinitions()) {
for (VerbDefinition vd : rd.getVerbs()) {
Object o = vd.getToOrRoute();
if (o instanceof RouteDefinition) {
RouteDefinition route = (RouteDefinition) o;
findInputComponents(route.getInputs(), components, languages, dataformats);
findOutputComponents(route.getOutputs(), components, languages, dataformats);
} else if (o instanceof ToDefinition) {
findUriComponent(((ToDefinition) o).getUri(), components);
} else if (o instanceof ToDynamicDefinition) {
findUriComponent(((ToDynamicDefinition) o).getUri(), components);
}
}
}
if (ccfb.getRestConfiguration() != null) {
// rest configuration may refer to a component to use
String component = ccfb.getRestConfiguration().getComponent();
if (component != null) {
components.add(component);
}
component = ccfb.getRestConfiguration().getApiComponent();
if (component != null) {
components.add(component);
}
// check what data formats are used in binding mode
RestBindingMode mode = ccfb.getRestConfiguration().getBindingMode();
String json = ccfb.getRestConfiguration().getJsonDataFormat();
if (json == null && mode != null) {
if (RestBindingMode.json.equals(mode) || RestBindingMode.json_xml.equals(mode)) {
// jackson is the default json data format
json = "json-jackson";
}
}
if (json != null) {
dataformats.add(json);
}
String xml = ccfb.getRestConfiguration().getXmlDataFormat();
if (xml == null && mode != null) {
if (RestBindingMode.xml.equals(mode) || RestBindingMode.json_xml.equals(mode)) {
// jaxb is the default xml data format
dataformats.add("jaxb");
}
}
if (xml != null) {
dataformats.add(xml);
}
}
// We can only add service references to resolvers, but we can't make the factory depends on those
// because the factory has already been instantiated
try {
for (String component : components) {
if (camelContext.getComponent(component) == null) {
getComponentResolverReference(context, component);
} else {
LOG.debug("Not creating a service reference for component {} because a component already exists in the Camel Context", component);
}
}
for (String language : languages) {
getLanguageResolverReference(context, language);
}
for (String dataformat : dataformats) {
getDataformatResolverReference(context, dataformat);
}
} catch (UnsupportedOperationException e) {
LOG.warn("Unable to add dependencies to Camel components OSGi services. "
+ "The Apache Aries blueprint implementation used is too old and the blueprint bundle cannot see the org.apache.camel.spi package.");
components.clear();
languages.clear();
dataformats.clear();
}
}
private void findInputComponents(List<FromDefinition> defs, Set<String> components, Set<String> languages, Set<String> dataformats) {
if (defs != null) {
for (FromDefinition def : defs) {
findUriComponent(def.getUri(), components);
findSchedulerUriComponent(def.getUri(), components);
}
}
}
@SuppressWarnings({"rawtypes"})
private void findOutputComponents(List<ProcessorDefinition<?>> defs, Set<String> components, Set<String> languages, Set<String> dataformats) {
if (defs != null) {
for (ProcessorDefinition<?> def : defs) {
if (def instanceof SendDefinition) {
findUriComponent(((SendDefinition) def).getUri(), components);
}
if (def instanceof MarshalDefinition) {
findDataFormat(((MarshalDefinition) def).getDataFormatType(), dataformats);
}
if (def instanceof UnmarshalDefinition) {
findDataFormat(((UnmarshalDefinition) def).getDataFormatType(), dataformats);
}
if (def instanceof ExpressionNode) {
findLanguage(((ExpressionNode) def).getExpression(), languages);
}
if (def instanceof ResequenceDefinition) {
findLanguage(((ResequenceDefinition) def).getExpression(), languages);
}
if (def instanceof AggregateDefinition) {
findLanguage(((AggregateDefinition) def).getExpression(), languages);
findLanguage(((AggregateDefinition) def).getCorrelationExpression(), languages);
findLanguage(((AggregateDefinition) def).getCompletionPredicate(), languages);
findLanguage(((AggregateDefinition) def).getCompletionTimeoutExpression(), languages);
findLanguage(((AggregateDefinition) def).getCompletionSizeExpression(), languages);
}
if (def instanceof CatchDefinition) {
findLanguage(((CatchDefinition) def).getHandled(), languages);
}
if (def instanceof OnExceptionDefinition) {
findLanguage(((OnExceptionDefinition) def).getRetryWhile(), languages);
findLanguage(((OnExceptionDefinition) def).getHandled(), languages);
findLanguage(((OnExceptionDefinition) def).getContinued(), languages);
}
if (def instanceof SortDefinition) {
findLanguage(((SortDefinition) def).getExpression(), languages);
}
if (def instanceof WireTapDefinition) {
findLanguage(((WireTapDefinition<?>) def).getNewExchangeExpression(), languages);
}
findOutputComponents(def.getOutputs(), components, languages, dataformats);
}
}
}
private void findLanguage(ExpressionDefinition expression, Set<String> languages) {
if (expression != null) {
String lang = expression.getLanguage();
if (lang != null && lang.length() > 0) {
languages.add(lang);
}
}
}
private void findLanguage(ExpressionSubElementDefinition expression, Set<String> languages) {
if (expression != null) {
findLanguage(expression.getExpressionType(), languages);
}
}
private void findDataFormat(DataFormatDefinition dfd, Set<String> dataformats) {
if (dfd != null && dfd.getDataFormatName() != null) {
dataformats.add(dfd.getDataFormatName());
}
}
private void findUriComponent(String uri, Set<String> components) {
// if the uri is a placeholder then skip it
if (uri == null || uri.startsWith(PropertiesComponent.DEFAULT_PREFIX_TOKEN)) {
return;
}
// validate uri here up-front so a meaningful error can be logged for blueprint
// it will also speed up tests in case of failure
if (!validateUri(uri)) {
return;
}
String splitURI[] = ObjectHelper.splitOnCharacter(uri, ":", 2);
if (splitURI[1] != null) {
String scheme = splitURI[0];
components.add(scheme);
}
}
private void findSchedulerUriComponent(String uri, Set<String> components) {
// the input may use a scheduler which can be quartz or spring
if (uri != null) {
try {
URI u = new URI(uri);
Map<String, Object> parameters = URISupport.parseParameters(u);
Object value = parameters.get("scheduler");
if (value == null) {
value = parameters.get("consumer.scheduler");
}
if (value != null) {
// the scheduler can be quartz2 or spring based, so add reference to camel component
// from these components os blueprint knows about the requirement
String name = value.toString();
if ("quartz2".equals(name)) {
components.add("quartz2");
} else if ("spring".equals(name)) {
components.add("spring-event");
}
}
} catch (URISyntaxException e) {
// ignore as uri should be already validated at findUriComponent method
}
}
}
private static boolean validateUri(String uri) {
try {
// the same validation as done in DefaultCamelContext#normalizeEndpointUri(String)
URISupport.normalizeUri(uri);
} catch (URISyntaxException | UnsupportedEncodingException e) {
LOG.error("Endpoint URI '" + uri + "' is not valid due to: " + e.getMessage(), e);
return false;
}
return true;
}
}
}