Package org.candlepin.util.apicrawl

Source Code of org.candlepin.util.apicrawl.ApiCrawler$RestApiCall$ApiParam

/**
* Copyright (c) 2009 - 2012 Red Hat, Inc.
*
* This software is licensed to you under the GNU General Public License,
* version 2 (GPLv2). There is NO WARRANTY for this software, express or
* implied, including the implied warranties of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
* along with this software; if not, see
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
*
* Red Hat trademarks are not licensed under GPLv2. No permission is
* granted to use or replicate Red Hat trademarks that are incorporated
* in this software or its documentation.
*/
package org.candlepin.util.apicrawl;

import org.candlepin.auth.interceptor.Verify;
import org.candlepin.guice.HttpMethodMatcher;
import org.candlepin.resource.RootResource;
import org.candlepin.resteasy.JsonProvider;

import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.module.jsonSchema.JsonSchema;
import com.fasterxml.jackson.module.jsonSchema.JsonSchemaGenerator;
import com.google.inject.matcher.Matcher;

import java.io.FileWriter;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.LinkedList;
import java.util.List;

import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.HEAD;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;

/**
* ApiCrawler - Uses Java Reflection API to crawl the classes in our resources
* namespace looking for exposed API calls.
*/
public class ApiCrawler {
    // Let's just set it to pretty_print the output instead of having
    // to construct a configuration only to get a simple value.
    private ObjectMapper mapper = new JsonProvider(true)
            .locateMapper(Object.class, MediaType.APPLICATION_JSON_TYPE);
    private List<Class> httpClasses;
    private NonRecursiveModule dontRecurse;

    public ApiCrawler() {
        httpClasses = new LinkedList<Class>();
        httpClasses.add(GET.class);
        httpClasses.add(POST.class);
        httpClasses.add(PUT.class);
        httpClasses.add(DELETE.class);
        httpClasses.add(HEAD.class);

        // jackson schema generation in the 1.x series breaks when you have a cycle in your
        // schema graph (ie, owner defines a parent owner. even though they aren't the same
        // instance in an instantiated object, the class refers to itself).
        //
        // This bug isn't likely to get fixed in 1.x, so we work around it by defining this
        // module that keeps track of seen classes, and when it sees one again, just
        // outputs an empty schema. We have to reset the seen classes between method
        // generation, so that each REST API definition will still have a proper toplevel
        // return type.
        //
        // for more info, see http://jira.codehaus.org/browse/JACKSON-439
        dontRecurse = new NonRecursiveModule(mapper);
        mapper.registerModule(dontRecurse);
    }

    public void run(String apiFile) throws IOException {
        List<RestApiCall> allApiCalls = new LinkedList<RestApiCall>();
        for (Object o : RootResource.RESOURCE_CLASSES.keySet()) {
            if (o instanceof Class) {
                Class c = (Class) o;
                allApiCalls.addAll(processClass(c));
            }
        }

        // we need a different mapper to write the output, one without our
        // schema hack module installed.
        // Let's just set it to pretty_print the output instead of having
        // to construct a configuration only to get a simple value.
        ObjectMapper mapper = new JsonProvider(true)
            .locateMapper(Object.class, MediaType.APPLICATION_JSON_TYPE);
        FileWriter jsonFile = new FileWriter(apiFile);
        try {
            mapper.writeValue(jsonFile, allApiCalls);
        }
        finally {
            jsonFile.close();
        }
    }

    private List<RestApiCall> processClass(Class c) {
        Path a = (Path) c.getAnnotation(Path.class);
        String parentUrl = a.value();
        List<RestApiCall> classApiCalls = new LinkedList<RestApiCall>();
        Matcher<Method> matcher = new HttpMethodMatcher();
        for (Method m : c.getMethods()) {
            if (Modifier.isPublic(m.getModifiers()) && matcher.matches(m)) {
                classApiCalls.add(processMethod(parentUrl, m));
            }
        }
        return classApiCalls;
    }

    private RestApiCall processMethod(String rootPath, Method m) {
        RestApiCall apiCall = new RestApiCall();

        processPath(rootPath, m, apiCall);
        processHttpVerb(m, apiCall);
        processQueryParams(m, apiCall);
        processVerifiedParams(m, apiCall);

        dontRecurse.resetSeen();
        try {
            apiCall.setReturnType(getReturnType(m));
        }
        catch (JsonMappingException e) {
            apiCall.setReturnType(null);
        }

        // for matching up with documentation
        apiCall.setMethod(getQualifiedName(m));

        return apiCall;
    }

    private void processPath(String rootPath, Method m, RestApiCall apiCall) {
        Path subPath = m.getAnnotation(Path.class);
        if (subPath != null) {
            if (rootPath.endsWith("/") || subPath.value().startsWith("/")) {
                apiCall.setUrl(rootPath + subPath.value());
            }
            else {
                apiCall.setUrl(rootPath + "/" + subPath.value());
            }
        }
        else {
            apiCall.setUrl(rootPath);
        }
    }

    private void processHttpVerb(Method m, RestApiCall apiCall) {
        for (Class httpClass : httpClasses) {
            if (m.getAnnotation(httpClass) != null) {
                apiCall.addHttpVerb(httpClass.getSimpleName());
            }
        }
    }

    private void processQueryParams(Method m, RestApiCall apiCall) {
        for (int i = 0; i < m.getParameterAnnotations().length; i++) {
            for (Annotation a : m.getParameterAnnotations()[i]) {
                if (a instanceof QueryParam) {
                    String type = m.getParameterTypes()[i].getSimpleName().toLowerCase();
                    apiCall.addQueryParam(((QueryParam) a).value(), type);
                }

            }
        }
    }

    /* Find parameters that are run through the security interceptor.
     * right now this expects them to only be path params (not query params),
     * but you can have more than one per method.
     */
    private void processVerifiedParams(Method m, RestApiCall apiCall) {
        for (int i = 0; i < m.getParameterAnnotations().length; i++) {
            boolean hasVerify = false;
            String pathName = null;
            for (Annotation a : m.getParameterAnnotations()[i]) {
                if (a instanceof Verify) {
                    hasVerify = true;
                }
                else if (a instanceof PathParam) {
                    PathParam p = (PathParam) a;
                    pathName = p.value();
                }
            }

            if (hasVerify && pathName != null) {
                apiCall.verifiedParams.add(pathName);
            }
        }
    }

    private JsonSchema getReturnType(Method method) throws JsonMappingException {
        Class<?> returnType = method.getReturnType();
        if (returnType.equals(Void.TYPE)) {
            return null;
        }
        JsonSchemaGenerator generator = new JsonSchemaGenerator(mapper);
        return generator.generateSchema(method.getReturnType());
    }

    private static String getQualifiedName(Method method) {
        return method.getDeclaringClass().getName() + "." + method.getName();
    }

    /**
     * RestApiCall: Helper object for storing information about an API method.
     */
    static class RestApiCall {
        private String method;
        private String url;
        private List<String> verifiedParams;
        private List<String> httpVerbs;
        private List<ApiParam> queryParams;
        private JsonSchema returnType;

        public RestApiCall() {
            httpVerbs = new LinkedList<String>();
            queryParams = new LinkedList<ApiParam>();

            // these are the names of the security enforced path params
            verifiedParams = new LinkedList<String>();
        }

        public void setMethod(String method) {
            this.method = method;
        }

        public void setUrl(String url) {
            this.url = url;
        }

        public void addHttpVerb(String verb) {
            this.httpVerbs.add(verb);
        }

        public void addQueryParam(String name, String type) {
            queryParams.add(new ApiParam(name, type));
        }

        public void setReturnType(JsonSchema type) {
            returnType = type;
        }

        public JsonSchema getReturnType() {
            return returnType;
        }

        public List<String> getHttpVerbs() {
            return httpVerbs;
        }

        public List<String> getVerifiedParams() {
            return verifiedParams;
        }

        public List<ApiParam> getQueryParams() {
            return queryParams;
        }

        public String getUrl() {
            return url;
        }

        public String getMethod() {
            return method;
        }

        private static class ApiParam {
            private String name;
            private String type;

            public ApiParam(String name, String type) {
                this.name = name;
                this.type = type;
            }

            public String getName() {
                return name;
            }

            public String getType() {
                return type;
            }
        }
    }

    public static void main(String [] args) throws Exception {
        ApiCrawler crawler = new ApiCrawler();
        if (args.length != 1) {
            throw new IllegalArgumentException("Must provide file name to write to");
        }
        crawler.run(args[0]);
    }
}
TOP

Related Classes of org.candlepin.util.apicrawl.ApiCrawler$RestApiCall$ApiParam

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.