View Javadoc
1   /**
2    * Copyright 1&1 Internet AG, https://github.com/1and1/
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package net.oneandone.maven.plugins.billofmaterials;
17  
18  import com.google.common.base.*;
19  import com.google.common.collect.Collections2;
20  import com.google.common.collect.Lists;
21  import com.google.common.hash.HashCode;
22  import com.google.common.hash.HashFunction;
23  import com.google.common.hash.Hashing;
24  import com.google.common.io.Files;
25  import java.io.File;
26  import java.io.IOException;
27  import java.util.ArrayList;
28  import java.util.List;
29  import java.util.Locale;
30  
31  import org.apache.maven.artifact.Artifact;
32  import org.apache.maven.plugin.MojoExecutionException;
33  import org.apache.maven.plugin.MojoFailureException;
34  import org.apache.maven.plugins.annotations.LifecyclePhase;
35  import org.apache.maven.plugins.annotations.Mojo;
36  import org.apache.maven.project.MavenProject;
37  
38  /**
39   * Creates a bill of materials for all installed artifacts.
40   *
41   * <p>This in the standard format for the <tt>sha1sum</tt> command including meta information:</p>
42   * <pre>
43   * # company:company-parent-pom:1.0-SNAPSHOT user=mirko
44   * 2dcb20b977ff170dd802c30b804229264c97ebf6  company-parent-pom-1.0-SNAPSHOT.pom
45   * # company:child1:1.0-SNAPSHOT user=mirko
46   * ed5b932c3157b347d0f7a4ec773ae5d5890c1ada  child1-1.0-SNAPSHOT-sources.jar
47   * 8294565e2a5d99b548b111fe6262719331436143  child1-1.0-SNAPSHOT.jar
48   * 082fa2206c4a00e3f428e9100199a0337ad42fdb  child1-1.0-SNAPSHOT.pom
49   * # company:child2:1.0-SNAPSHOT user=mirko
50   * 05d419cf53e175c6e84ddc1cf2fccdc9dd109c6b  child2-1.0-SNAPSHOT-sources.jar
51   * df633b963220ba124ffa80eb6ceab676934bb387  child2-1.0-SNAPSHOT.jar
52   * 5661e9270a02c5359be47615bb6ed9911105d878  child2-1.0-SNAPSHOT.pom
53   * </pre>
54   *
55   * @author Mirko Friedenhagen &lt;mirko.friedenhagen@1und1.de&gt;
56   */
57  @Mojo(name = "create", aggregator = false, defaultPhase = LifecyclePhase.INSTALL)
58  public class CreateBillOfMaterialsMojo extends AbstractBillOfMaterialsMojo {
59  
60      /**
61       * SHA1 hash function.
62       */
63      private final HashFunction sha1 = Hashing.sha1();
64  
65      /**
66       * Function to calculate the hash of a file.
67       */
68      private final Function<File, String> toBomStringFunction;
69  
70      /**
71       * Function to get the file from the artifact.
72       */
73      private final Function<Artifact, File> toFileFunction;
74  
75      /**
76       * Default constructor for maven.
77       */
78      CreateBillOfMaterialsMojo() {
79          super();
80          toFileFunction = new ToFileFunction();
81          toBomStringFunction = new ToBomStringFunction(sha1);
82      }
83  
84      /**
85       * Just for tests.
86       * @param billOfMaterialsPath path to bom.
87       * @param project current project
88       */
89      CreateBillOfMaterialsMojo(File billOfMaterialsPath, MavenProject project) {
90          super(billOfMaterialsPath, project);
91          toFileFunction = new ToFileFunction();
92          toBomStringFunction = new ToBomStringFunction(sha1);
93      }
94  
95      @Override
96      public void execute() throws MojoExecutionException, MojoFailureException {
97          try {
98              final List<File> files = getListOfArtifactsAsFiles();
99              // We need a copy here as transform will return a fixed size list.
100             final List<String> hashBaseNames = new ArrayList<>(Lists.transform(files, toBomStringFunction));
101             addHashEntryForPom(hashBaseNames);
102             writeResults(hashBaseNames);
103         } catch (IOException ex) {
104             throw new MojoExecutionException(ex.toString(), ex);
105         }
106     }
107 
108     /**
109      * Creates a list of all artifacts for the build.
110      * @return a list of all artifacts for the build including the attached ones.
111      */
112     final List<File> getListOfArtifactsAsFiles() {
113         final MavenProject project = getProject();
114         final List<Artifact> attachedArtifacts = project.getAttachedArtifacts();
115         // We need a copy here as otherwise install and deploy will choke later on because
116         // we attach the POM as well.
117         final List<File> files = new ArrayList<>(
118                 Collections2.filter(
119                     Lists.transform(attachedArtifacts, toFileFunction),
120                         Files.isFile()));
121         final String packaging = project.getPackaging();
122         // POMs return null as their artifact, which will crash the transformation lateron.
123         if (!"pom".equals(packaging)) {
124             files.add(project.getArtifact().getFile());
125         }
126         return files;
127     }
128 
129     /**
130      * Adds the hash entry for the POM.
131      * @param hashBaseNames to add the entry to.
132      * @throws IOException when the POM could not be read.
133      */
134     void addHashEntryForPom(final List<String> hashBaseNames) throws IOException {
135         final MavenProject project = getProject();
136         final HashCode sha1OfPom = Files.hash(project.getFile(), sha1);
137         final String pomLine = String.format(Locale.ENGLISH, "%s  %s-%s.pom",
138                     sha1OfPom, project.getArtifactId(), project.getVersion());
139         hashBaseNames.add(pomLine);
140     }
141 
142     /**
143      * Writes the resulting hash file to {@link CreateBillOfMaterialsMojo#billOfMaterialsPath}.
144      *
145      * @param hashBaseNames to write
146      * @throws IOException when the parent directory could not be created or something went wrong while writing the result.
147      */
148     void writeResults(final List<String> hashBaseNames) throws IOException {
149         final String hashBaseNamesAsString = Joiner.on("\n").join(hashBaseNames) + "\n";
150         final String userName = System.getProperty("user.name");
151         write(projectCommentToString(userName));
152         write(hashBaseNamesAsString);
153     }
154 
155     /**
156      * Writes content to the bomFile creating intermediate directories.
157      *
158      * @param content to write
159      * @throws IOException when the target directory could not be created or the content could not be written.
160      */
161     void write(final String content) throws IOException {
162         final File bomFile = calculateBillOfMaterialsFile();
163         final File parentDirectory = bomFile.getParentFile();
164         if (!createParentDirectory(parentDirectory)) {
165             throw new IOException("Could not create parent directory for " + bomFile);
166         }
167         Files.append(content, bomFile, Charsets.UTF_8);
168     }
169 
170     /**
171      * Returns a string representation for the comment.
172      *
173      * @param userName current user
174      * @return string representation for the comment.
175      */
176     String projectCommentToString(final String userName) {
177         final MavenProject project = getProject();
178         return String.format(
179                 Locale.ENGLISH,
180                 "# %s:%s:%s user=%s\n",
181                 project.getGroupId(), project.getArtifactId(), project.getVersion(), userName);
182     }
183 
184     /**
185      * Creates directory for storage.
186      *
187      * @param parentDirectory
188      * @return true when parentDirectory could not be created.
189      */
190     boolean createParentDirectory(final File parentDirectory) {
191         return parentDirectory.exists() || parentDirectory.mkdirs();
192     }
193 }