All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.camel.dsl.xml.io.XmlRoutesBuilderLoader Maven / Gradle / Ivy

There is a newer version: 4.9.0
Show newest version
/*
 * 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.dsl.xml.io;

import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

import org.w3c.dom.Document;

import org.apache.camel.BindToRegistry;
import org.apache.camel.CamelContextAware;
import org.apache.camel.api.management.ManagedResource;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.builder.RouteConfigurationBuilder;
import org.apache.camel.dsl.support.RouteBuilderLoaderSupport;
import org.apache.camel.model.RouteConfigurationDefinition;
import org.apache.camel.model.RouteConfigurationsDefinition;
import org.apache.camel.model.RouteDefinition;
import org.apache.camel.model.RouteTemplateDefinition;
import org.apache.camel.model.RouteTemplatesDefinition;
import org.apache.camel.model.RoutesDefinition;
import org.apache.camel.model.TemplatedRouteDefinition;
import org.apache.camel.model.TemplatedRoutesDefinition;
import org.apache.camel.model.app.BeansDefinition;
import org.apache.camel.model.app.RegistryBeanDefinition;
import org.apache.camel.model.rest.RestDefinition;
import org.apache.camel.model.rest.RestsDefinition;
import org.apache.camel.spi.Injector;
import org.apache.camel.spi.PackageScanClassResolver;
import org.apache.camel.spi.Registry;
import org.apache.camel.spi.Resource;
import org.apache.camel.spi.annotations.RoutesLoader;
import org.apache.camel.support.CachedResource;
import org.apache.camel.support.PropertyBindingSupport;
import org.apache.camel.xml.in.ModelParser;
import org.apache.camel.xml.io.util.XmlStreamDetector;
import org.apache.camel.xml.io.util.XmlStreamInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@ManagedResource(description = "Managed XML RoutesBuilderLoader")
@RoutesLoader(XmlRoutesBuilderLoader.EXTENSION)
public class XmlRoutesBuilderLoader extends RouteBuilderLoaderSupport {
    public static final Logger LOG = LoggerFactory.getLogger(XmlRoutesBuilderLoader.class);

    public static final String EXTENSION = "xml";
    public static final String NAMESPACE = "http://camel.apache.org/schema/spring";
    private static final List NAMESPACES = List.of("", NAMESPACE);

    private final Map resourceCache = new ConcurrentHashMap<>();
    private final Map xmlInfoCache = new ConcurrentHashMap<>();
    private final Map camelAppCache = new ConcurrentHashMap<>();

    private final AtomicInteger counter = new AtomicInteger(0);

    public XmlRoutesBuilderLoader() {
        super(EXTENSION);
    }

    XmlRoutesBuilderLoader(String extension) {
        super(extension);
    }

    @Override
    public void preParseRoute(Resource resource) throws Exception {
        // preparsing is done at early stage, so we have a chance to load additional beans and populate
        // Camel registry
        XmlStreamInfo xmlInfo = xmlInfo(resource);
        if (xmlInfo.isValid()) {
            String root = xmlInfo.getRootElementName();
            if ("beans".equals(root) || "camel".equals(root)) {
                new ModelParser(resource, xmlInfo.getRootElementNamespace())
                        .parseBeansDefinition()
                        .ifPresent(bd -> {
                            registerBeans(resource, bd);
                            camelAppCache.put(resource.getLocation(), bd);
                        });
            }
        }
    }

    @Override
    public RouteBuilder doLoadRouteBuilder(Resource input) throws Exception {
        final Resource resource = resource(input);
        XmlStreamInfo xmlInfo = xmlInfo(input);
        if (!xmlInfo.isValid()) {
            // should be valid, because we checked it before
            LOG.warn("Invalid XML document: {}", xmlInfo.getProblem().getMessage());
            return null;
        }

        return new RouteConfigurationBuilder() {
            @Override
            public void configure() throws Exception {
                String resourceLocation = input.getLocation();
                switch (xmlInfo.getRootElementName()) {
                    case "beans", "camel" -> {
                        BeansDefinition def = camelAppCache.get(resourceLocation);
                        if (def != null) {
                            configureCamel(def);
                        } else {
                            new ModelParser(resource, xmlInfo.getRootElementNamespace())
                                    .parseBeansDefinition()
                                    .ifPresent(this::configureCamel);
                        }
                    }
                    case "routeTemplate", "routeTemplates" ->
                        new ModelParser(resource, xmlInfo.getRootElementNamespace())
                                .parseRouteTemplatesDefinition()
                                .ifPresent(this::setRouteTemplateCollection);
                    case "templatedRoutes", "templatedRoute" ->
                        new ModelParser(resource, xmlInfo.getRootElementNamespace())
                                .parseTemplatedRoutesDefinition()
                                .ifPresent(this::setTemplatedRouteCollection);
                    case "rests", "rest" ->
                        new ModelParser(resource, xmlInfo.getRootElementNamespace())
                                .parseRestsDefinition()
                                .ifPresent(this::setRestCollection);
                    case "routes", "route" ->
                        new ModelParser(resource, xmlInfo.getRootElementNamespace())
                                .parseRoutesDefinition()
                                .ifPresent(this::addRoutes);
                    default -> {
                    }
                }

                // knowing this is the last time an XML may have been parsed, we can clear the cache
                // (route may get reloaded later)
                resourceCache.remove(resourceLocation);
                xmlInfoCache.remove(resourceLocation);
                camelAppCache.remove(resourceLocation);
            }

            @Override
            public void configuration() throws Exception {
                switch (xmlInfo.getRootElementName()) {
                    case "routeConfigurations", "routeConfiguration" ->
                        new ModelParser(resource, xmlInfo.getRootElementNamespace())
                                .parseRouteConfigurationsDefinition()
                                .ifPresent(this::addConfigurations);
                    default -> {
                    }
                }
            }

            private void configureCamel(BeansDefinition app) {
                // we have access to beans and spring beans, but these are already processed
                // in preParseRoute() and possibly registered in
                // org.apache.camel.main.BaseMainSupport.postProcessCamelRegistry() (if given Main implementation
                // decides to do so)

                app.getRests().forEach(r -> {
                    List list = new ArrayList<>();
                    list.add(r);
                    RestsDefinition def = new RestsDefinition();
                    def.setRests(list);
                    setRestCollection(def);
                });

                app.getRouteConfigurations().forEach(rc -> {
                    List list = new ArrayList<>();
                    list.add(rc);
                    RouteConfigurationsDefinition def = new RouteConfigurationsDefinition();
                    def.setRouteConfigurations(list);
                    addConfigurations(def);
                });

                app.getRouteTemplates().forEach(rt -> {
                    List list = new ArrayList<>();
                    list.add(rt);
                    RouteTemplatesDefinition def = new RouteTemplatesDefinition();
                    def.setRouteTemplates(list);
                    setRouteTemplateCollection(def);
                });

                app.getTemplatedRoutes().forEach(tr -> {
                    List list = new ArrayList<>();
                    list.add(tr);
                    TemplatedRoutesDefinition def = new TemplatedRoutesDefinition();
                    def.setTemplatedRoutes(list);
                    setTemplatedRouteCollection(def);
                });

                app.getRoutes().forEach(r -> {
                    List list = new ArrayList<>();
                    list.add(r);
                    RoutesDefinition def = new RoutesDefinition();
                    def.setRoutes(list);
                    addRoutes(def);
                });
            }

            private void addRoutes(RoutesDefinition routes) {
                CamelContextAware.trySetCamelContext(routes, getCamelContext());

                // xml routes must be prepared in the same way java-dsl (via RoutesDefinition)
                // so create a copy and use the fluent builder to add the route
                for (RouteDefinition route : routes.getRoutes()) {
                    getRouteCollection().route(route);
                }
            }

            private void addConfigurations(RouteConfigurationsDefinition configurations) {
                CamelContextAware.trySetCamelContext(configurations, getCamelContext());

                // xml routes must be prepared in the same way java-dsl (via RouteConfigurationDefinition)
                // so create a copy and use the fluent builder to add the route
                for (RouteConfigurationDefinition config : configurations.getRouteConfigurations()) {
                    getRouteConfigurationCollection().routeConfiguration(config);
                }
            }
        };
    }

    @Override
    protected void doStop() throws Exception {
        resourceCache.clear();
        xmlInfoCache.clear();
        camelAppCache.clear();
    }

    private Resource resource(Resource resource) {
        return resourceCache.computeIfAbsent(resource.getLocation(), l -> new CachedResource(resource));
    }

    private XmlStreamInfo xmlInfo(Resource resource) {
        return xmlInfoCache.computeIfAbsent(resource.getLocation(), l -> {
            try {
                // instead of parsing the document NxM times (for each namespace x root element combination),
                // we preparse it using XmlStreamDetector and then parse it fully knowing what's inside.
                // we could even do better, by passing already preparsed information through config file, but
                // it's getting complicated when using multiple files.
                XmlStreamDetector detector = new XmlStreamDetector(resource.getInputStream());
                return detector.information();
            } catch (IOException e) {
                XmlStreamInfo invalid = new XmlStreamInfo();
                invalid.setProblem(e);
                return invalid;
            }
        });
    }

    private void registerBeans(Resource resource, BeansDefinition app) {
        //  - discover and register beans directly with Camel injection
        Set packagesToScan = new LinkedHashSet<>();
        app.getComponentScanning().forEach(cs -> {
            packagesToScan.add(cs.getBasePackage());
        });
        if (!packagesToScan.isEmpty()) {
            Registry registry = getCamelContext().getRegistry();
            if (registry != null) {
                PackageScanClassResolver scanner
                        = getCamelContext().getCamelContextExtension().getContextPlugin(PackageScanClassResolver.class);
                Injector injector = getCamelContext().getInjector();
                if (scanner != null && injector != null) {
                    for (String pkg : packagesToScan) {
                        Set> classes = scanner.findAnnotated(BindToRegistry.class, pkg);
                        for (Class c : classes) {
                            // should:
                            // - call org.apache.camel.spi.CamelBeanPostProcessor.postProcessBeforeInitialization
                            // - call org.apache.camel.spi.CamelBeanPostProcessor.postProcessAfterInitialization
                            // - bind to registry if @org.apache.camel.BindToRegistry is present
                            injector.newInstance(c, true);
                        }
                    }
                }
            }
        }

        // s - register Camel beans directly with Camel injection
        for (RegistryBeanDefinition bean : app.getBeans()) {
            String type = bean.getType();
            String name = bean.getName();
            if (name == null || "".equals(name.trim())) {
                name = type;
            }
            if (type != null && !type.startsWith("#")) {
                type = "#class:" + type;
                try {
                    final Object target = PropertyBindingSupport.resolveBean(getCamelContext(), type);

                    if (bean.getProperties() != null && !bean.getProperties().isEmpty()) {
                        PropertyBindingSupport.setPropertiesOnTarget(getCamelContext(), target, bean.getProperties());
                    }
                    getCamelContext().getRegistry().unbind(name);
                    getCamelContext().getRegistry().bind(name, target);
                } catch (Exception e) {
                    LOG.warn("Problem creating bean {}", type, e);
                }
            }
        }

        // ,  and  elements - all the elements in single BeansDefinition have
        // one parent org.w3c.dom.Document - and this is what we collect from each resource
        if (!app.getSpringBeans().isEmpty()) {
            Document doc = app.getSpringBeans().get(0).getOwnerDocument();
            // bind as Document, to be picked up later - bean id allows nice sorting
            // (can also be single ID - documents will get collected in LinkedHashMap, so we'll be fine)
            String id = String.format("spring-document:%05d:%s", counter.incrementAndGet(), resource.getLocation());
            getCamelContext().getRegistry().bind(id, doc);
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy