com.amazonaws.service.apigateway.importer.impl.SchemaTransformer.java Source code

Java tutorial

Introduction

Here is the source code for com.amazonaws.service.apigateway.importer.impl.SchemaTransformer.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;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import com.github.fge.jsonschema.core.exceptions.ProcessingException;
import com.github.fge.jsonschema.core.report.ProcessingReport;
import com.github.fge.jsonschema.main.JsonSchemaFactory;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Deserializes and transforms schema schemas into normalized form
 *
 * @author rpgreen
 */
public class SchemaTransformer {
    protected final static Logger LOG = Logger.getLogger(SchemaTransformer.class);

    /**
     * Get a schema schema in "flattened" form whereby all dependent references are resolved
     * and included as inline schema definitions
     *
     * @return the json-schema string in flattened form
     */
    public String flatten(String model, String models) {
        return getFlattened(deserialize(model), deserialize(models));
    }

    public String flatten(String model, String models, List<String> modelNames) {
        return getFlattened(deserialize(model), deserialize(models), modelNames);
    }

    private void buildSchemaReferenceMap(JsonNode model, JsonNode models, Map<String, String> modelMap) {
        Map<JsonNode, JsonNode> refs = new HashMap<>();
        findReferences(model, refs);
        for (JsonNode ref : refs.keySet()) {
            String canonicalRef = ref.textValue();

            String schemaName = getSchemaName(canonicalRef);

            JsonNode subSchema = getSchema(schemaName, models);

            // replace reference values with inline definitions
            replaceRef((ObjectNode) refs.get(ref), schemaName);

            buildSchemaReferenceMap(subSchema, models, modelMap);

            modelMap.put(schemaName, serializeExisting(subSchema));
        }
    }

    private void buildSchemaReferenceMap(JsonNode model, JsonNode models, Map<String, String> modelMap,
            List<String> modelNames) {
        Map<JsonNode, JsonNode> refs = new HashMap<>();
        findReferences(model, refs);

        for (JsonNode ref : refs.keySet()) {
            String canonicalRef = ref.textValue();
            String schemaName = getSchemaName(canonicalRef);
            JsonNode subSchema = getSchema(schemaName, models);
            // replace reference values with inline definitions
            replaceRef((ObjectNode) refs.get(ref), schemaName);
            if (!modelNames.contains(schemaName)) {
                modelNames.add(schemaName);
                buildSchemaReferenceMap(subSchema, models, modelMap, modelNames);
            }
            modelMap.put(schemaName, serializeExisting(subSchema));
        }
    }

    private JsonNode getSchema(String schemaName, JsonNode models) {
        return models.findPath(schemaName);
    }

    private String getFlattened(JsonNode model, JsonNode models) {
        HashMap<String, String> schemaMap = new HashMap<>();
        buildSchemaReferenceMap(model, models, schemaMap);
        replaceRefs(model, schemaMap);
        if (LOG.isTraceEnabled()) {
            try {
                LOG.trace("Flattened schema to: "
                        + new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(model));
            } catch (JsonProcessingException ignored) {
            }
        }

        String flattened = serializeExisting(model);

        validate(model);

        return flattened;
    }

    private String getFlattened(JsonNode model, JsonNode models, List<String> modelNames) {
        HashMap<String, String> schemaMap = new HashMap<>();
        buildSchemaReferenceMap(model, models, schemaMap, modelNames);
        replaceRefs(model, schemaMap);
        removeExampleFromBooleanNode(model);
        if (LOG.isTraceEnabled()) {
            try {
                LOG.trace("Flattened schema to: "
                        + new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(model));
            } catch (JsonProcessingException ignored) {
            }
        }
        String flattened = serializeExisting(model);
        validate(model);
        return flattened;
    }

    private void validate(JsonNode rootNode) {
        final JsonSchemaFactory factory;
        try {
            factory = JsonSchemaFactory.byDefault();
            factory.getJsonSchema(rootNode);
        } catch (ProcessingException e) {
            throw new IllegalStateException("Invalid schema json was generated", e);
        } catch (ExceptionInInitializerError | NoClassDefFoundError e) {
            return; // this should only happen from test code. JsonSchemaFactory not easily mocked
        }

        ProcessingReport report = factory.getSyntaxValidator().validateSchema(rootNode);
        if (!report.isSuccess()) {
            throw new IllegalStateException(
                    "Invalid schema json was generated" + report.iterator().next().getMessage());
        }
    }

    /*
     * Add schema references as inline definitions to the root schema
     */
    private void replaceRefs(JsonNode root, HashMap<String, String> schemaMap) {

        ObjectNode definitionsNode = new ObjectNode(JsonNodeFactory.instance);

        for (Map.Entry<String, String> entry : schemaMap.entrySet()) {
            JsonNode schemaNode = deserialize(entry.getValue());
            definitionsNode.set(entry.getKey(), schemaNode);
        }

        ((ObjectNode) root).set("definitions", definitionsNode);
    }

    /*
     * Replace a reference node with an inline reference
     */
    private void replaceRef(ObjectNode parent, String schemaName) {
        parent.set("$ref", new TextNode("#/definitions/" + schemaName));
    }

    /*
     * Find all reference node in the schema tree. Build a map of the reference node to its parent
     */
    private void findReferences(JsonNode node, Map<JsonNode, JsonNode> refNodes) {
        JsonNode refNode = node.path("$ref");
        if (!refNode.isMissingNode()) {
            refNodes.put(refNode, node);
        }

        for (JsonNode child : node) {
            findReferences(child, refNodes);
        }
    }

    private void removeExampleFromBooleanNode(JsonNode node) {
        JsonNode refNode = node.path("type");
        if (!refNode.isMissingNode()) {
            if (refNode.asText().equalsIgnoreCase("boolean")) {
                ((ObjectNode) node).remove("example");
            }
        }

        for (JsonNode child : node) {
            removeExampleFromBooleanNode(child);
        }
    }

    /*
    * Attempt to serialize an existing schema
    * If this fails something is seriously wrong, because this schema has already been saved by the control plane
    */
    JsonNode deserialize(String schemaText) {
        try {
            if (StringUtils.isBlank(schemaText) || schemaText.equals("null")) {
                schemaText = "{}";
            }
            ObjectMapper mapper = new ObjectMapper();
            return mapper.readTree(schemaText);
        } catch (IOException e) {
            throw new IllegalStateException("Invalid schema found. Could not deserialize schema: " + schemaText, e);
        }
    }

    /*
     * Attempt to serialize an existing schema
     * If this fails something is seriously wrong, because this schema has already been saved by the control plane
     */
    private String serializeExisting(JsonNode root) {
        try {
            return new ObjectMapper().writeValueAsString(root);
        } catch (JsonProcessingException e) {
            throw new IllegalStateException("Could not serialize generated schema json", e);
        }
    }

    public static String getSchemaName(String refVal) {
        String schemaName;
        try {
            schemaName = refVal.substring(refVal.lastIndexOf("/") + 1, refVal.length());
        } catch (Throwable t) {
            throw new IllegalStateException("Invalid reference found: " + refVal, t);
        }

        return schemaName;
    }

    public static String getRestApiId(String refVal) {
        String apiId;
        try {
            apiId = refVal.substring(refVal.indexOf("restapis/"), refVal.length()).split("/")[1];
        } catch (Throwable t) {
            throw new IllegalStateException("Invalid reference found: " + refVal, t);
        }

        return apiId;
    }

}