Java tutorial
/* * 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"); } }