com.github.pierods.ramltoapidocconverter.RAMLToApidocConverter.java Source code

Java tutorial

Introduction

Here is the source code for com.github.pierods.ramltoapidocconverter.RAMLToApidocConverter.java

Source

/**
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
    
This program 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 General Public License for more details.
    
You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
    
 */
package com.github.pierods.ramltoapidocconverter;

import com.google.gson.Gson;

import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.raml.model.Action;
import org.raml.model.ActionType;
import org.raml.model.Raml;
import org.raml.model.Resource;
import org.raml.model.Response;
import org.raml.model.parameter.QueryParameter;
import org.raml.parser.visitor.RamlDocumentBuilder;
import org.yaml.snakeyaml.Yaml;

import java.io.IOException;
import java.io.StringReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;

import joptsimple.OptionParser;
import joptsimple.OptionSet;

/**
 * RAML is a programmer-friendly representation of an API, whose implementations
 * can be easily tested with RAML-tester To get a correct REST representation of
 * it, we convert it to the Apidoc format. This converter assumes that: 
 * - 1 there are no Resource Types defined in the source RAML file 
 * - 2 no type in Actions (it would be a resource type if there was one) 
 * - 3 there are no Traits defined in the source RAML file 
 * - 4 there is a top level schema defined for every root resource and return type 
 * in the RAML file. 
 * - 5 base URI must not end with a / 
 * - 6 responses must contain a schema: with the name of a previously defined schema
 * (see 4)
 *
 * Created by piero on 4/18/16.
 */
public class RAMLToApidocConverter {

    /**
     * This converter assumes that: 
     * - 1 there are no Resource Types defined in the source RAML file 
     * - 2 there are no Traits defined in the source RAML file 
     * - 3 there is a top level schema defined for every root resource in the RAML
     * file.
     *
     * @param URI a file: or http: resource
     * @return a Json Apidoc string
     */
    public String convert(String URI) {

        Raml raml = new RamlDocumentBuilder().build(URI); // new StringWriter(data), new File("").getPath() for a String

        version = raml.getVersion();

        Apidoc apidoc = new Apidoc();

        apidoc.name = raml.getTitle();
        apidoc.base_url = raml.getBaseUri();
        apidoc.description = raml.getDocumentation().toString();

        List<Map<String, String>> schemas = raml.getSchemas();

        apidoc.models = getModels(schemas);

        Map<String, Resource> ramlResources = raml.getResources();

        apidoc.resources = getResources(ramlResources);

        String result = gson.toJson(apidoc);

        return result;
    }

    public String getVersion(String uriString) throws IOException, URISyntaxException {

        String data;

        if (uriString.startsWith("http")) {

            CloseableHttpClient client = HttpClients.createDefault();
            HttpGet get = new HttpGet(uriString);
            CloseableHttpResponse response = client.execute(get);

            data = response.getEntity().toString();
        } else {
            URI uri = new URI(uriString);
            data = new String(Files.readAllBytes(Paths.get(uri.getRawPath())));
        }

        Yaml yaml = new Yaml();

        Map raml = (Map) yaml.load(new StringReader(data));

        return (String) raml.get("version");
    }

    private class JsonSchema {

        String description;
        List<String> required;
        Map<String, Map<String, String>> properties;
    }

    private Map<String, Apidoc.Model> getModels(List<Map<String, String>> schemas) {

        Map<String, Apidoc.Model> result = new HashMap<>();

        for (Map<String, String> schema : schemas) {

            String modelName = schema.keySet().iterator().next();

            String schemaString = schema.get(modelName);

            JsonSchema jsonSchema = gson.fromJson(schemaString, JsonSchema.class);

            Apidoc.Model model = new Apidoc().new Model();

            model.description = jsonSchema.description;

            List<String> requiredFields = jsonSchema.required;

            Set<String> fieldNames = jsonSchema.properties.keySet();

            List<Apidoc.Field> fields = new ArrayList<>();

            for (String fieldName : fieldNames) {

                Apidoc.Field field = new Apidoc().new Field();

                field.name = fieldName;
                field.type = jsonSchema.properties.get(fieldName).get("type");
                if (requiredFields.contains(fieldName)) {
                    field.required = true;
                }
                fields.add(field);
            }
            model.fields = fields;
            result.put(modelName, model);
        }
        return result;
    }

    /**
     * Scans RAML resources, in the form of /xxx:GET, /xxx/yyy:GET/POST,
     * /zzz:GET, /zzz/jjjj:POST/DELETE into an Apidoc RESTful representation of
     * it, i.e resources/xxx: ops GET path /xxx, GET path /xxx/yyy, POST path
     * /xxx/yyy, resources/zzz: ops GET path /zzz, ops POST path /zzz/jjjj,
     * DELETE path /zzz/jjjj
     *
     * @return a Map of Apidoc resources (top-level resources)
     */
    private Map<String, Apidoc.Resource> getResources(Map<String, Resource> ramlResources) {

        Map<String, Apidoc.Resource> apidocResources = new HashMap<>();

        Set<String> ramlResourceNames = ramlResources.keySet();

        for (String ramlResourceName : ramlResourceNames) {

            Resource ramlResource = ramlResources.get(ramlResourceName);

            String resourceName = ramlResourceName.replaceFirst("/", "");

            Apidoc.Resource resource = new Apidoc().new Resource();

            resource.description = ramlResource.getDescription();
            resource.path = ramlResourceName;
            resource.operations = walkSubresources(ramlResource);

            apidocResources.put(resourceName, resource);
        }
        return apidocResources;
    }

    private List<Apidoc.Operation> walkSubresources(Resource rootResource) {

        List<Apidoc.Operation> operations = new ArrayList<>();

        class NameAndResource {

            public NameAndResource(String name, Resource resource) {
                this.name = name;
                this.resource = resource;
            }

            public String name;
            public Resource resource;
        }

        Queue<NameAndResource> bfsAccumulator = new LinkedList<>();

        // path is specified only once for the resource. Subpaths will be only specified if parameters (:xxx), see getOperations()
        bfsAccumulator.add(new NameAndResource("", rootResource));

        while (!bfsAccumulator.isEmpty()) {

            NameAndResource nr = bfsAccumulator.remove();

            operations.addAll(getOperations(nr.name, nr.resource));

            Map<String, Resource> subresources = nr.resource.getResources();

            for (String resourceName : subresources.keySet()) {
                bfsAccumulator.add(new NameAndResource(nr.name + resourceName, subresources.get(resourceName)));
            }

        }

        operations.sort((operation1, operation2) -> {
            if (operation1.path == null) {
                return 1;
            }
            if (operation2.path == null) {
                return -1;
            }

            return operation1.path.compareTo(operation2.path);
        });

        return operations;
    }

    private List<Apidoc.Operation> getOperations(String resourceName, Resource ramlResource) {

        List<Apidoc.Operation> operations = new ArrayList<>();

        Map<ActionType, Action> ramlActions = ramlResource.getActions();

        Set<ActionType> ramlActionTypes = ramlActions.keySet();

        for (ActionType ramlActionType : ramlActionTypes) {

            Action ramlAction = ramlActions.get(ramlActionType);

            Apidoc.Operation operation = new Apidoc().new Operation();

            operation.method = ramlActionType.name();
            operation.description = ramlAction.getDescription();

            // only specify a path for operation if it is a parameter
            if (resourceName.contains("{")) {
                operation.path = resourceName.replaceAll("\\{", ":").replaceAll("}", "");
            }

            Map<String, QueryParameter> ramlQueryParameters = ramlAction.getQueryParameters();

            operation.parameters = getQueryParameters(ramlQueryParameters);

            operation.responses = getResponses(ramlAction);

            operations.add(operation);
        }

        return operations;
    }

    private Map<String, Apidoc.Response> getResponses(Action ramlAction) {

        Map<String, Response> ramlResponses = ramlAction.getResponses();

        Map<String, Apidoc.Response> responses = new HashMap<>();

        for (String responseCode : ramlResponses.keySet()) {

            Apidoc.Response response = new Apidoc().new Response();

            Response ramlResponse = ramlResponses.get(responseCode);

            response.description = ramlResponse.getDescription();
            response.type = ramlResponse.getBody().get("application/json").getSchema();

            responses.put(responseCode, response);
        }

        return responses;
    }

    private List<Apidoc.Parameter> getQueryParameters(Map<String, QueryParameter> ramlQueryParameters) {

        List<Apidoc.Parameter> parameters = new ArrayList<>();

        Set<String> ramlParameterNames = ramlQueryParameters.keySet();

        for (String ramlParameterName : ramlParameterNames) {

            QueryParameter ramlQueryParameter = ramlQueryParameters.get(ramlParameterName);
            Apidoc.Parameter parameter = new Apidoc().new Parameter();

            parameter.name = ramlParameterName;
            parameter.description = ramlQueryParameter.getDescription();
            parameter.example = ramlQueryParameter.getExample();
            parameter.type = ramlQueryParameter.getType().name();
            parameter.required = ramlQueryParameter.isRequired();

            parameters.add(parameter);
        }

        return parameters;
    }

    public static void main(String[] args) throws IOException, URISyntaxException {

        OptionParser optionParser = new OptionParser("h");

        optionParser.accepts("raml").withRequiredArg();
        optionParser.accepts("apidoc").withRequiredArg();
        optionParser.accepts("version");
        optionParser.accepts("help");

        OptionSet optionSet = optionParser.parse(args);

        if (optionSet.has("help") || optionSet.has("h")) {
            System.out.println("Usage: -raml uri of raml " + "-apidoc name of apidoc file /n" + "or /"
                    + "-raml uri of raml -version");
            System.exit(0);
        }

        if (!optionSet.has("raml")) {
            System.out.println("Missing raml option");
            System.exit(-1);
        }

        URI uri = new URI((String) optionSet.valueOf("raml"));

        if (uri.getScheme() == null) {
            System.out.println("Bad raml uri - should be file:// or http:// ");
            System.exit(-1);
        }

        RAMLToApidocConverter instance = new RAMLToApidocConverter();

        if (optionSet.has("version")) {
            System.out.print(instance.getVersion((String) optionSet.valueOf("raml")));
            return;
        }

        String data = instance.convert((String) optionSet.valueOf("raml"));
        if (optionSet.has("apidoc")) {
            Files.write(Paths.get((String) optionSet.valueOf("apidoc")), data.getBytes());
        } else {
            System.out.print(data);
        }

    }

    private Gson gson = new Gson();
    private String version;
}