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

org.apache.camel.xml.jaxb.JaxbHelper Maven / Gradle / Ivy

/*
 * 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.xml.jaxb;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import jakarta.xml.bind.Binder;
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.Unmarshaller;

import org.w3c.dom.Attr;
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.camel.CamelContext;
import org.apache.camel.Expression;
import org.apache.camel.NamedNode;
import org.apache.camel.TypeConversionException;
import org.apache.camel.converter.jaxp.XmlConverter;
import org.apache.camel.model.ExpressionNode;
import org.apache.camel.model.FromDefinition;
import org.apache.camel.model.OptionalIdentifiedDefinition;
import org.apache.camel.model.OutputDefinition;
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.SendDefinition;
import org.apache.camel.model.TemplatedRouteDefinition;
import org.apache.camel.model.TemplatedRoutesDefinition;
import org.apache.camel.model.ToDynamicDefinition;
import org.apache.camel.model.language.ExpressionDefinition;
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.NamespaceAware;
import org.apache.camel.support.PluginHelper;
import org.apache.camel.util.KeyValueHolder;
import org.apache.camel.util.URISupport;

import static org.apache.camel.model.ProcessorDefinitionHelper.filterTypeInOutputs;

public final class JaxbHelper {
    private static final String CAMEL_NS = "http://camel.apache.org/schema/spring";

    private JaxbHelper() {
    }

    public static JAXBContext getJAXBContext(CamelContext context) throws Exception {
        return (JAXBContext) PluginHelper.getModelJAXBContextFactory(context).newJAXBContext();
    }

    /**
     * Extract all XML namespaces from the expressions in the route
     *
     * @param route      the route
     * @param namespaces the map of namespaces to add discovered XML namespaces into
     */
    public static void extractNamespaces(RouteDefinition route, Map namespaces) {
        Collection col = filterTypeInOutputs(route.getOutputs(), ExpressionNode.class);
        for (ExpressionNode en : col) {
            NamespaceAware na = getNamespaceAwareFromExpression(en);
            if (na != null) {
                Map map = na.getNamespaces();
                if (map != null && !map.isEmpty()) {
                    namespaces.putAll(map);
                }
            }
        }
    }

    /**
     * Extract all source locations from the route
     *
     * @param route     the route
     * @param locations the map of source locations for EIPs in the route
     */
    public static void extractSourceLocations(RouteDefinition route, Map> locations) {
        // input
        String id = route.getRouteId();
        String loc = route.getInput().getLocation();
        int line = route.getInput().getLineNumber();
        if (id != null && line != -1) {
            locations.put(id, new KeyValueHolder<>(line, loc));
        }
        // and then walk all nodes in the route graphs
        for (var def : filterTypeInOutputs(route.getOutputs(), OptionalIdentifiedDefinition.class)) {
            id = def.getId();
            loc = def.getLocation();
            line = def.getLineNumber();
            if (id != null && line != -1) {
                locations.put(id, new KeyValueHolder<>(line, loc));
            }
        }
    }

    /**
     * If the route has been built with endpoint-dsl, then the model will not have uri set which then cannot be included
     * in the JAXB model dump
     */
    public static void resolveEndpointDslUris(RouteDefinition route) {
        FromDefinition from = route.getInput();
        if (from != null && from.getEndpointConsumerBuilder() != null) {
            String uri = from.getEndpointConsumerBuilder().getRawUri();
            from.setUri(uri);
        }
        Collection col = filterTypeInOutputs(route.getOutputs(), SendDefinition.class);
        for (SendDefinition to : col) {
            if (to.getEndpointProducerBuilder() != null) {
                String uri = to.getEndpointProducerBuilder().getRawUri();
                to.setUri(uri);
            }
        }
        Collection col2 = filterTypeInOutputs(route.getOutputs(), ToDynamicDefinition.class);
        for (ToDynamicDefinition to : col2) {
            if (to.getEndpointProducerBuilder() != null) {
                String uri = to.getEndpointProducerBuilder().getRawUri();
                to.setUri(uri);
            }
        }
    }

    public static NamespaceAware getNamespaceAwareFromExpression(ExpressionNode expressionNode) {
        ExpressionDefinition ed = expressionNode.getExpression();

        NamespaceAware na = null;
        Expression exp = ed.getExpressionValue();
        if (exp instanceof NamespaceAware namespaceAware) {
            na = namespaceAware;
        } else if (ed instanceof NamespaceAware namespaceAware) {
            na = namespaceAware;
        }

        return na;
    }

    /**
     * Creates a new {@link XmlConverter}
     *
     * @param  context CamelContext if provided
     * @return         a new XmlConverter instance
     */
    public static XmlConverter newXmlConverter(CamelContext context) {
        return new XmlConverter();
    }

    /**
     * Extract all XML namespaces from the root element in a DOM Document
     *
     * @param document   the DOM document
     * @param namespaces the map of namespaces to add new found XML namespaces
     */
    public static void extractNamespaces(Document document, Map namespaces) {
        NamedNodeMap attributes = document.getDocumentElement().getAttributes();
        for (int i = 0; i < attributes.getLength(); i++) {
            Node item = attributes.item(i);
            String nsPrefix = item.getNodeName();
            if (nsPrefix.startsWith("xmlns")) {
                String nsValue = item.getNodeValue();
                String[] nsParts = nsPrefix.split(":");
                if (nsParts.length == 1) {
                    namespaces.put(nsParts[0], nsValue);
                } else if (nsParts.length == 2) {
                    namespaces.put(nsParts[1], nsValue);
                } else {
                    // Fallback on adding the namespace prefix as we find it
                    namespaces.put(nsPrefix, nsValue);
                }
            }
        }
    }

    /**
     * Extract all source locations from the XML routes
     *
     * @param element   the XML element
     * @param locations the map of source locations for EIPs in the route
     */
    public static void extractSourceLocations(Element element, Map> locations) {
        NamedNodeMap attributes = element.getAttributes();
        String id = null;
        Integer sourceLineNumber = null;
        String sourceLocation = null;
        for (int i = 0; i < attributes.getLength(); i++) {
            Node item = attributes.item(i);
            String name = item.getNodeName();
            if ("id".equals(name)) {
                id = item.getNodeValue();
            } else if ("sourceLineNumber".equals(name)) {
                sourceLineNumber = Integer.parseInt(item.getNodeValue());
            } else if ("sourceLocation".equals(name)) {
                sourceLocation = item.getNodeValue();
            }
        }
        if (id != null && sourceLineNumber != null && sourceLocation != null) {
            locations.put(id, new KeyValueHolder<>(sourceLineNumber, sourceLocation));
        }

        final NodeList children = element.getChildNodes();
        for (int index = 0; index < children.getLength(); index++) {
            final Node child = children.item(index);
            if (child.getNodeType() == Node.ELEMENT_NODE) {
                extractSourceLocations((Element) child, locations);
            }
        }
    }

    public static void applyNamespaces(RouteDefinition route, Map namespaces) {
        Collection col = filterTypeInOutputs(route.getOutputs(), ExpressionNode.class);
        for (ExpressionNode en : col) {
            NamespaceAware na = getNamespaceAwareFromExpression(en);
            if (na != null) {
                na.setNamespaces(namespaces);
            }
        }
    }

    public static void applyNamespaces(RouteConfigurationDefinition config, Map namespaces) {
        List> defs = new ArrayList<>();
        defs.addAll(config.getIntercepts());
        defs.addAll(config.getInterceptFroms());
        defs.addAll(config.getInterceptSendTos());
        defs.addAll(config.getOnCompletions());
        defs.addAll(config.getOnExceptions());
        for (OutputDefinition def : defs) {
            Collection col = filterTypeInOutputs(def.getOutputs(), ExpressionNode.class);
            for (ExpressionNode en : col) {
                NamespaceAware na = getNamespaceAwareFromExpression(en);
                if (na != null) {
                    na.setNamespaces(namespaces);
                }
            }
        }
    }

    public static void applySourceLocations(RouteDefinition route, Map> locations) {
        KeyValueHolder kv = locations.get(route.getRouteId());
        if (kv != null && route.getInput() != null) {
            route.getInput().setLineNumber(kv.getKey());
            route.getInput().setLocation(kv.getValue());
        }

        Collection def
                = filterTypeInOutputs(route.getOutputs(), OptionalIdentifiedDefinition.class);
        for (OptionalIdentifiedDefinition out : def) {
            kv = locations.get(out.getId());
            if (kv != null) {
                out.setLineNumber(kv.getKey());
                out.setLocation(kv.getValue());
            }
        }
    }

    public static  T modelToXml(CamelContext context, String xml, Class type) throws Exception {
        JAXBContext jaxbContext = getJAXBContext(context);

        XmlConverter xmlConverter = newXmlConverter(context);
        Document dom;
        try {
            dom = xmlConverter.toDOMDocument(xml, null);
        } catch (Exception e) {
            throw new TypeConversionException(xml, Document.class, e);
        }
        if (dom == null) {
            throw new IllegalArgumentException("InputStream and XML is both null");
        }

        Map> locations = new HashMap<>();
        if (context.isDebugging()) {
            extractSourceLocations(dom.getDocumentElement(), locations);
        }
        Map namespaces = new LinkedHashMap<>();
        extractNamespaces(dom, namespaces);

        Binder binder = jaxbContext.createBinder();
        Object result = binder.unmarshal(dom);

        if (result == null) {
            throw new JAXBException("Cannot unmarshal to " + type + " using JAXB");
        }

        // Restore namespaces to anything that's NamespaceAware
        if (result instanceof RouteTemplatesDefinition routeTemplatesDefinition) {
            List templates = routeTemplatesDefinition.getRouteTemplates();
            for (RouteTemplateDefinition template : templates) {
                RouteDefinition route = template.getRoute();
                applyNamespaces(route, namespaces);
                if (!locations.isEmpty()) {
                    applySourceLocations(route, locations);
                }
                resolveEndpointDslUris(route);
            }
        } else if (result instanceof RouteTemplateDefinition template) {
            RouteDefinition route = template.getRoute();
            applyNamespaces(route, namespaces);
            if (!locations.isEmpty()) {
                applySourceLocations(route, locations);
            }
            resolveEndpointDslUris(route);
        } else if (result instanceof RoutesDefinition routesDefinition) {
            List routes = routesDefinition.getRoutes();
            for (RouteDefinition route : routes) {
                applyNamespaces(route, namespaces);
                if (!locations.isEmpty()) {
                    applySourceLocations(route, locations);
                }
                resolveEndpointDslUris(route);
            }
        } else if (result instanceof RouteDefinition route) {
            applyNamespaces(route, namespaces);
            if (!locations.isEmpty()) {
                applySourceLocations(route, locations);
            }
            resolveEndpointDslUris(route);
        }

        return type.cast(result);
    }

    public static RoutesDefinition loadRoutesDefinition(CamelContext context, InputStream inputStream) throws Exception {
        XmlConverter xmlConverter = newXmlConverter(context);
        Document dom = xmlConverter.toDOMDocument(inputStream, null);
        removeNoiseFromUris(dom.getDocumentElement());

        JAXBContext jaxbContext = getJAXBContext(context);

        Map namespaces = doExtractNamespaces(dom);

        Binder binder = jaxbContext.createBinder();
        Object result = binder.unmarshal(dom);

        if (result == null) {
            throw new JAXBException("Cannot unmarshal to RoutesDefinition using JAXB");
        }

        // can either be routes or a single route
        RoutesDefinition answer;
        if (result instanceof RouteDefinition route) {
            answer = new RoutesDefinition();
            applyNamespaces(route, namespaces);
            answer.getRoutes().add(route);
        } else if (result instanceof RoutesDefinition routesDefinition) {
            answer = routesDefinition;
            for (RouteDefinition route : answer.getRoutes()) {
                applyNamespaces(route, namespaces);
            }
        } else {
            // ignore not supported type
            return null;
        }

        return answer;
    }

    public static RouteConfigurationsDefinition loadRouteConfigurationsDefinition(CamelContext context, InputStream inputStream)
            throws Exception {
        XmlConverter xmlConverter = newXmlConverter(context);
        Document dom = xmlConverter.toDOMDocument(inputStream, null);
        removeNoiseFromUris(dom.getDocumentElement());

        JAXBContext jaxbContext = getJAXBContext(context);

        Map namespaces = doExtractNamespaces(dom);

        Binder binder = jaxbContext.createBinder();
        Object result = binder.unmarshal(dom);

        if (result == null) {
            throw new JAXBException("Cannot unmarshal to RouteConfigurationsDefinition using JAXB");
        }

        // can either be routes or a single route
        RouteConfigurationsDefinition answer;
        if (result instanceof RouteConfigurationDefinition config) {
            answer = new RouteConfigurationsDefinition();
            applyNamespaces(config, namespaces);
            answer.getRouteConfigurations().add(config);
        } else if (result instanceof RouteConfigurationsDefinition routeConfigurationsDefinition) {
            answer = routeConfigurationsDefinition;
            for (RouteConfigurationDefinition config : answer.getRouteConfigurations()) {
                applyNamespaces(config, namespaces);
            }
        } else {
            // ignore not supported type
            return null;
        }

        return answer;
    }

    public static RouteTemplatesDefinition loadRouteTemplatesDefinition(CamelContext context, InputStream inputStream)
            throws Exception {
        XmlConverter xmlConverter = newXmlConverter(context);
        Document dom = xmlConverter.toDOMDocument(inputStream, null);
        removeNoiseFromUris(dom.getDocumentElement());

        JAXBContext jaxbContext = getJAXBContext(context);

        Map namespaces = doExtractNamespaces(dom);

        Binder binder = jaxbContext.createBinder();
        Object result = binder.unmarshal(dom);

        if (result == null) {
            throw new JAXBException("Cannot unmarshal to RouteTemplatesDefinition using JAXB");
        }

        // can either be routes or a single route
        RouteTemplatesDefinition answer;
        if (result instanceof RouteTemplateDefinition route) {
            answer = new RouteTemplatesDefinition();
            applyNamespaces(route.getRoute(), namespaces);
            answer.getRouteTemplates().add(route);
        } else if (result instanceof RouteTemplatesDefinition routeTemplatesDefinition) {
            answer = routeTemplatesDefinition;
            for (RouteTemplateDefinition route : answer.getRouteTemplates()) {
                applyNamespaces(route.getRoute(), namespaces);
            }
        } else {
            // ignore not supported type
            return null;
        }

        return answer;
    }

    private static Map doExtractNamespaces(Document dom) {
        Map namespaces = new LinkedHashMap<>();
        extractNamespaces(dom, namespaces);
        if (!namespaces.containsValue(CAMEL_NS)) {
            addNamespaceToDom(dom);
        }
        return namespaces;
    }

    /**
     * Un-marshals the content of the input stream to an instance of {@link TemplatedRoutesDefinition}.
     *
     * @param  context     the Camel context from which the JAXBContext is extracted
     * @param  inputStream the input stream to unmarshal
     * @return             the content unmarshalled as a {@link TemplatedRoutesDefinition}.
     * @throws Exception   if an exception occurs while unmarshalling
     */
    public static TemplatedRoutesDefinition loadTemplatedRoutesDefinition(CamelContext context, InputStream inputStream)
            throws Exception {
        XmlConverter xmlConverter = newXmlConverter(context);
        Document dom = xmlConverter.toDOMDocument(inputStream, null);
        removeNoiseFromUris(dom.getDocumentElement());

        JAXBContext jaxbContext = getJAXBContext(context);

        doExtractNamespaces(dom);

        Binder binder = jaxbContext.createBinder();
        Object result = binder.unmarshal(dom);

        if (result == null) {
            throw new JAXBException("Cannot unmarshal to TemplatedRoutesDefinition using JAXB");
        }

        // can either be routes or a single route
        TemplatedRoutesDefinition answer;
        if (result instanceof TemplatedRouteDefinition templatedRoute) {
            answer = new TemplatedRoutesDefinition();
            answer.getTemplatedRoutes().add(templatedRoute);
        } else if (result instanceof TemplatedRoutesDefinition templatedRoutesDefinition) {
            answer = templatedRoutesDefinition;
        } else {
            // ignore not supported type
            return null;
        }

        return answer;
    }

    private static void addNamespaceToDom(Document dom) {
        // Add the namespace URI to all elements
        Element root = dom.getDocumentElement();
        renameElementWithNamespace(dom, root, CAMEL_NS);
    }

    private static void renameElementWithNamespace(Document doc, Element elem, String camelNs) {
        doc.renameNode(elem, camelNs, elem.getLocalName());
        for (Node child = elem.getFirstChild(); child != null; child = child.getNextSibling()) {
            if (child.getNodeType() == Node.ELEMENT_NODE) {
                renameElementWithNamespace(doc, (Element) child, camelNs);
            }
        }
    }

    public static RestsDefinition loadRestsDefinition(CamelContext context, InputStream inputStream) throws Exception {
        // load routes using JAXB
        Document dom = newXmlConverter(context).toDOMDocument(inputStream, null);
        removeNoiseFromUris(dom.getDocumentElement());

        if (!CAMEL_NS.equals(dom.getDocumentElement().getNamespaceURI())) {
            addNamespaceToDom(dom);
        }
        Unmarshaller unmarshaller = getJAXBContext(context).createUnmarshaller();
        Object result = unmarshaller.unmarshal(dom);

        if (result == null) {
            throw new IOException("Cannot unmarshal to rests using JAXB from input stream: " + inputStream);
        }

        // can either be routes or a single route
        RestsDefinition answer;
        if (result instanceof RestDefinition rest) {
            answer = new RestsDefinition();
            answer.getRests().add(rest);
        } else if (result instanceof RestsDefinition restsDefinition) {
            answer = restsDefinition;
        } else {
            // ignore not supported type
            return null;
        }

        return answer;
    }

    public static RestConfigurationDefinition loadRestConfigurationDefinition(CamelContext context, InputStream inputStream)
            throws Exception {
        // load rest configuration using JAXB
        Document dom = newXmlConverter(context).toDOMDocument(inputStream, null);

        if (!CAMEL_NS.equals(dom.getDocumentElement().getNamespaceURI())) {
            addNamespaceToDom(dom);
        }
        Unmarshaller unmarshaller = getJAXBContext(context).createUnmarshaller();
        Object result = unmarshaller.unmarshal(dom);

        if (result == null) {
            throw new IOException("Cannot unmarshal to rest configuration using JAXB from input stream: " + inputStream);
        }

        if (result instanceof RestConfigurationDefinition restConfigurationDefinition) {
            return restConfigurationDefinition;
        } else {
            // ignore not supported type
            return null;
        }
    }

    private static void removeNoiseFromUris(Element element) {
        final NamedNodeMap attrs = element.getAttributes();

        for (int index = 0; index < attrs.getLength(); index++) {
            final Attr attr = (Attr) attrs.item(index);
            final String attName = attr.getName();

            if (attName.equals("uri") || attName.endsWith("Uri")) {
                attr.setValue(URISupport.removeNoiseFromUri(attr.getValue()));
            }
        }

        final NodeList children = element.getChildNodes();

        for (int index = 0; index < children.getLength(); index++) {
            final Node child = children.item(index);

            if (child.getNodeType() == Node.ELEMENT_NODE) {
                removeNoiseFromUris((Element) child);
            }
        }
    }

    public static void removeAutoAssignedIds(Element element) {
        final NamedNodeMap attrs = element.getAttributes();

        Attr id = null;
        Attr customId = null;
        for (int index = 0; index < attrs.getLength(); index++) {
            final Attr attr = (Attr) attrs.item(index);
            final String attName = attr.getName();

            if (attName.equals("id")) {
                id = attr;
            } else if (attName.equals("customId")) {
                customId = attr;
            }
        }

        // remove auto-assigned id
        if (id != null && customId == null) {
            attrs.removeNamedItem("id");
        }
        // remove customId as its noisy
        if (customId != null) {
            attrs.removeNamedItem("customId");
        }

        final NodeList children = element.getChildNodes();
        for (int index = 0; index < children.getLength(); index++) {
            final Node child = children.item(index);
            if (child.getNodeType() == Node.ELEMENT_NODE) {
                removeAutoAssignedIds((Element) child);
            }
        }
    }

    public static void enrichLocations(Node node, Map> locations) {
        if (node instanceof Element el) {
            // from should grab it from parent (route)
            String id = el.getAttribute("id");
            if ("from".equals(el.getNodeName())) {
                Node parent = el.getParentNode();
                if (parent instanceof Element parentElement) {
                    id = parentElement.getAttribute("id");
                }
            }
            var loc = locations.get(id);
            if (loc != null) {
                el.setAttribute("sourceLineNumber", loc.getKey().toString());
                el.setAttribute("sourceLocation", loc.getValue());
            }
        }
        if (node.hasChildNodes()) {
            for (int i = 0; i < node.getChildNodes().getLength(); i++) {
                Node child = node.getChildNodes().item(i);
                enrichLocations(child, locations);
            }
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy