org.jooby.JoobyMojo.java Source code

Java tutorial

Introduction

Here is the source code for org.jooby.JoobyMojo.java

Source

/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.jooby;

import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import org.apache.maven.Maven;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.execution.DefaultMavenExecutionRequest;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Resource;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Execute;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import org.jooby.run.Watcher;

import javaslang.control.Try;

@Mojo(name = "run", threadSafe = true, requiresDependencyResolution = ResolutionScope.TEST)
@Execute(phase = LifecyclePhase.TEST_COMPILE)
public class JoobyMojo extends AbstractMojo {

    private static class ShutdownHook extends Thread {
        private Log log;

        private List<Command> commands;

        private Watcher watcher;

        public ShutdownHook(final Log log, final List<Command> commands) {
            this.log = log;
            this.commands = commands;
            setDaemon(true);
        }

        @Override
        public void run() {
            if (watcher != null) {
                log.info("stopping: watcher");
                Try.run(watcher::stop).onFailure(ex -> log.debug("Stop of watcher resulted in error", ex));
            }
            commands.forEach(cmd -> {
                log.info("stopping: " + cmd);
                Try.run(cmd::stop).onFailure(ex -> log.error("Stop of " + cmd + " resulted in error", ex));
            });
        }
    }

    @Parameter(defaultValue = "${project}", required = true, readonly = true)
    private MavenProject mavenProject;

    @Parameter(defaultValue = "${session}", required = true, readonly = true)
    protected MavenSession session;

    @Parameter(property = "main.class", defaultValue = "${application.class}")
    protected String mainClass;

    @Parameter(defaultValue = "${project.build.outputDirectory}")
    private String buildOutputDirectory;

    @Parameter(property = "jooby.commands")
    private List<ExternalCommand> commands;

    @Parameter(property = "jooby.vmArgs")
    private List<String> vmArgs;

    @Parameter(property = "jooby.includes")
    private List<String> includes;

    @Parameter(property = "jooby.watchDirs")
    private List<String> watchDirs;

    @Parameter(property = "jooby.excludes")
    private List<String> excludes;

    @Parameter(property = "application.debug", defaultValue = "true")
    private String debug;

    @Parameter(defaultValue = "${plugin.artifacts}")
    private List<org.apache.maven.artifact.Artifact> pluginArtifacts;

    @Parameter(property = "compiler", defaultValue = "on")
    private String compiler;

    @Component
    protected Maven maven;

    @Parameter(property = "application.fork", defaultValue = "false")
    private boolean fork = false;

    @SuppressWarnings("unchecked")
    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {

        Set<File> appcp = new LinkedHashSet<>();

        // public / config, etc..
        appcp.addAll(resources(mavenProject.getResources()));

        // target/classes
        appcp.add(new File(buildOutputDirectory));

        // references project
        Set<Artifact> references = references(mavenProject);
        Set<File> refbasedir = refbasedir(mavenProject, references);
        Set<File> refcp = refcp(refbasedir);
        appcp.addAll(refcp);

        // *.jar
        Set<Artifact> artifacts = new LinkedHashSet<Artifact>(mavenProject.getArtifacts());

        artifacts.forEach(artifact -> {
            if (!"pom".equals(artifact.getType())) {
                // ignore self reference
                appcp.add(new File(artifact.getFile().getAbsolutePath()));
            }
        });

        Set<File> classpath = new LinkedHashSet<>();

        File hotreload = extra(pluginArtifacts, "jooby-run").get();
        File jbossModules = extra(pluginArtifacts, "jboss-modules").get();
        classpath.add(hotreload);
        classpath.add(jbossModules);

        // prepare commands
        List<Command> cmds = new ArrayList<>();
        if (commands != null && commands.size() > 0) {
            cmds.addAll(this.commands);
        }

        // watch dir
        List<File> watchDirs = new ArrayList<>();
        watchDirs.add(mavenProject.getBasedir());
        watchDirs.addAll(refbasedir);
        if (this.watchDirs != null) {
            this.watchDirs.forEach(f -> watchDirs.add(new File(f)));
        }

        // includes/excludes pattern
        String includes = null;
        if (this.includes != null && this.includes.size() > 0) {
            includes = this.includes.stream().collect(Collectors.joining(File.pathSeparator));
        }
        String excludes = null;
        if (this.excludes != null && this.excludes.size() > 0) {
            excludes = this.excludes.stream().collect(Collectors.joining(File.pathSeparator));
        }
        String watchDirStr = watchDirs.stream().filter(File::exists).map(File::getAbsolutePath)
                .collect(Collectors.joining(File.pathSeparator));
        // moduleId
        String mId = mavenProject.getGroupId() + "." + mavenProject.getArtifactId();

        // logback and application.version
        setLogback();
        System.setProperty("application.version", mavenProject.getVersion());

        // fork?
        Command runapp = fork
                ? new RunForkedApp(mavenProject.getBasedir(), debug, vmArgs, classpath, mId, mainClass, appcp,
                        includes, excludes, watchDirStr)
                : new RunApp(mId, mainClass, appcp, includes, excludes, watchDirs);

        // run app at the end
        cmds.add(runapp);

        for (Command cmd : cmds) {
            cmd.setWorkdir(mavenProject.getBasedir());
            getLog().debug("cmd: " + cmd.debug());
        }

        Watcher watcher = setupCompiler(mavenProject, compiler, goal -> {
            maven.execute(DefaultMavenExecutionRequest.copy(session.getRequest()).setGoals(Arrays.asList(goal)));
        });

        ShutdownHook shutdownHook = new ShutdownHook(getLog(), cmds);
        shutdownHook.watcher = watcher;
        /**
         * Shutdown hook
         */
        Runtime.getRuntime().addShutdownHook(shutdownHook);

        if (watcher != null) {
            watcher.start();
        }

        /**
         * Start process
         */
        for (Command cmd : cmds) {
            try {
                getLog().debug("Starting process: " + cmd.debug());
                cmd.execute();
            } catch (Exception ex) {
                throw new MojoFailureException("Execution of " + cmd + " resulted in error", ex);
            }
        }

    }

    @SuppressWarnings("unchecked")
    private Set<Artifact> references(final MavenProject project) {
        MavenProject parent = project.getParent();
        if (parent != null) {
            List<String> modules = parent.getModules();
            if (modules != null) {
                Set<Artifact> artifacts = new LinkedHashSet<Artifact>(mavenProject.getArtifacts());
                String groupId = project.getGroupId();
                String version = project.getVersion();
                return artifacts.stream().filter(a -> a.getGroupId().equals(groupId)
                        && a.getVersion().equals(version) && modules.contains(a.getArtifactId()))
                        .collect(Collectors.toSet());
            }
        }
        return Collections.emptySet();
    }

    private Set<File> refbasedir(final MavenProject project, final Set<Artifact> references) {
        Set<File> cp = new LinkedHashSet<>();
        for (Artifact reference : references) {
            File basedir = project.getParent().getBasedir();
            cp.add(new File(basedir, reference.getArtifactId()));
        }
        return cp;
    }

    private Set<File> refcp(final Set<File> files) {
        Set<File> cp = new LinkedHashSet<>();
        for (File basedir : files) {
            cp.add(new File(new File(basedir, "target"), "classes"));
        }
        return cp;
    }

    @SuppressWarnings("unchecked")
    private static Watcher setupCompiler(final MavenProject project, final String compiler,
            final Consumer<String> task) throws MojoFailureException {
        File eclipseClasspath = new File(project.getBasedir(), ".classpath");
        if ("off".equalsIgnoreCase(compiler) || eclipseClasspath.exists()) {
            return null;
        }
        List<File> resources = resources(project.getResources());
        resources.add(0, new File(project.getBuild().getSourceDirectory()));
        List<Path> paths = resources.stream().filter(File::exists).map(File::toPath).collect(Collectors.toList());
        try {
            ClassLoader backloader = Thread.currentThread().getContextClassLoader();
            return new Watcher((kind, path) -> {
                ClassLoader currentloader = Thread.currentThread().getContextClassLoader();
                try {
                    Thread.currentThread().setContextClassLoader(backloader);
                    if (path.toString().endsWith(".java")) {
                        task.accept("compile");
                    } else if (path.toString().endsWith(".conf") || path.toString().endsWith(".properties")) {
                        task.accept("compile");
                    }
                } finally {
                    Thread.currentThread().setContextClassLoader(currentloader);
                }
            }, paths.toArray(new Path[paths.size()]));
        } catch (Exception ex) {
            throw new MojoFailureException("Can't compile source code", ex);
        }
    }

    private void setLogback() {
        // logback
        File[] logbackFiles = { localFile("conf", "logback-test.xml"), localFile("conf", "logback.dev.xml"),
                localFile("conf", "logback.xml") };
        for (File logback : logbackFiles) {
            if (logback.exists()) {
                System.setProperty("logback.configurationFile", logback.getAbsolutePath());
                break;
            }
        }
    }

    private File localFile(final String... paths) {
        File result = mavenProject.getBasedir();
        for (String path : paths) {
            result = new File(result, path);
        }
        return result;
    }

    private static List<File> resources(final Iterable<Resource> resources) {
        List<File> result = new ArrayList<>();
        for (Resource resource : resources) {
            String dir = resource.getDirectory();
            File file = new File(dir);
            if (file.exists()) {
                result.add(file);
            }
        }
        return result;
    }

    private Optional<File> extra(final List<Artifact> artifacts, final String name) {
        for (Artifact artifact : artifacts) {
            for (String tail : artifact.getDependencyTrail()) {
                if (tail.contains(name)) {
                    return Optional.of(artifact.getFile());
                }
            }
        }
        return Optional.empty();
    }

}