com.ibm.soatf.component.soap.builder.SchemaUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.ibm.soatf.component.soap.builder.SchemaUtils.java

Source

/**
 * Copyright (c) 2012-2013 Reficio (TM) - Reestablish your software!. All Rights Reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301  USA
 */
package com.ibm.soatf.component.soap.builder;

import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.xml.namespace.QName;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.xmlbeans.SchemaType;
import org.apache.xmlbeans.SchemaTypeSystem;
import org.apache.xmlbeans.SimpleValue;
import org.apache.xmlbeans.XmlBeans;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlOptions;
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;

/**
 * This class was extracted from the soapUI code base by centeractive ag in October 2011.
 * The main reason behind the extraction was to separate the code that is responsible
 * for the generation of the SOAP messages from the rest of the soapUI's code that is
 * tightly coupled with other modules, such as soapUI's graphical user interface, etc.
 * The goal was to create an open-source java project whose main responsibility is to
 * handle SOAP message generation and SOAP transmission purely on an XML level.
 * <br/>
 * centeractive ag would like to express strong appreciation to SmartBear Software and
 * to the whole team of soapUI's developers for creating soapUI and for releasing its
 * source code under a free and open-source licence. centeractive ag extracted and
 * modifies some parts of the soapUI's code in good faith, making every effort not
 * to impair any existing functionality and to supplement it according to our
 * requirements, applying best practices of software design.
 *
 * Changes done:
 * - changing location in the package structure
 * - removal of dependencies and code parts that are out of scope of SOAP message generation
 * - minor fixes to make the class compile out of soapUI's code base
 * - changing the mechanism using which external resources are loaded
 */

/**
 * XML-Schema related tools
 *
 * @author Ole.Matzura
 */
class SchemaUtils {
    private final static Logger log = LogManager.getLogger(SchemaUtils.class);
    private static Map<String, XmlObject> defaultSchemas = new HashMap<String, XmlObject>();

    public static final boolean STRICT_SCHEMA_TYPES = false;

    static {
        initDefaultSchemas();
    }

    public static InputStream loadResourceAsStream(String resourceName) {
        return ResourceUtils.getResourceAsInputStream(SchemaUtils.class, "/xsds/" + resourceName);
    }

    public static void initDefaultSchemas() {
        try {
            defaultSchemas.clear();
            loadDefaultSchema(loadResourceAsStream("xop.xsd"));
            loadDefaultSchema(loadResourceAsStream("XMLSchema.xsd"));
            loadDefaultSchema(loadResourceAsStream("xml.xsd"));
            loadDefaultSchema(loadResourceAsStream("swaref.xsd"));
            loadDefaultSchema(loadResourceAsStream("xmime200505.xsd"));
            loadDefaultSchema(loadResourceAsStream("xmime200411.xsd"));
            loadDefaultSchema(loadResourceAsStream("soapEnvelope.xsd"));
            loadDefaultSchema(loadResourceAsStream("soapEncoding.xsd"));
            loadDefaultSchema(loadResourceAsStream("soapEnvelope12.xsd"));
            loadDefaultSchema(loadResourceAsStream("soapEncoding12.xsd"));
        } catch (Exception e) {
            throw new SoapBuilderException(e);
        }
    }

    private static void loadDefaultSchema(URL url) throws Exception {
        XmlObject xmlObject = XmlUtils.createXmlObject(url);
        if (!((Document) xmlObject.getDomNode()).getDocumentElement().getNamespaceURI().equals(Constants.XSD_NS))
            return;

        String targetNamespace = getTargetNamespace(xmlObject);

        if (defaultSchemas.containsKey(targetNamespace))
            log.warn("Overriding schema for targetNamespace " + targetNamespace);

        defaultSchemas.put(targetNamespace, xmlObject);

        log.debug("Added default schema from " + url.getPath() + " with targetNamespace " + targetNamespace);
    }

    private static void loadDefaultSchema(InputStream is) throws Exception {
        XmlObject xmlObject = XmlUtils.createXmlObject(is);
        if (!((Document) xmlObject.getDomNode()).getDocumentElement().getNamespaceURI().equals(Constants.XSD_NS))
            return;

        String targetNamespace = getTargetNamespace(xmlObject);

        if (defaultSchemas.containsKey(targetNamespace))
            log.warn("Overriding schema for targetNamespace " + targetNamespace);

        defaultSchemas.put(targetNamespace, xmlObject);

        //log.debug("Added default schema from " + is.getPath() + " with targetNamespace " + targetNamespace);
    }

    public static SchemaTypeSystem loadSchemaTypes(String wsdlUrl, SchemaLoader loader) {
        try {
            log.debug("Loading schema types from [" + wsdlUrl + "]");
            ArrayList<XmlObject> schemas = new ArrayList<XmlObject>(getSchemas(wsdlUrl, loader).values());
            return buildSchemaTypes(schemas);
        } catch (Exception e) {
            throw new SoapBuilderException(e);
        }
    }

    public static SchemaTypeSystem buildSchemaTypes(List<XmlObject> schemas) {
        XmlOptions options = new XmlOptions();
        options.setCompileNoValidation();
        options.setCompileNoPvrRule();
        options.setCompileDownloadUrls();
        options.setCompileNoUpaRule();
        options.setValidateTreatLaxAsSkip();

        for (int c = 0; c < schemas.size(); c++) {
            XmlObject xmlObject = schemas.get(c);
            if (xmlObject == null || !((Document) xmlObject.getDomNode()).getDocumentElement().getNamespaceURI()
                    .equals(Constants.XSD_NS)) {
                schemas.remove(c);
                c--;
            }
        }

        // TODO
        boolean strictSchemaTypes = STRICT_SCHEMA_TYPES;//SoapUI.getSettings().getBoolean( WsdlSettings.STRICT_SCHEMA_TYPES );
        if (!strictSchemaTypes) {
            Set<String> mdefNamespaces = new HashSet<String>();

            for (XmlObject xObj : schemas) {
                mdefNamespaces.add(getTargetNamespace(xObj));
            }

            options.setCompileMdefNamespaces(mdefNamespaces);
        }

        ArrayList<?> errorList = new ArrayList<Object>();
        options.setErrorListener(errorList);

        XmlCursor cursor = null;

        try {
            // remove imports
            for (int c = 0; c < schemas.size(); c++) {
                XmlObject s = schemas.get(c);

                Map<?, ?> map = new HashMap<String, String>();
                cursor = s.newCursor();
                cursor.toStartDoc();
                if (toNextContainer(cursor))
                    cursor.getAllNamespaces(map);
                else
                    log.warn("Can not get namespaces for " + s);

                String tns = getTargetNamespace(s);

                // log.info( "schema for [" + tns + "] contained [" + map.toString()
                // + "] namespaces" );

                if (strictSchemaTypes && defaultSchemas.containsKey(tns)) {
                    schemas.remove(c);
                    c--;
                } else {
                    removeImports(s);
                }

                cursor.dispose();
                cursor = null;
            }

            // schemas.add( soapVersion.getSoapEncodingSchema());
            // schemas.add( soapVersion.getSoapEnvelopeSchema());
            schemas.addAll(defaultSchemas.values());

            SchemaTypeSystem sts = XmlBeans.compileXsd(schemas.toArray(new XmlObject[schemas.size()]),
                    XmlBeans.getBuiltinTypeSystem(), options);

            return sts;
            // return XmlBeans.typeLoaderUnion(new SchemaTypeLoader[] { sts,
            // XmlBeans.getBuiltinTypeSystem() });
        } catch (Exception e) {
            throw new SoapBuilderException(e);
        } finally {
            for (int c = 0; c < errorList.size(); c++) {
                log.warn("Error: " + errorList.get(c));
            }

            if (cursor != null)
                cursor.dispose();
        }
    }

    public static boolean toNextContainer(XmlCursor cursor) {
        while (!cursor.isContainer() && !cursor.isEnddoc())
            cursor.toNextToken();

        return cursor.isContainer();
    }

    public static String getTargetNamespace(XmlObject s) {
        return ((Document) s.getDomNode()).getDocumentElement().getAttribute("targetNamespace");
    }

    public static Map<String, XmlObject> getSchemas(String wsdlUrl, SchemaLoader loader) {
        Map<String, XmlObject> result = new HashMap<String, XmlObject>();
        getSchemas(wsdlUrl, result, loader, null /* , false */);
        return result;
    }

    /**
     * Returns a map mapping urls to corresponding XmlSchema XmlObjects for the
     * specified wsdlUrl
     */
    public static void getSchemas(final String wsdlUrl, Map<String, XmlObject> existing, SchemaLoader loader,
            String tns) {
        if (existing.containsKey(wsdlUrl)) {
            return;
        }

        log.debug("Getting schema " + wsdlUrl);

        ArrayList<?> errorList = new ArrayList<>();

        Map<String, XmlObject> result = new HashMap<>();

        boolean common = false;

        try {
            XmlOptions options = new XmlOptions();
            options.setCompileNoValidation();
            options.setSaveUseOpenFrag();
            options.setErrorListener(errorList);
            options.setSaveSyntheticDocumentElement(new QName(Constants.XSD_NS, "schema"));

            XmlObject xmlObject = loader.loadXmlObject(wsdlUrl, options);
            if (xmlObject == null)
                throw new Exception("Failed to load schema from [" + wsdlUrl + "]");

            Document dom = (Document) xmlObject.getDomNode();
            Node domNode = dom.getDocumentElement();

            // is this an xml schema?
            if (domNode.getLocalName().equals("schema") && Constants.XSD_NS.equals(domNode.getNamespaceURI())) {
                // set targetNamespace (this happens if we are following an include
                // statement)
                if (tns != null) {
                    Element elm = ((Element) domNode);
                    if (!elm.hasAttribute("targetNamespace")) {
                        common = true;
                        elm.setAttribute("targetNamespace", tns);
                    }

                    // check for namespace prefix for targetNamespace
                    NamedNodeMap attributes = elm.getAttributes();
                    int c = 0;
                    for (; c < attributes.getLength(); c++) {
                        Node item = attributes.item(c);
                        if (item.getNodeName().equals("xmlns"))
                            break;

                        if (item.getNodeValue().equals(tns) && item.getNodeName().startsWith("xmlns"))
                            break;
                    }

                    if (c == attributes.getLength())
                        elm.setAttribute("xmlns", tns);
                }

                if (common && !existing.containsKey(wsdlUrl + "@" + tns))
                    result.put(wsdlUrl + "@" + tns, xmlObject);
                else
                    result.put(wsdlUrl, xmlObject);
            } else {
                existing.put(wsdlUrl, null);

                XmlObject[] schemas = xmlObject
                        .selectPath("declare namespace s='" + Constants.XSD_NS + "' .//s:schema");

                for (int i = 0; i < schemas.length; i++) {
                    XmlCursor xmlCursor = schemas[i].newCursor();
                    String xmlText = xmlCursor.getObject().xmlText(options);
                    // schemas[i] = XmlObject.Factory.parse( xmlText, options );
                    schemas[i] = XmlUtils.createXmlObject(xmlText, options);
                    schemas[i].documentProperties().setSourceName(wsdlUrl);

                    result.put(wsdlUrl + "@" + (i + 1), schemas[i]);
                }

                XmlObject[] wsdlImports = xmlObject
                        .selectPath("declare namespace s='" + Constants.WSDL11_NS + "' .//s:import/@location");
                for (int i = 0; i < wsdlImports.length; i++) {
                    String location = ((SimpleValue) wsdlImports[i]).getStringValue();
                    if (location != null) {
                        if (!location.startsWith("file:") && location.indexOf("://") == -1)
                            location = joinRelativeUrl(wsdlUrl, location);

                        getSchemas(location, existing, loader, null);
                    }
                }

                XmlObject[] wadl10Imports = xmlObject.selectPath(
                        "declare namespace s='" + Constants.WADL10_NS + "' .//s:grammars/s:include/@href");
                for (int i = 0; i < wadl10Imports.length; i++) {
                    String location = ((SimpleValue) wadl10Imports[i]).getStringValue();
                    if (location != null) {
                        if (!location.startsWith("file:") && location.indexOf("://") == -1)
                            location = joinRelativeUrl(wsdlUrl, location);

                        getSchemas(location, existing, loader, null);
                    }
                }

                XmlObject[] wadlImports = xmlObject.selectPath(
                        "declare namespace s='" + Constants.WADL11_NS + "' .//s:grammars/s:include/@href");
                for (int i = 0; i < wadlImports.length; i++) {
                    String location = ((SimpleValue) wadlImports[i]).getStringValue();
                    if (location != null) {
                        if (!location.startsWith("file:") && location.indexOf("://") == -1)
                            location = joinRelativeUrl(wsdlUrl, location);

                        getSchemas(location, existing, loader, null);
                    }
                }
            }

            existing.putAll(result);

            XmlObject[] schemas = result.values().toArray(new XmlObject[result.size()]);
            for (int c = 0; c < schemas.length; c++) {
                xmlObject = schemas[c];

                XmlObject[] schemaImports = xmlObject
                        .selectPath("declare namespace s='" + Constants.XSD_NS + "' .//s:import/@schemaLocation");
                for (int i = 0; i < schemaImports.length; i++) {
                    String location = ((SimpleValue) schemaImports[i]).getStringValue();
                    Element elm = ((Attr) schemaImports[i].getDomNode()).getOwnerElement();

                    if (location != null && !defaultSchemas.containsKey(elm.getAttribute("namespace"))) {
                        if (!location.startsWith("file:") && location.indexOf("://") == -1)
                            location = joinRelativeUrl(wsdlUrl, location);

                        getSchemas(location, existing, loader, null);
                    }
                }

                XmlObject[] schemaIncludes = xmlObject
                        .selectPath("declare namespace s='" + Constants.XSD_NS + "' .//s:include/@schemaLocation");
                for (int i = 0; i < schemaIncludes.length; i++) {
                    String location = ((SimpleValue) schemaIncludes[i]).getStringValue();
                    if (location != null) {
                        String targetNS = getTargetNamespace(xmlObject);

                        if (!location.startsWith("file:") && location.indexOf("://") == -1)
                            location = joinRelativeUrl(wsdlUrl, location);

                        getSchemas(location, existing, loader, targetNS);
                    }
                }
            }
        } catch (Exception e) {
            throw new SoapBuilderException(e);
        }
    }

    public static void getDefinitionParts(String origWsdlUrl, Map<String, XmlObject> existing, SchemaLoader loader)
            throws Exception {
        String wsdlUrl = origWsdlUrl;
        if (existing.containsKey(wsdlUrl))
            return;

        XmlObject xmlObject = loader.loadXmlObject(wsdlUrl, null);
        existing.put(wsdlUrl, xmlObject);
        // wsdlUrl = loader.getBaseURI();

        selectDefinitionParts(wsdlUrl, existing, loader, xmlObject,
                "declare namespace s='" + Constants.WSDL11_NS + "' .//s:import/@location");
        selectDefinitionParts(wsdlUrl, existing, loader, xmlObject,
                "declare namespace s='" + Constants.WADL10_NS + "' .//s:grammars/s:include/@href");
        selectDefinitionParts(wsdlUrl, existing, loader, xmlObject,
                "declare namespace s='" + Constants.WADL11_NS + "' .//s:grammars/s:include/@href");
        selectDefinitionParts(wsdlUrl, existing, loader, xmlObject,
                "declare namespace s='" + Constants.XSD_NS + "' .//s:import/@schemaLocation");
        selectDefinitionParts(wsdlUrl, existing, loader, xmlObject,
                "declare namespace s='" + Constants.XSD_NS + "' .//s:include/@schemaLocation");
    }

    public static String joinRelativeUrl(String baseUrl, String url) {
        if (baseUrl.indexOf('?') > 0)
            baseUrl = baseUrl.substring(0, baseUrl.indexOf('?'));

        boolean isWindowsUrl = baseUrl.indexOf('\\') >= 0;
        boolean isUsedInUnix = File.separatorChar == '/';

        if (isUsedInUnix && isWindowsUrl) {
            baseUrl = baseUrl.replace('\\', '/');
            url = url.replace('\\', '/');
        }

        boolean isFile = baseUrl.startsWith("file:");

        int ix = baseUrl.lastIndexOf('\\');
        if (ix == -1)
            ix = baseUrl.lastIndexOf('/');

        // absolute?
        if (url.startsWith("/") && !isFile) {
            ix = baseUrl.indexOf("/", baseUrl.indexOf("//") + 2);
            return baseUrl.substring(0, ix) + url;
        }

        // remove leading "./"
        while (url.startsWith(".\\") || url.startsWith("./"))
            url = url.substring(2);

        // remove leading "../"
        while (url.startsWith("../") || url.startsWith("..\\")) {
            int ix2 = baseUrl.lastIndexOf('\\', ix - 1);
            if (ix2 == -1)
                ix2 = baseUrl.lastIndexOf('/', ix - 1);
            if (ix2 == -1)
                break;

            baseUrl = baseUrl.substring(0, ix2 + 1);
            ix = ix2;

            url = url.substring(3);
        }

        // remove "/./"
        while (url.indexOf("/./") != -1 || url.indexOf("\\.\\") != -1) {
            int ix2 = url.indexOf("/./");
            if (ix2 == -1)
                ix2 = url.indexOf("\\.\\");

            url = url.substring(0, ix2) + url.substring(ix2 + 2);
        }

        // remove "/../"
        while (url.indexOf("/../") != -1 || url.indexOf("\\..\\") != -1) {
            int ix2 = -1;

            int ix3 = url.indexOf("/../");
            if (ix3 == -1) {
                ix3 = url.indexOf("\\..\\");
                ix2 = url.lastIndexOf('\\', ix3 - 1);
            } else {
                ix2 = url.lastIndexOf('/', ix3 - 1);
            }

            if (ix2 == -1)
                break;

            url = url.substring(0, ix2) + url.substring(ix3 + 3);
        }

        String result = baseUrl.substring(0, ix + 1) + url;
        if (isFile)
            result = result.replace('/', File.separatorChar);

        return result;
    }

    private static void selectDefinitionParts(String wsdlUrl, Map<String, XmlObject> existing, SchemaLoader loader,
            XmlObject xmlObject, String path) throws Exception {
        XmlObject[] wsdlImports = xmlObject.selectPath(path);
        for (int i = 0; i < wsdlImports.length; i++) {
            String location = ((SimpleValue) wsdlImports[i]).getStringValue();
            if (location != null) {
                if (StringUtils.isNotBlank(location)) {
                    if (!location.startsWith("file:") && location.indexOf("://") == -1)
                        location = joinRelativeUrl(wsdlUrl, location);

                    getDefinitionParts(location, existing, loader);
                } else {
                    Node domNode = ((Attr) wsdlImports[i].getDomNode()).getOwnerElement();
                    domNode.getParentNode().removeChild(domNode);
                }
            }
        }
    }

    /**
     * Extracts namespaces - used in tool integrations for mapping..
     */
    public static Collection<String> extractNamespaces(SchemaTypeSystem schemaTypes, boolean removeDefault) {
        Set<String> namespaces = new HashSet<String>();
        SchemaType[] globalTypes = schemaTypes.globalTypes();
        for (int c = 0; c < globalTypes.length; c++) {
            namespaces.add(globalTypes[c].getName().getNamespaceURI());
        }

        if (removeDefault) {
            namespaces.removeAll(defaultSchemas.keySet());
            namespaces.remove(Constants.SOAP11_ENVELOPE_NS);
            namespaces.remove(Constants.SOAP_ENCODING_NS);
        }

        return namespaces;
    }

    /**
     * Used when creating a TypeSystem from a complete collection of
     * SchemaDocuments so that referenced types are not downloaded (again)
     */
    public static void removeImports(XmlObject xmlObject) throws XmlException {
        XmlObject[] imports = xmlObject.selectPath("declare namespace s='" + Constants.XSD_NS + "' .//s:import");

        for (int c = 0; c < imports.length; c++) {
            XmlCursor cursor = imports[c].newCursor();
            cursor.removeXml();
            cursor.dispose();
        }

        XmlObject[] includes = xmlObject.selectPath("declare namespace s='" + Constants.XSD_NS + "' .//s:include");

        for (int c = 0; c < includes.length; c++) {
            XmlCursor cursor = includes[c].newCursor();
            cursor.removeXml();
            cursor.dispose();
        }
    }

}