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