Package org.sonatype.maven.shell.maven

Source Code of org.sonatype.maven.shell.maven.MavenSystemImpl$MavenRuntimeImpl

/*******************************************************************************
* Copyright (c) 2009-2011 Sonatype, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
* The Eclipse Public License is available at
*   http://www.eclipse.org/legal/epl-v10.html
* The Apache License v2.0 is available at
*   http://www.apache.org/licenses/LICENSE-2.0.html
* You may elect to redistribute this code under either of these licenses.
*******************************************************************************/

package org.sonatype.maven.shell.maven;

import com.google.inject.Inject;
import com.google.inject.Provider;
import jline.Terminal;
import org.apache.maven.Maven;
import org.apache.maven.cli.CLIReportingUtils;
import org.apache.maven.cli.MavenLoggerManager;
import org.apache.maven.cli.PrintStreamLogger;
import org.apache.maven.exception.DefaultExceptionHandler;
import org.apache.maven.exception.ExceptionHandler;
import org.apache.maven.exception.ExceptionSummary;
import org.apache.maven.execution.DefaultMavenExecutionRequest;
import org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.execution.MavenExecutionRequestPopulator;
import org.apache.maven.execution.MavenExecutionResult;
import org.apache.maven.lifecycle.LifecycleExecutionException;
import org.apache.maven.model.building.ModelProcessor;
import org.apache.maven.project.MavenProject;
import org.apache.maven.settings.building.DefaultSettingsBuildingRequest;
import org.apache.maven.settings.building.SettingsBuilder;
import org.apache.maven.settings.building.SettingsBuildingRequest;
import org.apache.maven.settings.building.SettingsBuildingResult;
import org.apache.maven.settings.building.SettingsProblem;
import org.codehaus.plexus.ContainerConfiguration;
import org.codehaus.plexus.DefaultContainerConfiguration;
import org.codehaus.plexus.DefaultPlexusContainer;
import org.codehaus.plexus.classworlds.ClassWorld;
import org.codehaus.plexus.classworlds.realm.ClassRealm;
import org.codehaus.plexus.util.Os;
import org.codehaus.plexus.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonatype.aether.transfer.TransferListener;
import org.sonatype.gshell.util.io.Closer;
import org.sonatype.gshell.util.io.StreamSet;
import org.sonatype.gshell.util.yarn.Yarn;
import org.sonatype.maven.shell.maven.internal.BatchModeMavenTransferListener;
import org.sonatype.maven.shell.maven.internal.ConsoleMavenTransferListener;
import org.sonatype.maven.shell.maven.internal.ExecutionEventLogger;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;

/**
* {@link MavenSystem} implementation.
*
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @since 0.9
*/
public class MavenSystemImpl
    implements MavenSystem
{
    private static final Logger log = LoggerFactory.getLogger(MavenSystemImpl.class);

    static {
        // This prevents a thread leak in the org.apache.maven.artifact.resolver.  DefaultArtifactResolver (maven-compat)
        // which uses a thread pool that gets only shutdown when the artifact resolver is finalized.
        System.setProperty("maven.artifact.threads", "0");
    }

    private final Provider<Terminal> terminal;

    @Inject
    public MavenSystemImpl(final Provider<Terminal> terminal) {
        assert terminal != null;
        this.terminal = terminal;
    }

    public String getVersion() {
        ByteArrayOutputStream buff = new ByteArrayOutputStream();
        PrintStream out = new PrintStream(buff);
        CLIReportingUtils.showVersion(out);
        Closer.close(out);
        return new String(buff.toByteArray());
    }

    public MavenRuntime create(final MavenRuntimeConfiguration config) throws Exception {
        assert config != null;

        if (log.isDebugEnabled()) {
            log.debug("Creating runtime w/config: {}", Yarn.render(config, Yarn.Style.MULTI));
        }

        MavenRuntimeImpl runtime = new MavenRuntimeImpl(config);
        runtime.init();

        return runtime;
    }

    private File resolveFile(final File file, final File dir) {
        if (file == null) {
            return null;
        }
        else if (file.isAbsolute()) {
            return file;
        }
        else if (file.getPath().startsWith(File.separator)) {
            // drive-relative Windows path
            return file.getAbsoluteFile();
        }
        else {
            return new File(dir, file.getPath());
        }
    }

    //
    // FIXME: Make this puppy extensible, need to extend for pmaven at the least
    //

    private class MavenRuntimeImpl
        implements MavenRuntime
    {
        private final Logger log = LoggerFactory.getLogger(MavenRuntimeImpl.class);

        private final MavenRuntimeConfiguration config;

        private PrintStreamLogger logger;

        private PrintStream logStream;

        private DefaultPlexusContainer container;

        private MavenRuntimeImpl(final MavenRuntimeConfiguration config) {
            assert config != null;
            this.config = config;
        }

        public MavenRuntimeConfiguration getConfiguration() {
            return config;
        }

        private void init() throws Exception {
            log.debug("Initializing");

            // Make sure maven.home is absolute to avoid confusion on windows
            String mavenHome = System.getProperty(MAVEN_HOME);
            if (mavenHome != null) {
                System.setProperty(MAVEN_HOME, new File(mavenHome).getAbsolutePath());
            }

            if (config.getBaseDirectory() == null) {
                config.setBaseDirectory(new File(System.getProperty("user.dir")));
            }

            StreamSet streams = config.getStreams();
            if (streams == null) {
                streams = StreamSet.system();
            }
            config.setStreams(streams);

            // Configure logging
            this.logger = config.getLogger();
            if (logger == null) {
                logger = new PrintStreamLogger(streams.out);
            }
            else {
                logger.setStream(streams.out);
            }
            config.setLogger(logger);

            int level = MavenExecutionRequest.LOGGING_LEVEL_INFO;
            if (config.isDebug()) {
                level = MavenExecutionRequest.LOGGING_LEVEL_DEBUG;
            }
            else if (config.isQuiet()) {
                level = MavenExecutionRequest.LOGGING_LEVEL_ERROR;
            }
            logger.setThreshold(level);

            File logFile = config.getLogFile();
            if (logFile != null) {
                logFile = resolveFile(logFile, config.getBaseDirectory());

                try {
                    logStream = new PrintStream(logFile);
                    logger.setStream(logStream);
                }
                catch (FileNotFoundException e) {
                    log.warn("Failed to open logging stream for file: " + logFile, e);
                    logger.setStream(streams.out);
                }
            }

            // Setup the container
            this.container = createContainer();
            log.debug("Using container: {}", container);

            Thread.currentThread().setContextClassLoader(container.getContainerRealm());
        }

        private DefaultPlexusContainer createContainer() throws Exception {
            ContainerConfiguration cc = new DefaultContainerConfiguration()
                .setClassWorld(config.getClassWorld())
                .setName("maven");

            // NOTE: This causes wiring failures for jline.Terminal, investigate further
            //.setAutoWiring(true)
            //.setClassPathScanning(PlexusConstants.SCANNING_CACHE);

            DefaultPlexusContainer c = new DefaultPlexusContainer(cc);
            configureContainer(c);

            return c;
        }

        protected void configureContainer(final DefaultPlexusContainer c) throws Exception {
            assert c != null;

            c.setLookupRealm(null);
            c.setLoggerManager(new MavenLoggerManager(config.getLogger()));
            c.getLoggerManager().setThresholds(logger.getThreshold());

            // If there is a configuration delegate then call it
            if (config.getDelegate() != null) {
                config.getDelegate().configure(c);
            }
        }

        public MavenExecutionRequest create() throws Exception {
            MavenExecutionRequest request = new DefaultMavenExecutionRequest();
            request.setCacheNotFound(true);
            request.setCacheTransferError(false);
            configureSettings(request);
            return request;
        }

        public int execute(final MavenExecutionRequest request) throws Exception {
            assert request != null;

            if (log.isDebugEnabled()) {
                log.debug("Processing request: {}", Yarn.render(request, Yarn.Style.MULTI));
            }
            configureRequest(request);

            try {
                return doExecute(request);
            }
            catch (Exception e) {
                CLIReportingUtils.showError(logger, "Error executing Maven.", e, request.isShowErrors()); // TODO: i81n
                return 1;
            }
            finally {
                cleanup();
                Closer.close(logStream);
            }
        }

        private void configureSettings(final MavenExecutionRequest request) throws Exception {
            assert request != null;
            assert config != null;

            File userSettingsFile = config.getSettingsFile();
            if (userSettingsFile != null) {
                userSettingsFile = resolveFile(userSettingsFile, config.getBaseDirectory());
                if (!userSettingsFile.isFile()) {
                    throw new FileNotFoundException("The specified user settings file does not exist: " + userSettingsFile); // TODO: i18n
                }
            }
            else {
                userSettingsFile = DEFAULT_USER_SETTINGS_FILE;
            }

            logger.debug("Reading user settings from: " + userSettingsFile);
            request.setUserSettingsFile(userSettingsFile);

            File globalSettingsFile = config.getGlobalSettingsFile();
            if (globalSettingsFile != null) {
                globalSettingsFile = resolveFile(globalSettingsFile, config.getBaseDirectory());
                if (!globalSettingsFile.isFile()) {
                    throw new FileNotFoundException("The specified global settings file does not exist: " + globalSettingsFile); // TODO: i18n
                }
            }
            else {
                globalSettingsFile = DEFAULT_GLOBAL_SETTINGS_FILE;
            }

            logger.debug("Reading global settings from: " + globalSettingsFile);
            request.setGlobalSettingsFile(globalSettingsFile);

            configureProperties(request);

            SettingsBuildingRequest settingsRequest = new DefaultSettingsBuildingRequest()
                .setGlobalSettingsFile(globalSettingsFile)
                .setUserSettingsFile(userSettingsFile)
                .setSystemProperties(request.getSystemProperties())
                .setUserProperties(request.getUserProperties());

            SettingsBuildingResult settingsResult;
            SettingsBuilder settingsBuilder = container.lookup(SettingsBuilder.class);
            try {
                settingsResult = settingsBuilder.build(settingsRequest);
            }
            finally {
                container.release(settingsBuilder);
            }

            // NOTE: This will nuke some details from the request; profiles, online, etc... :-(
            MavenExecutionRequestPopulator populator = container.lookup(MavenExecutionRequestPopulator.class);
            try {
                populator.populateFromSettings(request, settingsResult.getEffectiveSettings());
            }
            finally {
                container.release(populator);
            }

            if (!settingsResult.getProblems().isEmpty() && logger.isWarnEnabled()) {
                logger.warn("");
                logger.warn("Some problems were encountered while building the effective settings"); // TODO: i18n

                for (SettingsProblem problem : settingsResult.getProblems()) {
                    logger.warn(problem.getMessage() + " @ " + problem.getLocation()); // TODO: i18n
                }

                logger.warn("");
            }
        }

        private void configureProperties(final MavenExecutionRequest request) {
            assert request != null;
            assert config != null;

            Properties sys = new Properties();
            sys.putAll(System.getProperties());

            Properties user = new Properties();
            user.putAll(config.getProperties());

            // Add the env vars to the property set, with the "env." prefix
            boolean caseSensitive = !Os.isFamily(Os.FAMILY_WINDOWS);
            for (Map.Entry<String, String> entry : System.getenv().entrySet()) {
                String key = "env." + (caseSensitive ? entry.getKey() : entry.getKey().toUpperCase(Locale.ENGLISH));
                sys.setProperty(key, entry.getValue());
            }

            request.setUserProperties(user);

            // HACK: Some bits of Maven still require using System.properties :-(
            sys.putAll(user);
            System.getProperties().putAll(user);

            request.setSystemProperties(sys);
        }

        private void configureRequest(final MavenExecutionRequest request) throws Exception {
            assert request != null;
            assert config != null;

            File dir = new File(config.getBaseDirectory(), "").getAbsoluteFile();
            request.setBaseDirectory(dir);

            // HACK: Some bits need user.dir to be set, or use un-rooted File's :-(
            System.setProperty("user.dir", dir.getAbsolutePath());

            // Configure profiles
            for (String profile : config.getProfiles()) {
                profile = profile.trim();

                if (profile.startsWith("-") || profile.startsWith("!")) {
                    request.addInactiveProfile(profile.substring(1));
                }
                else if (profile.startsWith("+")) {
                    request.addActiveProfile(profile.substring(1));
                }
                else {
                    request.addActiveProfile(profile);
                }
            }

            // Configure user toolchains
            File userToolchainsFile = request.getUserToolchainsFile();
            if (userToolchainsFile != null) {
                userToolchainsFile = resolveFile(userToolchainsFile, config.getBaseDirectory());
            }
            else {
                userToolchainsFile = DEFAULT_USER_TOOLCHAINS_FILE;
            }
            request.setUserToolchainsFile(userToolchainsFile);

            // Configure the pom
            File alternatePomFile = config.getPomFile();
            if (alternatePomFile != null) {
                request.setPom(resolveFile(alternatePomFile, config.getBaseDirectory()));
            }
            else if (request.getPom() != null && !request.getPom().isAbsolute()) {
                request.setPom(request.getPom().getAbsoluteFile());
            }

            if ((request.getPom() != null) && (request.getPom().getParentFile() != null)) {
                request.setBaseDirectory(request.getPom().getParentFile());
            }
            else if (request.getPom() == null && request.getBaseDirectory() != null) {
                ModelProcessor modelProcessor = container.lookup(ModelProcessor.class);
                try {
                    File pom = modelProcessor.locatePom(new File(request.getBaseDirectory()));
                    if (pom.isFile()) {
                        request.setPom(pom);
                    }
                }
                finally {
                    container.release(modelProcessor);
                }
            }

            // Configure the local repo path
            String localRepoPath = request.getUserProperties().getProperty(LOCAL_REPO);
            if (localRepoPath == null) {
                localRepoPath = request.getSystemProperties().getProperty(LOCAL_REPO);
            }
            if (localRepoPath != null) {
                request.setLocalRepositoryPath(localRepoPath);
            }

            // Setup the xfr listener
            TransferListener transferListener;
            if (request.isInteractiveMode()) {
                transferListener = new ConsoleMavenTransferListener(config.getStreams().out);
            }
            else {
                transferListener = new BatchModeMavenTransferListener(config.getStreams().out);
            }
            request.setTransferListener(transferListener);

            // Configure request logging
            request.setLoggingLevel(logger.getThreshold());
            request.setExecutionListener(new ExecutionEventLogger(terminal.get(), logger));
        }

        private int doExecute(final MavenExecutionRequest request) throws Exception {
            assert request != null;
            assert config != null;

            if (config.isDebug() || config.isShowVersion()) {
                CLIReportingUtils.showVersion(config.getStreams().out);
            }

            //
            // TODO: i18n all of this
            //

            if (request.isShowErrors()) {
                logger.info("Error stack-traces are turned on.");
            }
            if (MavenExecutionRequest.CHECKSUM_POLICY_WARN.equals(request.getGlobalChecksumPolicy())) {
                logger.info("Disabling strict checksum verification on all artifact downloads.");
            }
            else if (MavenExecutionRequest.CHECKSUM_POLICY_FAIL.equals(request.getGlobalChecksumPolicy())) {
                logger.info("Enabling strict checksum verification on all artifact downloads.");
            }

            if (log.isDebugEnabled()) {
                log.debug("Executing request: {}", Yarn.render(request, Yarn.Style.MULTI));
            }

            // FIXME: Hook up EventSpy support

            MavenExecutionResult result;
            Maven maven = container.lookup(Maven.class);
            try {
                result = maven.execute(request);
            }
            finally {
                container.release(maven);
            }

            if (!result.hasExceptions()) {
                return 0;
            }
            // else process exceptions

            ExceptionHandler handler = new DefaultExceptionHandler();
            Map<String, String> references = new LinkedHashMap<String, String>();
            MavenProject project = null;

            for (Throwable exception : result.getExceptions()) {
                ExceptionSummary summary = handler.handleException(exception);

                logSummary(summary, references, "", request.isShowErrors());

                if (project == null && exception instanceof LifecycleExecutionException) {
                    project = ((LifecycleExecutionException) exception).getProject();
                }
            }

            logger.error("");

            if (!request.isShowErrors()) {
                logger.error("To see the full stack-trace of the errors, re-run Maven with the -e switch.");
            }
            if (!logger.isDebugEnabled()) {
                logger.error("Re-run Maven using the -X switch to enable full debug logging.");
            }

            if (!references.isEmpty()) {
                logger.error("");
                logger.error("For more information about the errors and possible solutions, please read the following articles:");

                for (Map.Entry<String, String> entry : references.entrySet()) {
                    logger.error(entry.getValue() + " " + entry.getKey());
                }
            }

            if (project != null && !project.equals(result.getTopologicallySortedProjects().get(0))) {
                logger.error("");
                logger.error("After correcting the problems, you can resume the build with the command");
                logger.error("  mvn <goals> -rf :" + project.getArtifactId());
            }

            if (MavenExecutionRequest.REACTOR_FAIL_NEVER.equals(request.getReactorFailureBehavior())) {
                logger.info("Build failures were ignored.");
                return 0;
            }
            else {
                return 1;
            }
        }

        private void logSummary(final ExceptionSummary summary, final Map<String, String> references, String indent, final boolean showErrors) {
            assert summary != null;

            String referenceKey = "";

            // TODO: i18n

            if (StringUtils.isNotEmpty(summary.getReference())) {
                referenceKey = references.get(summary.getReference());
                if (referenceKey == null) {
                    referenceKey = "[Help " + (references.size() + 1) + "]";
                    references.put(summary.getReference(), referenceKey);
                }
            }

            String msg = indent + summary.getMessage();

            if (StringUtils.isNotEmpty(referenceKey)) {
                if (msg.indexOf('\n') < 0) {
                    msg += " -> " + referenceKey;
                }
                else {
                    msg += '\n' + indent + "-> " + referenceKey;
                }
            }

            if (showErrors) {
                //noinspection ThrowableResultOfMethodCallIgnored
                logger.error(msg, summary.getException());
            }
            else {
                logger.error(msg);
            }

            indent += "  ";

            for (ExceptionSummary child : summary.getChildren()) {
                logSummary(child, references, indent, showErrors);
            }
        }

        private void cleanup() {
            ClassWorld world = container.getClassWorld();
            log.debug("Removing all realms from: {}", world);

            //noinspection unchecked
            for (ClassRealm realm : (List<ClassRealm>)world.getRealms()) {
                String id = realm.getId();
                try {
                    log.debug("Disposing class realm: {}", id);
                    world.disposeRealm(id);
                }
                catch (Exception e) {
                    log.warn("Failed to dispose class realm: {}", id, e);
                }
            }

            //noinspection unchecked
            purgeStrayShutdownHooks(world.getRealms());

            container.dispose();
        }

        // TODO: May actually want to snapshot current hooks before executing mvn, then remove all that leaked in after execution when we clean up

        private void purgeStrayShutdownHooks(final Collection<? extends ClassLoader> loaders) {
            // CommandLineUtils from plexus-utils registers a (needless) shutdown hook which in turn causes a mem leak. As
            // counter measure, we inspect all created (plugin) class loaders and try to unregister the hook.
            final String[] CLASSES = {
                "org.codehaus.plexus.util.cli.CommandLineUtils",
                "org.apache.maven.surefire.booter.shade.org.codehaus.plexus.util.cli.CommandLineUtils"
            };

            for (ClassLoader loader : loaders) {
                for (String className : CLASSES) {
                    String resName = className.replace('.', '/') + ".class";
                    if (loader.getResource(resName) != null) {
                        try {
                            Class<?> type = loader.loadClass(className);
                            Method method = type.getMethod("removeShutdownHook", Boolean.TYPE);
                            log.debug("Invoking: {}", method);
                            method.invoke(null, Boolean.TRUE);
                        }
                        catch (Exception e) {
                            // to be expected for plexus-utils 1.5.12-
                        }
                    }
                }
            }

            // The above block only works for recent plexus-utils versions, the following block is a fallback attempt to remove hooks directly from the JRE.
            try {
                Class<?> shutdown = ClassLoader.getSystemClassLoader().loadClass("java.lang.ApplicationShutdownHooks");
                Field field = shutdown.getDeclaredField("hooks");
                field.setAccessible(true);

                @SuppressWarnings("unchecked")
                Collection<Thread> hooks = new ArrayList<Thread>(((Map<Thread, ?>) field.get(null)).keySet());

                Runtime rt = Runtime.getRuntime();
                for (Thread hook : hooks) {
                    String name = hook.getClass().getName();
                    log.debug("Inspecting hook: {}", name);
                    if (name.contains("CommandLineUtils$") || name.equals("jline.TerminalSupport$RestoreHook")) {
                        rt.removeShutdownHook(hook);
                        log.debug("Removed shutdown hook: {} - {}", name, hook);
                    }
                }
            }
            catch (Exception e) {
                // to be expected on jre != 1.6
            }
        }
    }
}
TOP

Related Classes of org.sonatype.maven.shell.maven.MavenSystemImpl$MavenRuntimeImpl

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.