info.ronjenkins.maven.rtr.RTR.java Source code

Java tutorial

Introduction

Here is the source code for info.ronjenkins.maven.rtr.RTR.java

Source

/*
 * Copyright (C) 2016 Ronald Jack Jenkins Jr.
 *
 * 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 info.ronjenkins.maven.rtr;

import info.ronjenkins.maven.rtr.exceptions.SmartReactorSanityCheckException;
import info.ronjenkins.maven.rtr.steps.SmartReactorStep;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.maven.AbstractMavenLifecycleParticipant;
import org.apache.maven.MavenExecutionException;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.graph.DefaultProjectDependencyGraph;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuilder;
import org.codehaus.plexus.PlexusContainer;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
import org.codehaus.plexus.logging.Logger;

/**
 * The entry point for the Smart Reactor Maven Extension.
 *
 * @author Ronald Jack Jenkins Jr.
 */
@Component(role = AbstractMavenLifecycleParticipant.class, hint = "rtr")
public class RTR extends AbstractMavenLifecycleParticipant {
    private static void checkForRequiredClasses() {
        try {
            new DefaultProjectDependencyGraph(new ArrayList<MavenProject>());
            throw new Exception();
        } catch (final Exception e) {
            // Irrelevant.
        }
    }

    @Requirement
    private PlexusContainer container;
    @Requirement
    private Logger logger;
    @Requirement
    private ProjectBuilder builder;
    protected List<String> startSteps;
    protected List<String> endSuccessSteps;
    protected List<String> endFailureSteps;
    @Requirement(role = SmartReactorStep.class)
    protected Map<String, SmartReactorStep> availableSteps;
    private RTRComponents components;
    private boolean disabledDueToDoubleLoad;
    private boolean disabled;
    private boolean release;
    private boolean backupPomsCreated;
    private boolean externalSnapshotsAllowed;

    /**
     * RTR entry point.
     *
     * @param session
     *          the current Maven session, never null.
     */
    @Override
    public void afterProjectsRead(final MavenSession session) throws MavenExecutionException {
        // Don't allow this extension to be loaded as a build extension.
        try {
            RTR.checkForRequiredClasses();
        } catch (final NoClassDefFoundError e) {
            throw new SmartReactorSanityCheckException(
                    "This extension must be loaded as a core extension, not as a build extension.");
        }
        // Don't allow double-execution due to double-classloading.
        this.detectDoubleExecution(session);
        if (this.disabledDueToDoubleLoad) {
            return;
        }
        // Don't do anything if the Smart Reactor is disabled.
        final MavenProject executionRoot = session.getTopLevelProject();
        this.disabled = RTRConfig.isDisabled(session, executionRoot);
        if (this.disabled) {
            return;
        }
        this.release = RTRConfig.isRelease(session, executionRoot);
        this.externalSnapshotsAllowed = RTRConfig.isExternalSnapshotsAllowed(session, executionRoot);
        this.logger.info("Assembling smart reactor...");
        this.components = new RTRComponents(this.builder);
        this.executeSteps(this.startSteps, session, this.components);
        // Done. Maven build will proceed from here, none the wiser. ;)
    }

    @Override
    public void afterSessionEnd(final MavenSession session) throws MavenExecutionException {
        // Don't allow double-execution due to double-classloading.
        if (this.disabledDueToDoubleLoad) {
            return;
        }
        if (this.disabled) {
            return;
        }
        if (session.getResult().hasExceptions()) {
            this.executeSteps(this.endFailureSteps, session, this.components);
        } else {
            this.executeSteps(this.endSuccessSteps, session, this.components);
        }
    }

    private void detectDoubleExecution(final MavenSession session) throws SmartReactorSanityCheckException {
        // Get the list of core extensions.
        final List<AbstractMavenLifecycleParticipant> extensions;
        try {
            extensions = this.getExtensions(session);
        } catch (final ComponentLookupException e) {
            this.logger.error(ExceptionUtils.getFullStackTrace(e));
            throw new SmartReactorSanityCheckException(
                    "Error while checking extension classloaders. Please report this as a bug.");
        }
        // If we find this FQCN more than once, "this" is a double-loaded instance
        // of the libext extension. Disable it so it doesn't cause a failure.
        final String thisFqcn = this.getClass().getName();
        boolean found = false;
        for (final AbstractMavenLifecycleParticipant extension : extensions) {
            if (extension.getClass().getName().equals(thisFqcn)) {
                if (found) {
                    // Found twice. Stop searching.
                    this.disabledDueToDoubleLoad = true;
                    break;
                } else {
                    // Found once.
                    found = true;
                }
            }
        }
    }

    private void executeSteps(final List<String> steps, final MavenSession session, final RTRComponents components)
            throws MavenExecutionException {
        SmartReactorStep step;
        for (final String name : steps) {
            step = this.availableSteps.get(name);
            if (step == null) {
                throw new MavenExecutionException("Unable to find step '" + name + "' to execute",
                        new IllegalStateException());
            }
            step.execute(session, components);
        }
    }

    // Factored into a separate method for mocking purposes, since JMockit can't
    // mock ClassLoader.
    private List<AbstractMavenLifecycleParticipant> getExtensions(final MavenSession session)
            throws ComponentLookupException {
        final List<AbstractMavenLifecycleParticipant> mvnExtensionsXml = new ArrayList<>();
        // Save the original classloader.
        final ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
        try {
            // Get the libext extensions.
            ClassLoader plexusCore = this.getClass().getClassLoader();
            while (plexusCore.getParent() != null) {
                plexusCore = plexusCore.getParent();
            }
            Thread.currentThread().setContextClassLoader(plexusCore);
            mvnExtensionsXml.addAll(this.container.lookupList(AbstractMavenLifecycleParticipant.class));
            // Get the .mvn/extensions.xml extensions.
            for (final MavenProject project : session.getProjects()) {
                // getClassRealm() is not considered part of Maven's public API for
                // plugins, but no mention is made of extensions. It's the only reliable
                // way to see which extensions are loaded for each project, so we'll use
                // it as long as we can get away with it.
                final ClassLoader projectRealm = project.getClassRealm();
                if (projectRealm != null) {
                    Thread.currentThread().setContextClassLoader(projectRealm);
                    mvnExtensionsXml.addAll(this.container.lookupList(AbstractMavenLifecycleParticipant.class));
                }
            }
        } finally {
            // Restore the original classloader.
            Thread.currentThread().setContextClassLoader(originalClassLoader);
        }
        return mvnExtensionsXml;
    }

    /**
     * Indicates whether or not backup POMs were created by the release process.
     *
     * @return backupPomsCreated true if backup POMs have been created, false
     *         otherwise.
     */
    public boolean isBackupPomsCreated() {
        return this.backupPomsCreated;
    }

    /**
     * Indicates whether or not the Smart Reactor should allow a release reactor
     * containing references to any non-reactor SNAPSHOT artifacts.
     *
     * @return true if allowed, false if prohibited.
     */
    public boolean isExternalSnapshotsAllowed() {
        return this.externalSnapshotsAllowed;
    }

    /**
     * Indicates whether or not a release was requested.
     *
     * @return true if a release was requested, false otherwise.
     */
    public boolean isRelease() {
        return this.release;
    }

    /**
     * Sets the flag that indicates whether or not backup POMs were created by the
     * release process.
     *
     * @param backupPomsCreated
     *          true if backup POMs have been created, false otherwise.
     */
    public void setBackupPomsCreated(final boolean backupPomsCreated) {
        this.backupPomsCreated = backupPomsCreated;
    }
}