com.st.maven.debian.DebianPackageMojo.java Source code

Java tutorial

Introduction

Here is the source code for com.st.maven.debian.DebianPackageMojo.java

Source

package com.st.maven.debian;

/*
 * Copyright 2001-2005 The Apache Software Foundation.
 *
 * 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.
 */

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPOutputStream;

import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.compress.utils.IOUtils;
import org.apache.maven.model.Developer;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;

import com.google.code.ar.ArEntry;
import com.google.code.ar.ArFileOutputStream;

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateExceptionHandler;

/**
 * @goal package
 * @phase install
 */
public class DebianPackageMojo extends AbstractMojo {

    /**
     * The maven project.
     * 
     * @parameter property="project"
     * @readonly
     */
    private MavenProject project;

    /**
     * @parameter
     */
    private Map<Object, Object> osDependencies;

    /**
     * @parameter
     */
    private List<Fileset> fileSets;

    /**
     * @parameter
     */
    private String section;

    /**
     * @parameter
     */
    private String arch;

    /**
     * @parameter
     */
    private String priority;

    /**
     * @parameter
     * @required
     */
    private String unixUserId;

    /**
     * @parameter
     * @required
     */
    private String unixGroupId;

    /**
     * @parameter default-value=true;
     */
    private Boolean javaServiceWrapper;

    /**
     * @parameter default-value=true;
     */
    private Boolean attachArtifact;

    private final static String BASE_DIR = "./src/main/deb";
    private final static Pattern email = Pattern
            .compile("^[_A-Za-z0-9-]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$");
    private Configuration freemarkerConfig = new Configuration();
    private Set<String> dirsAdded = new HashSet<String>();
    private Set<String> ignore = new HashSet<String>();

    @Override
    public void execute() throws MojoExecutionException {

        validate();
        fillDefaults();

        freemarkerConfig.setDefaultEncoding("UTF-8");
        freemarkerConfig.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
        freemarkerConfig.setClassForTemplateLoading(DebianPackageMojo.class, "/");
        freemarkerConfig.setTimeZone(TimeZone.getTimeZone("GMT"));

        Config config = new Config();
        config.setArtifactId(project.getArtifactId());
        config.setDescription(project.getDescription());
        config.setGroup(unixGroupId);
        config.setUser(unixUserId);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
        config.setVersion(sdf.format(new Date()));
        Developer dev = project.getDevelopers().get(0);
        String maintainer = dev.getName() + " <" + dev.getEmail() + ">";
        config.setMaintainer(maintainer);
        config.setName(project.getName());
        config.setDescription(project.getDescription());
        config.setDepends(formatDependencies(osDependencies));
        config.setInstallDir("/home/" + unixUserId + "/" + project.getArtifactId());
        if (section == null || section.trim().length() == 0) {
            config.setSection("java");
        } else {
            config.setSection(section);
        }
        if (arch == null || arch.trim().length() == 0) {
            config.setArch("all");
        } else {
            config.setArch(arch);
        }
        if (priority == null || priority.trim().length() == 0) {
            config.setPriority(Priority.STANDARD);
        } else {
            config.setPriority(Priority.valueOf(priority.toUpperCase(Locale.UK)));
        }

        ArFileOutputStream aros = null;
        try {
            File debFile = new File(project.getBuild().getDirectory() + File.separator + project.getArtifactId()
                    + "-" + config.getVersion() + ".deb");
            getLog().info("Building deb: " + debFile.getAbsolutePath());
            aros = new ArFileOutputStream(debFile.getAbsolutePath());
            aros.putNextEntry(createEntry("debian-binary"));
            aros.write("2.0\n".getBytes(StandardCharsets.US_ASCII));
            aros.closeEntry();
            aros.putNextEntry(createEntry("control.tar.gz"));
            fillControlTar(config, aros);
            aros.closeEntry();
            aros.putNextEntry(createEntry("data.tar.gz"));
            fillDataTar(config, aros);
            aros.closeEntry();
            if (attachArtifact) {
                VersionableAttachedArtifact artifact = new VersionableAttachedArtifact(project.getArtifact(), "deb",
                        config.getVersion());
                artifact.setFile(debFile);
                artifact.setResolved(true);
                project.addAttachedArtifact(artifact);
            }
        } catch (Exception e) {
            throw new MojoExecutionException("unable to create .deb file", e);
        } finally {
            if (aros != null) {
                try {
                    aros.close();
                } catch (IOException e) {
                    throw new MojoExecutionException("unable to close .deb file", e);
                }
            }
        }
    }

    private void fillDefaults() {
        unixUserId = unixUserId.trim();
        if (Boolean.TRUE.equals(javaServiceWrapper) && !osDependencies.containsKey("service-wrapper")) {
            osDependencies.put("service-wrapper", null);
        }
        ignore.add(".svn");
        ignore.add("preinst");
        ignore.add("postinst");
        ignore.add("prerm");
        ignore.add("postrm");
    }

    private void validate() throws MojoExecutionException {
        if (unixUserId == null || unixUserId.trim().length() == 0) {
            throw new MojoExecutionException("unixUserId should be specified");
        }
        if (unixUserId.trim().length() > 8) {
            throw new MojoExecutionException("unixUserId lenght should be less than 8");
        }
        File debDir = new File(BASE_DIR);
        if (!debDir.exists()) {
            throw new MojoExecutionException(".deb base directory doesnt exist: " + BASE_DIR);
        }
        if (project.getDescription() == null || project.getDescription().trim().length() == 0) {
            throw new MojoExecutionException("project description is mandatory");
        }
        if (project.getDevelopers() == null || project.getDevelopers().isEmpty()) {
            throw new MojoExecutionException(
                    "project maintainer is mandatory. Please specify valid \"developers\" entry");
        }
        Developer dev = project.getDevelopers().get(0);
        if (dev == null) {
            throw new MojoExecutionException(
                    "project maintainer is mandatory. Please specify valid \"developers\" entry");
        }
        if (dev.getName() == null || dev.getName().trim().length() == 0) {
            throw new MojoExecutionException(
                    "project maintainer name is mandatory. Please fill valid developer name");
        }
        if (dev.getEmail() == null || dev.getEmail().trim().length() == 0) {
            throw new MojoExecutionException(
                    "project maintainer email is mandatory. Please specify valid developer email");
        }
        Matcher m = email.matcher(dev.getEmail());
        if (!m.matches()) {
            throw new MojoExecutionException("invalid project maintainer email: " + dev.getEmail());
        }
    }

    private void fillDataTar(Config config, ArFileOutputStream output) throws MojoExecutionException {
        TarArchiveOutputStream tar = null;
        try {
            tar = new TarArchiveOutputStream(new GZIPOutputStream(new ArWrapper(output)));
            tar.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
            if (Boolean.TRUE.equals(javaServiceWrapper)) {
                byte[] daemonData = processTemplate(freemarkerConfig, config, "daemon.ftl");
                TarArchiveEntry initScript = new TarArchiveEntry("etc/init.d/" + project.getArtifactId());
                initScript.setSize(daemonData.length);
                initScript.setMode(040755);
                tar.putArchiveEntry(initScript);
                tar.write(daemonData);
                tar.closeArchiveEntry();
            }
            String packageBaseDir = "home/" + unixUserId + "/" + project.getArtifactId() + "/";
            if (fileSets != null && !fileSets.isEmpty()) {
                writeDirectory(tar, packageBaseDir);

                Collections.sort(fileSets, MappingPathComparator.INSTANCE);
                for (Fileset curPath : fileSets) {
                    curPath.setTarget(packageBaseDir + curPath.getTarget());
                    addRecursively(config, tar, curPath);
                }
            }

        } catch (Exception e) {
            throw new MojoExecutionException("unable to create data tar", e);
        } finally {
            IOUtils.closeQuietly(tar);
        }
    }

    private void addRecursively(Config config, TarArchiveOutputStream tar, Fileset fileset)
            throws MojoExecutionException {
        File sourceFile = new File(fileset.getSource());
        String targetFilename = fileset.getTarget();
        // skip well-known ignore directories
        if (ignore.contains(sourceFile.getName()) || sourceFile.getName().endsWith(".rrd")
                || sourceFile.getName().endsWith(".log")) {
            return;
        }
        FileInputStream fis = null;
        try {
            if (!sourceFile.isDirectory()) {
                TarArchiveEntry curEntry = new TarArchiveEntry(targetFilename);
                if (fileset.isFilter()) {
                    byte[] bytes = processTemplate(freemarkerConfig, config, fileset.getSource());
                    curEntry.setSize(bytes.length);
                    tar.putArchiveEntry(curEntry);
                    tar.write(bytes);
                } else {
                    curEntry.setSize(sourceFile.length());
                    tar.putArchiveEntry(curEntry);
                    fis = new FileInputStream(sourceFile);
                    IOUtils.copy(fis, tar);
                }
                tar.closeArchiveEntry();
            } else if (sourceFile.isDirectory()) {
                targetFilename += "/";
                if (!dirsAdded.contains(targetFilename)) {
                    dirsAdded.add(targetFilename);
                    writeDirectory(tar, targetFilename);
                }
            }
        } catch (Exception e) {
            throw new MojoExecutionException("unable to write", e);
        } finally {
            IOUtils.closeQuietly(fis);
        }

        if (sourceFile.isDirectory()) {
            File[] subFiles = sourceFile.listFiles();
            for (File curSubFile : subFiles) {
                Fileset curSubFileset = new Fileset(fileset.getSource() + "/" + curSubFile.getName(),
                        fileset.getTarget() + "/" + curSubFile.getName(), fileset.isFilter());
                addRecursively(config, tar, curSubFileset);
            }
        }
    }

    private static void writeDirectory(TarArchiveOutputStream tar, String dirName) throws MojoExecutionException {
        try {
            if (!dirName.endsWith("/")) {
                dirName = dirName + "/";
            }
            TarArchiveEntry curEntry = new TarArchiveEntry(dirName);
            tar.putArchiveEntry(curEntry);
            tar.closeArchiveEntry();
        } catch (Exception e) {
            throw new MojoExecutionException("unable to add directory: " + dirName, e);
        }
    }

    private void fillControlTar(Config config, ArFileOutputStream output) throws MojoExecutionException {
        TarArchiveOutputStream tar = null;
        try {
            tar = new TarArchiveOutputStream(new GZIPOutputStream(new ArWrapper(output)));
            tar.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
            TarArchiveEntry rootDir = new TarArchiveEntry("./");
            tar.putArchiveEntry(rootDir);
            tar.closeArchiveEntry();

            byte[] controlData = processTemplate(freemarkerConfig, config, "control.ftl");
            TarArchiveEntry controlEntry = new TarArchiveEntry("./control");
            controlEntry.setSize(controlData.length);
            tar.putArchiveEntry(controlEntry);
            tar.write(controlData);
            tar.closeArchiveEntry();

            byte[] preinstBaseData = processTemplate("preinst", freemarkerConfig, config,
                    combine("preinst.ftl", BASE_DIR + File.separator + "preinst", false));
            long size = preinstBaseData.length;
            TarArchiveEntry preinstEntry = new TarArchiveEntry("./preinst");
            preinstEntry.setSize(size);
            preinstEntry.setMode(0755);
            tar.putArchiveEntry(preinstEntry);
            tar.write(preinstBaseData);
            tar.closeArchiveEntry();

            byte[] postinstBaseData = processTemplate("postinst", freemarkerConfig, config,
                    combine("postinst.ftl", BASE_DIR + File.separator + "postinst", true));
            size = postinstBaseData.length;
            TarArchiveEntry postinstEntry = new TarArchiveEntry("./postinst");
            postinstEntry.setSize(size);
            postinstEntry.setMode(0755);
            tar.putArchiveEntry(postinstEntry);
            tar.write(postinstBaseData);
            tar.closeArchiveEntry();

            byte[] prermBaseData = processTemplate("prerm", freemarkerConfig, config,
                    combine("prerm.ftl", BASE_DIR + File.separator + "prerm", false));
            size = prermBaseData.length;
            TarArchiveEntry prermEntry = new TarArchiveEntry("./prerm");
            prermEntry.setSize(size);
            prermEntry.setMode(0755);
            tar.putArchiveEntry(prermEntry);
            tar.write(prermBaseData);
            tar.closeArchiveEntry();

            byte[] postrmBaseData = processTemplate("postrm", freemarkerConfig, config,
                    combine("postrm.ftl", BASE_DIR + File.separator + "postrm", false));
            size = postrmBaseData.length;
            TarArchiveEntry postrmEntry = new TarArchiveEntry("./postrm");
            postrmEntry.setSize(size);
            postrmEntry.setMode(0755);
            tar.putArchiveEntry(postrmEntry);
            tar.write(postrmBaseData);
            tar.closeArchiveEntry();

        } catch (Exception e) {
            throw new MojoExecutionException("unable to create control tar", e);
        } finally {
            if (tar != null) {
                try {
                    tar.close();
                } catch (IOException e) {
                    getLog().error("unable to finish tar", e);
                }
            }
        }
    }

    private static String combine(String classpathResource, String file, boolean inverse)
            throws MojoExecutionException {
        StringBuilder builder = new StringBuilder();
        builder.append("#!/bin/bash -e\n");
        if (inverse) {
            appendUserScript(file, builder);
            appendSystemScript(classpathResource, builder);
        } else {
            appendSystemScript(classpathResource, builder);
            appendUserScript(file, builder);
        }
        return builder.toString();
    }

    private static void appendUserScript(String file, StringBuilder builder) throws MojoExecutionException {
        BufferedReader r = null;
        try {
            File f = new File(file);
            String curLine = null;
            if (f.exists()) {
                r = new BufferedReader(new FileReader(file));
                while ((curLine = r.readLine()) != null) {
                    builder.append(curLine).append('\n');
                }
            }
        } catch (Exception e) {
            throw new MojoExecutionException("unable to combine data", e);
        } finally {
            IOUtils.closeQuietly(r);
        }
    }

    private static void appendSystemScript(String classpathResource, StringBuilder builder)
            throws MojoExecutionException {
        BufferedReader isr = null;
        try {
            isr = new BufferedReader(new InputStreamReader(
                    DebianPackageMojo.class.getClassLoader().getResourceAsStream(classpathResource), "UTF-8"));
            String curLine = null;
            while ((curLine = isr.readLine()) != null) {
                builder.append(curLine).append('\n');
            }
        } catch (Exception e) {
            throw new MojoExecutionException("unable to combine data", e);
        } finally {
            IOUtils.closeQuietly(isr);
        }
    }

    private static String formatDependencies(Map<Object, Object> osDependencies) {
        StringBuilder result = new StringBuilder();
        if (osDependencies != null && !osDependencies.isEmpty()) {
            Iterator<Object> keys = osDependencies.keySet().iterator();
            while (keys.hasNext()) {
                Object key = keys.next();
                if (key == null) {
                    continue;
                }
                Object value = osDependencies.get(key);
                if (result.length() != 0) {
                    result.append(", ");
                }
                result.append(key.toString());
                if (value != null) {
                    result.append(" (").append(value.toString()).append(")");
                }
            }
        }
        return result.toString();
    }

    private static byte[] processTemplate(Configuration freemarkerConfig, Config config, String name)
            throws IOException, TemplateException {
        Template fTemplate = freemarkerConfig.getTemplate(name);
        return processTemplate(config, fTemplate);
    }

    private static byte[] processTemplate(String name, Configuration freemarkerConfig, Config config,
            String filedata) throws TemplateException, IOException {
        Template fTemplate = new Template(name, new StringReader(filedata), freemarkerConfig);
        return processTemplate(config, fTemplate);
    }

    private static byte[] processTemplate(Config config, Template fTemplate) throws TemplateException, IOException {
        StringWriter w = new StringWriter();
        Map<String, Object> data = new HashMap<>();
        data.put("config", config);
        fTemplate.process(data, w);
        return w.toString().getBytes(StandardCharsets.UTF_8);
    }

    private static ArEntry createEntry(String name) {
        ArEntry result = new ArEntry();
        result.setOwnerId(0);
        result.setGroupId(0);
        result.setFilename(name);
        result.setFileMode(100644);
        return result;
    }
}