org.eclipse.scada.build.helper.AbstractSetQualifierMojo.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.scada.build.helper.AbstractSetQualifierMojo.java

Source

/*******************************************************************************
 * Copyright (c) 2013 IBH SYSTEMS GmbH and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBH SYSTEMS GmbH - initial API and implementation
 *******************************************************************************/
package org.eclipse.scada.build.helper;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.jar.Manifest;

import javax.xml.xpath.XPathExpressionException;

import org.apache.maven.model.Dependency;
import org.apache.maven.model.Model;
import org.apache.maven.model.Parent;
import org.apache.maven.model.Profile;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

/**
 * Abstract set qualifier
 * 
 * @author Jens Reimann
 * @since 0.0.1
 */
public abstract class AbstractSetQualifierMojo extends AbstractHelperMojo {
    /**
     * A set of properties that should be set to the qualifier value if already
     * present
     */
    @Parameter(property = "qualifierProperties")
    private Set<String> qualifierProperties;

    /**
     * A set of properties that should overridden if already present
     */
    @Parameter(property = "additionalProperties")
    private Map<String, String> additionalProperties;

    /**
     * Perform a dry run
     */
    @Parameter(property = "dryRun")
    private boolean dryRun;

    private ChangeManager changeManager;

    @Component(role = PomHelper.class)
    private PomHelper helper;

    /**
     * Define parent references that will get updated if the parent
     * cannot be found locally. The unqualified version will be kept.
     * <p>
     * The syntax is <code>groupId:artifactId:qualifier</code>. On the command
     * line multiple parents can be specified with a comma separated list.
     * </p>
     * 
     * @since 0.0.14
     */
    @Parameter(property = "forceUpdateParentQualifiers")
    private Set<String> forceUpdateParentQualifiers;

    protected abstract String getQualifier(MavenProject project) throws MojoExecutionException;

    @Override
    public synchronized void execute() throws MojoExecutionException {
        fillFromProperties("additionalProperties", this.additionalProperties);

        this.changeManager = new ChangeManager(getLog());

        try {
            getLog().info("Overwriting qualifier properties: " + this.qualifierProperties);
            getLog().info("Overwriting properties: " + this.additionalProperties);
            getLog().info("Force update parents: " + this.forceUpdateParentQualifiers);

            if (this.dryRun) {
                getLog().info("This is a dry run");
            }

            final Collection<MavenProject> projects = this.helper.expandProjects(getReactorProjects(), getLog(),
                    this.session);

            getLog().info(String.format("Processing %s modules", projects.size()));

            for (final MavenProject project : projects) {
                getLog().debug(String.format(" -> %s", project));
                process(projects, project);
            }

            if (!this.dryRun) {
                this.changeManager.applyAll();
            }
        } catch (final Exception e) {
            throw new MojoExecutionException("Failed to set qualifier", e);
        }
    }

    private void process(final Collection<MavenProject> projects, final MavenProject project) throws Exception {
        getLog().debug("Processing: " + project + " / " + project.getVersion());

        final String qualifier = getQualifier(project);
        final String version = makeVersion(project, qualifier);

        recordVersion("pom", String.format("%s:%s", project.getGroupId(), project.getArtifactId()), version);

        addChange(project.getFile(), new ModelModifier() {

            @Override
            public boolean apply(final Model model) {
                if (version != null && !version.equals(project.getVersion())) {
                    getLog().info(String.format("Update from %s to %s on project %s", project.getVersion(), version,
                            project));
                    model.setVersion(version);
                    return true;
                }
                return false;
            }
        });

        addChange(project.getFile(), new ModelModifier() {

            @Override
            public boolean apply(final Model model) {
                boolean change = false;
                final Properties p = model.getProperties();

                getLog().debug("Project Properties: " + p);

                for (final String prop : AbstractSetQualifierMojo.this.qualifierProperties) {
                    if (p.containsKey(prop)) {
                        getLog().info(String.format("%s: Setting property - %s -> %s", project, prop, qualifier));
                        p.put(prop, qualifier);
                        change = true;
                    }
                }

                for (final Map.Entry<String, String> entry : AbstractSetQualifierMojo.this.additionalProperties
                        .entrySet()) {
                    if (p.containsKey(entry.getKey())) {
                        if (entry.getValue() != null) {
                            getLog().info(String.format("%s: Setting property - %s -> %s", project, entry.getKey(),
                                    entry.getValue()));
                            p.put(entry.getKey(), entry.getValue());
                            change = true;
                        } else {
                            getLog().info(String.format("%s: Removing property - %s", entry.getKey()));
                            p.remove(entry.getKey());
                            change = true;
                        }
                    }
                }

                return change;
            }

            @Override
            public String toString() {
                return String.format("Change properties: " + project);
            };
        });

        addChange(project.getFile(), new ModelModifier() {

            @Override
            public boolean apply(final Model model) {
                boolean changed = false;
                for (final Dependency dep : model.getDependencies()) {
                    changed |= syncDep(dep);
                }
                for (final Profile profile : model.getProfiles()) {
                    for (final Dependency dep : profile.getDependencies()) {
                        changed |= syncDep(dep);
                    }
                }
                return changed;
            }

            @Override
            public String toString() {
                return String.format("Change dependencies: " + project);
            };
        });

        // visit all modules that have this project as a parent
        this.helper.visitModulesWithParent(projects, project, new VisitorChange(this.changeManager) {
            @Override
            protected boolean performChange(final Model model) {
                getLog().debug(String.format("Update parent version in module: %s", model));
                model.getParent().setVersion(version);
                return true;
            }
        });

        checkNonRelativeParent(project);

        syncModule(project, version);
    }

    private void checkNonRelativeParent(final MavenProject project) {
        if (project.getModel().getParent() == null) {
            // no parent
            return;
        }

        if (project.getParentFile() != null) {
            // is a local parent
            return;
        }

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

        final String prefix = String.format("%s:%s:", parent.getGroupId(), parent.getArtifactId());
        for (final String entry : this.forceUpdateParentQualifiers) {
            if (entry.startsWith(prefix)) {
                final String qualifier = entry.substring(prefix.length());
                final String newVersion = makeVersion(parent.getVersion(), qualifier);

                getLog().info(String.format("Force update parent of %s to %s -> %s", project.getId(),
                        parent.getId(), newVersion));

                addChange(project.getFile(), new ModelModifier() {

                    @Override
                    public boolean apply(final Model model) {
                        model.getParent().setVersion(newVersion);
                        return true;
                    }
                });
            }
        }
    }

    protected boolean syncDep(final Dependency dep) {
        final String id = String.format("%s:%s", dep.getGroupId(), dep.getArtifactId());
        final String version = getVersionUnchecked("pom", id);
        if (version != null) {
            getLog().info(String.format("Synchronizing depencency: %s -> %s", dep, version));
            dep.setVersion(version);
            return true;
        }
        return false;
    }

    private void addChange(final File file, final ModelModifier modelModifier) {
        this.changeManager.addChange(file, modelModifier);
    }

    protected void syncModule(final MavenProject project, final String version) throws Exception {
        final String packaging = project.getPackaging();
        getLog().debug("Project type: " + packaging);
        if ("eclipse-plugin".equals(packaging)) {
            handleBundle(project, version);
        } else if ("eclipse-feature".equals(packaging)) {
            handleFeature(project, version);
        } else if ("eclipse-repository".equals(packaging)) {
            handleRepository(project, version);
        } else if ("eclipse-test-plugin".equals(packaging)) {
            handleBundle(project, version);
        }
    }

    private void handleRepository(final MavenProject project, final String version) throws Exception {
        this.changeManager.addChange(new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                performHandleRepository(project);
                return null;
            };
        });
    }

    private void performHandleRepository(final MavenProject project) throws Exception, XPathExpressionException {
        final File file = new File(project.getBasedir(), "category.xml");
        if (!file.exists()) {
            return;
        }

        final Document doc = XmlHelper.parse(file);

        for (final Node node : XmlHelper.findNodes(doc, "/site/feature")) {
            final Element ele = (Element) node;

            final String featureId = ele.getAttribute("id");
            final String featureVersion = getVersion("feature", featureId);

            ele.setAttribute("version", featureVersion);
            ele.setAttribute("url", String.format("features/%s_%s.jar", featureId, featureVersion));
        }

        XmlHelper.write(doc, file);
    }

    private void handleBundle(final MavenProject project, final String version) throws Exception {
        this.changeManager.addChange(new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                performHandleBundle(project, version);
                return null;
            };
        });
    }

    private void performHandleBundle(final MavenProject project, final String version)
            throws IOException, FileNotFoundException {
        final File file = new File(project.getBasedir(), "META-INF/MANIFEST.MF");

        final Manifest manifest;

        final FileInputStream in = new FileInputStream(file);
        try {
            manifest = new Manifest(in);
        } finally {
            in.close();
        }

        // process

        manifest.getMainAttributes().putValue("Bundle-Version", version);

        // write

        final FileOutputStream out = new FileOutputStream(file);
        try {
            manifest.write(out);
        } finally {
            out.close();
        }
    }

    private void handleFeature(final MavenProject project, final String version) throws Exception {
        recordVersion("feature", project.getArtifactId(), version);

        this.changeManager.addChange(new Callable<Void>() {

            @Override
            public Void call() throws Exception {
                final File file = new File(project.getBasedir(), "feature.xml");
                final Document doc = XmlHelper.parse(file);
                XmlHelper.updateValue(doc, "/feature/@version", version);
                XmlHelper.write(doc, file);
                return null;
            }
        });
    }

    private final Map<String, Map<String, String>> versions = new HashMap<String, Map<String, String>>();

    private void recordVersion(final String type, final String artifactId, final String version) {
        Map<String, String> map = this.versions.get(type);
        if (map == null) {
            map = new HashMap<String, String>();
            this.versions.put(type, map);
        }
        final String oldVersion = map.put(artifactId, version);
        if (oldVersion != null) {
            throw new RuntimeException(String.format("Duplicate version entry for '%s':'%s'", type, artifactId));
        }
    }

    private String getVersion(final String type, final String artifcatId) {
        final String version = getVersionUnchecked(type, artifcatId);
        if (version == null) {
            throw new RuntimeException(String.format("No version recorded for '%s':'%s'", type, artifcatId));
        }
        return version;
    }

    private String getVersionUnchecked(final String type, final String artifactId) {
        final Map<String, String> map = this.versions.get(type);
        if (map == null) {
            return null;
        }
        return map.get(artifactId);
    }

    private String makeVersion(final String version, final String qualifier) {
        if (version == null) {
            // no version at all
            return null;
        }

        final LinkedList<String> toks = new LinkedList<String>(Arrays.asList(version.split("(\\.|-)")));

        getLog().debug("Version split: " + toks);

        while (toks.size() > 3) {
            toks.removeLast();
        }
        while (toks.size() < 3) {
            toks.add("0");
        }

        if (qualifier != null && !qualifier.isEmpty()) {
            toks.add(qualifier);
        }

        final StringBuilder sb = new StringBuilder();
        while (!toks.isEmpty()) {
            if (sb.length() > 0) {
                sb.append('.');
            }
            sb.append(toks.pollFirst());
        }
        return sb.toString();
    }

    private String makeVersion(final MavenProject project, final String qualifier) {
        return makeVersion(project.getVersion(), qualifier);
    }

}