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.8.1
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.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.BeanFactoryDefinition;
import org.apache.camel.model.BeanModelHelper;
import org.apache.camel.model.Model;
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.rest.RestConfigurationDefinition;
import org.apache.camel.model.rest.RestDefinition;
import org.apache.camel.model.rest.RestsDefinition;
import org.apache.camel.spi.Resource;
import org.apache.camel.spi.annotations.RoutesLoader;
import org.apache.camel.support.CachedResource;
import org.apache.camel.support.scan.PackageScanHelper;
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";

    private final Map preparseDone = new ConcurrentHashMap<>();
    private final Map resourceCache = new ConcurrentHashMap<>();
    private final Map xmlInfoCache = new ConcurrentHashMap<>();
    private final Map camelAppCache = new ConcurrentHashMap<>();
    private final List> delayedRegistrations = new ArrayList<>();

    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
        if (preparseDone.getOrDefault(resource.getLocation(), false)) {
            return;
        }
        XmlStreamInfo xmlInfo = xmlInfo(resource);
        if (xmlInfo.isValid()) {
            String root = xmlInfo.getRootElementName();
            if ("beans".equals(root) || "blueprint".equals(root) || "camel".equals(root)) {
                new XmlModelParser(resource, xmlInfo.getRootElementNamespace())
                        .parseBeansDefinition()
                        .ifPresent(bd -> {
                            registerBeans(resource, bd);
                            camelAppCache.put(resource.getLocation(), bd);
                        });
            }
        }
        preparseDone.put(resource.getLocation(), true);
    }

    @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();
                try {
                    switch (xmlInfo.getRootElementName()) {
                        case "beans", "blueprint", "camel" -> {
                            BeansDefinition def = camelAppCache.get(resourceLocation);
                            if (def != null) {
                                configureCamel(def);
                            } else {
                                new XmlModelParser(resource, xmlInfo.getRootElementNamespace())
                                        .parseBeansDefinition()
                                        .ifPresent(this::configureCamel);
                            }
                        }
                        case "routeTemplate", "routeTemplates" ->
                            new XmlModelParser(resource, xmlInfo.getRootElementNamespace())
                                    .parseRouteTemplatesDefinition()
                                    .ifPresent(this::setRouteTemplateCollection);
                        case "templatedRoutes", "templatedRoute" ->
                            new XmlModelParser(resource, xmlInfo.getRootElementNamespace())
                                    .parseTemplatedRoutesDefinition()
                                    .ifPresent(this::setTemplatedRouteCollection);
                        case "rests", "rest" -> new XmlModelParser(resource, xmlInfo.getRootElementNamespace())
                                .parseRestsDefinition()
                                .ifPresent(this::setRestCollection);
                        case "routes", "route" -> new XmlModelParser(resource, xmlInfo.getRootElementNamespace())
                                .parseRoutesDefinition()
                                .ifPresent(this::addRoutes);
                        default -> {
                        }
                    }
                } finally {
                    // 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);
                    preparseDone.remove(resourceLocation);
                }
            }

            @Override
            public void configuration() throws Exception {
                switch (xmlInfo.getRootElementName()) {
                    // load any route configuration before that may be nested under camel/spring/blueprint root tag
                    case "beans", "blueprint", "camel", "routeConfigurations", "routeConfiguration": {
                        BeansDefinition bp = camelAppCache.get(input.getLocation());
                        if (bp != null) {
                            bp.getRouteConfigurations().forEach(rc -> {
                                rc.setResource(getResource());
                                List list = new ArrayList<>();
                                list.add(rc);
                                RouteConfigurationsDefinition def = new RouteConfigurationsDefinition();
                                def.setResource(getResource());
                                def.setRouteConfigurations(list);
                                addConfigurations(def);
                            });
                            // remove the configurations we have added
                            bp.getRouteConfigurations().clear();
                        }
                        new XmlModelParser(resource, xmlInfo.getRootElementNamespace())
                                .parseRouteConfigurationsDefinition()
                                .ifPresent(this::addConfigurations);
                    }
                }
            }

            private void configureCamel(BeansDefinition app) {
                if (!delayedRegistrations.isEmpty()) {
                    // some of the beans were not available yet, so we have to try register them now
                    for (BeanFactoryDefinition def : delayedRegistrations) {
                        def.setResource(getResource());
                        registerBeanDefinition(def, false);
                    }
                    delayedRegistrations.clear();
                }

                // 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)

                if (app.getRestConfigurations().size() > 1) {
                    throw new RuntimeException("There should only be one ");
                }
                if (app.getRestConfigurations().size() == 1) {
                    RestConfigurationDefinition config = app.getRestConfigurations().get(0);
                    try {
                        config.asRestConfiguration(getCamelContext(), getCamelContext().getRestConfiguration());
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }

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

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

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

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

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

            private void addRoutes(RoutesDefinition routes) {
                CamelContextAware.trySetCamelContext(getRouteCollection(), 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);
                }
            }
        };
    }

    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());
        });
        PackageScanHelper.registerBeans(getCamelContext(), packagesToScan);

        // s - register Camel beans directly with Camel injection
        for (BeanFactoryDefinition def : app.getBeans()) {
            def.setResource(resource);
            registerBeanDefinition(def, true);
        }

        // ,  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("camel-xml-io-dsl-spring-xml:%05d:%s", counter.incrementAndGet(), resource.getLocation());
            getCamelContext().getRegistry().bind(id, doc);
        }

        //  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.getBlueprintBeans().isEmpty()) {
            Document doc = app.getBlueprintBeans().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("camel-xml-io-dsl-blueprint-xml:%05d:%s", counter.incrementAndGet(),
                    resource.getLocation());
            getCamelContext().getRegistry().bind(id, doc);
        }
    }

    /**
     * Try to instantiate bean from the definition. Depending on the stage ({@link #preParseRoute} or
     * {@link #doLoadRouteBuilder}), a failure may lead to delayed registration.
     */
    private void registerBeanDefinition(BeanFactoryDefinition def, boolean delayIfFailed) {
        String name = def.getName();
        String type = def.getType();
        try {
            Object target = BeanModelHelper.newInstance(def, getCamelContext());
            bindBean(def, name, target);
        } catch (Exception e) {
            if (delayIfFailed) {
                delayedRegistrations.add(def);
            } else {
                String msg
                        = name != null ? "Error creating bean: " + name + " of type: " + type : "Error creating bean: " + type;
                throw new RuntimeException(msg, e);
            }
        }
    }

    protected void bindBean(BeanFactoryDefinition def, String name, Object target) throws Exception {
        // unbind in case we reload
        getCamelContext().getRegistry().unbind(name);
        getCamelContext().getRegistry().bind(name, target, def.getInitMethod(), def.getDestroyMethod());

        // register bean in model
        Model model = getCamelContext().getCamelContextExtension().getContextPlugin(Model.class);
        model.addCustomBean(def);
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy