View Javadoc

1   package org.apache.maven.plugin.jar;
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.commons.lang.SystemUtils;
23  import org.apache.maven.plugin.AbstractMojo;
24  import org.apache.maven.plugin.MojoExecutionException;
25  import org.apache.maven.plugin.logging.Log;
26  import org.apache.maven.project.MavenProject;
27  import org.apache.maven.project.MavenProjectHelper;
28  import org.apache.maven.artifact.handler.ArtifactHandler;
29  import org.codehaus.plexus.util.StringUtils;
30  import org.codehaus.plexus.util.cli.CommandLineException;
31  import org.codehaus.plexus.util.cli.CommandLineUtils;
32  import org.codehaus.plexus.util.cli.Commandline;
33  import org.codehaus.plexus.util.cli.StreamConsumer;
34  
35  import java.io.File;
36  import java.io.InputStream;
37  import java.util.ArrayList;
38  import java.util.Iterator;
39  import java.util.List;
40  import java.util.StringTokenizer;
41  
42  /**
43   * Signs a JAR using jarsigner.
44   *
45   * @author <a href="jerome@coffeebreaks.org">Jerome Lacoste</a>
46   * @version $Id: JarSignMojo.java 802529 2009-08-09 12:05:35Z bentmann $
47   * @goal sign
48   * @phase package
49   * @requiresProject
50   * @todo refactor the common code with javadoc plugin
51   * @requiresDependencyResolution runtime
52   * @deprecated As of version 2.3, this goal is no longer supported in favor of the dedicated maven-jarsigner-plugin.
53   */
54  public class JarSignMojo
55      extends AbstractMojo
56  {
57      /**
58       * Set this to <code>true</code> to disable signing.
59       * Useful to speed up build process in development environment.
60       *
61       * @parameter expression="${maven.jar.sign.skip}" default-value="false"
62       */
63      private boolean skip;
64  
65      /**
66       * The working directory in which the jarsigner executable will be run.
67       *
68       * @parameter expression="${workingdir}" default-value="${basedir}"
69       * @required
70       */
71      private File workingDirectory;
72  
73      /**
74       * Directory containing the generated JAR.
75       *
76       * @parameter expression="${project.build.directory}"
77       * @required
78       * @readonly
79       */
80      private File basedir;
81  
82      /**
83       * Name of the generated JAR (without classifier and extension).
84       *
85       * @parameter alias="jarname" expression="${project.build.finalName}"
86       * @required
87       */
88      private String finalName;
89  
90      /**
91       * Path of the jar to sign. When specified, the finalName is ignored.
92       *
93       * @parameter alias="jarpath" default-value="${project.build.directory}/${project.build.finalName}.${project.packaging}"
94       */
95      private File jarPath;
96  
97      /**
98       * See <a href="http://java.sun.com/j2se/1.4.2/docs/tooldocs/windows/jarsigner.html#Options">options</a>.
99       *
100      * @parameter expression="${keystore}"
101      */
102     private String keystore;
103 
104     /**
105      * See <a href="http://java.sun.com/j2se/1.4.2/docs/tooldocs/windows/jarsigner.html#Options">options</a>.
106      *
107      * @parameter expression="${storepass}"
108      */
109     private String storepass;
110 
111     /**
112      * See <a href="http://java.sun.com/j2se/1.4.2/docs/tooldocs/windows/jarsigner.html#Options">options</a>.
113      *
114      * @parameter expression="${keypass}"
115      */
116     private String keypass;
117 
118     /**
119      * See <a href="http://java.sun.com/j2se/1.4.2/docs/tooldocs/windows/jarsigner.html#Options">options</a>.
120      *
121      * @parameter expression="${sigfile}"
122      * @todo make a File?
123      */
124     private String sigfile;
125 
126     /**
127      * See <a href="http://java.sun.com/j2se/1.4.2/docs/tooldocs/windows/jarsigner.html#Options">options</a>.
128      * <p/>
129      * Not specifying this argument will sign the jar in-place (your original jar is going to be overwritten).
130      *
131      * @parameter expression="${signedjar}"
132      */
133     private File signedjar;
134 
135     /**
136      * See <a href="http://java.sun.com/j2se/1.4.2/docs/tooldocs/windows/jarsigner.html#Options">options</a>.
137      * The corresponding option in the command line is -storetype.
138      *
139      * @parameter expression="${type}"
140      */
141     private String type;
142 
143     /**
144      * See <a href="http://java.sun.com/j2se/1.4.2/docs/tooldocs/windows/jarsigner.html#Options">options</a>.
145      *
146      * @parameter expression="${alias}"
147      * @required
148      */
149     private String alias;
150 
151     /**
152      * Automatically verify a jar after signing it.
153      * <p/>
154      * See <a href="http://java.sun.com/j2se/1.4.2/docs/tooldocs/windows/jarsigner.html#Options">options</a>.
155      *
156      * @parameter expression="${verify}" default-value="false"
157      */
158     private boolean verify;
159 
160     /**
161      * Skip attaching the signed artifact. By default the signed artifact is attached.
162      * This is not a Mojo parameter as we shouldn't need this when using this mojo.
163      * Just needed when reusing the implementation. See MJAR-84 for discussions.
164      */
165     private boolean skipAttachSignedArtifact;
166 
167     /**
168      * Enable verbose.
169      * See <a href="http://java.sun.com/j2se/1.4.2/docs/tooldocs/windows/jarsigner.html#Options">options</a>.
170      *
171      * @parameter expression="${verbose}" default-value="false"
172      */
173     private boolean verbose;
174 
175     /**
176      * @component
177      */
178     private MavenProjectHelper projectHelper;
179 
180     /**
181      * The Maven project.
182      *
183      * @parameter expression="${project}"
184      * @required
185      * @readonly
186      */
187     private MavenProject project;
188 
189     /**
190      * Classifier to use for the generated artifact.
191      * If not specified, the generated artifact becomes the primary artifact.
192      *
193      * @parameter expression="${classifier}"
194      */
195     private String classifier;
196 
197     public void execute()
198         throws MojoExecutionException
199     {
200         if ( skip )
201         {
202             getLog().info( "Skipping JAR signing for file: " + getJarFile().getAbsolutePath() );
203             return;
204         }
205 
206         if ( project != null )
207         {
208             ArtifactHandler artifactHandler = project.getArtifact().getArtifactHandler();
209             if ( artifactHandler != null && !"java".equals( artifactHandler.getLanguage() ) )
210             {
211                 getLog().debug( "Not executing jar:sign as the project is not a Java module" );
212                 return;
213             }
214         }
215 
216         // we use this mojo to check if there's a need to sign.
217         // If we sign and if we need to verify, we reuse it to check the signature
218         JarSignVerifyMojo verifyMojo = createJarSignVerifyMojo();
219 
220         verifyMojo.setWorkingDir( workingDirectory );
221 
222         verifyMojo.setBasedir( basedir );
223 
224         File signedJarFile = signedjar != null ? signedjar : getJarFile();
225 
226         verifyMojo.setVerbose( verbose );
227 
228         verifyMojo.setJarPath( signedJarFile );
229 
230         if ( signedJarFile.exists() )
231         {
232             verifyMojo.setErrorWhenNotSigned( false );
233             verifyMojo.execute();
234         }
235 
236         if ( verifyMojo.isSigned() )
237         {
238             getLog().info( "JAR " + signedJarFile.getAbsoluteFile() + " is already signed. Skipping." );
239             return;
240         }
241 
242         signJar();
243 
244         if ( this.verify )
245         {
246             verifyMojo.setErrorWhenNotSigned( true );
247             verifyMojo.execute();
248         }
249     }
250 
251     protected JarSignVerifyMojo createJarSignVerifyMojo()
252     {
253         return new JarSignVerifyMojo();
254     }
255 
256 
257     File getJarFile()
258     {
259         if ( jarPath != null )
260         {
261             return jarPath;
262         }
263         else
264         {
265             return AbstractJarMojo.getJarFile( basedir, finalName, null );
266         }
267     }
268 
269     void signJar()
270         throws MojoExecutionException
271     {
272         List arguments = new ArrayList();
273 
274         Commandline commandLine = new Commandline();
275 
276         commandLine.setExecutable( getJarsignerPath() );
277 
278         addArgIf( arguments, verbose, "-verbose" );
279 
280         // I believe Commandline to add quotes where appropriate, although I haven't tested it enough.
281         // FIXME addArgIfNotEmpty will break those parameters containing a space.
282         // Look at webapp:gen-keystore for a way to fix that
283         addArgIfNotEmpty( arguments, "-keystore", this.keystore );
284         addArgIfNotEmpty( arguments, "-storepass", this.storepass );
285         addArgIfNotEmpty( arguments, "-keypass", this.keypass );
286         addArgIfNotEmpty( arguments, "-signedjar", this.signedjar );
287         addArgIfNotEmpty( arguments, "-storetype", this.type );
288         addArgIfNotEmpty( arguments, "-sigfile", this.sigfile );
289 
290         arguments.add( getJarFile() );
291 
292         addArgIf( arguments, alias != null, this.alias );
293 
294         for ( Iterator it = arguments.iterator(); it.hasNext(); )
295         {
296             commandLine.createArgument().setValue( it.next().toString() );
297         }
298 
299         commandLine.setWorkingDirectory( workingDirectory.getAbsolutePath() );
300 
301         createParentDirIfNecessary( signedjar );
302 
303         if ( signedjar == null )
304         {
305             getLog().debug( "Signing JAR in-place (overwritting original JAR)." );
306         }
307 
308         if ( getLog().isDebugEnabled() )
309         {
310             getLog().debug( "Executing: " + purgePassword( commandLine ) );
311         }
312 
313         // jarsigner may ask for some input if the parameters are missing or incorrect.
314         // This should take care of it and make it fail gracefully
315         final InputStream inputStream = new InputStream()
316         {
317             public int read()
318             {
319                 return -1;
320             }
321         };
322         StreamConsumer outConsumer = new StreamConsumer()
323         {
324             public void consumeLine( String line )
325             {
326                 getLog().info( line );
327             }
328         };
329         final StringBuffer errBuffer = new StringBuffer();
330         StreamConsumer errConsumer = new StreamConsumer()
331         {
332             public void consumeLine( String line )
333             {
334                 errBuffer.append( line );
335                 getLog().warn( line );
336             }
337         };
338 
339         try
340         {
341             int result = executeCommandLine( commandLine, inputStream, outConsumer, errConsumer );
342 
343             if ( result != 0 )
344             {
345                 throw new MojoExecutionException( "Result of " + purgePassword( commandLine ) +
346                     " execution is: \'" + result + "\'." );
347             }
348         }
349         catch ( CommandLineException e )
350         {
351             throw new MojoExecutionException( "command execution failed", e );
352         }
353 
354         // signed in place, no need to attach
355         if ( signedjar == null || skipAttachSignedArtifact )
356         {
357             return;
358         }
359 
360         if ( classifier != null )
361         {
362             projectHelper.attachArtifact( project, "jar", classifier, signedjar );
363         }
364         else
365         {
366             project.getArtifact().setFile( signedjar );
367         }
368     }
369 
370     private String purgePassword( Commandline commandLine )
371     {
372         String out = commandLine.toString();
373         if ( keypass != null && out.indexOf( keypass ) != -1 )
374         {
375             out = StringUtils.replace( out, keypass, "******" );
376         }
377         return out;
378     }
379 
380     private void createParentDirIfNecessary( File file )
381     {
382         if ( file != null )
383         {
384             File fileDir = file.getParentFile();
385 
386             if ( fileDir != null )
387             { // not a relative path
388                 boolean mkdirs = fileDir.mkdirs();
389                 getLog().debug( "mdkirs: " + mkdirs + " " + fileDir );
390             }
391         }
392     }
393 
394     // taken from JavadocReport then slightly refactored
395     // should probably share with other plugins that use $JAVA_HOME/bin tools
396 
397     /**
398      * Get the path of jarsigner tool depending the OS.
399      *
400      * @return the path of the jarsigner tool
401      */
402     private String getJarsignerPath()
403     {
404         return getJDKCommandPath( "jarsigner", getLog() );
405     }
406 
407     private static String getJDKCommandPath( String command, Log logger )
408     {
409         String path = getJDKCommandExe( command ).getAbsolutePath();
410         logger.debug( command + " executable=[" + path + "]" );
411         return path;
412     }
413 
414     private static File getJDKCommandExe( String command )
415     {
416         String fullCommand = command + ( SystemUtils.IS_OS_WINDOWS ? ".exe" : "" );
417 
418         File exe;
419 
420         // For IBM's JDK 1.2
421         if ( SystemUtils.IS_OS_AIX )
422         {
423             exe = new File( SystemUtils.getJavaHome() + "/../sh", fullCommand );
424         }
425         else if ( SystemUtils.IS_OS_MAC_OSX )
426         {
427             exe = new File( SystemUtils.getJavaHome() + "/bin", fullCommand );
428         }
429         else
430         {
431             exe = new File( SystemUtils.getJavaHome() + "/../bin", fullCommand );
432         }
433 
434         return exe;
435     }
436 
437     // Helper methods. Could/should be shared e.g. with JavadocReport
438 
439     /**
440      * Convenience method to add an argument to the <code>command line</code>
441      * conditionally based on the given flag.
442      *
443      * @param arguments
444      * @param b         the flag which controls if the argument is added or not.
445      * @param value     the argument value to be added.
446      */
447     private void addArgIf( List arguments, boolean b, String value )
448     {
449         if ( b )
450         {
451             arguments.add( value );
452         }
453     }
454 
455     /**
456      * Convenience method to add an argument to the <code>command line</code>
457      * if the the value is not null or empty.
458      * <p/>
459      * Moreover, the value could be comma separated.
460      *
461      * @param arguments
462      * @param key       the argument name.
463      * @param value     the argument value to be added.
464      * @see #addArgIfNotEmpty(java.util.List,String,Object,boolean)
465      */
466     private void addArgIfNotEmpty( List arguments, String key, Object value )
467     {
468         addArgIfNotEmpty( arguments, key, value, false );
469     }
470 
471     /**
472      * Convenience method to add an argument to the <code>command line</code>
473      * if the the value is not null or empty.
474      * <p/>
475      * Moreover, the value could be comma separated.
476      *
477      * @param arguments
478      * @param key       the argument name.
479      * @param value     the argument value to be added.
480      * @param repeatKey repeat or not the key in the command line
481      */
482     private void addArgIfNotEmpty( List arguments, String key, Object value, boolean repeatKey )
483     {
484         if ( value != null && !StringUtils.isEmpty( value.toString() ) )
485         {
486             arguments.add( key );
487 
488             StringTokenizer token = new StringTokenizer( value.toString(), "," );
489             while ( token.hasMoreTokens() )
490             {
491                 String current = token.nextToken().trim();
492 
493                 if ( !StringUtils.isEmpty( current ) )
494                 {
495                     arguments.add( current );
496 
497                     if ( token.hasMoreTokens() && repeatKey )
498                     {
499                         arguments.add( key );
500                     }
501                 }
502             }
503         }
504     }
505 
506     //
507     // methods used for tests purposes - allow mocking and simulate automatic setters
508     //
509 
510     protected int executeCommandLine( Commandline commandLine, InputStream inputStream, StreamConsumer stream1,
511                                       StreamConsumer stream2 )
512         throws CommandLineException
513     {
514         return CommandLineUtils.executeCommandLine( commandLine, inputStream, stream1, stream2 );
515     }
516 
517     public void setWorkingDir( File workingDir )
518     {
519         this.workingDirectory = workingDir;
520     }
521 
522     public void setBasedir( File basedir )
523     {
524         this.basedir = basedir;
525     }
526 
527     public void setKeystore( String keystore )
528     {
529         this.keystore = keystore;
530     }
531 
532     public void setKeypass( String keypass )
533     {
534         this.keypass = keypass;
535     }
536 
537     public void setSignedJar( File signedjar )
538     {
539         this.signedjar = signedjar;
540     }
541 
542     public void setAlias( String alias )
543     {
544         this.alias = alias;
545     }
546 
547     // hiding for now - I don't think this is required to be seen
548     /*
549      public void setFinalName( String finalName )
550      {
551      this.finalName = finalName;
552      }
553      */
554 
555     public void setJarPath( File jarPath )
556     {
557         this.jarPath = jarPath;
558     }
559 
560     public void setStorepass( String storepass )
561     {
562         this.storepass = storepass;
563     }
564 
565     public void setSigFile( String sigfile )
566     {
567         this.sigfile = sigfile;
568     }
569 
570     public void setType( String type )
571     {
572         this.type = type;
573     }
574 
575     public void setVerbose( boolean verbose )
576     {
577         this.verbose = verbose;
578     }
579 
580     public void setSkipAttachSignedArtifact( boolean skipAttachSignedArtifact )
581     {
582         this.skipAttachSignedArtifact = skipAttachSignedArtifact;
583     }
584 
585     public void setProject( MavenProject project )
586     {
587         this.project = project;
588     }
589 
590     public void setVerify( boolean verify )
591     {
592         this.verify = verify;
593     }
594 }