/** * Copyright (C) 2015 Michael Schnell. All rights reserved. * http://www.fuin.org/ * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at your option) any * later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see http://www.gnu.org/licenses/. */ package org.fuin.esmp; import static org.twdata.maven.mojoexecutor.MojoExecutor.artifactId; import static org.twdata.maven.mojoexecutor.MojoExecutor.configuration; import static org.twdata.maven.mojoexecutor.MojoExecutor.element; import static org.twdata.maven.mojoexecutor.MojoExecutor.executeMojo; import static org.twdata.maven.mojoexecutor.MojoExecutor.executionEnvironment; import static org.twdata.maven.mojoexecutor.MojoExecutor.goal; import static org.twdata.maven.mojoexecutor.MojoExecutor.groupId; import static org.twdata.maven.mojoexecutor.MojoExecutor.name; import static org.twdata.maven.mojoexecutor.MojoExecutor.plugin; import static org.twdata.maven.mojoexecutor.MojoExecutor.version; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.Enumeration; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream; import org.apache.commons.exec.CommandLine; import org.apache.commons.exec.DefaultExecutor; import org.apache.commons.exec.Executor; import org.apache.commons.exec.OS; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.apache.maven.execution.MavenSession; import org.apache.maven.plugin.BuildPluginManager; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; import org.fuin.utils4j.Utils4J; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Downloads the eventstore archive and unpacks it into a defined directory. */ @Mojo(name = "download", defaultPhase = LifecyclePhase.PRE_INTEGRATION_TEST, requiresProject = true) public final class EventStoreDownloadMojo extends AbstractEventStoreMojo { private static final Logger LOG = LoggerFactory.getLogger(EventStoreDownloadMojo.class); private static final int MB = 1024 * 1024; @Parameter(defaultValue = "${project}", readonly = true) private MavenProject mavenProject; @Parameter(defaultValue = "${session}", readonly = true) private MavenSession mavenSession; @Component private BuildPluginManager pluginManager; @Override protected final void executeGoal() throws MojoExecutionException { assertParametersNotNull(); // Do nothing if already in place if (getEventStoreDir().exists()) { LOG.info("Events store directory already exists: " + getEventStoreDir()); } else { final File archive = downloadEventStoreArchive(); unpack(archive); } } private void assertParametersNotNull() throws MojoExecutionException { LOG.info("mavenProject={}", mavenProject); LOG.info("mavenSession={}", mavenSession); LOG.info("pluginManager={}", pluginManager); if (mavenProject == null) { throw new MojoExecutionException("mavenProject==null"); } if (mavenSession == null) { throw new MojoExecutionException("mavenSession==null"); } if (pluginManager == null) { throw new MojoExecutionException("pluginManager==null"); } } /** * Returns the file where the result of the download is located. * * @return File where loaded bytes are stored. * * @throws MojoExecutionException * Error initializing the variables necessary to construct the * result. */ public final File getDownloadFile() throws MojoExecutionException { final String name = FilenameUtils.getName(getDownloadUrl()); return new File(getEventStoreDir().getParentFile(), name); } private URL createDownloadURL() throws MojoExecutionException { try { return new URL(getDownloadUrl()); } catch (final MalformedURLException ex) { throw new MojoExecutionException("Failed to construct download URL for the event store", ex); } } private File downloadEventStoreArchive() throws MojoExecutionException { final URL url = createDownloadURL(); try { final File file = getDownloadFile(); if (file.exists()) { LOG.info("Archive already exists in target directory: " + file); } else { LOG.info("Dowloading archive: " + url); // Cache the file locally in the temporary directory final File tmpFile = new File(Utils4J.getTempDir(), file.getName()); if (!tmpFile.exists()) { download(url, tmpFile); LOG.info("Archive downloaded to: " + tmpFile); } FileUtils.copyFile(tmpFile, file); LOG.info("Archive copied from '" + tmpFile + "' to:" + file); } return file; } catch (final IOException ex) { throw new MojoExecutionException("Error downloading event store archive: " + url, ex); } } private void download(final URL url, final File file) throws MojoExecutionException { executeMojo( plugin(groupId("com.googlecode.maven-download-plugin"), artifactId("download-maven-plugin"), version("1.3.0")), goal("wget"), configuration(element(name("url"), url.toString()), element(name("outputDirectory"), file.getParent()), element(name("outputFileName"), file.getName()), element(name("skipCache"), "true")), executionEnvironment(mavenProject, mavenSession, pluginManager)); } private void unpack(final File archive) throws MojoExecutionException { LOG.info("Unpack event store to target directory: " + getEventStoreDir()); if (archive.getName().endsWith(".zip")) { // All files are in the root of the ZIP file (not in a sub folder as // with "tar.gz") final File destDir = getEventStoreDir(); unzip(archive, destDir); } else if (archive.getName().endsWith(".tar.gz")) { final File destDir = getEventStoreDir().getParentFile(); unTarGz(archive, destDir); } else { throw new MojoExecutionException("Cannot unpack file: " + archive.getName()); } } /** * Unzips the given ZIP file into a target directory. * * @param zipFile * ZIP file. * @param destDir * Target directory. * * @throws MojoExecutionException * Error unzipping the file. */ public static void unzip(final File zipFile, final File destDir) throws MojoExecutionException { try { final ZipFile zip = new ZipFile(zipFile); try { final Enumeration<? extends ZipEntry> enu = zip.entries(); while (enu.hasMoreElements()) { final ZipEntry entry = (ZipEntry) enu.nextElement(); final File file = new File(entry.getName()); if (file.isAbsolute()) { throw new IllegalArgumentException( "Only relative path entries are allowed! [" + entry.getName() + "]"); } if (entry.isDirectory()) { final File dir = new File(destDir, entry.getName()); mkDirsIfNecessary(dir); } else { LOG.info("Extracting: " + entry.getName()); final File outFile = new File(destDir, entry.getName()); mkDirsIfNecessary(outFile.getParentFile()); final InputStream in = new BufferedInputStream(zip.getInputStream(entry)); try { final OutputStream out = new BufferedOutputStream(new FileOutputStream(outFile)); try { final byte[] buf = new byte[4096]; int len; while ((len = in.read(buf)) > 0) { out.write(buf, 0, len); } } finally { out.close(); } } finally { in.close(); } } } } finally { zip.close(); } } catch (final IOException ex) { throw new MojoExecutionException("Error unzipping event store archive: " + zipFile, ex); } } /** * Unpacks the given TAR/GZ file into a target directory. It assumes that * the content of the archive only contains relative paths. * * @param archive * TAR/GZ archive file. * @param destDir * Target directory. * * @throws MojoExecutionException * Error unpacking the file. */ public static void unTarGz(final File archive, final File destDir) throws MojoExecutionException { try { final TarArchiveInputStream tarIn = new TarArchiveInputStream( new GzipCompressorInputStream(new BufferedInputStream(new FileInputStream(archive)))); try { TarArchiveEntry entry; while ((entry = (TarArchiveEntry) tarIn.getNextEntry()) != null) { LOG.info("Extracting: " + entry.getName()); final File file = new File(destDir, entry.getName()); if (entry.isDirectory()) { mkDirsIfNecessary(file); } else { mkDirsIfNecessary(file.getParentFile()); int count; final byte[] data = new byte[MB]; final FileOutputStream fos = new FileOutputStream(file); final BufferedOutputStream dest = new BufferedOutputStream(fos, MB); try { while ((count = tarIn.read(data, 0, MB)) != -1) { dest.write(data, 0, count); } } finally { dest.close(); } entry.getMode(); } applyFileMode(file, new FileMode(entry.getMode())); } } finally { tarIn.close(); } } catch (final IOException ex) { throw new MojoExecutionException("Error uncompressing event store archive: " + archive, ex); } } private static void mkDirsIfNecessary(final File dir) throws IOException { if (dir.exists()) { return; } if (!dir.mkdirs()) { throw new IOException("Error creating directory '" + dir + "'!"); } } // CHECKSTYLE:OFF External code // Inspired by: // https://raw.githubusercontent.com/bluemel/RapidEnv/master/org.rapidbeans.rapidenv/src/org/rapidbeans/rapidenv/Unpacker.java private static void applyFileMode(final File file, final FileMode fileMode) throws MojoExecutionException { if (OS.isFamilyUnix() || OS.isFamilyMac()) { final String smode = fileMode.toChmodStringFull(); final CommandLine cmdLine = new CommandLine("chmod"); cmdLine.addArgument(smode); cmdLine.addArgument(file.getAbsolutePath()); final Executor executor = new DefaultExecutor(); try { final int result = executor.execute(cmdLine); if (result != 0) { throw new MojoExecutionException("Error # " + result + " while trying to set mode \"" + smode + "\" for file: " + file.getAbsolutePath()); } } catch (final IOException ex) { throw new MojoExecutionException("Error while trying to set mode \"" + smode + "\" for file: " + file.getAbsolutePath(), ex); } } else { file.setReadable(fileMode.isUr() || fileMode.isGr() || fileMode.isOr()); file.setWritable(fileMode.isUw() || fileMode.isGw() || fileMode.isOw()); file.setExecutable(fileMode.isUx() || fileMode.isGx() || fileMode.isOx()); } } // CHECKSTYLE:ON }