001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.release.plugin.mojos;
018
019import org.apache.commons.compress.archivers.zip.ParallelScatterZipCreator;
020import org.apache.commons.compress.archivers.zip.ScatterZipOutputStream;
021import org.apache.commons.lang3.StringUtils;
022import org.apache.commons.release.plugin.SharedFunctions;
023import org.apache.maven.plugin.AbstractMojo;
024import org.apache.maven.plugin.MojoExecutionException;
025import org.apache.maven.plugin.MojoFailureException;
026import org.apache.maven.plugins.annotations.LifecyclePhase;
027import org.apache.maven.plugins.annotations.Mojo;
028import org.apache.maven.plugins.annotations.Parameter;
029
030import java.io.File;
031import java.io.FileInputStream;
032import java.io.FileOutputStream;
033import java.io.IOException;
034import java.util.ArrayList;
035import java.util.List;
036import java.util.zip.ZipEntry;
037import java.util.zip.ZipOutputStream;
038
039/**
040 * Takes the built <code>./target/site</code> directory and compresses it to
041 * <code>./target/commons-release-plugin/site.zip</code>.
042 *
043 * @author chtompki
044 * @since 1.0
045 */
046@Mojo(name = "compress-site",
047        defaultPhase = LifecyclePhase.POST_SITE,
048        threadSafe = true,
049        aggregator = true)
050public class CommonsSiteCompressionMojo extends AbstractMojo {
051
052    /**
053     * The working directory for the plugin which, assuming the maven uses the default
054     * <code>${project.build.directory}</code>, this becomes <code>target/commons-release-plugin</code>.
055     */
056    @Parameter(defaultValue = "${project.build.directory}/commons-release-plugin",
057            property = "commons.outputDirectory")
058    private File workingDirectory;
059
060    /**
061     */
062    @Parameter(defaultValue = "${project.build.directory}/site", property = "commons.siteOutputDirectory")
063    private File siteDirectory;
064
065    /**
066     * The url of the subversion repository to which we wish the artifacts to be staged. Typicallly
067     * this would need to be of the form:
068     * <code>scm:svn:https://dist.apache.org/repos/dist/dev/commons/foo</code>. Note. that the prefix to the
069     * substring <code>https</code> is a requirement.
070     */
071    @Parameter(defaultValue = "", property = "commons.distSvnStagingUrl")
072    private String distSvnStagingUrl;
073
074    /**
075     * A parameter to generally avoid running unless it is specifically turned on by the consuming module.
076     */
077    @Parameter(defaultValue = "false", property = "commons.release.isDistModule")
078    private Boolean isDistModule;
079
080    /**
081     * A variable for the process of creating the site.zip file.
082     */
083    private ScatterZipOutputStream dirs;
084
085    /**
086     * A second variable for the process of creating the site.zip file.
087     */
088    private ParallelScatterZipCreator scatterZipCreator;
089
090    /**
091     * The list of files to compress into the site.zip file.
092     */
093    private List<File> filesToCompress;
094
095    @Override
096    public void execute() throws MojoExecutionException, MojoFailureException {
097        if (!isDistModule) {
098            getLog().info("This module is marked as a non distribution " +
099                    "or assembly module, and the plugin will not run.");
100            return;
101        }
102        if (StringUtils.isEmpty(distSvnStagingUrl)) {
103            getLog().warn("commons.distSvnStagingUrl is not set, the commons-release-plugin will not run.");
104            return;
105        }
106        if (!siteDirectory.exists()) {
107            getLog().error("\"mvn site\" was not run before this goal, or a siteDirectory did not exist.");
108            throw new MojoFailureException(
109                    "\"mvn site\" was not run before this goal, or a siteDirectory did not exist."
110            );
111        }
112        if (!workingDirectory.exists()) {
113            getLog().info("Current project contains no distributions. Not executing.");
114            return;
115        }
116        try {
117            filesToCompress = new ArrayList<>();
118            getAllSiteFiles(siteDirectory, filesToCompress);
119            writeZipFile(workingDirectory, siteDirectory, filesToCompress);
120        } catch (IOException e) {
121            getLog().error("Failed to create ./target/commons-release-plugin/site.zip: " + e.getMessage(), e);
122            throw new MojoExecutionException(
123                    "Failed to create ./target/commons-release-plugin/site.zip: " + e.getMessage(),
124                    e
125            );
126        }
127    }
128
129    /**
130     * By default this method iterates across the <code>target/site</code> directory and adds all of the files
131     * to the {@link CommonsSiteCompressionMojo#filesToCompress} {@link List}.
132     *
133     * @param siteDirectory the {@link File} that represents the <code>target/site</code> directory.
134     * @param filesToCompress the {@link List} to which to add all the files.
135     */
136    private void getAllSiteFiles(File siteDirectory, List<File> filesToCompress) {
137        File[] files = siteDirectory.listFiles();
138        for (File file : files) {
139            filesToCompress.add(file);
140            if (file.isDirectory()) {
141                getAllSiteFiles(file, filesToCompress);
142            }
143        }
144    }
145
146    /**
147     * A helper method for writing all of the files in our <code>fileList</code> to a <code>site.zip</code> file
148     * in the <code>workingDirectory</code>.
149     *
150     * @param workingDirectory is a {@link File} representing the place to put the site.zip file.
151     * @param directoryToZip is a {@link File} representing the directory of the site (normally
152     *                       <code>target/site</code>).
153     * @param fileList the list of files to be zipped up, generally generated by
154     *                 {@link CommonsSiteCompressionMojo#getAllSiteFiles(File, List)}.
155     * @throws IOException when the copying of the files goes incorrectly.
156     */
157    private void writeZipFile(File workingDirectory, File directoryToZip, List<File> fileList) throws IOException {
158        FileOutputStream fos = new FileOutputStream(workingDirectory.getAbsolutePath() + "/site.zip");
159        ZipOutputStream zos = new ZipOutputStream(fos);
160        for (File file : fileList) {
161            if (!file.isDirectory()) { // we only zip files, not directories
162                addToZip(directoryToZip, file, zos);
163            }
164        }
165        zos.close();
166        fos.close();
167    }
168
169    /**
170     * Given the <code>directoryToZip</code> we add the <code>file</code> to the zip archive represented by
171     * <code>zos</code>.
172     *
173     * @param directoryToZip a {@link File} representing the directory from which the file exists that we are
174     *                       compressing. Generally this is <code>target/site</code>.
175     * @param file a {@link File} to add to the {@link ZipOutputStream} <code>zos</code>.
176     * @param zos the {@link ZipOutputStream} to which to add our <code>file</code>.
177     * @throws IOException if adding the <code>file</code> doesn't work out properly.
178     */
179    private void addToZip(File directoryToZip, File file, ZipOutputStream zos) throws IOException {
180        FileInputStream fis = new FileInputStream(file);
181        // we want the zipEntry's path to be a relative path that is relative
182        // to the directory being zipped, so chop off the rest of the path
183        String zipFilePath = file.getCanonicalPath().substring(directoryToZip.getCanonicalPath().length() + 1,
184                file.getCanonicalPath().length());
185        ZipEntry zipEntry = new ZipEntry(zipFilePath);
186        zos.putNextEntry(zipEntry);
187        byte[] bytes = new byte[SharedFunctions.BUFFER_BYTE_SIZE];
188        int length;
189        while ((length = fis.read(bytes)) >= 0) {
190            zos.write(bytes, 0, length);
191        }
192        zos.closeEntry();
193        fis.close();
194    }
195}