com.redhat.rcm.version.mgr.VersionManager.java Source code

Java tutorial

Introduction

Here is the source code for com.redhat.rcm.version.mgr.VersionManager.java

Source

/*
 * Copyright (c) 2012 Red Hat, Inc.
 *
 * 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 com.redhat.rcm.version.mgr;

import static com.redhat.rcm.version.util.InputUtils.getIncludedSubpaths;
import static com.redhat.rcm.version.util.PomUtils.writeModifiedPom;
import static org.apache.commons.io.IOUtils.closeQuietly;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.maven.mae.MAEException;
import org.apache.maven.mae.app.AbstractMAEApplication;
import org.apache.maven.mae.boot.embed.MAEEmbedderBuilder;
import org.apache.maven.mae.internal.container.ComponentSelector;
import org.apache.maven.mae.project.ProjectToolsException;
import org.apache.maven.mae.project.key.FullProjectKey;
import org.apache.maven.mae.project.key.ProjectKey;
import org.apache.maven.mae.project.key.VersionlessProjectKey;
import org.apache.maven.mae.project.session.SessionInitializer;
import org.apache.maven.model.Model;
import org.apache.maven.model.Parent;
import org.apache.maven.model.building.DefaultModelBuildingRequest;
import org.apache.maven.model.building.FileModelSource;
import org.apache.maven.model.building.ModelBuilder;
import org.apache.maven.model.building.ModelBuildingException;
import org.apache.maven.model.building.ModelBuildingRequest;
import org.apache.maven.model.building.ModelBuildingResult;
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonatype.aether.impl.ArtifactResolver;
import org.sonatype.aether.impl.RemoteRepositoryManager;

import com.redhat.rcm.version.VManException;
import com.redhat.rcm.version.config.SessionConfigurator;
import com.redhat.rcm.version.maven.VManModelResolver;
import com.redhat.rcm.version.mgr.capture.MissingInfoCapture;
import com.redhat.rcm.version.mgr.mod.ProjectModder;
import com.redhat.rcm.version.mgr.session.VersionManagerSession;
import com.redhat.rcm.version.mgr.verify.ProjectVerifier;
import com.redhat.rcm.version.model.Project;
import com.redhat.rcm.version.report.Report;
import com.redhat.rcm.version.util.PomPeek;

@Component(role = VersionManager.class)
public class VersionManager extends AbstractMAEApplication {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Requirement
    private ModelBuilder modelBuilder;

    @Requirement
    private ArtifactResolver artifactResolver;

    @Requirement
    private RemoteRepositoryManager remoteRepositoryManager;

    @Requirement(role = Report.class)
    private Map<String, Report> reports;

    @Requirement(role = ProjectModder.class)
    private Map<String, ProjectModder> modders;

    @Requirement(role = ProjectVerifier.class)
    private Map<String, ProjectVerifier> verifiers;

    @Requirement
    private MissingInfoCapture capturer;

    @Requirement
    private SessionConfigurator sessionConfigurator;

    private DefaultModelBuildingRequest baseMbr;

    private static boolean useClasspathScanning = false;

    private static Object lock = new Object();

    private static VersionManager instance;

    public static VersionManager getInstance() throws MAEException {
        synchronized (lock) {
            if (instance == null) {
                instance = new VersionManager();
                instance.load();
            }
        }

        return instance;
    }

    public void generateReports(final File reportsDir, final VersionManagerSession sessionData) {
        if (reports != null) {
            final Set<String> ids = new HashSet<String>();
            for (final Map.Entry<String, Report> entry : reports.entrySet()) {
                final String id = entry.getKey();
                final Report report = entry.getValue();

                if (!id.endsWith("_")) {
                    try {
                        ids.add(id);
                        report.generate(reportsDir, sessionData);
                    } catch (final VManException e) {
                        logger.error("Failed to generate report: " + id, e);
                    }
                }
            }

            logger.info("Wrote reports: [" + StringUtils.join(ids.iterator(), ", ") + "] to:\n\t" + reportsDir);
        }
    }

    public Set<File> modifyVersions(final File dir, final String pomNamePattern, final String pomExcludePattern,
            final List<String> boms, final String toolchain, final VersionManagerSession session)
            throws VManException {
        final String[] includedSubpaths = getIncludedSubpaths(dir, pomNamePattern, pomExcludePattern, session);
        final List<File> pomFiles = new ArrayList<File>();

        for (final String subpath : includedSubpaths) {
            File pom = new File(dir, subpath);
            try {
                pom = pom.getCanonicalFile();
            } catch (final IOException e) {
                pom = pom.getAbsoluteFile();
            }

            if (!pomFiles.contains(pom)) {
                logger.info("Loading POM: '" + pom + "'");
                pomFiles.add(pom);
            }
        }

        final File[] pomFileArray = pomFiles.toArray(new File[] {});

        configureSession(boms, toolchain, session, pomFileArray);

        final Set<File> outFiles = modVersions(dir, session, session.isPreserveFiles(), pomFileArray);

        logger.info("Modified " + outFiles.size() + " POM versions in directory.\n\n\tDirectory: " + dir
                + "\n\tBOMs:\t" + StringUtils.join(boms.iterator(), "\n\t\t") + "\n\tPOM Backups: "
                + session.getBackups() + "\n\n");

        return outFiles;
    }

    public Set<File> modifyVersions(File pom, final List<String> boms, final String toolchain,
            final VersionManagerSession session) throws VManException {
        try {
            pom = pom.getCanonicalFile();
        } catch (final IOException e) {
            pom = pom.getAbsoluteFile();
        }

        configureSession(boms, toolchain, session, pom);

        final Set<File> result = modVersions(pom.getParentFile(), session, true, pom);
        if (!result.isEmpty()) {
            final File out = result.iterator().next();

            logger.info("Modified POM versions.\n\n\tTop POM: " + out + "\n\tBOMs:\t"
                    + (boms == null ? "-NONE-" : StringUtils.join(boms.iterator(), "\n\t\t")) + "\n\tPOM Backups: "
                    + session.getBackups() + "\n\n");
        }

        return result;
    }

    public void configureSession(final List<String> boms, final String toolchain,
            final VersionManagerSession session, final File... pomFiles) throws VManException {
        sessionConfigurator.configureSession(boms, toolchain, session, pomFiles);

        final List<Throwable> errors = session.getErrors();
        if (errors != null && !errors.isEmpty()) {
            throw new MultiVManException("Failed to configure session.", errors);
        }
    }

    protected LinkedHashSet<Project> loadProjectWithModules(final File topPom, final VersionManagerSession session)
            throws ProjectToolsException, IOException {
        final List<PomPeek> peeked = peekAtPomHierarchy(topPom, session);
        final LinkedHashSet<Project> projects = new LinkedHashSet<Project>();

        for (final PomPeek peek : peeked) {
            final File pom = peek.getPom();

            // Sucks, but we have to brute-force reading in the raw model.
            // The effective-model building, below, has a tantalizing getRawModel()
            // method on the result, BUT this seems to return models that have
            // the plugin versions set inside profiles...so they're not entirely
            // raw.
            Model raw = null;
            InputStream in = null;
            try {
                in = new FileInputStream(pom);
                raw = new MavenXpp3Reader().read(in);
            } catch (final IOException e) {
                session.addError(
                        new VManException("Failed to build model for POM: %s.\n--> %s", e, pom, e.getMessage()));
            } catch (final XmlPullParserException e) {
                session.addError(
                        new VManException("Failed to build model for POM: %s.\n--> %s", e, pom, e.getMessage()));
            } finally {
                closeQuietly(in);
            }

            if (raw == null) {
                continue;
            }

            final Project project;
            if (session.isUseEffectivePoms()) {
                // FIXME: Need an option to disable this for self-contained use cases...
                //    Is this the same as 'non-strict' mode??
                final ModelBuildingRequest req = newModelBuildingRequest(pom, session);
                ModelBuildingResult mbResult = null;
                try {
                    mbResult = modelBuilder.build(req);
                } catch (final ModelBuildingException e) {
                    session.addError(new VManException("Failed to build model for POM: %s.\n--> %s", e, pom,
                            e.getMessage()));
                }

                if (mbResult == null) {
                    continue;
                }

                project = new Project(raw, mbResult, pom);
            } else {
                project = new Project(pom, raw);
            }

            projects.add(project);
        }

        return projects;
    }

    protected List<PomPeek> peekAtPomHierarchy(final File topPom, final VersionManagerSession session)
            throws IOException {
        final LinkedList<File> pendingPoms = new LinkedList<File>();
        pendingPoms.add(topPom.getCanonicalFile());

        final String topDir = topPom.getParentFile().getCanonicalPath();

        final Set<File> seen = new HashSet<File>();
        final List<PomPeek> peeked = new ArrayList<PomPeek>();

        while (!pendingPoms.isEmpty()) {
            final File pom = pendingPoms.removeFirst();
            seen.add(pom);

            logger.info("PEEK: " + pom);

            final PomPeek peek = new PomPeek(pom);
            final FullProjectKey key = peek.getKey();
            if (key != null) {
                session.addPeekPom(key, pom);
                peeked.add(peek);

                final File dir = pom.getParentFile();

                final String relPath = peek.getParentRelativePath();
                if (relPath != null) {
                    logger.info("Found parent relativePath: " + relPath + " in pom: " + pom);
                    File parent = new File(dir, relPath);
                    if (parent.isDirectory()) {
                        parent = new File(parent, "pom.xml");
                    }

                    logger.info("Looking for parent POM: " + parent);

                    parent = parent.getCanonicalFile();
                    if (parent.getParentFile().getCanonicalPath().startsWith(topDir) && parent.exists()
                            && !seen.contains(parent) && !pendingPoms.contains(parent)) {
                        pendingPoms.add(parent);
                    } else {
                        logger.info("Skipping reference to non-existent parent relativePath: '" + relPath + "' in: "
                                + pom);
                    }
                }

                final Set<String> modules = peek.getModules();
                if (modules != null && !modules.isEmpty()) {
                    for (final String module : modules) {
                        logger.info("Found module: " + module + " in pom: " + pom);
                        File modPom = new File(dir, module);
                        if (modPom.isDirectory()) {
                            modPom = new File(modPom, "pom.xml");
                        }

                        logger.info("Looking for module POM: " + modPom);

                        if (modPom.getParentFile().getCanonicalPath().startsWith(topDir) && modPom.exists()
                                && !seen.contains(modPom) && !pendingPoms.contains(modPom)) {
                            pendingPoms.addLast(modPom);
                        } else {
                            logger.info("Skipping reference to non-existent module: '" + module + "' in: " + pom);
                        }
                    }
                }
            } else {
                logger.info("Skipping " + pom + " as its a template file.");
            }
        }

        return peeked;
    }

    private synchronized ModelBuildingRequest newModelBuildingRequest(final File pom,
            final VersionManagerSession session) {
        if (baseMbr == null) {
            final DefaultModelBuildingRequest mbr = new DefaultModelBuildingRequest();
            mbr.setSystemProperties(System.getProperties());
            mbr.setValidationLevel(ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL);
            mbr.setProcessPlugins(false);
            mbr.setLocationTracking(true);

            this.baseMbr = mbr;
        }

        final DefaultModelBuildingRequest req = new DefaultModelBuildingRequest(baseMbr);
        req.setModelSource(new FileModelSource(pom));

        final VManModelResolver resolver = new VManModelResolver(session, pom, artifactResolver,
                remoteRepositoryManager);

        req.setModelResolver(resolver);

        return req;
    }

    private Set<File> modVersions(final File basedir, final VersionManagerSession session,
            final boolean preserveDirs, final File... pomFiles) {
        final Set<File> result = new LinkedHashSet<File>();

        final Set<Project> projects = new HashSet<Project>();
        for (final File pom : pomFiles) {
            if (session.isExcludedModulePom(pom)) {
                logger.info("Skipping excluded module pom: {}.", pom);
                continue;
            }

            try {
                final Set<Project> pomProjects = loadProjectWithModules(pom, session);
                if (pomProjects != null && !pomProjects.isEmpty()) {
                    projects.addAll(pomProjects);
                }
            } catch (final ProjectToolsException e) {
                session.addError(e);
            } catch (final IOException e) {
                session.addError(e);
            }
        }

        if (!session.getErrors().isEmpty()) {
            return result;
        }

        session.setCurrentProjects(projects);

        logger.info("Modifying " + projects.size() + " project(s)...");

        // NOTE: Using sorted projects list from session instead of unsorted set from above.
        for (final Project project : session.getCurrentProjects()) {
            logger.info("Modifying '" + project.getKey() + "'...");

            final List<String> modderKeys = session.getModderKeys();
            Collections.sort(modderKeys, ProjectModder.KEY_COMPARATOR);

            boolean changed = false;
            if (modders != null) {
                for (final String key : modderKeys) {
                    final ProjectModder modder = modders.get(key);
                    if (modder == null) {
                        logger.info("Skipping missing project modifier: '" + key + "'");
                        session.addError(new VManException("Cannot find modder for key: '%s'. Skipping...", key));
                        continue;
                    }

                    logger.info("Modifying '" + project.getKey() + " using: '" + key + "' with modder "
                            + modder.getClass().getName());
                    changed = modder.inject(project, session) || changed;
                }
            }

            if (changed) {
                for (final String key : modderKeys) {
                    final ProjectVerifier verifier = verifiers.get(key);
                    if (verifier != null) {
                        logger.info("Verifying '" + project.getKey() + "' (" + key + ")...");
                        verifier.verify(project, session);
                    }
                }

                logger.info("Writing modified '" + project.getKey() + "'...");

                final Model model = project.getModel();
                final Parent parent = model.getParent();

                String groupId = model.getGroupId();
                String originalVersion = model.getVersion();
                if (parent != null) {
                    if (groupId == null) {
                        groupId = parent.getGroupId();
                    }

                    if (originalVersion == null) {
                        originalVersion = parent.getVersion();
                    }
                }

                final ProjectKey originalCoord = new VersionlessProjectKey(groupId, model.getArtifactId());
                final File pom = project.getPom();

                final File out = writePom(model, originalCoord, originalVersion, pom, basedir, session,
                        preserveDirs);
                if (out != null) {
                    result.add(out);
                }
            } else {
                logger.info(project.getKey() + " NOT modified.");
            }
        }

        if (session.getCapturePom() != null) {
            capturer.captureMissing(session);

            logger.warn("\n\n\n\nMissing version information has been logged to:\n\n\t"
                    + session.getCapturePom().getAbsolutePath() + "\n\n\n\n");
        }

        return result;
    }

    private File writePom(final Model model, final ProjectKey originalCoord, final String originalVersion,
            final File pom, final File basedir, final VersionManagerSession session, final boolean preserveDirs) {
        File backup = pom;

        final File backupDir = session.getBackups();
        if (backupDir != null) {
            String path = pom.getParent();
            path = path.substring(basedir.getPath().length());

            final File dir = new File(backupDir, path);
            if (!dir.exists() && !dir.mkdirs()) {
                session.addError(new VManException("Failed to create backup subdirectory: %s", dir));
                return null;
            }

            backup = new File(dir, pom.getName());
            try {
                session.getLog(pom).add("Writing: %s\nTo backup: %s", pom, backup);
                FileUtils.copyFile(pom, backup);
            } catch (final IOException e) {
                session.addError(new VManException("Error making backup of POM: %s.\n\tTarget: %s\n\tReason: %s", e,
                        pom, backup, e.getMessage()));
                return null;
            }
        }

        String version = model.getVersion();
        String groupId = model.getGroupId();
        if (model.getParent() != null) {
            final Parent parent = model.getParent();
            if (version == null) {
                version = parent.getVersion();
            }

            if (groupId == null) {
                groupId = parent.getGroupId();
            }
        }

        boolean relocatePom = false;

        final ProjectKey coord = new VersionlessProjectKey(groupId, model.getArtifactId());
        if (!preserveDirs && (!coord.equals(originalCoord) || !version.equals(originalVersion))) {
            relocatePom = true;
        }

        return writeModifiedPom(model, pom, coord, version, basedir, session, relocatePom);
    }

    @Override
    public String getId() {
        return "rh.vmod";
    }

    @Override
    public String getName() {
        return "Red Hat POM Version Modifier";
    }

    @Override
    protected void configureBuilder(final MAEEmbedderBuilder builder) throws MAEException {
        super.configureBuilder(builder);
        if (useClasspathScanning) {
            builder.withClassScanningEnabled(true);
        }
    }

    public static void setClasspathScanning(final boolean scanning) {
        if (instance == null) {
            useClasspathScanning = scanning;
        }
    }

    public Map<String, ProjectModder> getModders() {
        return modders;
    }

    public Map<String, Report> getReports() {
        return reports;
    }

    @Override
    public ComponentSelector getComponentSelector() {
        return new ComponentSelector().setSelection(SessionInitializer.class, "vman");
    }
}