com.amazonaws.service.apigateway.importer.impl.sdk.ApiGatewaySdkRamlApiImporter.java Source code

Java tutorial

Introduction

Here is the source code for com.amazonaws.service.apigateway.importer.impl.sdk.ApiGatewaySdkRamlApiImporter.java

Source

/*
 * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file 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 com.amazonaws.service.apigateway.importer.impl.sdk;

import com.amazonaws.service.apigateway.importer.RamlApiImporter;
import com.amazonaws.services.apigateway.model.*;
import com.amazonaws.services.apigateway.model.Resource;
import com.amazonaws.util.json.JSONArray;
import com.amazonaws.util.json.JSONException;
import com.amazonaws.util.json.JSONObject;
import com.google.inject.Inject;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.raml.model.*;
import org.raml.model.parameter.Header;
import org.raml.model.parameter.QueryParameter;
import org.raml.model.parameter.UriParameter;

import javax.annotation.Nullable;
import java.util.*;

import static com.amazonaws.service.apigateway.importer.util.PatchUtils.*;
import static com.amazonaws.service.apigateway.importer.util.PatchUtils.createReplaceOperation;
import static java.lang.String.format;

public class ApiGatewaySdkRamlApiImporter extends ApiGatewaySdkApiImporter implements RamlApiImporter {

    private static final Log LOG = LogFactory.getLog(ApiGatewaySdkRamlApiImporter.class);

    @Inject
    private Raml raml;

    @Inject
    private JSONObject config;

    // NOTE: This is the only shared state - is there a better approach? Why wasn't this used until now? Can this be
    // reused in the swagger implementation?
    private Set<String> models = new HashSet<>();
    private Set<String> paths = new HashSet<>();

    @Override
    public String createApi(Raml raml, String name, JSONObject config) {
        this.raml = raml;
        this.config = config;

        // TODO: What to use as description?
        final RestApi api = createApi(getApiName(raml, name), null);

        try {
            final Resource rootResource = getRootResource(api).get();
            deleteDefaultModels(api);
            createModels(api, raml.getSchemas(), false);
            createResources(api, createResourcePath(api, rootResource, raml.getBasePath()), raml.getResources(),
                    false);
        } catch (Throwable t) {
            LOG.error("Error creating API, rolling back", t);
            rollback(api);
            throw t;
        }
        return api.getId();
    }

    @Override
    public void updateApi(String apiId, Raml raml, JSONObject config) {
        this.raml = raml;
        this.config = config;

        RestApi api = getApi(apiId);
        Optional<Resource> rootResource = getRootResource(api);

        createModels(api, raml.getSchemas(), true);
        createResources(api, createResourcePath(api, rootResource.get(), raml.getBasePath()), raml.getResources(),
                true);

        cleanupResources(api, this.paths);
        cleanupModels(api, this.models);
    }

    private String getApiName(Raml raml, String fileName) {
        String title = raml.getTitle();
        return StringUtils.isNotBlank(title) ? title : fileName;
    }

    private void createModels(RestApi api, List<Map<String, String>> schemas, boolean update) {
        for (Map<String, String> entries : schemas) {
            for (Map.Entry<String, String> entry : entries.entrySet()) {
                final String schemaName = entry.getKey();
                final String schemaValue = entry.getValue();

                models.add(schemaName);

                if (update && getModel(api, schemaName).isPresent()) {
                    updateModel(api, schemaName, schemaValue);
                } else {
                    createModel(api, schemaName, schemaValue);
                }
            }
        }
    }

    private void createModel(RestApi api, String schemaName, String schemaValue) {
        // HACK: Attempt to detect JSON/XML bodies.
        final Integer openTagIndex = schemaValue.indexOf('<');
        final Integer openJsonIndex = schemaValue.indexOf('{');

        // Is this possible or is the parser validating schemas?
        if (openTagIndex == openJsonIndex) {
            return;
        }

        final boolean isJson = openJsonIndex > -1 && (openTagIndex == -1 || openJsonIndex < openTagIndex);

        // TODO: What to put as description?
        createModel(api, schemaName, null, schemaValue, isJson ? "application/json" : "text/xml");
    }

    private void createResources(RestApi api, Resource rootResource, Map<String, org.raml.model.Resource> resources,
            boolean update) {
        for (Map.Entry<String, org.raml.model.Resource> entry : resources.entrySet()) {
            final org.raml.model.Resource resource = entry.getValue();
            final Resource parentResource = createResourcePath(api, rootResource, entry.getKey());

            createMethods(api, parentResource, resource.getActions(), update);
            createResources(api, parentResource, resource.getResources(), update);
        }
    }

    private void cleanupResources(RestApi api, Set<String> paths) {
        buildResourceList(api).stream()
                .filter(resource -> !resource.getPath().equals("/") && !paths.contains(resource.getPath()))
                .forEach(resource -> {
                    LOG.info("Removing deleted resource " + resource.getPath());
                    deleteResource(resource);
                });
    }

    private Resource createResourcePath(RestApi api, Resource resource, String fullPath) {
        final String[] parts = fullPath.split("/");

        Resource parentResource = resource;

        for (int i = 1; i < parts.length; i++) {
            parentResource = createResource(api, parentResource.getId(), parts[i]);

            paths.add(parentResource.getPath());
        }

        return parentResource;
    }

    private void createMethods(RestApi api, Resource resource, Map<ActionType, Action> actions, boolean update) {
        for (Map.Entry<ActionType, Action> entry : actions.entrySet()) {
            createMethod(api, resource, entry.getKey(), entry.getValue(), update);
        }

        if (update) {
            cleanupMethods(api, resource, actions);
        }
    }

    private void cleanupMethods(RestApi api, Resource resource, Map<ActionType, Action> actions) {
        final HashSet<String> methods = new HashSet<>();

        for (ActionType action : actions.keySet()) {
            methods.add(action.toString());
        }

        for (Method m : resource.getResourceMethods().values()) {
            String httpMethod = m.getHttpMethod().toUpperCase();

            if (!methods.contains(httpMethod)) {
                LOG.info(format("Removing deleted method %s for resource %s", httpMethod, resource.getId()));

                m.deleteMethod();
            }
        }
    }

    private void createMethod(final RestApi api, final Resource resource, final ActionType httpMethod,
            final Action action, boolean update) {
        Method method;

        if (update && methodExists(resource, httpMethod.toString())) {
            method = resource.getMethodByHttpMethod(httpMethod.toString());

            PatchDocument pd = createPatchDocument(
                    createReplaceOperation("/authorizationType",
                            getAuthorizationTypeFromConfig(resource, httpMethod.toString(), this.config)),
                    createReplaceOperation("/apiKeyRequired", "false"));

            method.updateMethod(pd);

            if (action.hasBody()) {
                for (Map.Entry<String, MimeType> entry : action.getBody().entrySet()) {
                    final String mime = entry.getKey();
                    final String modelName = createModel(api, mime, entry.getValue());

                    if (modelName != null) {
                        method.updateMethod(createPatchDocument(
                                createAddOperation("/requestModels/" + escapeOperationString(mime), modelName)));
                    }
                }
            }

            cleanupMethodModels(api, method, action.getBody());
        } else {
            LOG.info(format("Creating method for api id %s and resource id %s with method %s", api.getId(),
                    resource.getId(), httpMethod));

            PutMethodInput input = new PutMethodInput();

            // TODO: Figure out API key.
            input.setApiKeyRequired(false);
            input.setAuthorizationType(
                    getAuthorizationTypeFromConfig(resource, httpMethod.toString(), this.config));
            input.setRequestModels(new HashMap<>());

            if (action.hasBody()) {
                for (Map.Entry<String, MimeType> entry : action.getBody().entrySet()) {
                    final String mime = entry.getKey();
                    final String modelName = createModel(api, mime, entry.getValue());

                    if (modelName != null) {
                        input.getRequestModels().put(mime, modelName);
                    }
                }
            }

            method = resource.putMethod(input, httpMethod.toString());
        }

        createIntegration(resource, method, this.config);

        for (Map.Entry<String, UriParameter> entry : action.getResource().getUriParameters().entrySet()) {
            updateMethod(api, method, "path", entry.getKey(), entry.getValue().isRequired());
        }

        for (Map.Entry<String, Header> entry : action.getHeaders().entrySet()) {
            updateMethod(api, method, "header", entry.getKey(), entry.getValue().isRequired());
        }

        for (Map.Entry<String, QueryParameter> entry : action.getQueryParameters().entrySet()) {
            updateMethod(api, method, "querystring", entry.getKey(), entry.getValue().isRequired());
        }

        if (update) {
            cleanupMethod(api, method, "path", action.getResource().getUriParameters().keySet());
            cleanupMethod(api, method, "header", action.getHeaders().keySet());
            cleanupMethod(api, method, "querystring", action.getQueryParameters().keySet());
        }

        createMethodResponses(api, method, action.getResponses(), update);
    }

    private void createIntegration(Resource resource, Method method, JSONObject config) {
        if (config == null) {
            return;
        }

        try {
            final JSONObject integ = config.getJSONObject(resource.getPath())
                    .getJSONObject(method.getHttpMethod().toLowerCase()).getJSONObject("integration");

            IntegrationType type = IntegrationType.valueOf(integ.getString("type").toUpperCase());

            LOG.info("Creating integration with type " + type);

            PutIntegrationInput input = new PutIntegrationInput().withType(type).withUri(integ.getString("uri"))
                    .withCredentials(integ.optString("credentials")).withHttpMethod(integ.optString("httpMethod"))
                    .withRequestParameters(jsonObjectToHashMapString(integ.optJSONObject("requestParameters")))
                    .withRequestTemplates(jsonObjectToHashMapString(integ.optJSONObject("requestTemplates")))
                    .withCacheNamespace(integ.optString("cacheNamespace"))
                    .withCacheKeyParameters(jsonObjectToListString(integ.optJSONArray("cacheKeyParameters")));

            Integration integration = method.putIntegration(input);

            createIntegrationResponses(integration, integ.optJSONObject("responses"));
        } catch (JSONException e) {
            LOG.info(format("Skipping integration for method %s of %s: %s", method.getHttpMethod(),
                    resource.getPath(), e));
        }
    }

    private void createIntegrationResponses(Integration integration, JSONObject responses) {
        if (responses == null) {
            return;
        }

        final Iterator<String> keysIterator = responses.keys();

        while (keysIterator.hasNext()) {
            String key = keysIterator.next();

            try {
                String pattern = key.equals("default") ? null : key;
                JSONObject response = responses.getJSONObject(key);

                String status = (String) response.get("statusCode");

                PutIntegrationResponseInput input = new PutIntegrationResponseInput()
                        .withResponseParameters(
                                jsonObjectToHashMapString(response.optJSONObject("responseParameters")))
                        .withResponseTemplates(
                                jsonObjectToHashMapString(response.optJSONObject("responseTemplates")))
                        .withSelectionPattern(pattern);

                integration.putIntegrationResponse(input, status);
            } catch (JSONException e) {
            }
        }
    }

    private List<String> jsonObjectToListString(JSONArray json) {
        if (json == null) {
            return null;
        }

        final List<String> list = new ArrayList<>();

        for (int i = 0; i < json.length(); i++) {
            try {
                list.add(json.getString(i));
            } catch (JSONException e) {
            }
        }

        return list;
    }

    private Map<String, String> jsonObjectToHashMapString(JSONObject json) {
        if (json == null) {
            return null;
        }

        final Map<String, String> map = new HashMap<>();
        final Iterator<String> keysIterator = json.keys();

        while (keysIterator.hasNext()) {
            String key = keysIterator.next();

            try {
                map.put(key, json.getString(key));
            } catch (JSONException e) {
            }
        }

        return map;
    }

    private void cleanupMethodModels(RestApi api, Method method, Map<String, MimeType> body) {
        if (method.getRequestModels() != null) {
            for (Map.Entry<String, String> entry : method.getRequestModels().entrySet()) {
                if (!body.containsKey(entry.getKey()) || body.get(entry.getKey()).getSchema() == null) {
                    LOG.info(format("Removing model %s from method %s", entry.getKey(), method.getHttpMethod()));

                    method.updateMethod(createPatchDocument(
                            createRemoveOperation("/requestModels/" + escapeOperationString(entry.getKey()))));
                }
            }
        }
    }

    private void cleanupMethod(RestApi api, Method method, String type, Set<String> parameterSet) {
        if (method.getRequestParameters() != null) {
            method.getRequestParameters().keySet().forEach(key -> {
                final String[] parts = key.split("\\.");
                final String paramType = parts[2];
                final String paramName = parts[3];

                if (paramType.equals(type) && !parameterSet.contains(paramName)) {
                    method.updateMethod(createPatchDocument(createRemoveOperation("/requestParameters/" + key)));
                }
            });
        }
    }

    private String generateModelName(MimeType mimeType) {
        return "model" + UUID.randomUUID().toString().substring(0, 8);
    }

    private void createMethodResponses(RestApi api, Method method, Map<String, Response> responses,
            boolean update) {
        for (Map.Entry<String, Response> entry : responses.entrySet()) {
            createMethodResponse(api, method, entry.getKey(), entry.getValue(), update);
        }

        if (update) {
            cleanupMethodResponses(api, method, responses);
        }
    }

    private void cleanupMethodResponses(RestApi api, Method method, Map<String, Response> responses) {
        method.getMethodResponses().entrySet().forEach(entry -> {
            if (!responses.containsKey(entry.getKey())) {
                entry.getValue().deleteMethodResponse();
            }
        });
    }

    private void createMethodResponse(RestApi api, Method method, String statusCode, Response response,
            boolean update) {
        // TODO: Improve implementation by patching.
        if (update && method.getMethodResponses().containsKey(statusCode)) {
            final MethodResponse methodResponse = method.getMethodResponses().get(statusCode);

            methodResponse.deleteMethodResponse();
        }

        final PutMethodResponseInput input = new PutMethodResponseInput();

        input.setResponseModels(new HashMap<>());
        input.setResponseParameters(new HashMap<>());

        for (Map.Entry<String, Header> entry : response.getHeaders().entrySet()) {
            input.getResponseParameters().put(escapeOperationString("method.response.header." + entry.getKey()),
                    entry.getValue().isRequired());
        }

        if (response.hasBody()) {
            for (Map.Entry<String, MimeType> entry : response.getBody().entrySet()) {
                final String mime = entry.getKey();
                final String modelName = createModel(api, mime, entry.getValue());

                if (modelName != null) {
                    input.getResponseModels().put(mime, modelName);
                }
            }
        }

        method.putMethodResponse(input, statusCode);
    }

    @Nullable
    private String createModel(RestApi api, String mime, MimeType mimeType) {
        final String schema = mimeType.getSchema();

        if (schema != null) {
            if (schema.matches("\\w+")) {
                return schema;
            }

            final String modelName = generateModelName(mimeType);

            models.add(modelName);
            createModel(api, modelName, null, schema, mime);

            return modelName;
        }

        return null;
    }

}