org.springframework.cloud.release.internal.PomUpdater.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.cloud.release.internal.PomUpdater.java

Source

/*
 *  Copyright 2013-2017 the original author or authors.
 *
 *  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 org.springframework.cloud.release.internal;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;

import org.apache.maven.model.Model;
import org.apache.maven.plugin.logging.Log;
import org.codehaus.mojo.versions.api.PomHelper;
import org.codehaus.mojo.versions.change.AbstractVersionChanger;
import org.codehaus.mojo.versions.change.VersionChange;
import org.codehaus.mojo.versions.change.VersionChanger;
import org.codehaus.mojo.versions.change.VersionChangerFactory;
import org.codehaus.mojo.versions.rewriting.ModifiedPomXMLEventReader;
import org.codehaus.stax2.XMLInputFactory2;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

/**
 * @author Marcin Grzejszczak
 */
class PomUpdater {

    private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

    private final PomReader pomReader = new PomReader();
    private final PomWriter pomWriter = new PomWriter();

    /**
     * Basing on the contents of the root pom and the versions will decide whether
     * the project should be updated or not.
     *
     * @param rootFolder - root folder of the project
     * @param versions - list of dependencies to be updated
     * @return {@code true} if the project is on the list of projects to be updated
     */
    boolean shouldProjectBeUpdated(File rootFolder, Versions versions) {
        File rootPom = rootPom(rootFolder);
        Model model = this.pomReader.readPom(rootPom);
        String artifactId = artifactId(model);
        if (!versions.shouldBeUpdated(artifactId)) {
            log.info("Skipping project [{}] since it's not on the list of projects to update",
                    model.getArtifactId());
            return false;
        }
        log.info("Project [{}] will have its dependencies updated", model.getArtifactId());
        return true;
    }

    private File rootPom(File rootFolder) {
        if (rootFolder.getName().endsWith(".xml")) {
            return rootFolder;
        }
        return new File(rootFolder, "pom.xml");
    }

    private String artifactId(Model model) {
        boolean parent = model.getArtifactId().endsWith("-parent");
        if (!parent) {
            return model.getArtifactId();
        }
        return model.getArtifactId().substring(0, model.getArtifactId().indexOf("-parent"));
    }

    ModelWrapper readModel(File pom) {
        return new ModelWrapper(this.pomReader.readPom(pom));
    }

    /**q
     * Updates the root / child module model
     *
     * @param rootProject - root project model
     * @param pom - file with the pom
     * @param versions - versions to update
     * @return updated model
     */
    ModelWrapper updateModel(ModelWrapper rootProject, File pom, Versions versions) {
        Model model = this.pomReader.readPom(pom);
        List<VersionChange> sourceChanges = new ArrayList<>();
        sourceChanges = updateParentIfPossible(rootProject, versions, model, sourceChanges);
        sourceChanges = updateVersionIfPossible(rootProject, versions, model, sourceChanges);
        return new ModelWrapper(model, sourceChanges, versions);
    }

    /**
     * Overwrites the pom.xml with data from {@link ModelWrapper} only if there were
     * any changes in the model.
     *
     * @return - the pom file
     */
    File overwritePomIfDirty(ModelWrapper wrapper, Versions versions, File pom) {
        if (wrapper.isDirty()) {
            log.debug("There were changes in the pom so file will be overridden");
            this.pomWriter.write(wrapper, versions, pom);
            log.info("Successfully stored [{}]", pom);
        }
        return pom;
    }

    private List<VersionChange> updateParentIfPossible(ModelWrapper wrapper, Versions versions, Model model,
            List<VersionChange> sourceChanges) {
        String rootProjectName = wrapper.projectName();
        List<VersionChange> changes = new ArrayList<>(sourceChanges);
        if (model.getParent() == null || StringUtils.isEmpty(model.getParent().getVersion())) {
            return changes;
        }
        String parentGroupId = model.getParent().getGroupId();
        String parentArtifactId = model.getParent().getArtifactId();
        log.debug("Searching for a version of parent [{}:{}]", parentGroupId, parentArtifactId);
        String oldVersion = model.getParent().getVersion();
        String version = versions.versionForProject(parentArtifactId);
        log.debug("Found version is [{}]", version);
        if (StringUtils.isEmpty(version)) {
            if (StringUtils.hasText(model.getParent().getRelativePath())) {
                version = versions.versionForProject(rootProjectName);
            } else {
                log.warn("There is no info on the [{}:{}] version", parentGroupId, parentArtifactId);
                return changes;
            }
        }
        if (oldVersion.equals(version)) {
            log.debug("Won't update the version of [{}:{}] since you're already using the proper one",
                    parentGroupId, parentArtifactId);
            return changes;
        }
        log.info("Setting version of parent [{}] to [{}] for module [{}]", parentArtifactId, version,
                model.getArtifactId());
        changes.add(new VersionChange(parentGroupId, parentArtifactId, oldVersion, version));
        return changes;
    }

    private List<VersionChange> updateVersionIfPossible(ModelWrapper wrapper, Versions versions, Model model,
            List<VersionChange> sourceChanges) {
        String rootProjectName = wrapper.projectName();
        List<VersionChange> changes = new ArrayList<>(sourceChanges);
        String groupId = groupId(model);
        String artifactId = model.getArtifactId();
        log.debug("Searching for a version [{}:{}]", groupId, artifactId);
        String oldVersion = model.getVersion();
        String version = versions.versionForProject(rootProjectName);
        log.debug("Found version is [{}]", version);
        if (StringUtils.isEmpty(version) || StringUtils.isEmpty(model.getVersion())) {
            log.debug("There was no version set for project [{}], skipping version setting for module [{}]",
                    rootProjectName, model.getArtifactId());
            return changes;
        }
        if (oldVersion.equals(version)) {
            log.debug("Won't update the version of [{}]:[{}] since you're already using the proper one", groupId,
                    artifactId);
            return changes;
        }
        log.info("Setting [{}] version to [{}]", artifactId, version);
        changes.add(new VersionChange(groupId, artifactId, oldVersion, version));
        return changes;
    }

    private String groupId(Model model) {
        if (StringUtils.hasText(model.getGroupId())) {
            return model.getGroupId();
        }
        if (model.getParent() != null) {
            return model.getParent().getGroupId();
        }
        return "";
    }
}

class ModelWrapper {
    final Model model;
    final Versions versions;
    final List<VersionChange> sourceChanges = new ArrayList<>();

    ModelWrapper(Model model, List<VersionChange> sourceChanges, Versions versions) {
        this.model = model;
        this.versions = versions;
        this.sourceChanges.addAll(sourceChanges);
    }

    ModelWrapper(Model model) {
        this.model = model;
        this.versions = Versions.EMPTY_VERSION;
    }

    String projectName() {
        return this.model.getArtifactId();
    }

    boolean isDirty() {
        return !this.sourceChanges.isEmpty() || this.versions.shouldSetProperty(this.model.getProperties());
    }
}

class PomWriter {

    private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

    void write(ModelWrapper wrapper, Versions versions, File pom) {
        try {
            VersionChangerFactory versionChangerFactory = new VersionChangerFactory();
            StringBuilder input = PomHelper.readXmlFile(pom);
            ModifiedPomXMLEventReader parsedPom = newModifiedPomXER(input);
            versionChangerFactory.setPom(parsedPom);
            LoggerToMavenLog loggerToMavenLog = new LoggerToMavenLog(PomWriter.log);
            versionChangerFactory.setLog(loggerToMavenLog);
            versionChangerFactory.setModel(wrapper.model);
            log.info("Applying version / parent / plugin / project changes to the pom [{}]", pom);
            VersionChanger changer = versionChangerFactory.newVersionChanger(true, true, true, true);
            for (VersionChange versionChange : wrapper.sourceChanges) {
                changer.apply(versionChange);
            }
            log.debug("Applying properties changes to the pom [{}]", pom);
            new PropertyVersionChanger(wrapper, versions, parsedPom, loggerToMavenLog).apply(null);
            try (BufferedWriter bw = new BufferedWriter(new FileWriter(pom))) {
                bw.write(input.toString());
            }
            log.debug("Flushed changes to the pom file [{}]", pom);
        } catch (Exception e) {
            log.error("Exception occurred while trying to apply changes to the POM", e);
        }
    }

    /**
     * Creates a {@link org.codehaus.mojo.versions.rewriting.ModifiedPomXMLEventReader} from a StringBuilder.
     *
     * @param input The XML to read and modify.
     * @return The {@link org.codehaus.mojo.versions.rewriting.ModifiedPomXMLEventReader}.
     */
    private ModifiedPomXMLEventReader newModifiedPomXER(StringBuilder input) {
        ModifiedPomXMLEventReader newPom = null;
        try {
            XMLInputFactory inputFactory = XMLInputFactory2.newInstance();
            inputFactory.setProperty(XMLInputFactory2.P_PRESERVE_LOCATION, Boolean.TRUE);
            newPom = new ModifiedPomXMLEventReader(input, inputFactory);
        } catch (XMLStreamException e) {
            log.error("Exception occurred while trying to parse pom", e);
        }
        return newPom;
    }
}

class PropertyVersionChanger extends AbstractVersionChanger {

    private final Versions versions;
    private final PropertyStorer propertyStorer;

    PropertyVersionChanger(ModelWrapper wrapper, Versions versions, ModifiedPomXMLEventReader pom, Log log) {
        super(wrapper.model, pom, log);
        this.versions = versions;
        this.propertyStorer = new PropertyStorer(log, pom);
    }

    PropertyVersionChanger(ModelWrapper wrapper, Versions versions, ModifiedPomXMLEventReader pom, Log log,
            PropertyStorer propertyStorer) {
        super(wrapper.model, pom, log);
        this.versions = versions;
        this.propertyStorer = propertyStorer;
    }

    @Override
    public void apply(final VersionChange versionChange) throws XMLStreamException {
        this.versions.projects.stream().filter(project -> {
            Properties properties = getModel().getProperties();
            String projectVersionKey = propertyName(project);
            if (!properties.containsKey(projectVersionKey)) {
                return false;
            }
            String version = properties.getProperty(projectVersionKey);
            return !version.equals(project.version);
        }).forEach(this.propertyStorer::setPropertyVersionIfApplicable);
    }

    private String propertyName(Project project) {
        return project.name + ".version";
    }
}

class PropertyStorer {

    private final Log log;
    private final ModifiedPomXMLEventReader pom;

    PropertyStorer(Log log, ModifiedPomXMLEventReader pom) {
        this.log = log;
        this.pom = pom;
    }

    void setPropertyVersionIfApplicable(Project project) {
        String propertyName = propertyName(project);
        if (setPropertyVersion(propertyName, project.version)) {
            log.info("    Updating property " + propertyName);
            log.info("        to version " + project.version);
        }
    }

    private String propertyName(Project project) {
        return project.name + ".version";
    }

    private boolean setPropertyVersion(String propertyName, String version) {
        try {
            return PomHelper.setPropertyVersion(this.pom, null, propertyName, version);
        } catch (XMLStreamException e) {
            this.log.error("Exception occurred while trying to set property version", e);
            return false;
        }
    }
}

class LoggerToMavenLog implements Log {

    private final Logger logger;

    LoggerToMavenLog(Logger logger) {
        this.logger = logger;
    }

    @Override
    public boolean isDebugEnabled() {
        return this.logger.isDebugEnabled();
    }

    @Override
    public void debug(CharSequence content) {
        this.logger.debug(content.toString());
    }

    @Override
    public void debug(CharSequence content, Throwable error) {
        this.logger.debug(content.toString(), error);
    }

    @Override
    public void debug(Throwable error) {
        this.debug("Exception occurred", error);
    }

    @Override
    public boolean isInfoEnabled() {
        return this.logger.isInfoEnabled();
    }

    @Override
    public void info(CharSequence content) {
        this.logger.info(content.toString());
    }

    @Override
    public void info(CharSequence content, Throwable error) {
        this.logger.info(content.toString(), error);
    }

    @Override
    public void info(Throwable error) {
        this.info("Exception occurred", error);
    }

    @Override
    public boolean isWarnEnabled() {
        return this.logger.isWarnEnabled();
    }

    @Override
    public void warn(CharSequence content) {
        this.logger.warn(content.toString());
    }

    @Override
    public void warn(CharSequence content, Throwable error) {
        this.logger.warn(content.toString(), error);
    }

    @Override
    public void warn(Throwable error) {
        this.warn("Exception occurred", error);
    }

    @Override
    public boolean isErrorEnabled() {
        return this.logger.isErrorEnabled();
    }

    @Override
    public void error(CharSequence content) {
        this.logger.error(content.toString());
    }

    @Override
    public void error(CharSequence content, Throwable error) {
        this.logger.error(content.toString(), error);
    }

    @Override
    public void error(Throwable error) {
        this.error("Exception occurred", error);
    }
}