org.wso2.carbon.apimgt.impl.definitions.APIDefinitionUsingOASParser.java Source code

Java tutorial

Introduction

Here is the source code for org.wso2.carbon.apimgt.impl.definitions.APIDefinitionUsingOASParser.java

Source

/*
 *   Copyright (c) 2019, 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.definitions;

import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import io.swagger.models.Contact;
import io.swagger.models.HttpMethod;
import io.swagger.models.Info;
import io.swagger.models.Model;
import io.swagger.models.Operation;
import io.swagger.models.Path;
import io.swagger.models.RefModel;
import io.swagger.models.RefPath;
import io.swagger.models.RefResponse;
import io.swagger.models.Response;
import io.swagger.models.SecurityRequirement;
import io.swagger.models.Swagger;
import io.swagger.models.auth.OAuth2Definition;
import io.swagger.models.auth.SecuritySchemeDefinition;
import io.swagger.models.parameters.PathParameter;
import io.swagger.models.parameters.RefParameter;
import io.swagger.models.properties.Property;
import io.swagger.models.properties.RefProperty;
import io.swagger.parser.SwaggerParser;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.apimgt.api.APIDefinition;
import org.wso2.carbon.apimgt.api.APIManagementException;
import org.wso2.carbon.apimgt.api.model.API;
import org.wso2.carbon.apimgt.api.model.APIIdentifier;
import org.wso2.carbon.apimgt.api.model.Scope;
import org.wso2.carbon.apimgt.api.model.URITemplate;
import org.wso2.carbon.apimgt.impl.APIConstants;
import org.wso2.carbon.registry.api.Registry;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.HashSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Models API definition using OAS (swagger 2.0) parser
 */
public class APIDefinitionUsingOASParser extends APIDefinition {

    private static final Log log = LogFactory.getLog(APIDefinitionUsingOASParser.class);

    @Override
    public Set<URITemplate> getURITemplates(API api, String apiDefinition) throws APIManagementException {
        SwaggerParser parser = new SwaggerParser();
        Swagger swagger = parser.parse(apiDefinition);
        Set<URITemplate> urlTemplates = new LinkedHashSet<>();

        String oauth2SchemeKey = getOAuth2SecuritySchemeKey(swagger);
        List<String> globalScopes = getOAuth2ScopeListFromSecurityElement(oauth2SchemeKey, swagger.getSecurity());

        for (String pathString : swagger.getPaths().keySet()) {
            Path path = swagger.getPath(pathString);
            Map<HttpMethod, Operation> operationMap = path.getOperationMap();
            for (Map.Entry<HttpMethod, Operation> entry : operationMap.entrySet()) {
                Operation op = entry.getValue();
                URITemplate template = new URITemplate();
                template.setHTTPVerb(entry.getKey().name().toUpperCase());
                template.setUriTemplate(pathString);

                if (op.getSecurity() != null) {
                    // If scopes defined in the operation level set those to the URL template 
                    List<String> scopes = getOAuth2ScopeListFromSecurityElementMap(oauth2SchemeKey,
                            op.getSecurity());
                    if (scopes != null) {
                        template = setScopesToTemplate(template, scopes);
                    }
                } else if (globalScopes != null && globalScopes.size() > 0) {
                    // If there are no scopes defined in the operation level but there are global scopes, then set those 
                    template = setScopesToTemplate(template, globalScopes);
                }

                urlTemplates.add(template);
            }
        }

        return urlTemplates;
    }

    @Override
    public Set<Scope> getScopes(String apiDefinition) throws APIManagementException {
        SwaggerParser parser = new SwaggerParser();
        Swagger swagger = parser.parse(apiDefinition);
        Map<String, SecuritySchemeDefinition> securityDefinitions = swagger.getSecurityDefinitions();
        Set<Scope> scopeSet = new HashSet<>();
        for (Map.Entry<String, String> entry : ((OAuth2Definition) (securityDefinitions.get("OAuth2Security")))
                .getScopes().entrySet()) {
            Scope scope = new Scope();
            scope.setKey(entry.getKey());
            scopeSet.add(scope);
        }
        return scopeSet;
    }

    @Override
    public void saveAPIDefinition(API api, String apiDefinitionJSON, Registry registry)
            throws APIManagementException {

    }

    @Override
    public String getAPIDefinition(APIIdentifier apiIdentifier, Registry registry) throws APIManagementException {
        return null;
    }

    @Override
    public String generateAPIDefinition(API api) throws APIManagementException {
        Swagger swagger = new Swagger();

        //Create info object
        Info info = new Info();
        info.setTitle(api.getId().getApiName());
        if (api.getDescription() != null) {
            info.setDescription(api.getDescription());
        }

        Contact contact = new Contact();
        //Create contact object and map business owner info
        if (api.getBusinessOwner() != null) {
            contact.setName(api.getBusinessOwner());
        }
        if (api.getBusinessOwnerEmail() != null) {
            contact.setEmail(api.getBusinessOwnerEmail());
        }
        if (api.getBusinessOwner() != null || api.getBusinessOwnerEmail() != null) {
            //put contact object to info object
            info.setContact(contact);
        }

        info.setVersion(api.getId().getVersion());
        OAuth2Definition oAuth2Definition = new OAuth2Definition().password("https://test.com");

        Set<Scope> scopes = api.getScopes();

        if (scopes != null && !scopes.isEmpty()) {
            List<Map<String, String>> xSecurityScopesArray = new ArrayList<>();
            for (Scope scope : scopes) {
                oAuth2Definition.addScope(scope.getName(), scope.getDescription());

                Map<String, String> xWso2ScopesObject = new LinkedHashMap<>();
                xWso2ScopesObject.put(APIConstants.SWAGGER_SCOPE_KEY, scope.getKey());
                xWso2ScopesObject.put(APIConstants.SWAGGER_NAME, scope.getName());
                xWso2ScopesObject.put(APIConstants.SWAGGER_ROLES, scope.getRoles());
                xWso2ScopesObject.put(APIConstants.SWAGGER_DESCRIPTION, scope.getDescription());
                xSecurityScopesArray.add(xWso2ScopesObject);
            }
            Map<String, Object> xWSO2Scopes = new LinkedHashMap<>();
            xWSO2Scopes.put(APIConstants.SWAGGER_X_WSO2_SCOPES, xSecurityScopesArray);
            Map<String, Object> xWSO2SecurityDefinitionObject = new LinkedHashMap<>();
            xWSO2SecurityDefinitionObject.put(APIConstants.SWAGGER_OBJECT_NAME_APIM, xWSO2Scopes);

            swagger.setVendorExtension(APIConstants.SWAGGER_X_WSO2_SECURITY, xWSO2SecurityDefinitionObject);
        }

        swagger.addSecurityDefinition("OAuth2Security", oAuth2Definition);
        swagger.setInfo(info);

        for (URITemplate uriTemplate : api.getUriTemplates()) {
            addOrUpdatePathToSwagger(swagger, uriTemplate);
        }

        return getSwaggerJsonString(swagger);
    }

    @Override
    public String generateAPIDefinition(API api, String apiDefinition) throws APIManagementException {
        SwaggerParser parser = new SwaggerParser();
        Swagger swaggerObj = parser.parse(apiDefinition);

        //Generates below model using the API's URI template
        // path -> [verb1 -> template1, verb2 -> template2, ..]
        Map<String, Map<String, URITemplate>> uriTemplateMap = getURITemplateMap(api);

        for (Map.Entry<String, Path> pathEntry : swaggerObj.getPaths().entrySet()) {
            String pathName = pathEntry.getKey();
            Path path = pathEntry.getValue();
            Map<String, URITemplate> uriTemplatesForPath = uriTemplateMap.get(pathName);
            if (uriTemplatesForPath == null) {
                //remove paths that are not in URI Templates
                swaggerObj.getPaths().remove(pathName);
            } else {
                //If path is available in the URI template, then check for operations(verbs) 
                for (Map.Entry<HttpMethod, Operation> operationEntry : path.getOperationMap().entrySet()) {
                    HttpMethod httpMethod = operationEntry.getKey();
                    Operation operation = operationEntry.getValue();
                    URITemplate template = uriTemplatesForPath.get(httpMethod.toString().toUpperCase());
                    if (template == null) {
                        // if particular operation is not available in URI templates, then remove it from swagger
                        path.set(httpMethod.toString().toLowerCase(), null);
                    } else {
                        // if operation is available in URI templates, update swagger operation 
                        // with auth type, scope etc
                        updateOperationManagedInfo(template, operation);
                    }
                }

                // if there are any verbs (operations) not defined in swagger then add them
                for (Map.Entry<String, URITemplate> uriTemplatesForPathEntry : uriTemplatesForPath.entrySet()) {
                    String verb = uriTemplatesForPathEntry.getKey();
                    URITemplate uriTemplate = uriTemplatesForPathEntry.getValue();
                    HttpMethod method = HttpMethod.valueOf(verb.toUpperCase());
                    Operation operation = path.getOperationMap().get(method);
                    if (operation == null) {
                        operation = createOperation(uriTemplate);
                        path.set(uriTemplate.getHTTPVerb().toLowerCase(), operation);
                    }
                }
            }
        }

        // add to swagger if there are any new templates 
        for (Map.Entry<String, Map<String, URITemplate>> uriTemplateMapEntry : uriTemplateMap.entrySet()) {
            String path = uriTemplateMapEntry.getKey();
            Map<String, URITemplate> verbMap = uriTemplateMapEntry.getValue();
            if (swaggerObj.getPath(path) == null) {
                for (Map.Entry<String, URITemplate> verbMapEntry : verbMap.entrySet()) {
                    URITemplate uriTemplate = verbMapEntry.getValue();
                    addOrUpdatePathToSwagger(swaggerObj, uriTemplate);
                }
            }
        }

        return getSwaggerJsonString(swaggerObj);
    }

    @Override
    public Map<String, String> getAPIOpenAPIDefinitionTimeStamps(APIIdentifier apiIdentifier, Registry registry)
            throws APIManagementException {
        return null;
    }

    /**
     * Retrieves the "Auth2" security scheme key
     *
     * @param swagger Swgger object
     * @return "Auth2" security scheme key
     */
    private String getOAuth2SecuritySchemeKey(Swagger swagger) {
        final String oauth2Type = new OAuth2Definition().getType();
        Map<String, SecuritySchemeDefinition> securityDefinitions = swagger.getSecurityDefinitions();
        for (Map.Entry<String, SecuritySchemeDefinition> definitionEntry : securityDefinitions.entrySet()) {
            if (oauth2Type.equals(definitionEntry.getValue().getType())) {
                return definitionEntry.getKey();
            }
        }

        return null;
    }

    /**
     * Gets a list of scopes using the security requirements
     *
     * @param oauth2SchemeKey      OAuth2 security element key
     * @param securityRequirements list of security requirements
     * @return list of scopes using the security requirements
     */
    private List<String> getOAuth2ScopeListFromSecurityElement(String oauth2SchemeKey,
            List<SecurityRequirement> securityRequirements) {

        if (securityRequirements != null) {
            for (SecurityRequirement requirement : securityRequirements) {
                if (requirement.getRequirements() != null
                        && requirement.getRequirements().get(oauth2SchemeKey) != null) {
                    return requirement.getRequirements().get(oauth2SchemeKey);
                }
            }
        }
        return new ArrayList<>();
    }

    /**
     * Gets a list of scopes using the security requirements
     *
     * @param oauth2SchemeKey      OAuth2 security element key
     * @param securityRequirements map of security requirements
     * @return list of scopes using the security requirements
     */
    private List<String> getOAuth2ScopeListFromSecurityElementMap(String oauth2SchemeKey,
            List<Map<String, List<String>>> securityRequirements) {

        if (securityRequirements != null) {
            for (Map<String, List<String>> requirement : securityRequirements) {
                if (requirement.get(oauth2SchemeKey) != null) {
                    return requirement.get(oauth2SchemeKey);
                }
            }
        }
        return new ArrayList<>();
    }

    /**
     * Sets the scopes to the URL template object using the given list of scopes
     *
     * @param template URL template
     * @param scopes   list of scopes
     * @return URL template after setting the scopes
     */
    private URITemplate setScopesToTemplate(URITemplate template, List<String> scopes) {
        for (String scope : scopes) {
            Scope scopeObj = new Scope();
            scopeObj.setKey(scope);
            scopeObj.setName(scope);

            template.setScopes(scopeObj);
        }
        return template;
    }

    /**
     * Add a new path based on the provided URI template to swagger if it does not exists. If it exists,
     * adds the respective operation to the existing path
     * 
     * @param swagger swagger object
     * @param uriTemplate URI template
     */
    private void addOrUpdatePathToSwagger(Swagger swagger, URITemplate uriTemplate) {
        Path path;
        if (swagger.getPath(uriTemplate.getUriTemplate()) != null) {
            path = swagger.getPath(uriTemplate.getUriTemplate());
        } else {
            path = new Path();
        }

        Operation operation = createOperation(uriTemplate);
        path.set(uriTemplate.getHTTPVerb().toLowerCase(), operation);

        swagger.path(uriTemplate.getUriTemplate(), path);
    }

    /**
     * Creates a new operation object using the URI template object
     * 
     * @param uriTemplate URI template
     * @return a new operation object using the URI template object
     */
    private Operation createOperation(URITemplate uriTemplate) {
        Operation operation = new Operation();
        List<String> pathParams = getPathParamNames(uriTemplate.getUriTemplate());
        for (String pathParam : pathParams) {
            PathParameter pathParameter = new PathParameter();
            pathParameter.setName(pathParam);
            pathParameter.setType("string");
            operation.addParameter(pathParameter);
        }

        updateOperationManagedInfo(uriTemplate, operation);

        Response response = new Response();
        response.setDescription("OK");
        operation.addResponse(APIConstants.SWAGGER_RESPONSE_200, response);
        return operation;
    }

    /**
     *  Updates managed info of a provided operation such as auth type and throttling
     * 
     * @param uriTemplate URI template
     * @param operation swagger operation
     */
    private void updateOperationManagedInfo(URITemplate uriTemplate, Operation operation) {
        String authType = uriTemplate.getAuthType();
        if (APIConstants.AUTH_APPLICATION_OR_USER_LEVEL_TOKEN.equals(authType)) {
            authType = APIConstants.OASResourceAuthTypes.APPLICATION_OR_APPLICATION_USER;
        }
        if (APIConstants.AUTH_APPLICATION_USER_LEVEL_TOKEN.equals(authType)) {
            authType = APIConstants.OASResourceAuthTypes.APPLICATION_USER;
        }
        if (APIConstants.AUTH_APPLICATION_LEVEL_TOKEN.equals(authType)) {
            authType = APIConstants.OASResourceAuthTypes.APPLICATION;
        }
        operation.setVendorExtension(APIConstants.SWAGGER_X_AUTH_TYPE, authType);
        operation.setVendorExtension(APIConstants.SWAGGER_X_THROTTLING_TIER, uriTemplate.getThrottlingTier());
    }

    /**
     * Creates a json string using the swagger object. 
     *
     * @param swaggerObj swagger object
     * @return json string using the swagger object
     * @throws APIManagementException error while creating swagger json
     */
    private String getSwaggerJsonString(Swagger swaggerObj) throws APIManagementException {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        mapper.enable(SerializationFeature.INDENT_OUTPUT);

        //this is to ignore "originalRef" in schema objects
        mapper.addMixIn(RefModel.class, IgnoreOriginalRefMixin.class);
        mapper.addMixIn(RefProperty.class, IgnoreOriginalRefMixin.class);
        mapper.addMixIn(RefPath.class, IgnoreOriginalRefMixin.class);
        mapper.addMixIn(RefParameter.class, IgnoreOriginalRefMixin.class);
        mapper.addMixIn(RefResponse.class, IgnoreOriginalRefMixin.class);

        //this is to ignore "responseSchema" in response schema objects
        mapper.addMixIn(Response.class, ResponseSchemaMixin.class);
        try {
            return new String(mapper.writeValueAsBytes(swaggerObj));
        } catch (JsonProcessingException e) {
            throw new APIManagementException("Error while generating Swagger json from model", e);
        }
    }

    /**
     * Used to ignore "originalRef" objects when generating the swagger
     */
    private abstract class IgnoreOriginalRefMixin {
        public IgnoreOriginalRefMixin() {
        }

        @JsonIgnore
        public abstract String getOriginalRef();
    }

    /**
     * Used to ignore "responseSchema" objects when generating the swagger
     */
    private abstract class ResponseSchemaMixin {
        public ResponseSchemaMixin() {
        }

        @JsonIgnore
        public abstract Property getSchema();

        @JsonIgnore
        public abstract void setSchema(Property var1);

        @JsonGetter("schema")
        public abstract Model getResponseSchema();

        @JsonSetter("schema")
        public abstract void setResponseSchema(Model var1);
    }
}