com.strandls.alchemy.rest.client.stubgenerator.RestProxyGenerator.java Source code

Java tutorial

Introduction

Here is the source code for com.strandls.alchemy.rest.client.stubgenerator.RestProxyGenerator.java

Source

/*
 * Copyright (C) 2015 Strand Life Sciences.
 *
 * Licensed 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 com.strandls.alchemy.rest.client.stubgenerator;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import javax.ws.rs.Path;

import lombok.Cleanup;
import lombok.Getter;
import lombok.Setter;

import org.apache.commons.lang3.StringUtils;
import org.apache.tools.ant.AntClassLoader;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.reflections.Reflections;
import org.stringtemplate.v4.STGroupDir;

import com.google.common.base.Predicate;
import com.google.common.collect.Sets;
import com.strandls.alchemy.rest.client.RestInterfaceAnalyzer;
import com.sun.codemodel.writer.FileCodeWriter;

/**
 * Ant {@link Task} that scan classpath for matching rest service
 * implementations and generates the stub, proxy implementation sources and
 * corresponding guice bindings.
 *
 * @author Ashish Shinde
 *
 */
@Setter
public class RestProxyGenerator extends Task {
    /**
     * The suffix to be appended to the generated package. Can be
     * <code>null</code>.
     */
    private String classSuffix;

    /**
     * The destination package for generated classes. Can be <code>null</code>
     * or blank string for using the default package.
     */
    private String destinationPackage = "";

    /**
     * Comma separated regexes on canonical class names to exclude.
     */
    private String excludes;

    /**
     * Comma separated regexes on canonical class names to include, .
     */
    private String includes;

    /**
     * The output directory where generated stubs would be added.
     */
    private File outputDir;

    /**
     * The service classes.
     */
    @Getter(lazy = true, onMethod = @_({ @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = {
            "JLM_JSR166_UTILCONCURRENT_MONITORENTER",
            "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE" }, justification = "Findbugs warnings on lombok generated code not critical."),
            @SuppressWarnings("unchecked") }))
    private final Set<Class<?>> serviceClasses = findServiceClasses();

    /*
     * (non-Javadoc)
     * @see org.apache.tools.ant.Task#execute()
     */
    @Override
    public void execute() {
        // validate parameters
        validate();

        log("Starting client code generation", Project.MSG_ERR);
        try {
            // generate service stubs.
            generateServiceStubs();

            // generate proxy implementations.
            generateProxyImplementations();

            // generate guice module with stub to proxy bindings.
            generateGuiceModule();
        } catch (final IOException e) {
            throw new BuildException(e);
        }

        log("Finished client code generation", Project.MSG_ERR);

    }

    /**
     * @return the rest webservice classes to process.
     * @throws MalformedURLException
     */
    private Set<Class<?>> findServiceClasses() {
        final List<Object> params = new ArrayList<Object>();
        final ClassLoader loader = getClass().getClassLoader();
        if (loader instanceof AntClassLoader) {
            final String[] path = ((AntClassLoader) loader).getClasspath()
                    .split(System.getProperty("path.separator"));
            for (final String string : path) {
                try {
                    params.add(new URL(string));
                } catch (final MalformedURLException e) {
                    try {
                        params.add(new URL("file://" + string));
                    } catch (final MalformedURLException e1) {
                        throw new RuntimeException(e1);
                    }
                }
            }

        }

        final Reflections reflections = new Reflections(params.toArray(new Object[0]));
        final Set<Class<?>> allRestServices = reflections.getTypesAnnotatedWith(Path.class);
        log("Locating service classes", Project.MSG_ERR);

        final Set<Class<?>> filtered = Sets.filter(allRestServices, new Predicate<Class<?>>() {
            @Override
            public boolean apply(final Class<?> input) {
                return doesMatch(input, includes) && !doesMatch(input, excludes);
            }

            /**
             * Indicates if the input class matches any one of the comma
             * separated regex patterns.
             *
             * <code>null</code> or empty pattern implies no match.
             *
             * @param input
             *            the input class.
             * @param patterns
             *            comma separated list of patterns.
             *
             * @return <code>true</code> if any one of the pattern matches the
             *         canonical name of the class.
             */
            private boolean doesMatch(final Class<?> input, final String patterns) {
                boolean matches = false;
                if (patterns != null) {
                    for (final String include : patterns.split("\\s*,\\s*")) {
                        matches |= Pattern.matches(include, input.getCanonicalName());
                    }
                }
                return matches;
            }
        });

        for (final Class<?> klass : filtered) {
            log("Will process class: " + klass.getCanonicalName(), Project.MSG_ERR);
        }

        return filtered;
    }

    /**
     * Generate the guice module with bindings.
     *
     * @throws IOException
     */
    private void generateGuiceModule() throws IOException {
        final Set<Class<?>> classes = getServiceClasses();
        new STGroupDir(getClass().getPackage().getName().replaceAll("\\.", "/"));
        final File generatedSourceDirectory = getTargetSourceDirectory();

        final Map<String, String> stubProxyMap = new HashMap<>();
        for (final Class<?> klass : classes) {
            stubProxyMap.put(getStubClassName(klass), getProxyImplemenationName(klass));
        }

        log("Generating guice module", Project.MSG_ERR);

        final GuiceModuleGenerator moduleGenerator = new GuiceModuleGenerator();
        moduleGenerator.generateGuiceModule(destinationPackage, generatedSourceDirectory, stubProxyMap);
    }

    /**
     * Generate the proxy implementations for the services.
     *
     * @throws IOException
     */
    private void generateProxyImplementations() throws IOException {
        final Set<Class<?>> classes = getServiceClasses();
        new STGroupDir(getClass().getPackage().getName().replaceAll("\\.", "/"));
        final File generatedSourceDirectory = getTargetSourceDirectory();

        log("Generating proxies", Project.MSG_ERR);

        final ProxyImplementationGenerator proxyGenerator = new ProxyImplementationGenerator();
        for (final Class<?> klass : classes) {
            proxyGenerator.generateProxy(getStubClassName(klass), getProxyImplemenationName(klass),
                    destinationPackage, generatedSourceDirectory);
            log("Generated " + getProxyImplemenationName(klass), Project.MSG_INFO);
        }

    }

    /**
     * Generate service stubs.
     *
     * @throws IOException
     */
    private void generateServiceStubs() throws IOException {
        final ServiceStubGenerator stubGenerator = new ServiceStubGenerator(new RestInterfaceAnalyzer());

        final File generatedSourceDirectory = outputDir;

        final Set<Class<?>> classes = getServiceClasses();
        @Cleanup
        final FileCodeWriter codeWriter = new FileCodeWriter(generatedSourceDirectory);

        log("Generating service stubs", Project.MSG_ERR);

        for (final Class<?> klass : classes) {
            final String stubClassName = getStubClassName(klass);
            try {

                stubGenerator.generateStubInterface(klass, stubClassName, destinationPackage, codeWriter);
                log("Generated " + stubClassName, Project.MSG_INFO);
            } catch (final Exception e) {
                log("Stub generation failed for " + klass.getCanonicalName(), Project.MSG_ERR);
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * The name of the generated proxy implementation.
     *
     * @param klass
     *            the service class.
     * @return the name of the proxy implemenation
     */
    private String getProxyImplemenationName(final Class<?> klass) {
        return getStubClassName(klass) + "Proxy";
    }

    /**
     * Return the name of the generated stub class.
     *
     * @param klass
     *            the service class.
     * @return the name of the generated stub class.
     */
    private String getStubClassName(final Class<?> klass) {
        return klass.getSimpleName() + (!StringUtils.isBlank(classSuffix) ? classSuffix : "");
    }

    /**
     * @return the target generates source directory.
     */
    private File getTargetSourceDirectory() {
        final String packageFolder = !StringUtils.isBlank(destinationPackage)
                ? destinationPackage.replaceAll("\\.", File.separator)
                : "";
        final File generatedSourceDirectory = !StringUtils.isBlank(packageFolder)
                ? new File(outputDir, packageFolder)
                : outputDir;

        if (!generatedSourceDirectory.isDirectory() && !generatedSourceDirectory.mkdirs()) {
            throw new RuntimeException("Could not create " + generatedSourceDirectory);
        }
        return generatedSourceDirectory;
    }

    /**
     * Validate the arguments.
     */
    private void validate() {
        if (outputDir == null || (outputDir.exists() && !outputDir.isDirectory())) {
            throw new IllegalArgumentException("Not a valid directory : " + outputDir);
        }

        if (!outputDir.isDirectory() && !outputDir.mkdirs()) {
            throw new RuntimeException("Could not create " + outputDir);
        }
    }
}