View Javadoc
1   package org.apache.maven.plugin.coreit;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.maven.artifact.Artifact;
23  import org.apache.maven.plugin.AbstractMojo;
24  import org.apache.maven.plugin.MojoExecutionException;
25  import org.apache.maven.project.MavenProject;
26  
27  import java.io.BufferedWriter;
28  import java.io.File;
29  import java.io.FileInputStream;
30  import java.io.FileOutputStream;
31  import java.io.IOException;
32  import java.io.OutputStreamWriter;
33  import java.security.DigestInputStream;
34  import java.security.MessageDigest;
35  import java.security.NoSuchAlgorithmException;
36  import java.util.Collection;
37  import java.util.Properties;
38  
39  /**
40   * Provides common services for all mojos of this plugin.
41   *
42   * @author Benjamin Bentmann
43   *
44   */
45  public abstract class AbstractDependencyMojo
46      extends AbstractMojo
47  {
48  
49      /**
50       * The current Maven project.
51       *
52       * @parameter default-value="${project}"
53       * @required
54       * @readonly
55       */
56      protected MavenProject project;
57  
58      /**
59       * The number of trailing path levels that should be used to denote a class path element. If positive, each class
60       * path element is trimmed down to the specified number of path levels by discarding leading directories, e.g. set
61       * this parameter to 1 to keep only the simple file name. The trimmed down paths will always use the forward slash
62       * as directory separator. For non-positive values, the full/absolute path is returned, using the platform-specific
63       * separator.
64       *
65       * @parameter property="depres.significantPathLevels"
66       */
67      private int significantPathLevels;
68  
69      /**
70       * Writes the specified artifacts to the given output file.
71       *
72       * @param pathname  The path to the output file, relative to the project base directory, may be <code>null</code> or
73       *                  empty if the output file should not be written.
74       * @param artifacts The list of artifacts to write to the file, may be <code>null</code>.
75       * @throws MojoExecutionException If the output file could not be written.
76       */
77      protected void writeArtifacts( String pathname, Collection artifacts )
78          throws MojoExecutionException
79      {
80          if ( pathname == null || pathname.length() <= 0 )
81          {
82              return;
83          }
84  
85          File file = resolveFile( pathname );
86  
87          getLog().info( "[MAVEN-CORE-IT-LOG] Dumping artifact list: " + file );
88  
89          BufferedWriter writer = null;
90          try
91          {
92              file.getParentFile().mkdirs();
93  
94              writer = new BufferedWriter( new OutputStreamWriter( new FileOutputStream( file ), "UTF-8" ) );
95  
96              if ( artifacts != null )
97              {
98                  for ( Object artifact1 : artifacts )
99                  {
100                     Artifact artifact = (Artifact) artifact1;
101                     String id = getId( artifact );
102                     writer.write( id );
103                     String optional = "";
104                     if ( artifact.isOptional() )
105                     {
106                         optional = " (optional)";
107                         writer.write( optional );
108                     }
109                     writer.newLine();
110                     getLog().info( "[MAVEN-CORE-IT-LOG]   " + id + optional );
111                 }
112             }
113         }
114         catch ( IOException e )
115         {
116             throw new MojoExecutionException( "Failed to write artifact list", e );
117         }
118         finally
119         {
120             if ( writer != null )
121             {
122                 try
123                 {
124                     writer.close();
125                 }
126                 catch ( IOException e )
127                 {
128                     // just ignore
129                 }
130             }
131         }
132     }
133 
134     private String getId( Artifact artifact )
135     {
136         artifact.isSnapshot(); // decouple from MNG-2961
137         return artifact.getId();
138     }
139 
140     /**
141      * Writes the specified class path elements to the given output file.
142      *
143      * @param pathname  The path to the output file, relative to the project base directory, may be <code>null</code> or
144      *                  empty if the output file should not be written.
145      * @param classPath The list of class path elements to write to the file, may be <code>null</code>.
146      * @throws MojoExecutionException If the output file could not be written.
147      */
148     protected void writeClassPath( String pathname, Collection classPath )
149         throws MojoExecutionException
150     {
151         if ( pathname == null || pathname.length() <= 0 )
152         {
153             return;
154         }
155 
156         File file = resolveFile( pathname );
157 
158         getLog().info( "[MAVEN-CORE-IT-LOG] Dumping class path: " + file );
159 
160         BufferedWriter writer = null;
161         try
162         {
163             file.getParentFile().mkdirs();
164 
165             writer = new BufferedWriter( new OutputStreamWriter( new FileOutputStream( file ), "UTF-8" ) );
166 
167             if ( classPath != null )
168             {
169                 for ( Object aClassPath : classPath )
170                 {
171                     String element = aClassPath.toString();
172                     writer.write( stripLeadingDirs( element, significantPathLevels ) );
173                     writer.newLine();
174                     getLog().info( "[MAVEN-CORE-IT-LOG]   " + element );
175                 }
176             }
177         }
178         catch ( IOException e )
179         {
180             throw new MojoExecutionException( "Failed to write class path list", e );
181         }
182         finally
183         {
184             if ( writer != null )
185             {
186                 try
187                 {
188                     writer.close();
189                 }
190                 catch ( IOException e )
191                 {
192                     // just ignore
193                 }
194             }
195         }
196     }
197 
198     protected void writeClassPathChecksums( String pathname, Collection classPath )
199         throws MojoExecutionException
200     {
201         if ( pathname == null || pathname.length() <= 0 )
202         {
203             return;
204         }
205 
206         File file = resolveFile( pathname );
207 
208         getLog().info( "[MAVEN-CORE-IT-LOG] Dumping class path checksums: " + file );
209 
210         Properties checksums = new Properties();
211 
212         if ( classPath != null )
213         {
214             for ( Object aClassPath : classPath )
215             {
216                 String element = aClassPath.toString();
217 
218                 File jarFile = new File( element );
219 
220                 if ( !jarFile.isFile() )
221                 {
222                     getLog().info( "[MAVEN-CORE-IT-LOG]   ( no file )                              < " + element );
223                     continue;
224                 }
225 
226                 String key = stripLeadingDirs( element, significantPathLevels );
227 
228                 String hash;
229                 try
230                 {
231                     hash = calcChecksum( jarFile );
232                 }
233                 catch ( NoSuchAlgorithmException e )
234                 {
235                     throw new MojoExecutionException( "Failed to lookup message digest", e );
236                 }
237                 catch ( IOException e )
238                 {
239                     throw new MojoExecutionException( "Failed to calculate checksum for " + jarFile, e );
240                 }
241 
242                 checksums.setProperty( key, hash );
243 
244                 getLog().info( "[MAVEN-CORE-IT-LOG]   " + hash + " < " + element );
245             }
246         }
247 
248         FileOutputStream os = null;
249         try
250         {
251             file.getParentFile().mkdirs();
252 
253             os = new FileOutputStream( file );
254 
255             checksums.store( os, "MAVEN-CORE-IT" );
256         }
257         catch ( IOException e )
258         {
259             throw new MojoExecutionException( "Failed to write class path checksums", e );
260         }
261         finally
262         {
263             if ( os != null )
264             {
265                 try
266                 {
267                     os.close();
268                 }
269                 catch ( IOException e )
270                 {
271                     // just ignore
272                 }
273             }
274         }
275     }
276 
277     private String calcChecksum( File jarFile )
278         throws IOException, NoSuchAlgorithmException
279     {
280         MessageDigest digester = MessageDigest.getInstance( "SHA-1" );
281 
282         try ( FileInputStream is = new FileInputStream( jarFile ) )
283         {
284             DigestInputStream dis = new DigestInputStream( is, digester );
285 
286             for ( byte[] buffer = new byte[1024 * 4]; dis.read( buffer ) >= 0; )
287             {
288                 // just read it
289             }
290         }
291 
292         byte[] digest = digester.digest();
293 
294         StringBuilder hash = new StringBuilder( digest.length * 2 );
295 
296         for ( byte aDigest : digest )
297         {
298             @SuppressWarnings( "checkstyle:magicnumber" ) int b = aDigest & 0xFF;
299 
300             if ( b < 0x10 )
301             {
302                 hash.append( '0' );
303             }
304 
305             hash.append( Integer.toHexString( b ) );
306         }
307 
308         return hash.toString();
309     }
310 
311     private String stripLeadingDirs( String path, int significantPathLevels )
312     {
313         String result;
314         if ( significantPathLevels > 0 )
315         {
316             result = "";
317             File file = new File( path );
318             for ( int i = 0; i < significantPathLevels && file != null; i++ )
319             {
320                 if ( result.length() > 0 )
321                 {
322                     // NOTE: Always use forward slash here to ease platform-independent testing
323                     result = '/' + result;
324                 }
325                 result = file.getName() + result;
326                 file = file.getParentFile();
327             }
328         }
329         else
330         {
331             result = path;
332         }
333         return result;
334     }
335 
336     // NOTE: We don't want to test path translation here so resolve relative path manually for robustness
337     private File resolveFile( String pathname )
338     {
339         File file = null;
340 
341         if ( pathname != null )
342         {
343             if ( pathname.contains( "@idx@" ) )
344             {
345                 // helps to distinguished forked executions of the same mojo
346                 pathname = pathname.replaceAll( "@idx@", String.valueOf( nextCounter() ) );
347             }
348 
349             file = new File( pathname );
350 
351             if ( !file.isAbsolute() )
352             {
353                 file = new File( project.getBasedir(), pathname );
354             }
355         }
356 
357         return file;
358     }
359 
360     private int nextCounter()
361     {
362         int counter = 0;
363 
364         String key = getClass().getName();
365 
366         synchronized ( System.class )
367         {
368             counter = Integer.getInteger( key, 0 );
369             System.setProperty( key, Integer.toString( counter + 1 ) );
370         }
371 
372         return counter;
373     }
374 
375 }