net.officefloor.maven.StartOfficeBuildingGoal.java Source code

Java tutorial

Introduction

Here is the source code for net.officefloor.maven.StartOfficeBuildingGoal.java

Source

/*
 * OfficeFloor - http://www.officefloor.net
 * Copyright (C) 2005-2013 Daniel Sagenschneider
 *
 * 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 net.officefloor.maven;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;

import net.officefloor.building.command.parameters.KeyStoreOfficeFloorCommandParameter;
import net.officefloor.building.command.parameters.KeyStorePasswordOfficeFloorCommandParameter;
import net.officefloor.building.command.parameters.OfficeBuildingPortOfficeFloorCommandParameter;
import net.officefloor.building.command.parameters.PasswordOfficeFloorCommandParameter;
import net.officefloor.building.command.parameters.UsernameOfficeFloorCommandParameter;
import net.officefloor.building.decorate.OfficeFloorDecorator;
import net.officefloor.building.manager.OfficeBuildingManager;
import net.officefloor.building.process.ProcessConfiguration;
import net.officefloor.building.process.ProcessOutputStreamFactory;
import net.officefloor.console.OfficeBuilding;
import net.officefloor.maven.classpath.ClassPathFactory;
import net.officefloor.maven.classpath.ClassPathFactoryImpl;
import net.officefloor.maven.classpath.RemoteRepository;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.repository.ArtifactRepository;
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.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.PlexusContainer;
import org.eclipse.aether.RepositorySystem;

/**
 * Maven goal to start the {@link OfficeBuilding}.
 * 
 * @author Daniel Sagenschneider
 */
@Mojo(name = "start", requiresDependencyResolution = ResolutionScope.COMPILE)
public class StartOfficeBuildingGoal extends AbstractGoal {

    /**
     * Default {@link OfficeBuilding} port.
     */
    public static final Integer DEFAULT_OFFICE_BUILDING_PORT = Integer
            .valueOf(OfficeBuildingPortOfficeFloorCommandParameter.DEFAULT_OFFICE_BUILDING_PORT);

    /**
     * Makes the default key store file available.
     * 
     * @return Key store {@link File}.
     * @throws MojoFailureException
     *             If fails to obtain the key store {@link File}.
     */
    public static File getKeyStoreFile() throws MojoFailureException {
        try {
            return KeyStoreOfficeFloorCommandParameter.getDefaultKeyStoreFile();
        } catch (IOException ex) {
            throw new MojoFailureException("Failed making default key store available: " + ex.getMessage() + " ["
                    + ex.getClass().getName() + "]");
        }
    }

    /**
     * Key store {@link File} password.
     */
    public static final String KEY_STORE_PASSWORD = KeyStorePasswordOfficeFloorCommandParameter.DEFAULT_KEY_STORE_PASSWORD;

    /**
     * Client user name.
     */
    public static final String USER_NAME = UsernameOfficeFloorCommandParameter.DEFAULT_USER_NAME;

    /**
     * Client password.
     */
    public static final String PASSWORD = PasswordOfficeFloorCommandParameter.DEFAULT_PASSWORD;

    /**
     * Creates the {@link StartOfficeBuildingGoal} with the required parameters.
     * 
     * @param project
     *            {@link MavenProject}.
     * @param pluginDependencies
     *            Plug-in dependencies.
     * @param localRepository
     *            Local repository.
     * @param repositorySystem
     *            {@link RepositorySystem}.
     * @param plexusContainer
     *            {@link PlexusContainer}.
     * @param log
     *            {@link Log}.
     * @return {@link StartOfficeBuildingGoal}.
     */
    public static StartOfficeBuildingGoal createStartOfficeBuildingGoal(MavenProject project,
            List<Artifact> pluginDependencies, ArtifactRepository localRepository,
            RepositorySystem repositorySystem, PlexusContainer plexusContainer, Log log) {
        StartOfficeBuildingGoal goal = new StartOfficeBuildingGoal();
        goal.project = project;
        goal.pluginDependencies = pluginDependencies;
        goal.localRepository = localRepository;
        goal.repositorySystem = repositorySystem;
        goal.plexusContainer = plexusContainer;
        goal.setLog(log);
        return goal;
    }

    /**
     * {@link MavenProject}.
     */
    @Parameter(defaultValue = "${project}", required = true)
    private MavenProject project;

    /**
     * Plug-in dependencies.
     */
    @Parameter(defaultValue = "${plugin.artifacts}", required = true)
    private List<Artifact> pluginDependencies;

    /**
     * Local repository.
     */
    @Parameter(defaultValue = "${localRepository}", required = true)
    private ArtifactRepository localRepository;

    /**
     * {@link RepositorySystem}.
     */
    @Component
    private RepositorySystem repositorySystem;

    /**
     * {@link PlexusContainer}.
     */
    @Component
    private PlexusContainer plexusContainer;

    /**
     * Port to run the {@link OfficeBuilding} on.
     */
    @Parameter(property = "port")
    private Integer port = DEFAULT_OFFICE_BUILDING_PORT;

    /**
     * {@link PluginDependencyInclusion} instances.
     */
    private final List<PluginDependencyInclusion> dependencyInclusions = new ArrayList<PluginDependencyInclusion>(
            2);

    /**
     * Initiate.
     */
    public StartOfficeBuildingGoal() {
        // Always include the OfficeBuilding
        this.includePluginDependencyToOfficeBuildingClassPath(OfficeBuildingManager.OFFICE_BUILDING_GROUP_ID,
                OfficeBuildingManager.OFFICE_BUILDING_ARTIFACT_ID, "jar", null);
    }

    /**
     * <p>
     * Provides ability to selectively include plugin dependencies on the class
     * path of the started {@link OfficeBuilding}.
     * <p>
     * This allows for additional plugins such {@link OfficeFloorDecorator}
     * instances.
     * <p>
     * Please note that it must be a dependency of the plugin. Not found
     * dependencies will result in a build failure.
     * <p>
     * The version is derived from the maven configuration.
     * 
     * @param groupId
     *            Group Id of the dependency.
     * @param artifactId
     *            Artifact Id of the dependency.
     * @param type
     *            Type of the dependency.
     * @param classifier
     *            Classifier. May be <code>null</code>.
     */
    public void includePluginDependencyToOfficeBuildingClassPath(String groupId, String artifactId, String type,
            String classifier) {
        this.dependencyInclusions.add(new PluginDependencyInclusion(groupId, artifactId, type, classifier));
    }

    /*
     * ======================== Mojo ==========================
     */

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

        // Ensure have configured values
        ensureNotNull("Must have project", this.project);
        ensureNotNull("Must have plug-in dependencies", this.pluginDependencies);
        ensureNotNull("Must have local repository", this.localRepository);
        ensureNotNull("Must have repository system", this.repositorySystem);
        ensureNotNull("Port not configured for the " + OfficeBuilding.class.getSimpleName(), this.port);

        // Indicate the configuration
        final Log log = this.getLog();
        log.debug(OfficeBuilding.class.getSimpleName() + " configuration:");
        log.debug("\tPort = " + this.port);

        // Create the environment properties
        Properties environment = new Properties();
        environment.putAll(this.project.getProperties());

        // Log the properties
        log.debug("\tProperties:");
        for (String propertyName : environment.stringPropertyNames()) {
            log.debug("\t\t" + propertyName + " = " + environment.getProperty(propertyName));
        }

        // Obtain the plugin dependency inclusions
        List<Artifact> artifactInclusions = new ArrayList<Artifact>(this.pluginDependencies.size());
        for (PluginDependencyInclusion inclusion : this.dependencyInclusions) {

            // Must match on dependency for inclusion
            Artifact includedDependency = null;
            for (Artifact dependency : this.pluginDependencies) {
                if ((inclusion.groupId.equals(dependency.getGroupId()))
                        && (inclusion.artifactId.equals(dependency.getArtifactId()))
                        && (inclusion.type.equals(dependency.getType())) && ((inclusion.classifier == null)
                                || (inclusion.classifier.equals(dependency.getClassifier())))) {
                    // Found the dependency to include
                    includedDependency = dependency;
                }
            }

            // Ensure have dependency for inclusion
            if (includedDependency == null) {
                throw newMojoExecutionException("Failed to obtain plug-in dependency " + inclusion.groupId + ":"
                        + inclusion.artifactId + (inclusion.classifier == null ? "" : ":" + inclusion.classifier)
                        + ":" + inclusion.type, null);

            }

            // Include the dependency
            artifactInclusions.add(includedDependency);
        }

        // Obtain the class path for OfficeBuilding
        String classPath = null;
        try {

            // Indicate the remote repositories
            log.debug("\tRemote repositories:");

            // Obtain remote repositories and load to class path factory
            List<RemoteRepository> remoteRepositories = new LinkedList<RemoteRepository>();
            List<String> urls = new LinkedList<String>();
            for (Object object : this.project.getRemoteArtifactRepositories()) {
                ArtifactRepository repository = (ArtifactRepository) object;
                String remoteRepositoryUrl = repository.getUrl();
                remoteRepositories.add(new RemoteRepository(repository.getId(), repository.getLayout().getId(),
                        remoteRepositoryUrl));
                urls.add(remoteRepositoryUrl);

                // Indicate the remote repository
                log.debug("\t\t" + remoteRepositoryUrl);
            }

            // Create the class path factory and add remote repositories
            File localRepositoryDirectory = new File(this.localRepository.getBasedir());
            ClassPathFactory classPathFactory = new ClassPathFactoryImpl(this.plexusContainer,
                    this.repositorySystem, localRepositoryDirectory,
                    remoteRepositories.toArray(new RemoteRepository[remoteRepositories.size()]));

            // Indicate the class path
            log.debug("\tClass path:");

            // Obtain the class path entries for each included artifact
            List<String> classPathEntries = new LinkedList<String>();
            for (Artifact dependency : artifactInclusions) {

                // Obtain the class path entries for the dependency
                String[] entries = classPathFactory.createArtifactClassPath(dependency.getGroupId(),
                        dependency.getArtifactId(), dependency.getVersion(), dependency.getType(),
                        dependency.getClassifier());

                // Uniquely include the class path entries
                for (String entry : entries) {
                    if (classPathEntries.contains(entry)) {
                        continue; // ignore as already included
                    }
                    classPathEntries.add(entry);

                    // Indicate class path entry
                    log.debug("\t\t" + entry);
                }
            }

            // Obtain the class path
            classPath = ClassPathFactoryImpl.transformClassPathEntriesToClassPath(
                    classPathEntries.toArray(new String[classPathEntries.size()]));

        } catch (Exception ex) {
            throw newMojoExecutionException("Failed obtaining dependencies for launching OfficeBuilding", ex);
        }

        // Create the process configuration
        ProcessConfiguration configuration = new ProcessConfiguration();
        configuration.setAdditionalClassPath(classPath);

        // Write output to file
        configuration.setProcessOutputStreamFactory(new ProcessOutputStreamFactory() {

            @Override
            public OutputStream createStandardProcessOutputStream(String processNamespace, String[] command)
                    throws IOException {

                // Log the command
                StringBuilder commandLine = new StringBuilder();
                commandLine.append(OfficeBuilding.class.getSimpleName() + " command line:");
                for (String commandItem : command) {
                    commandLine.append(" ");
                    commandLine.append(commandItem);
                }
                log.debug(commandLine);

                // Return the output stream
                return this.getOutputStream(processNamespace);
            }

            @Override
            public OutputStream createErrorProcessOutputStream(String processNamespace) throws IOException {
                return this.getOutputStream(processNamespace);
            }

            /**
             * Lazy instantiated {@link OutputStream}.
             */
            private OutputStream outputStream = null;

            /**
             * Obtains the {@link OutputStream}.
             * 
             * @param processNamespace
             *            Process name space.
             * @return {@link OutputStream}.
             * @throws IOException
             *             If fails to obtain the {@link OutputStream}.
             */
            private synchronized OutputStream getOutputStream(String processNamespace) throws IOException {

                // Lazy instantiate the output stream
                if (this.outputStream == null) {

                    // Create the output file
                    File file = File.createTempFile(processNamespace, ".log");
                    this.outputStream = new FileOutputStream(file);

                    // Log that outputting to file
                    log.info("Logging " + OfficeBuilding.class.getSimpleName() + " output to file "
                            + file.getAbsolutePath());
                }

                // Return the output stream
                return this.outputStream;
            }
        });

        // Start the OfficeBuilding
        try {
            OfficeBuildingManager.spawnOfficeBuilding(null, this.port.intValue(), getKeyStoreFile(),
                    KEY_STORE_PASSWORD, USER_NAME, PASSWORD, null, false, environment, null, true, configuration);
        } catch (Throwable ex) {
            // Provide details of the failure
            final String MESSAGE = "Failed starting the " + OfficeBuilding.class.getSimpleName();
            this.getLog().error(MESSAGE + ": " + ex.getMessage() + " [" + ex.getClass().getSimpleName() + "]");
            this.getLog().error("DIAGNOSIS INFORMATION:");
            this.getLog().error("   classpath='" + System.getProperty("java.class.path") + "'");
            this.getLog().error("   additional classpath='" + classPath + "'");

            // Propagate the failure
            throw newMojoExecutionException(MESSAGE, ex);
        }

        // Log started OfficeBuilding
        this.getLog().info("Started " + OfficeBuilding.class.getSimpleName() + " on port " + this.port.intValue());
    }

    /**
     * Plugin dependency inclusion.
     */
    private static class PluginDependencyInclusion {

        /**
         * Group Id.
         */
        public final String groupId;

        /**
         * Artifact Id.
         */
        public final String artifactId;

        /**
         * Type.
         */
        public final String type;

        /**
         * Classifier. May be <code>null</code>.
         */
        public final String classifier;

        /**
         * Initiate.
         * 
         * @param groupId
         *            Group Id.
         * @param artifactId
         *            Artifact Id.
         * @param type
         *            Type.
         * @param classifier
         *            Classifier. May be <code>null</code>.
         */
        public PluginDependencyInclusion(String groupId, String artifactId, String type, String classifier) {
            this.groupId = groupId;
            this.artifactId = artifactId;
            this.type = type;
            this.classifier = classifier;
        }
    }

}