org.nuxeo.runtime.deployment.preprocessor.PackWar.java Source code

Java tutorial

Introduction

Here is the source code for org.nuxeo.runtime.deployment.preprocessor.PackWar.java

Source

/*
 * (C) Copyright 2006-2016 Nuxeo SA (http://nuxeo.com/) and others.
 *
 * 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.
 *
 * Contributors:
 *     Bogdan Stefanescu
 *     Julien Carsique
 *     Florent Guillaume
 */
package org.nuxeo.runtime.deployment.preprocessor;

import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.nuxeo.common.Environment;
import org.nuxeo.launcher.config.ConfigurationException;
import org.nuxeo.launcher.config.ConfigurationGenerator;
import org.nuxeo.launcher.config.TomcatConfigurator;
import org.nuxeo.runtime.api.Framework;
import org.nuxeo.runtime.deployment.NuxeoStarter;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;

/**
 * Packs a Nuxeo Tomcat instance into a WAR file inside a ZIP.
 */
public class PackWar {

    private static Log log = LogFactory.getLog(PackWar.class);

    private static final List<String> ENDORSED_LIBS = Arrays.asList("xercesImpl");

    private static final List<String> MISSING_WEBINF_LIBS = Arrays.asList( //
            "mail", //
            "freemarker");

    private static final List<String> MISSING_LIBS = Arrays.asList( //
            // WSS
            "nuxeo-wss-front", //
            // Commons and logging
            "log4j", //
            "commons-logging", //
            "commons-lang", //
            "jcl-over-slf4j", //
            "slf4j-api", //
            "slf4j-log4j12", //
            "tomcat-juli-adapters", //
            // JDBC
            "derby", // Derby
            "h2", // H2
            "ojdbc", // Oracle
            "postgresql", // PostgreSQL
            "mysql-connector-java", // MySQL
            "nuxeo-core-storage-sql-extensions", // for Derby/H2
            "lucene", // for H2
            "elasticsearch");

    private static final String ENDORSED_LIB = "endorsed/";

    private static final String ZIP_LIB = "lib/";

    private static final String ZIP_WEBAPPS = "webapps/";

    private static final String ZIP_WEBINF = "WEB-INF/";

    private static final String ZIP_WEBINF_LIB = ZIP_WEBINF + "lib/";

    private static final String ZIP_README = "README-NUXEO.txt";

    private static final String README_BEGIN = //
            "This ZIP must be uncompressed at the root of your Tomcat instance.\n" //
                    + "\n" //
                    + "In order for Nuxeo to run, the following Resource defining your JDBC datasource configuration\n" //
                    + "must be added inside the <GlobalNamingResources> section of the file conf/server.xml\n" //
                    + "\n  ";

    private static final String README_END = "\n\n" //
            + "Make sure that the 'url' attribute above is correct.\n" //
            + "Note that the following file can also contains database configuration:\n" //
            + "\n" //
            + "  webapps/nuxeo/WEB-INF/default-repository-config.xml\n" //
            + "\n" //
            + "Also note that you should start Tomcat with more memory than its default, for instance:\n" //
            + "\n" //
            + "  JAVA_OPTS=\"-Xms512m -Xmx1024m -Dnuxeo.log.dir=logs\" bin/catalina.sh start\n" //
            + "\n" //
            + "";

    private static final String COMMAND_PREPROCESSING = "preprocessing";

    private static final String COMMAND_PACKAGING = "packaging";

    protected File nxserver;

    protected File tomcat;

    protected File zip;

    private ConfigurationGenerator cg;

    private TomcatConfigurator tomcatConfigurator;

    public PackWar(File nxserver, File zip) {
        if (!nxserver.isDirectory() || !nxserver.getName().equals("nxserver")) {
            fail("No nxserver found at " + nxserver);
        }
        if (zip.exists()) {
            fail("Target ZIP file " + zip + " already exists");
        }
        this.nxserver = nxserver;
        tomcat = nxserver.getParentFile();
        this.zip = zip;
    }

    public void execute(String command) throws ConfigurationException, IOException {
        boolean preprocessing = COMMAND_PREPROCESSING.equals(command) || StringUtils.isBlank(command);
        boolean packaging = COMMAND_PACKAGING.equals(command) || StringUtils.isBlank(command);
        if (!preprocessing && !packaging) {
            fail("Command parameter should be empty or " + COMMAND_PREPROCESSING + " or " + COMMAND_PACKAGING);
        }
        if (preprocessing) {
            executePreprocessing();
        }
        if (packaging) {
            executePackaging();
        }
    }

    protected void executePreprocessing() throws ConfigurationException, IOException {
        runTemplatePreprocessor();
        runDeploymentPreprocessor();
    }

    protected void runTemplatePreprocessor() throws ConfigurationException {
        if (System.getProperty(Environment.NUXEO_HOME) == null) {
            System.setProperty(Environment.NUXEO_HOME, tomcat.getAbsolutePath());
        }
        if (System.getProperty(ConfigurationGenerator.NUXEO_CONF) == null) {
            System.setProperty(ConfigurationGenerator.NUXEO_CONF, new File(tomcat, "bin/nuxeo.conf").getPath());
        }
        cg = new ConfigurationGenerator();
        cg.run();
        tomcatConfigurator = ((TomcatConfigurator) cg.getServerConfigurator());
    }

    protected void runDeploymentPreprocessor() throws IOException {
        DeploymentPreprocessor processor = new DeploymentPreprocessor(nxserver);
        processor.init();
        processor.predeploy();
    }

    protected void executePackaging() throws IOException {
        OutputStream out = new FileOutputStream(zip);
        ZipOutputStream zout = new ZipOutputStream(out);
        try {

            // extract jdbc datasource from server.xml into README
            ByteArrayOutputStream bout = new ByteArrayOutputStream();
            bout.write(README_BEGIN.getBytes("UTF-8"));
            ServerXmlProcessor.INSTANCE.process(newFile(tomcat, "conf/server.xml"), bout);
            bout.write(README_END.replace("webapps/nuxeo", "webapps/" + tomcatConfigurator.getContextName())
                    .getBytes("UTF-8"));
            zipBytes(ZIP_README, bout.toByteArray(), zout);

            File nuxeoXml = new File(tomcat, tomcatConfigurator.getTomcatConfig());
            String zipWebappsNuxeo = ZIP_WEBAPPS + tomcatConfigurator.getContextName() + "/";
            zipFile(zipWebappsNuxeo + "META-INF/context.xml", nuxeoXml, zout, NuxeoXmlProcessor.INSTANCE);
            zipTree(zipWebappsNuxeo, new File(nxserver, "nuxeo.war"), false, zout);
            zipTree(zipWebappsNuxeo + ZIP_WEBINF, new File(nxserver, "config"), false, zout);
            File nuxeoBundles = listNuxeoBundles();
            zipFile(zipWebappsNuxeo + ZIP_WEBINF + NuxeoStarter.NUXEO_BUNDLES_LIST, nuxeoBundles, zout, null);
            nuxeoBundles.delete();
            zipTree(zipWebappsNuxeo + ZIP_WEBINF_LIB, new File(nxserver, "bundles"), false, zout);
            zipTree(zipWebappsNuxeo + ZIP_WEBINF_LIB, new File(nxserver, "lib"), false, zout);
            zipLibs(zipWebappsNuxeo + ZIP_WEBINF_LIB, new File(tomcat, "lib"), MISSING_WEBINF_LIBS, zout);
            zipLibs(ZIP_LIB, new File(tomcat, "lib"), MISSING_LIBS, zout);
            zipFile(ZIP_LIB + "log4j.xml", newFile(tomcat, "lib/log4j.xml"), zout, null);
            zipTree(ENDORSED_LIB, new File(tomcat, "endorsed"), false, zout);
            zipLibs(ENDORSED_LIB, new File(tomcat, "lib"), ENDORSED_LIBS, zout);
        } finally {
            zout.finish();
            zout.close();
        }
    }

    /**
     * @throws IOException
     * @since 5.9.3
     */
    private File listNuxeoBundles() throws IOException {
        File nuxeoBundles = Framework.createTempFile(NuxeoStarter.NUXEO_BUNDLES_LIST, "");
        File[] bundles = new File(nxserver, "bundles").listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return name.endsWith(".jar");
            }
        });
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(nuxeoBundles))) {
            for (File bundle : bundles) {
                writer.write(bundle.getName());
                writer.newLine();
            }
        }
        return nuxeoBundles;
    }

    protected static File newFile(File base, String path) {
        return new File(base, path.replace("/", File.separator));
    }

    protected void zipLibs(String prefix, File dir, List<String> patterns, ZipOutputStream zout)
            throws IOException {
        for (String name : dir.list()) {
            for (String pat : patterns) {
                if ((name.startsWith(pat + '-') && name.endsWith(".jar")) || name.equals(pat + ".jar")) {
                    zipFile(prefix + name, new File(dir, name), zout, null);
                    break;
                }
            }
        }
    }

    protected void zipDirectory(String entryName, ZipOutputStream zout) throws IOException {
        ZipEntry zentry = new ZipEntry(entryName);
        zout.putNextEntry(zentry);
        zout.closeEntry();
    }

    protected void zipFile(String entryName, File file, ZipOutputStream zout, FileProcessor processor)
            throws IOException {
        ZipEntry zentry = new ZipEntry(entryName);
        if (processor == null) {
            processor = CopyProcessor.INSTANCE;
            zentry.setTime(file.lastModified());
        }
        zout.putNextEntry(zentry);
        processor.process(file, zout);
        zout.closeEntry();
    }

    protected void zipBytes(String entryName, byte[] bytes, ZipOutputStream zout) throws IOException {
        ZipEntry zentry = new ZipEntry(entryName);
        zout.putNextEntry(zentry);
        zout.write(bytes);
        zout.closeEntry();
    }

    /** prefix ends with '/' */
    protected void zipTree(String prefix, File root, boolean includeRoot, ZipOutputStream zout) throws IOException {
        if (includeRoot) {
            prefix += root.getName() + '/';
            zipDirectory(prefix, zout);
        }
        String zipWebappsNuxeo = ZIP_WEBAPPS + tomcatConfigurator.getContextName() + "/";
        for (String name : root.list()) {
            File file = new File(root, name);
            if (file.isDirectory()) {
                zipTree(prefix, file, true, zout);
            } else {
                if (name.endsWith("~") //
                        || name.endsWith("#") //
                        || name.endsWith(".bak") //
                        || name.equals("README.txt")) {
                    continue;
                }
                name = prefix + name;
                FileProcessor processor;
                if (name.equals(zipWebappsNuxeo + ZIP_WEBINF + "web.xml")) {
                    processor = WebXmlProcessor.INSTANCE;
                } else if (name.equals(zipWebappsNuxeo + ZIP_WEBINF + "opensocial.properties")) {
                    processor = new PropertiesFileProcessor("res://config/", zipWebappsNuxeo + ZIP_WEBINF);
                } else {
                    processor = null;
                }
                zipFile(name, file, zout, processor);
            }
        }
    }

    protected interface FileProcessor {
        void process(File file, OutputStream out) throws IOException;
    }

    protected static class CopyProcessor implements FileProcessor {

        public static final CopyProcessor INSTANCE = new CopyProcessor();

        @Override
        public void process(File file, OutputStream out) throws IOException {
            FileInputStream in = new FileInputStream(file);
            try {
                IOUtils.copy(in, out);
            } finally {
                in.close();
            }
        }
    }

    protected class PropertiesFileProcessor implements FileProcessor {

        protected String target;

        protected String replacement;

        public PropertiesFileProcessor(String target, String replacement) {
            this.target = target;
            this.replacement = replacement;
        }

        @Override
        public void process(File file, OutputStream out) throws IOException {
            FileInputStream in = new FileInputStream(file);
            try {
                List<String> lines = IOUtils.readLines(in, "UTF-8");
                List<String> outLines = new ArrayList<>();
                for (String line : lines) {
                    outLines.add(line.replace(target, replacement));
                }
                IOUtils.writeLines(outLines, null, out, "UTF-8");
            } finally {
                in.close();
            }
        }
    }

    protected static abstract class XmlProcessor implements FileProcessor {

        @Override
        public void process(File file, OutputStream out) throws IOException {
            DocumentBuilder parser;
            try {
                parser = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            } catch (ParserConfigurationException e) {
                throw (IOException) new IOException().initCause(e);
            }
            InputStream in = new FileInputStream(file);
            try {
                Document doc = parser.parse(in);
                doc.setStrictErrorChecking(false);
                process(doc);
                Transformer trans = TransformerFactory.newInstance().newTransformer();
                trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
                trans.setOutputProperty(OutputKeys.INDENT, "yes");
                trans.transform(new DOMSource(doc), new StreamResult(out));
            } catch (SAXException e) {
                throw (IOException) new IOException().initCause(e);
            } catch (TransformerException e) {
                throw (IOException) new IOException().initCause(e);
            } finally {
                in.close();
            }
        }

        protected abstract void process(Document doc);
    }

    protected static class WebXmlProcessor extends XmlProcessor {

        public static WebXmlProcessor INSTANCE = new WebXmlProcessor();

        private static final String LISTENER = "listener";

        private static final String LISTENER_CLASS = "listener-class";

        @Override
        protected void process(Document doc) {
            Node n = doc.getDocumentElement().getFirstChild();
            while (n != null) {
                if (LISTENER.equals(n.getNodeName())) {
                    // insert initial listener
                    Element listener = doc.createElement(LISTENER);
                    n.insertBefore(listener, n);
                    listener.appendChild(doc.createElement(LISTENER_CLASS))
                            .appendChild(doc.createTextNode(NuxeoStarter.class.getName()));
                    break;
                }
                n = n.getNextSibling();
            }
        }
    }

    protected static class NuxeoXmlProcessor extends XmlProcessor {

        public static NuxeoXmlProcessor INSTANCE = new NuxeoXmlProcessor();

        private static final String DOCBASE = "docBase";

        private static final String LOADER = "Loader";

        private static final String LISTENER = "Listener";

        @Override
        protected void process(Document doc) {
            Element root = doc.getDocumentElement();
            root.removeAttribute(DOCBASE);
            Node n = root.getFirstChild();
            while (n != null) {
                Node next = n.getNextSibling();
                String name = n.getNodeName();
                if (LOADER.equals(name) || LISTENER.equals(name)) {
                    root.removeChild(n);
                }
                n = next;
            }
        }
    }

    protected static class ServerXmlProcessor implements FileProcessor {

        public static ServerXmlProcessor INSTANCE = new ServerXmlProcessor();

        private static final String GLOBAL_NAMING_RESOURCES = "GlobalNamingResources";

        private static final String RESOURCE = "Resource";

        private static final String NAME = "name";

        private static final String JDBC_NUXEO = "jdbc/nuxeo";

        public String resource;

        @Override
        public void process(File file, OutputStream out) throws IOException {
            DocumentBuilder parser;
            try {
                parser = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            } catch (ParserConfigurationException e) {
                throw (IOException) new IOException().initCause(e);
            }
            InputStream in = new FileInputStream(file);
            try {
                Document doc = parser.parse(in);
                doc.setStrictErrorChecking(false);
                Element root = doc.getDocumentElement();
                Node n = root.getFirstChild();
                Element resourceElement = null;
                while (n != null) {
                    Node next = n.getNextSibling();
                    String name = n.getNodeName();
                    if (GLOBAL_NAMING_RESOURCES.equals(name)) {
                        next = n.getFirstChild();
                    }
                    if (RESOURCE.equals(name)) {
                        if (((Element) n).getAttribute(NAME).equals(JDBC_NUXEO)) {
                            resourceElement = (Element) n;
                            break;
                        }
                    }
                    n = next;
                }
                Transformer trans = TransformerFactory.newInstance().newTransformer();
                trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
                trans.setOutputProperty(OutputKeys.INDENT, "no");
                trans.transform(new DOMSource(resourceElement), // only resource
                        new StreamResult(out));
            } catch (SAXException e) {
                throw (IOException) new IOException().initCause(e);
            } catch (TransformerException e) {
                throw (IOException) new IOException().initCause(e);
            } finally {
                in.close();
            }
        }

    }

    public static void fail(String message) {
        fail(message, null);
    }

    public static void fail(String message, Throwable t) {
        log.error(message, t);
        System.exit(1);
    }

    public static void main(String[] args) {
        if (args.length < 2 || args.length > 3 || (args.length == 3
                && !Arrays.asList(COMMAND_PREPROCESSING, COMMAND_PACKAGING).contains(args[2]))) {
            fail(String.format(
                    "Usage: %s <nxserver_dir> <target_zip> [command]\n"
                            + "    command may be empty or '%s' or '%s'",
                    PackWar.class.getSimpleName(), COMMAND_PREPROCESSING, COMMAND_PACKAGING));
        }

        File nxserver = new File(args[0]).getAbsoluteFile();
        File zip = new File(args[1]).getAbsoluteFile();
        String command = args.length == 3 ? args[2] : null;

        log.info("Packing nuxeo WAR at " + nxserver + " into " + zip);
        try {
            new PackWar(nxserver, zip).execute(command);
        } catch (ConfigurationException | IOException e) {
            fail("Pack failed", e);
        }
    }

}