org.wso2.carbon.apimgt.impl.soaptorest.SequenceGenerator.java Source code

Java tutorial

Introduction

Here is the source code for org.wso2.carbon.apimgt.impl.soaptorest.SequenceGenerator.java

Source

/*
 * Copyright (c) 2018, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * WSO2 Inc. 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.wso2.carbon.apimgt.impl.soaptorest;

import com.fasterxml.jackson.databind.module.SimpleModule;
import io.swagger.inflector.examples.ExampleBuilder;
import io.swagger.inflector.examples.models.Example;
import io.swagger.inflector.processors.JsonNodeExampleSerializer;
import io.swagger.models.HttpMethod;
import io.swagger.models.Model;
import io.swagger.models.Operation;
import io.swagger.models.Path;
import io.swagger.models.RefModel;
import io.swagger.models.Swagger;
import io.swagger.models.parameters.BodyParameter;
import io.swagger.models.parameters.Parameter;
import io.swagger.models.parameters.QueryParameter;
import io.swagger.parser.SwaggerParser;
import io.swagger.util.Json;
import io.swagger.util.Yaml;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.json.JSONException;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.wso2.carbon.apimgt.api.APIManagementException;
import org.wso2.carbon.apimgt.impl.APIConstants;
import org.wso2.carbon.apimgt.impl.internal.ServiceReferenceHolder;
import org.wso2.carbon.apimgt.impl.soaptorest.template.RESTToSOAPMsgTemplate;
import org.wso2.carbon.apimgt.impl.soaptorest.util.SOAPToRESTConstants;
import org.wso2.carbon.apimgt.impl.soaptorest.util.SequenceUtils;
import org.wso2.carbon.apimgt.impl.utils.APIUtil;
import org.wso2.carbon.context.PrivilegedCarbonContext;
import org.wso2.carbon.registry.core.RegistryConstants;
import org.wso2.carbon.registry.core.exceptions.RegistryException;
import org.wso2.carbon.registry.core.service.RegistryService;
import org.wso2.carbon.registry.core.session.UserRegistry;
import org.wso2.carbon.user.api.UserStoreException;
import org.wso2.carbon.utils.multitenancy.MultitenantConstants;
import org.wso2.carbon.utils.multitenancy.MultitenantUtils;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import static org.wso2.carbon.apimgt.impl.utils.APIUtil.handleException;

/**
 * Class uses to generate api sequences for soap to rest conversion.
 */
public class SequenceGenerator {
    private static final Logger log = LoggerFactory.getLogger(SequenceGenerator.class);

    /**
     * Generates in/out sequences from the swagger given
     *
     * @param swaggerStr swagger string
     * @param apiDataStr api data as json string
     * @throws APIManagementException
     */
    public static void generateSequencesFromSwagger(String swaggerStr, String apiDataStr)
            throws APIManagementException {

        Swagger swagger = new SwaggerParser().parse(swaggerStr);
        Map<String, Model> definitions = swagger.getDefinitions();

        // Configure serializers
        SimpleModule simpleModule = new SimpleModule().addSerializer(new JsonNodeExampleSerializer());
        Json.mapper().registerModule(simpleModule);
        Yaml.mapper().registerModule(simpleModule);

        Map<String, Path> paths = swagger.getPaths();

        for (String pathName : paths.keySet()) {
            Path path = paths.get(pathName);

            Map<HttpMethod, Operation> operationMap = path.getOperationMap();
            for (HttpMethod httpMethod : operationMap.keySet()) {
                Map<String, String> parameterJsonPathMapping = new HashMap<>();
                Map<String, String> queryParameters = new HashMap<>();
                Operation operation = operationMap.get(httpMethod);
                String operationId = operation.getOperationId();

                //get vendor extensions
                Map<String, Object> vendorExtensions = operation.getVendorExtensions();
                Object vendorExtensionObj = vendorExtensions.get("x-wso2-soap");

                String soapAction = SOAPToRESTConstants.EMPTY_STRING;
                String namespace = SOAPToRESTConstants.EMPTY_STRING;
                String soapVersion = SOAPToRESTConstants.EMPTY_STRING;
                if (vendorExtensionObj != null) {
                    soapAction = (String) ((LinkedHashMap) vendorExtensionObj).get("soap-action");
                    namespace = (String) ((LinkedHashMap) vendorExtensionObj).get("namespace");
                    soapVersion = (String) ((LinkedHashMap) vendorExtensionObj)
                            .get(SOAPToRESTConstants.Swagger.SOAP_VERSION);
                }
                String soapNamespace = SOAPToRESTConstants.SOAP12_NAMSPACE;
                if (StringUtils.isNotBlank(soapVersion)
                        && SOAPToRESTConstants.SOAP_VERSION_11.equals(soapVersion)) {
                    soapNamespace = SOAPToRESTConstants.SOAP11_NAMESPACE;
                }

                List<Parameter> parameters = operation.getParameters();
                for (Parameter parameter : parameters) {
                    String name = parameter.getName();
                    if (parameter instanceof BodyParameter) {
                        Model schema = ((BodyParameter) parameter).getSchema();
                        if (schema instanceof RefModel) {
                            String $ref = ((RefModel) schema).get$ref();
                            if (StringUtils.isNotBlank($ref)) {
                                String defName = $ref.substring("#/definitions/".length());
                                Model model = definitions.get(defName);
                                Example example = ExampleBuilder.fromModel(defName, model, definitions,
                                        new HashSet<String>());

                                String jsonExample = Json.pretty(example);
                                try {
                                    org.json.JSONObject json = new org.json.JSONObject(jsonExample);
                                    SequenceUtils.listJson(json, parameterJsonPathMapping);
                                } catch (JSONException e) {
                                    log.error("Error occurred while generating json mapping for the definition", e);
                                }
                            }
                        }
                    }
                    if (parameter instanceof QueryParameter) {
                        String type = ((QueryParameter) parameter).getType();
                        queryParameters.put(name, type);
                    }
                }
                //populates body parameter json paths and query parameters to generate api sequence parameters
                populateParametersFromOperation(operation, definitions, parameterJsonPathMapping, queryParameters);

                Map<String, String> payloadSequence = createPayloadFacXMLForOperation(parameterJsonPathMapping,
                        queryParameters, namespace, SOAPToRESTConstants.EMPTY_STRING, operationId);
                try {
                    String[] propAndArgElements = getPropertyAndArgElementsForSequence(parameterJsonPathMapping,
                            queryParameters);
                    if (log.isDebugEnabled()) {
                        log.debug("properties string for the generated sequence: " + propAndArgElements[0]);
                        log.debug("arguments string for the generated sequence: " + propAndArgElements[1]);
                    }
                    org.json.simple.JSONArray arraySequenceElements = new org.json.simple.JSONArray();

                    //gets array elements for the sequence to be used
                    getArraySequenceElements(arraySequenceElements, parameterJsonPathMapping);
                    Map<String, String> sequenceMap = new HashMap<>();
                    sequenceMap.put("args", propAndArgElements[0]);
                    sequenceMap.put("properties", propAndArgElements[1]);
                    sequenceMap.put("sequence", payloadSequence.get(operationId));
                    RESTToSOAPMsgTemplate template = new RESTToSOAPMsgTemplate();
                    String inSequence = template.getMappingInSequence(sequenceMap, operationId, soapAction,
                            namespace, soapNamespace, arraySequenceElements);
                    String outSequence = template.getMappingOutSequence();
                    saveApiSequences(apiDataStr, inSequence, outSequence, httpMethod.toString().toLowerCase(),
                            pathName);
                } catch (APIManagementException e) {
                    handleException("Error when generating sequence property and arg elements for soap operation: "
                            + operationId, e);
                }
            }
        }
    }

    private static void populateParametersFromOperation(Operation operation, Map<String, Model> definitions,
            Map<String, String> parameterJsonPathMapping, Map<String, String> queryParameters) {

        List<Parameter> parameters = operation.getParameters();
        for (Parameter parameter : parameters) {
            String name = parameter.getName();
            if (parameter instanceof BodyParameter) {
                Model schema = ((BodyParameter) parameter).getSchema();
                if (schema instanceof RefModel) {
                    String $ref = ((RefModel) schema).get$ref();
                    if (StringUtils.isNotBlank($ref)) {
                        String defName = $ref.substring("#/definitions/".length());
                        Model model = definitions.get(defName);
                        Example example = ExampleBuilder.fromModel(defName, model, definitions,
                                new HashSet<String>());

                        String jsonExample = Json.pretty(example);
                        try {
                            org.json.JSONObject json = new org.json.JSONObject(jsonExample);
                            SequenceUtils.listJson(json, parameterJsonPathMapping);
                        } catch (JSONException e) {
                            log.error("Error occurred while generating json mapping for the definition: " + defName,
                                    e);
                        }
                    }
                }
            }
            if (parameter instanceof QueryParameter) {
                String type = ((QueryParameter) parameter).getType();
                queryParameters.put(name, type);
            }
        }
    }

    private static void saveApiSequences(String apiDataStr, String inSequence, String outSequence, String method,
            String resourcePath) throws APIManagementException {

        JSONParser parser = new JSONParser();
        boolean isTenantFlowStarted = false;

        org.json.simple.JSONObject apiData;
        try {
            apiData = (org.json.simple.JSONObject) parser.parse(apiDataStr);
            String provider = (String) apiData.get("provider");
            String name = (String) apiData.get("name");
            String version = (String) apiData.get("version");

            if (provider != null) {
                provider = APIUtil.replaceEmailDomain(provider);
            }

            provider = (provider != null ? provider.trim() : null);
            name = (name != null ? name.trim() : null);
            version = (version != null ? version.trim() : null);

            String tenantDomain = MultitenantUtils.getTenantDomain(APIUtil.replaceEmailDomainBack(provider));
            if (tenantDomain != null && !MultitenantConstants.SUPER_TENANT_DOMAIN_NAME.equals(tenantDomain)) {
                isTenantFlowStarted = true;
                PrivilegedCarbonContext.startTenantFlow();
                PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain(tenantDomain, true);
            }
            RegistryService registryService = ServiceReferenceHolder.getInstance().getRegistryService();
            int tenantId;
            UserRegistry registry;

            try {
                tenantId = ServiceReferenceHolder.getInstance().getRealmService().getTenantManager()
                        .getTenantId(tenantDomain);
                APIUtil.loadTenantRegistry(tenantId);
                registry = registryService.getGovernanceSystemRegistry(tenantId);

                String resourceInPath = APIConstants.API_LOCATION + RegistryConstants.PATH_SEPARATOR + provider
                        + RegistryConstants.PATH_SEPARATOR + name + RegistryConstants.PATH_SEPARATOR + version
                        + RegistryConstants.PATH_SEPARATOR
                        + SOAPToRESTConstants.SequenceGen.SOAP_TO_REST_IN_RESOURCE + resourcePath
                        + SOAPToRESTConstants.SequenceGen.RESOURCE_METHOD_SEPERATOR + method
                        + SOAPToRESTConstants.SequenceGen.XML_FILE_EXTENSION;
                String resourceOutPath = APIConstants.API_LOCATION + RegistryConstants.PATH_SEPARATOR + provider
                        + RegistryConstants.PATH_SEPARATOR + name + RegistryConstants.PATH_SEPARATOR + version
                        + RegistryConstants.PATH_SEPARATOR
                        + SOAPToRESTConstants.SequenceGen.SOAP_TO_REST_OUT_RESOURCE + resourcePath
                        + SOAPToRESTConstants.SequenceGen.RESOURCE_METHOD_SEPERATOR + method
                        + SOAPToRESTConstants.SequenceGen.XML_FILE_EXTENSION;

                SequenceUtils.saveRestToSoapConvertedSequence(registry, inSequence, method, resourceInPath);
                SequenceUtils.saveRestToSoapConvertedSequence(registry, outSequence, method, resourceOutPath);
            } catch (UserStoreException e) {
                handleException("Error while reading tenant information", e);
            } catch (RegistryException e) {
                handleException("Error while creating registry resource", e);
            } catch (APIManagementException e) {
                handleException(
                        "Error while saving the soap to rest converted sequence for resource path: " + resourcePath,
                        e);
            }

        } catch (ParseException e) {
            handleException("Error occurred while parsing the sequence json", e);
        } finally {
            if (isTenantFlowStarted) {
                PrivilegedCarbonContext.endTenantFlow();
            }
        }
    }

    private static Map<String, String> createPayloadFacXMLForOperation(Map<String, String> parameterJsonPathMapping,
            Map<String, String> queryPathParamMapping, String namespace, String prefix, String operationId)
            throws APIManagementException {

        DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        DocumentBuilder docBuilder;
        StringWriter stringWriter = new StringWriter();

        try {
            Transformer transformer = transformerFactory.newTransformer();
            docBuilder = docFactory.newDocumentBuilder();
            Document doc = docBuilder.newDocument();
            Element rootElement = doc.createElementNS(namespace, SOAPToRESTConstants.SequenceGen.NAMESPACE_PREFIX
                    + SOAPToRESTConstants.SequenceGen.NAMESPACE_SEPARATOR + operationId);
            doc.appendChild(rootElement);
            int count = 1;
            for (String parameter : parameterJsonPathMapping.keySet()) {
                String parameterType = parameterJsonPathMapping.get(parameter);
                String[] parameterTreeNodes = parameter.split("\\.");

                Element prevElement = rootElement;
                int elemPos = 0;
                int length = parameterType.equals(SOAPToRESTConstants.ParamTypes.ARRAY)
                        ? parameterTreeNodes.length - 1
                        : parameterTreeNodes.length;
                for (int i = 0; i < length; i++) {
                    String parameterTreeNode = parameterTreeNodes[i];
                    if (StringUtils.isNotBlank(parameterTreeNode)) {
                        Element element = doc.createElementNS(namespace,
                                SOAPToRESTConstants.SequenceGen.NAMESPACE_PREFIX
                                        + SOAPToRESTConstants.SequenceGen.NAMESPACE_SEPARATOR + parameterTreeNode);
                        if (doc.getElementsByTagName(element.getTagName()).getLength() > 0) {
                            prevElement = (Element) doc.getElementsByTagName(element.getTagName()).item(0);
                        } else {
                            if (elemPos == length - 1) {
                                element.setTextContent(SOAPToRESTConstants.SequenceGen.PROPERTY_ACCESSOR + count);
                                count++;
                            }
                            prevElement.appendChild(element);
                            prevElement = element;
                        }
                        elemPos++;
                    }
                }
            }
            count = 1;
            if (parameterJsonPathMapping.size() == 0) {
                for (String queryParam : queryPathParamMapping.keySet()) {
                    Element element = doc.createElementNS(namespace,
                            SOAPToRESTConstants.SequenceGen.NAMESPACE_PREFIX
                                    + SOAPToRESTConstants.SequenceGen.NAMESPACE_SEPARATOR + queryParam);
                    element.setTextContent(SOAPToRESTConstants.SequenceGen.PROPERTY_ACCESSOR + count);
                    count++;
                    rootElement.appendChild(element);
                }
            } else if (parameterJsonPathMapping.size() > 0 && queryPathParamMapping.size() > 0) {
                log.warn("Query parameters along with the body parameter is not allowed");
            }
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
            transformer.setOutputProperty(SOAPToRESTConstants.SequenceGen.INDENT_PROPERTY,
                    SOAPToRESTConstants.SequenceGen.INDENT_VALUE);
            transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
            transformer.transform(new DOMSource(doc), new StreamResult(stringWriter));
        } catch (ParserConfigurationException e) {
            handleException("Error occurred when building in sequence xml", e);
        } catch (TransformerConfigurationException e) {
            handleException("Error in transport configuration", e);
        } catch (TransformerException e) {
            handleException("Error occurred when transforming in sequence xml", e);
        }
        if (log.isDebugEnabled()) {
            log.debug("parameter mapping for used in payload factory for soap operation:" + operationId + " is "
                    + stringWriter.toString());
        }
        Map<String, String> paramMap = new HashMap<>();
        paramMap.put(operationId, stringWriter.toString());
        return paramMap;
    }

    private static String[] getPropertyAndArgElementsForSequence(Map<String, String> parameterJsonPathMapping,
            Map<String, String> queryPathParamMapping) throws APIManagementException {

        String argStr = SOAPToRESTConstants.EMPTY_STRING;
        String propertyStr = SOAPToRESTConstants.EMPTY_STRING;
        for (String parameter : parameterJsonPathMapping.keySet()) {
            String parameterType = parameterJsonPathMapping.get(parameter);
            String paramElements = SequenceGenerator.createParameterElements(parameter,
                    SOAPToRESTConstants.Swagger.BODY);
            String[] params = paramElements.split(SOAPToRESTConstants.SequenceGen.COMMA);
            if (!SOAPToRESTConstants.ParamTypes.ARRAY.equals(parameterType)) {
                propertyStr += params[0] + SOAPToRESTConstants.SequenceGen.NEW_LINE_CHAR;
            }
            argStr += params[1] + SOAPToRESTConstants.SequenceGen.NEW_LINE_CHAR;
        }

        if (MapUtils.isEmpty(parameterJsonPathMapping)) {
            for (String queryParam : queryPathParamMapping.keySet()) {
                String paramElements = SequenceGenerator.createParameterElements(queryParam,
                        SOAPToRESTConstants.ParamTypes.QUERY);
                String[] params = paramElements.split(SOAPToRESTConstants.SequenceGen.COMMA);
                argStr += params[1] + SOAPToRESTConstants.SequenceGen.NEW_LINE_CHAR;
                propertyStr += params[0] + SOAPToRESTConstants.SequenceGen.NEW_LINE_CHAR;
            }
        } else if (parameterJsonPathMapping.size() > 0 && queryPathParamMapping.size() > 0) {
            log.warn("Query parameters along with the body parameter is not allowed");
        }

        return new String[] { argStr, propertyStr };
    }

    private static void getArraySequenceElements(org.json.simple.JSONArray array,
            Map<String, String> parameterJsonPathMapping) {

        for (String parameter : parameterJsonPathMapping.keySet()) {
            String parameterType = parameterJsonPathMapping.get(parameter);
            String[] parameterTreeNodes = parameter.split("\\.");

            if (SOAPToRESTConstants.ParamTypes.ARRAY.equals(parameterType)) {
                org.json.simple.JSONObject arrayObj = new org.json.simple.JSONObject();
                arrayObj.put(SOAPToRESTConstants.SequenceGen.PROPERTY_NAME, parameter);
                arrayObj.put(SOAPToRESTConstants.SequenceGen.PARAMETER_NAME,
                        parameterTreeNodes[parameterTreeNodes.length - 1]);
                array.add(arrayObj);
            }
        }
    }

    /**
     * Creates property and argument elements needed to inject to the sequence.
     *
     * @param jsonPathElement json path of a parameter
     * @param type            type of the parameter (i.e query, body, path)
     * @return string of property and argument elements for a given parameter
     * @throws APIManagementException throws in xml to string transformation
     */
    private static String createParameterElements(String jsonPathElement, String type)
            throws APIManagementException {

        DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder docBuilder;
        StringWriter stringWriter = new StringWriter();
        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        String property = SOAPToRESTConstants.EMPTY_STRING;
        String argument = SOAPToRESTConstants.EMPTY_STRING;
        try {
            Transformer transformer = transformerFactory.newTransformer();
            docBuilder = docFactory.newDocumentBuilder();
            Document doc = docBuilder.newDocument();
            Element argElement = doc.createElement(SOAPToRESTConstants.SequenceGen.ARG_ELEMENT);
            Element propertyElement = doc.createElement(SOAPToRESTConstants.SequenceGen.PROPERTY_ELEMENT);
            argElement.setAttribute(SOAPToRESTConstants.SequenceGen.EVALUATOR_ATTR,
                    SOAPToRESTConstants.SequenceGen.XML_FILE);
            String expressionAttr = SOAPToRESTConstants.SequenceGen.EXPRESSION_FUNC_DEF + jsonPathElement
                    + SOAPToRESTConstants.SequenceGen.EXPRESSION_FUNC_DEF_CLOSING_TAG;
            argElement.setAttribute(SOAPToRESTConstants.SequenceGen.EXPRESSION_ATTR, expressionAttr);
            propertyElement.setAttribute(SOAPToRESTConstants.NAME_ATTRIBUTE,
                    SOAPToRESTConstants.SequenceGen.REQ_VARIABLE + jsonPathElement);
            if (SOAPToRESTConstants.ParamTypes.QUERY.equals(type)) {
                propertyElement.setAttribute(SOAPToRESTConstants.SequenceGen.EXPRESSION_ATTR,
                        SOAPToRESTConstants.SequenceGen.URL_OPERATOR + jsonPathElement);
            } else {
                propertyElement.setAttribute(SOAPToRESTConstants.SequenceGen.EXPRESSION_ATTR,
                        SOAPToRESTConstants.SequenceGen.JSON_EVAL + SOAPToRESTConstants.SequenceGen.ROOT_OPERATOR
                                + jsonPathElement + SOAPToRESTConstants.SequenceGen.CLOSING_PARANTHESIS);
            }
            transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
            transformer.transform(new DOMSource(propertyElement), new StreamResult(stringWriter));
            property = stringWriter.toString();
            stringWriter = new StringWriter();
            transformer.transform(new DOMSource(argElement), new StreamResult(stringWriter));
            argument = stringWriter.toString();
            if (log.isDebugEnabled()) {
                log.debug("Argument element for request parameter: " + jsonPathElement + " and parameter type: "
                        + type + " is: " + argument);
            }
        } catch (ParserConfigurationException e) {
            handleException("Error occurred when building in arg elements", e);
        } catch (TransformerConfigurationException e) {
            handleException("Error in transport configuration", e);
        } catch (TransformerException e) {
            handleException("Error occurred when transforming in sequence xml", e);
        }
        return property + SOAPToRESTConstants.SequenceGen.COMMA + argument;
    }
}