org.openengsb.openengsbplugin.base.ConfiguredMojo.java Source code

Java tutorial

Introduction

Here is the source code for org.openengsb.openengsbplugin.base.ConfiguredMojo.java

Source

/**
 * Licensed to the Austrian Association for Software Tool Integration (AASTI)
 * under one or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information regarding copyright
 * ownership. The AASTI licenses this file to you 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.openengsb.openengsbplugin.base;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import org.apache.maven.plugin.MojoExecutionException;
import org.openengsb.openengsbplugin.tools.Tools;
import org.openengsb.openengsbplugin.xml.OpenEngSBMavenPluginNSContext;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public abstract class ConfiguredMojo extends MavenExecutorMojo {

    public enum PomRestoreMode {
        CLEAN, RESTORE_BACKUP
    }

    private static final Logger LOG = Logger.getLogger(ConfiguredMojo.class);

    /**
     * Stores references to poms corresponding to keys in {@link ConfiguredMojo#pomConfigs}
     */
    private HashMap<String, File> poms = new HashMap<String, File>();

    /**
     * points to backups of modified poms in the local tmp dir. The key is the original
     * pom in the project (e.g. "docs/pom.xml"), where the value is the
     * reference to the file in the tmp dir.
     */
    private HashMap<File, File> pomBackups = new HashMap<File, File>();

    // #################################
    // set these in subclass constructor
    // #################################

    /**
     * Defines which configs (from {@code resources/xxx}) to merge into which pom
     * where key = path to pom, relative to current dir (where the mojo is invoked).
     * For examples please see constructor of every direct subclass of
     * {@link ConfiguredMojo} ( e.g. {@link LicenseMojo#LicenseMojo()}.
     */
    protected HashMap<String, List<String>> pomConfigs = new HashMap<String, List<String>>();

    // #################################

    protected String cocProfile;
    protected String cocProfileXpath;

    protected static final OpenEngSBMavenPluginNSContext NS_CONTEXT = new OpenEngSBMavenPluginNSContext();
    protected static final String POM_NS_URI = NS_CONTEXT.getNamespaceURI("pom");
    private static final String POM_PROFILE_XPATH = "/pom:project/pom:profiles";

    private static final String CONFIGNODE_XPATH = "/c:config";
    private static final String PLUGINS_XPATH = CONFIGNODE_XPATH + "/pom:plugins/pom:plugin";
    private static final String MODULES_XPATH = CONFIGNODE_XPATH + "/pom:modules/pom:module";
    private static final String RESOURCES_XPATH = CONFIGNODE_XPATH + "/pom:resources/pom:resource";

    protected static final List<File> FILES_TO_REMOVE_FINALLY = new ArrayList<File>();

    private PomRestoreMode pomRestoreMode = PomRestoreMode.RESTORE_BACKUP;
    private boolean pomCleanedSuccessfully = false;

    protected Node licenseHeaderComment = null;

    /**
     * If set to "true" prints the temporary pom to the console.
     *
     * @parameter expression="${debugMode}" default-value="false"
     */
    private boolean debugMode;

    @Override
    protected final void configure() throws MojoExecutionException {
        LOG.trace("-> configure");
        cocProfile = UUID.randomUUID().toString();
        cocProfileXpath = String.format("/pom:project/pom:profiles/pom:profile[pom:id[text()='%s']]", cocProfile);
        configureCoCMojo();

        checkForPoms();
        for (String pomPath : poms.keySet()) {
            configureTmpPom(pomPath, poms.get(pomPath), cocProfile);
        }
    }

    private void checkForPoms() throws MojoExecutionException {
        for (String pomPath : pomConfigs.keySet()) {
            File pom = new File(pomPath);
            if (!pom.exists()) {
                throw new MojoExecutionException(String.format("pom doesn't exist: %s", pomPath));
            }
            poms.put(pomPath, pom);
        }
    }

    /**
     * Configure how to restore (remove the unnecessary temporary profile) the
     * pom after successful mojo execution.
     *
     * @param mode <ul>
     *        <li>{@link PomRestoreMode#RESTORE_BACKUP}: replace the pom with
     *        the backup which has been created at mojo startup - this is the
     *        default setting for all CoC mojos</li>
     *        <li>{@link PomRestoreMode#CLEAN}: don't replace the pom with the
     *        backup, only remove the profile which has been created for this
     *        mojo run - this setting can be useful if the wrapped mojo also
     *        modifies this pom (e.g. the maven-release-plugin changing version
     *        numbers), so that these changes don't get lost</li>
     *        <ul>
     */
    protected final void setPomRestoreMode(PomRestoreMode mode) {
        pomRestoreMode = mode;
    }

    @Override
    protected final void postExec() throws MojoExecutionException {
        if (pomRestoreMode == PomRestoreMode.CLEAN) {
            cleanPoms(cocProfile);
            pomCleanedSuccessfully = true;
            afterPomCleaned();
        }
    }

    /**
     * Template method which may be overwritten by subclasses. It gets executed
     * iff {@link PomRestoreMode} is set to {@link PomRestoreMode#CLEAN} and the
     * pom has been cleaned successfully. For usage example see
     * {@link ReleaseMojo#afterPomCleaned()}
     */
    protected void afterPomCleaned() throws MojoExecutionException {
    }

    @Override
    protected final void postExecFinally() {
        if (pomRestoreMode == PomRestoreMode.RESTORE_BACKUP
                || pomRestoreMode == PomRestoreMode.CLEAN && !pomCleanedSuccessfully) {
            restoreOriginalPom();
        }
        cleanUp();
    }

    /**
     * remove profile with id {@code profile} from the pom (used to clean pom
     * from configuration which gets merged into it during runtime)
     */
    private void cleanPoms(String profile) throws MojoExecutionException {
        LOG.trace("cleanPom()");
        try {
            for (File pomFile : poms.values()) {
                Document docToClean = parsePom(pomFile);

                if (!Tools.removeNode(cocProfileXpath, docToClean, NS_CONTEXT, true)) {
                    throw new MojoExecutionException("Couldn't clean the pom!");
                }

                String cleanedContent = Tools.serializeXML(docToClean);

                writeIntoPom(getSession().getRequest().getPom(), cleanedContent);
            }
        } catch (Exception e) {
            if (e instanceof MojoExecutionException) {
                throw (MojoExecutionException) e;
            } else {
                throw new MojoExecutionException(e.getMessage(), e);
            }
        }
        LOG.trace("pom cleaned successfully");
    }

    /**
     * restore backup of the pom which has been created on mojo start
     */
    private void restoreOriginalPom() {
        LOG.trace("-> restoreOriginalPom");
        try {
            for (File pomFile : poms.values()) {
                FileUtils.copyFile(pomBackups.get(pomFile), pomFile);
            }
        } catch (Exception e) {
            // do nothing
        }
    }

    /**
     * template method for subclasses where you can declare which goals should
     * be used and which configuration should be merged into the poms
     */
    protected abstract void configureCoCMojo() throws MojoExecutionException;

    private void configureTmpPom(String pomPath, File pomFile, String profileName) throws MojoExecutionException {
        try {

            File backupPom = backupOriginalPom(pomFile);
            pomBackups.put(pomFile, backupPom);

            FILES_TO_REMOVE_FINALLY.add(backupPom);

            Document pomDocumentToConfigure = parsePom(pomFile);
            Document configDocument = collectConfigsAndBuildProfile(pomPath);

            insertConfigProfileIntoOrigPom(pomDocumentToConfigure, configDocument, profileName);

            modifyMojoConfiguration(pomPath, pomDocumentToConfigure);

            String serializedXml = Tools.serializeXML(pomDocumentToConfigure);

            if (debugMode) {
                System.out.print(serializedXml);
            }

            writeIntoPom(pomFile, serializedXml);
        } catch (Exception e) {
            LOG.warn(e.getMessage(), e);
            throw new MojoExecutionException("Couldn't configure temporary pom for this execution!", e);
        }
    }

    /**
     * If you want to modify the xml configuration of the mojo (e.g. add some
     * configuration which you is not known in
     * {@link ConfiguredMojo#configureTmpPom(String)}), then this is the place
     * where to do it. Simply overwrite this method in your subclass.
     */
    protected void modifyMojoConfiguration(String pomPath, Document configuredPom) throws MojoExecutionException {
    }

    protected String addHeader(String content) throws IOException {
        String result = "";
        StringReader stringReader = new StringReader(content);
        BufferedReader br = new BufferedReader(stringReader);

        String line;
        do {
            line = br.readLine();
            result += line + "\n";
        } while (!line.contains("<?xml"));

        result += "<!--";
        result += licenseHeaderComment.getTextContent();
        result += "-->\n\n";

        line = br.readLine();
        while (line != null) {
            result += line + "\n";
            line = br.readLine();
        }

        br.close();

        licenseHeaderComment = null;

        return result;
    }

    /**
     * parse pom and remove license header if available
     */
    private Document parsePom(File pom) throws Exception {
        Document doc = Tools.parseXMLFromString(FileUtils.readFileToString(pom));
        tryExtractLicenseHeader(doc);
        return doc;
    }

    protected void tryExtractLicenseHeader(Document doc) {
        Node firstNode = doc.getChildNodes().item(0);
        if (firstNode.getNodeType() == Node.COMMENT_NODE) {
            LOG.trace(String.format("found license header with content:\n%s", firstNode.getNodeValue()));
            licenseHeaderComment = doc.removeChild(firstNode);
        }
    }

    /**
     * Build a profile of alle specified config files which later gets merged into
     * the pom.
     */
    private Document collectConfigsAndBuildProfile(String pomPath)
            throws ParserConfigurationException, SAXException, IOException, XPathExpressionException {

        Document profileDoc = Tools.newDOM();
        Element profileElement = profileDoc.createElementNS(POM_NS_URI, "profile");
        profileDoc.appendChild(profileElement);
        Element buildElement = profileDoc.createElementNS(POM_NS_URI, "build");
        profileElement.appendChild(buildElement);
        Element pluginsElement = profileDoc.createElementNS(POM_NS_URI, "plugins");
        buildElement.appendChild(pluginsElement);
        Element modulesElement = profileDoc.createElementNS(POM_NS_URI, "modules");
        profileElement.appendChild(modulesElement);
        Element resourcesElement = profileDoc.createElementNS(POM_NS_URI, "resources");
        buildElement.appendChild(resourcesElement);

        /*
         * if you want to support further profile configuration (current:
         * <plugin>, <modules>, <resources>) this is the place where to put it
         */

        for (String configFilePath : pomConfigs.get(pomPath)) {
            Document configDocument = Tools.parseXMLFromString(
                    IOUtils.toString(getClass().getClassLoader().getResourceAsStream(configFilePath)));
            collectFromXPathAndImportConfigurations(PLUGINS_XPATH, configDocument, profileDoc, buildElement,
                    pluginsElement);
            collectFromXPathAndImportConfigurations(MODULES_XPATH, configDocument, profileDoc, profileElement,
                    modulesElement);
            collectFromXPathAndImportConfigurations(RESOURCES_XPATH, configDocument, profileDoc, buildElement,
                    resourcesElement);
        }

        return profileDoc;
    }

    private void collectFromXPathAndImportConfigurations(String xpath, Document source, Document dest,
            Element parentParent, Element targetParent) throws XPathExpressionException {
        NodeList nl = Tools.evaluateXPath(xpath, source, NS_CONTEXT, XPathConstants.NODESET, NodeList.class);
        LOG.trace(String.format("Found nodes: %s", nl.getLength()));
        if (nl.getLength() == 0) {
            return;
        }
        importNodesFromNodeList(dest, targetParent, nl);
    }

    private void importNodesFromNodeList(Document doc, Element parentElement, NodeList nodes) {
        for (int i = 0; i < nodes.getLength(); i++) {
            Node importedNode = doc.importNode(nodes.item(i), true);
            parentElement.appendChild(importedNode);
            LOG.trace(String.format("importing node (parent=%s): %s", parentElement.getLocalName(),
                    importedNode.getNodeName()));
        }
    }

    private void insertConfigProfileIntoOrigPom(Document originalPom, Document mojoConfiguration,
            String profileName) throws XPathExpressionException {
        Node profileNode = mojoConfiguration.getFirstChild();

        Node idNode = mojoConfiguration.createElementNS(POM_NS_URI, "id");
        idNode.setTextContent(profileName);
        profileNode.insertBefore(idNode, profileNode.getFirstChild());

        Node importedProfileNode = originalPom.importNode(profileNode, true);

        Tools.insertDomNode(originalPom, importedProfileNode, POM_PROFILE_XPATH, NS_CONTEXT);
    }

    private void writeIntoPom(File pomFile, String content) throws IOException {
        if (licenseHeaderComment != null) {
            content = addHeader(content);
        }
        FileUtils.writeStringToFile(pomFile, content + "\n");
    }

    private void cleanUp() {
        LOG.trace("-> cleanup()");
        for (File f : FILES_TO_REMOVE_FINALLY) {
            FileUtils.deleteQuietly(f);
        }
    }

    private File backupOriginalPom(File originalPom) throws IOException {
        return Tools.generateTmpFile(FileUtils.readFileToString(originalPom), ".xml");
    }

}