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 java.nio.file.Files;
020import org.apache.commons.codec.digest.DigestUtils;
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.plugins.annotations.LifecyclePhase;
026import org.apache.maven.plugins.annotations.Mojo;
027import org.apache.maven.plugins.annotations.Parameter;
028import org.apache.maven.project.MavenProject;
029import org.apache.maven.artifact.Artifact;
030
031import java.io.File;
032import java.io.FileInputStream;
033import java.io.IOException;
034import java.io.PrintWriter;
035import java.util.ArrayList;
036import java.util.Collections;
037import java.util.HashSet;
038import java.util.List;
039import java.util.Set;
040
041/**
042 * The purpose of this maven mojo is to detach the artifacts generated by the maven-assembly-plugin,
043 * which for the Apache Commons Project do not get uploaded to Nexus, and putting those artifacts
044 * in the dev distribution location for apache projects.
045 *
046 * @author chtompki
047 * @since 1.0
048 */
049@Mojo(name = "detach-distributions",
050        defaultPhase = LifecyclePhase.VERIFY,
051        threadSafe = true,
052        aggregator = true)
053public class CommonsDistributionDetachmentMojo extends AbstractMojo {
054
055    /**
056     * A list of "artifact types" in the maven vernacular, to
057     * be detached from the deployment. For the time being we want
058     * all artifacts generated by the maven-assembly-plugin to be detached
059     * from the deployment, namely *-src.zip, *-src.tar.gz, *-bin.zip,
060     * *-bin.tar.gz, and the corresponding .asc pgp signatures.
061     */
062    private static final Set<String> ARTIFACT_TYPES_TO_DETACH;
063    static {
064        Set<String> hashSet = new HashSet<>();
065        hashSet.add("zip");
066        hashSet.add("tar.gz");
067        hashSet.add("zip.asc");
068        hashSet.add("tar.gz.asc");
069        ARTIFACT_TYPES_TO_DETACH = Collections.unmodifiableSet(hashSet);
070    }
071
072    /**
073     * This list is supposed to hold the maven references to the aformentioned artifacts so that we
074     * can upload them to svn after they've been detached from the maven deployment.
075     */
076    private List<Artifact> detachedArtifacts = new ArrayList<>();
077
078    /**
079     * The maven project context injection so that we can get a hold of the variables at hand.
080     */
081    @Parameter(defaultValue = "${project}", required = true)
082    private MavenProject project;
083
084    /**
085     * The working directory in <code>target</code> that we use as a sandbox for the plugin.
086     */
087    @Parameter(defaultValue = "${project.build.directory}/commons-release-plugin",
088            property = "commons.outputDirectory")
089    private File workingDirectory;
090
091    /**
092     * The subversion staging url to which we upload all of our staged artifacts.
093     */
094    @Parameter(defaultValue = "", property = "commons.distSvnStagingUrl")
095    private String distSvnStagingUrl;
096
097    /**
098     * A parameter to generally avoid running unless it is specifically turned on by the consuming module.
099     */
100    @Parameter(defaultValue = "false", property = "commons.release.isDistModule")
101    private Boolean isDistModule;
102
103    @Override
104    public void execute() throws MojoExecutionException {
105        if (!isDistModule) {
106            getLog().info("This module is marked as a non distribution " +
107                    "or assembly module, and the plugin will not run.");
108            return;
109        }
110        if (StringUtils.isEmpty(distSvnStagingUrl)) {
111            getLog().warn("commons.distSvnStagingUrl is not set, the commons-release-plugin will not run.");
112            return;
113        }
114        getLog().info("Detaching Assemblies");
115        for (Object attachedArtifact : project.getAttachedArtifacts()) {
116            if (ARTIFACT_TYPES_TO_DETACH.contains(((Artifact) attachedArtifact).getType())) {
117                detachedArtifacts.add((Artifact) attachedArtifact);
118            }
119        }
120        if (detachedArtifacts.isEmpty()) {
121            getLog().info("Current project contains no distributions. Not executing.");
122            return;
123        }
124        for (Artifact artifactToRemove : detachedArtifacts) {
125            project.getAttachedArtifacts().remove(artifactToRemove);
126        }
127        if (!workingDirectory.exists()) {
128            SharedFunctions.initDirectory(getLog(), workingDirectory);
129        }
130        copyRemovedArtifactsToWorkingDirectory();
131        getLog().info("");
132        sha1AndMd5SignArtifacts();
133    }
134
135    /**
136     * A helper method to copy the newly detached artifacts to <code>target/commons-release-plugin</code>
137     * so that the {@link CommonsDistributionStagingMojo} can find the artifacts later.
138     *
139     * @throws MojoExecutionException if some form of an {@link IOException} occurs, we want it
140     *                                properly wrapped so that maven can handle it.
141     */
142    private void copyRemovedArtifactsToWorkingDirectory() throws MojoExecutionException {
143        StringBuffer copiedArtifactAbsolutePath;
144        getLog().info("Copying detached artifacts to working directory.");
145        for (Artifact artifact: detachedArtifacts) {
146            File artifactFile = artifact.getFile();
147            copiedArtifactAbsolutePath = new StringBuffer(workingDirectory.getAbsolutePath());
148            copiedArtifactAbsolutePath.append("/");
149            copiedArtifactAbsolutePath.append(artifactFile.getName());
150            File copiedArtifact = new File(copiedArtifactAbsolutePath.toString());
151            getLog().info("Copying: " + artifactFile.getName());
152            SharedFunctions.copyFile(getLog(), artifactFile, copiedArtifact);
153        }
154    }
155
156    /**
157     *  A helper method that creates md5 and sha1 signature files for our detached artifacts in the
158     *  <code>target/commons-release-plugin</code> directory for the purpose of being uploade by
159     *  the {@link CommonsDistributionStagingMojo}.
160     *
161     * @throws MojoExecutionException if some form of an {@link IOException} occurs, we want it
162     *                                properly wrapped so that maven can handle it.
163     */
164    private void sha1AndMd5SignArtifacts() throws MojoExecutionException {
165        for (Artifact artifact : detachedArtifacts) {
166            if (!artifact.getFile().getName().contains("asc")) {
167                try {
168                    String md5 = DigestUtils.md5Hex(Files.readAllBytes(artifact.getFile().toPath()));
169                    getLog().info(artifact.getFile().getName() + " md5: " + md5);
170                    PrintWriter md5Writer = new PrintWriter(getMd5FilePath(workingDirectory, artifact.getFile()));
171                    md5Writer.println(md5);
172                    String sha1 = DigestUtils.sha1Hex(Files.readAllBytes(artifact.getFile().toPath()));
173                    getLog().info(artifact.getFile().getName() + " sha1: " + sha1);
174                    PrintWriter sha1Writer = new PrintWriter(getSha1FilePath(workingDirectory, artifact.getFile()));
175                    sha1Writer.println(sha1);
176                    md5Writer.close();
177                    sha1Writer.close();
178                } catch (IOException e) {
179                    throw new MojoExecutionException("Could not sign file: " + artifact.getFile().getName(), e);
180                }
181            }
182        }
183    }
184
185    /**
186     * A helper method to create a file path for the <code>md5</code> signature file from a given file.
187     *
188     * @param workingDirectory is the {@link File} for the directory in which to make the <code>.md5</code> file.
189     * @param file the {@link File} whose name we should use to create the <code>.md5</code> file.
190     * @return a {@link String} that is the absolute path to the <code>.md5</code> file.
191     */
192    private String getMd5FilePath(File workingDirectory, File file) {
193        StringBuffer buffer = new StringBuffer(workingDirectory.getAbsolutePath());
194        buffer.append("/");
195        buffer.append(file.getName());
196        buffer.append(".md5");
197        return buffer.toString();
198    }
199
200    /**
201     * A helper method to create a file path for the <code>sha1</code> signature file from a given file.
202     *
203     * @param workingDirectory is the {@link File} for the directory in which to make the <code>.sha1</code> file.
204     * @param file the {@link File} whose name we should use to create the <code>.sha1</code> file.
205     * @return a {@link String} that is the absolute path to the <code>.sha1</code> file.
206     */
207    private String getSha1FilePath(File workingDirectory, File file) {
208        StringBuffer buffer = new StringBuffer(workingDirectory.getAbsolutePath());
209        buffer.append("/");
210        buffer.append(file.getName());
211        buffer.append(".sha1");
212        return buffer.toString();
213    }
214}