/*
 * 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.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 jakarta.xml.bind.Binder;
import jakarta.xml.bind.JAXBContext;
import jakarta.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.BlueprintCamelStateService;
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.CamelRouteConfigurationContextFactoryBean;
import org.apache.camel.blueprint.CamelRouteContextFactoryBean;
import org.apache.camel.blueprint.CamelRouteTemplateContextFactoryBean;
import org.apache.camel.builder.LegacyDeadLetterChannelBuilder;
import org.apache.camel.builder.LegacyDefaultErrorHandlerBuilder;
import org.apache.camel.builder.LegacyNoErrorHandlerBuilder;
import org.apache.camel.core.xml.AbstractCamelFactoryBean;
import org.apache.camel.impl.engine.CamelPostProcessorHelper;
import org.apache.camel.impl.engine.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.Model;
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.UnmarshalDefinition;
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.reifier.errorhandler.ErrorHandlerReifier;
import org.apache.camel.reifier.errorhandler.LegacyDeadLetterChannelReifier;
import org.apache.camel.reifier.errorhandler.LegacyDefaultErrorHandlerReifier;
import org.apache.camel.reifier.errorhandler.LegacyNoErrorHandlerReifier;
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.spi.PropertiesComponent;
import org.apache.camel.support.ObjectHelper;
import org.apache.camel.support.builder.Namespaces;
import org.apache.camel.support.builder.xml.NamespacesHelper;
import org.apache.camel.support.jsse.KeyStoreParameters;
import org.apache.camel.support.jsse.SSLContextParameters;
import org.apache.camel.support.jsse.SecureRandomParameters;
import org.apache.camel.util.StringHelper;
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.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 {

    static {
        // legacy camel-blueprint error-handling using its own model and parsers
        ErrorHandlerReifier.registerReifier(LegacyDeadLetterChannelBuilder.class, LegacyDeadLetterChannelReifier::new);
        ErrorHandlerReifier.registerReifier(LegacyDefaultErrorHandlerBuilder.class, LegacyDefaultErrorHandlerReifier::new);
        ErrorHandlerReifier.registerReifier(LegacyNoErrorHandlerBuilder.class, LegacyNoErrorHandlerReifier::new);
    }

    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 ROUTE_CONFIGURATION_CONTEXT = "routeConfigurationContext";
    private static final String ROUTE_TEMPLATE_CONTEXT = "routeTemplateContext";
    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 = StringHelper.before(value, "?");
                    String after = StringHelper.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);
        }
    }

    @Override
    public URL getSchemaLocation(String namespace) {
        if (BLUEPRINT_NS.equals(namespace)) {
            return getClass().getClassLoader().getResource("camel-blueprint.xsd");
        }
        return null;
    }

    @Override
    @SuppressWarnings({"rawtypes"})
    public Set<Class> getManagedClasses() {
        return Set.of(BlueprintCamelContext.class);
    }

    @Override
    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(ROUTE_CONFIGURATION_CONTEXT)) {
                return parseRouteConfigurationContextNode(element, context);
            }
            if (element.getLocalName().equals(ROUTE_TEMPLATE_CONTEXT)) {
                return parseRouteTemplateContextNode(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 (org.apache.camel.util.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);

        MutablePassThroughMetadata factory = context.createMetadata(MutablePassThroughMetadata.class);
        factory.setId(".camelBlueprint.passThrough." + contextId);
        factory.setObject(new PassThroughCallable<>(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"));
        // We need to add other components which the camel context dependsOn
        if (org.apache.camel.util.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.addProperty("bundleStateService", createRef(context, ".camelBlueprint.bundleStateService"));
        ctx.setInitMethod("build");
        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 single CamelBundleStateService - shared for all bundles and all Blueprint Camel contexts
        registerBundleStateService(context);

        // 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 = NamespacesHelper.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 parseRouteConfigurationContextNode(Element element, ParserContext context) {
        LOG.trace("Parsing RouteConfigurationContext {}", 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 CamelRouteConfigurationContextFactoryBean)) {
            throw new ComponentDefinitionException("Expected an instance of " + CamelRouteConfigurationContextFactoryBean.class);
        }

        CamelRouteConfigurationContextFactoryBean rcfb = (CamelRouteConfigurationContextFactoryBean) 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("getRouteConfigurations");
        // 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 RouteConfigurationContext {} done, returning {}", element, ctx);
        return ctx;
    }

    private Metadata parseRouteTemplateContextNode(Element element, ParserContext context) {
        LOG.trace("Parsing RouteTemplateContext {}", 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 CamelRouteTemplateContextFactoryBean)) {
            throw new ComponentDefinitionException("Expected an instance of " + CamelRouteTemplateContextFactoryBean.class);
        }

        CamelRouteTemplateContextFactoryBean rcfb = (CamelRouteTemplateContextFactoryBean) 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("getRouteTemplates");
        // 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 RouteTemplateContext {} 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);
    }

    /**
     * There's single instance of {@link BlueprintCamelStateService} that's used by all Blueprint Camel contexts
     * to inform about state of Camel contexts. If Karaf is available, this information will propagate to
     * <em>extended bundle info</em>.
     * See CAMEL-12980
     */
    private void registerBundleStateService(ParserContext context) {
        ComponentDefinitionRegistry componentDefinitionRegistry = context.getComponentDefinitionRegistry();
        ComponentMetadata cm = componentDefinitionRegistry.getComponentDefinition(".camelBlueprint.bundleStateService");
        if (cm == null) {
            MutableBeanMetadata ssm = context.createMetadata(MutableBeanMetadata.class);
            ssm.setId(".camelBlueprint.bundleStateService");
            ssm.setRuntimeClass(BlueprintCamelStateService.class);
            ssm.addProperty("bundleContext", createRef(context, "blueprintBundleContext"));
            ssm.setInitMethod("init");
            ssm.setDestroyMethod("destroy");
            componentDefinitionRegistry.registerComponentDefinition(ssm);
        }
    }

    protected BlueprintContainer getBlueprintContainer(ParserContext context) {
        PassThroughMetadata ptm = (PassThroughMetadata) context.getComponentDefinitionRegistry().getComponentDefinition("blueprintContainer");
        return (BlueprintContainer) ptm.getObject();
    }

    @Override
    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.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.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.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;
        }

        @Override
        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;
        }

        @Override
        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) {
                        injectFieldProperty(field, bean, propertyInject.value(), propertyInject.defaultValue(), propertyInject.separator());
                    }

                    BeanInject beanInject = field.getAnnotation(BeanInject.class);
                    if (beanInject != null) {
                        injectFieldBean(field, bean, beanInject.value());
                    }

                    EndpointInject endpointInject = field.getAnnotation(EndpointInject.class);
                    if (endpointInject != null) {
                        String uri = endpointInject.value();
                        injectField(field, uri, endpointInject.property(), bean, beanName);
                    }

                    Produce produce = field.getAnnotation(Produce.class);
                    if (produce != null) {
                        String uri = produce.value();
                        injectField(field, uri, produce.property(), bean, beanName);
                    }
                }
                clazz = clazz.getSuperclass();
            } while (clazz != null && clazz != Object.class);
        }

        protected void injectField(Field field, String endpointUri, String endpointProperty, Object bean, String beanName) {
            setField(field, bean, getInjectionValue(field.getType(), endpointUri, endpointProperty, field.getName(), bean, beanName));
        }

        protected void injectFieldProperty(Field field, Object bean, String propertyName, String propertyDefaultValue, String separator) {
            setField(field, bean, getInjectionPropertyValue(field.getType(), field.getGenericType(), propertyName, propertyDefaultValue, separator));
        }

        public void injectFieldBean(Field field, Object bean, String name) {
            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) {
                setterPropertyInjection(method, bean, propertyInject.value(), propertyInject.defaultValue(), propertyInject.separator());
            }

            BeanInject beanInject = method.getAnnotation(BeanInject.class);
            if (beanInject != null) {
                setterBeanInjection(method, beanInject.value(), bean, beanName);
            }

            EndpointInject endpointInject = method.getAnnotation(EndpointInject.class);
            if (endpointInject != null) {
                String uri = endpointInject.value();
                setterInjection(method, bean, beanName, uri, endpointInject.property());
            }

            Produce produce = method.getAnnotation(Produce.class);
            if (produce != null) {
                String uri = produce.value();
                setterInjection(method, bean, beanName, uri, produce.property());
            }
        }

        protected void setterPropertyInjection(Method method, Object bean, String propertyName, String propertyDefaultValue, String separator) {
            Class<?>[] parameterTypes = method.getParameterTypes();
            if (parameterTypes.length != 1) {
                LOG.warn("Ignoring badly annotated method for injection due to incorrect number of parameters: {}", method);
            } else {
                Object value = getInjectionPropertyValue(parameterTypes[0], method.getGenericParameterTypes()[0], propertyName, propertyDefaultValue, separator);
                ObjectHelper.invokeMethod(method, bean, value);
            }
        }

        protected void setterBeanInjection(Method method, String name, Object bean, String beanName) {
            Class<?>[] parameterTypes = method.getParameterTypes();
            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 endpointProperty) {
            Class<?>[] parameterTypes = method.getParameterTypes();
            if (parameterTypes.length != 1) {
                LOG.warn("Ignoring badly annotated method for injection due to incorrect number of parameters: {}", method);
            } else {
                String propertyName = org.apache.camel.util.ObjectHelper.getPropertyName(method);
                Object value = getInjectionValue(parameterTypes[0], endpointUri, endpointProperty, propertyName, bean, beanName);
                ObjectHelper.invokeMethod(method, bean, value);
            }
        }

        @Override
        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
            // TODO: use bean post processor instead
            injectFields(bean, beanName);
            injectMethods(bean, beanName);
            return bean;
        }

        @Override
        public void beforeDestroy(Object bean, String beanName) {
        }

        @Override
        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 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;
        }

        @Override
        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.getCamelContextExtension().getContextPlugin(Model.class).getRouteDefinitions()) {
                findInputComponents(rd.getInput(), components, languages, dataformats);
                findOutputComponents(rd.getOutputs(), components, languages, dataformats);
            }

            // rest services can have embedded routes or a singular to
            for (RestDefinition rd : camelContext.getCamelContextExtension().getContextPlugin(Model.class).getRestDefinitions()) {
                for (VerbDefinition vd : rd.getVerbs()) {
                    ToDefinition to = vd.getTo();
                    if (to != null) {
                        findUriComponent(to.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 = "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, false) == null) {
                        // component not already in camel-context so resolve an OSGi reference to it
                        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(FromDefinition from, Set<String> components, Set<String> languages, Set<String> dataformats) {
            if (from != null) {
                findUriComponent(from.getUri(), components);
                findSchedulerUriComponent(from.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) {
                        CatchDefinition doCatch = (CatchDefinition) def;
                        if (doCatch.getOnWhen() != null) {
                            findLanguage(doCatch.getOnWhen().getExpression(), 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);
                    }
                    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.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[] = StringHelper.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) {
                        // the scheduler can be quartz or spring based, so add reference to camel component
                        // from these components os blueprint knows about the requirement
                        String name = value.toString();
                        if ("quartz".equals(name)) {
                            components.add("quartz");
                        } 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 e) {
                LOG.error("Endpoint URI '" + uri + "' is not valid due to: " + e.getMessage(), e);
                return false;
            }
            return true;
        }
    }

}