Coverage Report - org.apache.maven.plugins.jarsigner.AbstractJarsignerMojo
 
Classes in this File Line Coverage Branch Coverage Complexity
AbstractJarsignerMojo
0%
0/110
0%
0/84
3,647
AbstractJarsignerMojo$1
0%
0/2
N/A
3,647
AbstractJarsignerMojo$2
0%
0/5
0%
0/2
3,647
AbstractJarsignerMojo$3
0%
0/3
N/A
3,647
 
 1  
 package org.apache.maven.plugins.jarsigner;
 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 java.io.File;
 23  
 import java.io.FileInputStream;
 24  
 import java.io.IOException;
 25  
 import java.io.InputStream;
 26  
 import java.text.MessageFormat;
 27  
 import java.util.Iterator;
 28  
 import java.util.List;
 29  
 import java.util.Properties;
 30  
 import java.util.ResourceBundle;
 31  
 import java.util.zip.ZipEntry;
 32  
 import java.util.zip.ZipInputStream;
 33  
 
 34  
 import org.apache.maven.artifact.Artifact;
 35  
 import org.apache.maven.plugin.AbstractMojo;
 36  
 import org.apache.maven.plugin.MojoExecutionException;
 37  
 import org.apache.maven.project.MavenProject;
 38  
 
 39  
 import org.codehaus.plexus.util.FileUtils;
 40  
 import org.codehaus.plexus.util.Os;
 41  
 import org.codehaus.plexus.util.StringUtils;
 42  
 import org.codehaus.plexus.util.cli.CommandLineException;
 43  
 import org.codehaus.plexus.util.cli.CommandLineUtils;
 44  
 import org.codehaus.plexus.util.cli.Commandline;
 45  
 import org.codehaus.plexus.util.cli.StreamConsumer;
 46  
 
 47  
 /**
 48  
  * Maven Jarsigner Plugin base class.
 49  
  *
 50  
  * @author <a href="cs@schulte.it">Christian Schulte</a>
 51  
  * @version $Id: AbstractJarsignerMojo.java 802605 2009-08-09 21:10:34Z bentmann $
 52  
  */
 53  0
 public abstract class AbstractJarsignerMojo
 54  
     extends AbstractMojo
 55  
 {
 56  
 
 57  
     /**
 58  
      * See <a href="http://java.sun.com/javase/6/docs/technotes/tools/windows/jarsigner.html#Options">options</a>.
 59  
      *
 60  
      * @parameter expression="${jarsigner.verbose}" default-value="false"
 61  
      */
 62  
     private boolean verbose;
 63  
 
 64  
     /**
 65  
      * The maximum memory available to the JAR signer, e.g. <code>256M</code>. See <a
 66  
      * href="http://java.sun.com/javase/6/docs/technotes/tools/windows/java.html#Xms">-Xmx</a> for more details.
 67  
      * 
 68  
      * @parameter expression="${jarsigner.maxMemory}"
 69  
      */
 70  
     private String maxMemory;
 71  
 
 72  
     /**
 73  
      * Archive to process. If set, neither the project artifact nor any attachments or archive sets are processed.
 74  
      *
 75  
      * @parameter expression="${jarsigner.archive}"
 76  
      */
 77  
     private File archive;
 78  
 
 79  
     /**
 80  
      * The base directory to scan for JAR files using Ant-like inclusion/exclusion patterns.
 81  
      * 
 82  
      * @parameter expression="${jarsigner.archiveDirectory}"
 83  
      * @since 1.1
 84  
      */
 85  
     private File archiveDirectory;
 86  
 
 87  
     /**
 88  
      * The Ant-like inclusion patterns used to select JAR files to process. The patterns must be relative to the
 89  
      * directory given by the parameter {@link #archiveDirectory}. By default, the pattern
 90  
      * <code>&#42;&#42;/&#42;.?ar</code> is used.
 91  
      * 
 92  
      * @parameter
 93  
      * @since 1.1
 94  
      */
 95  0
     private String[] includes = { "**/*.?ar" };
 96  
 
 97  
     /**
 98  
      * The Ant-like exclusion patterns used to exclude JAR files from processing. The patterns must be relative to the
 99  
      * directory given by the parameter {@link #archiveDirectory}.
 100  
      * 
 101  
      * @parameter
 102  
      * @since 1.1
 103  
      */
 104  0
     private String[] excludes = {};
 105  
 
 106  
     /**
 107  
      * List of additional arguments to append to the jarsigner command line.
 108  
      *
 109  
      * @parameter expression="${jarsigner.arguments}"
 110  
      */
 111  
     private String[] arguments;
 112  
 
 113  
     /**
 114  
      * Set to {@code true} to disable the plugin.
 115  
      *
 116  
      * @parameter expression="${jarsigner.skip}" default-value="false"
 117  
      */
 118  
     private boolean skip;
 119  
 
 120  
     /**
 121  
      * Controls processing of the main artifact produced by the project.
 122  
      * 
 123  
      * @parameter expression="${jarsigner.processMainArtifact}" default-value="true"
 124  
      * @since 1.1
 125  
      */
 126  
     private boolean processMainArtifact;
 127  
 
 128  
     /**
 129  
      * Controls processing of project attachments. If enabled, attached artifacts that are no JARs will be automatically
 130  
      * excluded from processing.
 131  
      * 
 132  
      * @parameter expression="${jarsigner.processAttachedArtifacts}" default-value="true"
 133  
      * @since 1.1
 134  
      */
 135  
     private boolean processAttachedArtifacts;
 136  
 
 137  
     /**
 138  
      * Controls processing of project attachments.
 139  
      * 
 140  
      * @parameter expression="${jarsigner.attachments}"
 141  
      * @deprecated As of version 1.1 in favor of the new parameter <code>processAttachedArtifacts</code>.
 142  
      */
 143  
     private Boolean attachments;
 144  
 
 145  
     /**
 146  
      * The Maven project.
 147  
      *
 148  
      * @parameter default-value="${project}"
 149  
      * @required
 150  
      * @readonly
 151  
      */
 152  
     private MavenProject project;
 153  
 
 154  
     /**
 155  
      * The path to the jarsigner we are going to use.
 156  
      */
 157  
     private String executable;
 158  
 
 159  
     public final void execute()
 160  
         throws MojoExecutionException
 161  
     {
 162  0
         if ( !this.skip )
 163  
         {
 164  0
             this.executable = getExecutable();
 165  
 
 166  0
             int processed = 0;
 167  
 
 168  0
             if ( this.archive != null )
 169  
             {
 170  0
                 processArchive( this.archive );
 171  0
                 processed++;
 172  
             }
 173  
             else
 174  
             {
 175  0
                 if ( processMainArtifact )
 176  
                 {
 177  0
                     processed += processArtifact( this.project.getArtifact() ) ? 1 : 0;
 178  
                 }
 179  
 
 180  0
                 if ( processAttachedArtifacts && !Boolean.FALSE.equals( attachments ) )
 181  
                 {
 182  0
                     for ( Iterator it = this.project.getAttachedArtifacts().iterator(); it.hasNext(); )
 183  
                     {
 184  0
                         final Artifact artifact = (Artifact) it.next();
 185  
 
 186  0
                         processed += processArtifact( artifact ) ? 1 : 0;
 187  
                     }
 188  
                 }
 189  
                 else
 190  
                 {
 191  0
                     if ( verbose )
 192  
                     {
 193  0
                         getLog().info( getMessage( "ignoringAttachments" ) );
 194  
                     }
 195  
                     else
 196  
                     {
 197  0
                         getLog().debug( getMessage( "ignoringAttachments" ) );
 198  
                     }
 199  
                 }
 200  
 
 201  0
                 if ( archiveDirectory != null )
 202  
                 {
 203  0
                     String includeList = ( includes != null ) ? StringUtils.join( includes, "," ) : null;
 204  0
                     String excludeList = ( excludes != null ) ? StringUtils.join( excludes, "," ) : null;
 205  
 
 206  
                     List jarFiles;
 207  
                     try
 208  
                     {
 209  0
                         jarFiles = FileUtils.getFiles( archiveDirectory, includeList, excludeList );
 210  
                     }
 211  0
                     catch ( IOException e )
 212  
                     {
 213  0
                         throw new MojoExecutionException( "Failed to scan archive directory for JARs: "
 214  
                             + e.getMessage(), e );
 215  0
                     }
 216  
 
 217  0
                     for ( Iterator it = jarFiles.iterator(); it.hasNext(); )
 218  
                     {
 219  0
                         File jarFile = (File) it.next();
 220  
 
 221  0
                         processArchive( jarFile );
 222  0
                         processed++;
 223  
                     }
 224  
                 }
 225  
             }
 226  
 
 227  0
             getLog().info( getMessage( "processed", new Integer( processed ) ) );
 228  
         }
 229  
         else
 230  
         {
 231  0
             getLog().info( getMessage( "disabled", null ) );
 232  
         }
 233  0
     }
 234  
 
 235  
     /**
 236  
      * Gets the {@code Commandline} to execute for a given Java archive taking a command line prepared for executing
 237  
      * jarsigner.
 238  
      *
 239  
      * @param archive The Java archive to get a {@code Commandline} to execute for.
 240  
      * @param commandLine A {@code Commandline} prepared for executing jarsigner without any arguments.
 241  
      *
 242  
      * @return A {@code Commandline} for executing jarsigner with {@code archive}.
 243  
      *
 244  
      * @throws NullPointerException if {@code archive} or {@code commandLine} is {@code null}.
 245  
      */
 246  
     protected abstract Commandline getCommandline( final File archive, final Commandline commandLine );
 247  
 
 248  
     /**
 249  
      * Gets a string representation of a {@code Commandline}.
 250  
      * <p>This method creates the string representation by calling {@code commandLine.toString()} by default.</p>
 251  
      *
 252  
      * @param commandLine The {@code Commandline} to get a string representation of.
 253  
      *
 254  
      * @return The string representation of {@code commandLine}.
 255  
      *
 256  
      * @throws NullPointerException if {@code commandLine} is {@code null}.
 257  
      */
 258  
     protected String getCommandlineInfo( final Commandline commandLine )
 259  
     {
 260  0
         if ( commandLine == null )
 261  
         {
 262  0
             throw new NullPointerException( "commandLine" );
 263  
         }
 264  
 
 265  0
         return commandLine.toString();
 266  
     }
 267  
 
 268  
     /**
 269  
      * Checks Java language capability of an artifact.
 270  
      *
 271  
      * @param artifact The artifact to check.
 272  
      *
 273  
      * @return {@code true} if {@code artifact} is Java language capable; {@code false} if not.
 274  
      */
 275  
     private boolean isJarFile( final Artifact artifact )
 276  
     {
 277  0
         return artifact != null && artifact.getFile() != null && isJarFile( artifact.getFile() );
 278  
     }
 279  
 
 280  
     /**
 281  
      * Checks whether the specified file is a JAR file. For our purposes, a JAR file is a (non-empty) ZIP stream with a
 282  
      * META-INF directory or some class files.
 283  
      * 
 284  
      * @param file The file to check, must not be <code>null</code>.
 285  
      * @return <code>true</code> if the file looks like a JAR file, <code>false</code> otherwise.
 286  
      */
 287  
     private boolean isJarFile( final File file )
 288  
     {
 289  
         try
 290  
         {
 291  
             // NOTE: ZipFile.getEntry() might be shorter but is several factors slower on large files
 292  
 
 293  0
             ZipInputStream zis = new ZipInputStream( new FileInputStream( file ) );
 294  
             try
 295  
             {
 296  0
                 for ( ZipEntry ze = zis.getNextEntry(); ze != null; ze = zis.getNextEntry() )
 297  
                 {
 298  0
                     if ( ze.getName().startsWith( "META-INF/" ) || ze.getName().endsWith( ".class" ) )
 299  
                     {
 300  0
                         return true;
 301  
                     }
 302  
                 }
 303  
             }
 304  
             finally
 305  
             {
 306  0
                 zis.close();
 307  0
             }
 308  
         }
 309  0
         catch ( Exception e )
 310  
         {
 311  
             // ignore, will fail below
 312  0
         }
 313  
 
 314  0
         return false;
 315  
     }
 316  
 
 317  
     /**
 318  
      * Processes a given artifact.
 319  
      *
 320  
      * @param artifact The artifact to process.
 321  
      * @return <code>true</code> if the artifact is a JAR and was processed, <code>false</code> otherwise.
 322  
      *
 323  
      * @throws NullPointerException if {@code artifact} is {@code null}.
 324  
      * @throws MojoExecutionException if processing {@code artifact} fails.
 325  
      */
 326  
     private boolean processArtifact( final Artifact artifact )
 327  
         throws MojoExecutionException
 328  
     {
 329  0
         if ( artifact == null )
 330  
         {
 331  0
             throw new NullPointerException( "artifact" );
 332  
         }
 333  
 
 334  0
         boolean processed = false;
 335  
 
 336  0
         if ( isJarFile( artifact ) )
 337  
         {
 338  0
             processArchive( artifact.getFile() );
 339  
 
 340  0
             processed = true;
 341  
         }
 342  
         else
 343  
         {
 344  0
             if ( this.verbose )
 345  
             {
 346  0
                 getLog().info( getMessage( "unsupported", artifact ) );
 347  
             }
 348  0
             else if ( getLog().isDebugEnabled() )
 349  
             {
 350  0
                 getLog().debug( getMessage( "unsupported", artifact ) );
 351  
             }
 352  
         }
 353  
 
 354  0
         return processed;
 355  
     }
 356  
 
 357  
     /**
 358  
      * Pre-processes a given archive.
 359  
      * 
 360  
      * @param archive The archive to process, must not be <code>null</code>.
 361  
      * @throws MojoExecutionException If pre-processing failed.
 362  
      */
 363  
     protected void preProcessArchive( final File archive )
 364  
         throws MojoExecutionException
 365  
     {
 366  
         // default does nothing
 367  0
     }
 368  
 
 369  
     /**
 370  
      * Processes a given archive.
 371  
      * 
 372  
      * @param archive The archive to process.
 373  
      * @throws NullPointerException if {@code archive} is {@code null}.
 374  
      * @throws MojoExecutionException if processing {@code archive} fails.
 375  
      */
 376  
     private void processArchive( final File archive )
 377  
         throws MojoExecutionException
 378  
     {
 379  0
         if ( archive == null )
 380  
         {
 381  0
             throw new NullPointerException( "archive" );
 382  
         }
 383  
 
 384  0
         preProcessArchive( archive );
 385  
 
 386  0
         if ( this.verbose )
 387  
         {
 388  0
             getLog().info( getMessage( "processing", archive ) );
 389  
         }
 390  0
         else if ( getLog().isDebugEnabled() )
 391  
         {
 392  0
             getLog().debug( getMessage( "processing", archive ) );
 393  
         }
 394  
 
 395  0
         Commandline commandLine = new Commandline();
 396  
 
 397  0
         commandLine.setExecutable( this.executable );
 398  
 
 399  0
         commandLine.setWorkingDirectory( this.project.getBasedir() );
 400  
 
 401  0
         if ( this.verbose )
 402  
         {
 403  0
             commandLine.createArg().setValue( "-verbose" );
 404  
         }
 405  
 
 406  0
         if ( StringUtils.isNotEmpty( maxMemory ) )
 407  
         {
 408  0
             commandLine.createArg().setValue( "-J-Xmx" + maxMemory );
 409  
         }
 410  
 
 411  0
         if ( this.arguments != null )
 412  
         {
 413  0
             commandLine.addArguments( this.arguments );
 414  
         }
 415  
 
 416  0
         commandLine = getCommandline( archive, commandLine );
 417  
 
 418  
         try
 419  
         {
 420  0
             if ( getLog().isDebugEnabled() )
 421  
             {
 422  0
                 getLog().debug( getMessage( "command", getCommandlineInfo( commandLine ) ) );
 423  
             }
 424  
 
 425  0
             final int result = CommandLineUtils.executeCommandLine( commandLine,
 426  
                 new InputStream()
 427  
             {
 428  
 
 429  0
                 public int read()
 430  
                 {
 431  0
                     return -1;
 432  
                 }
 433  
 
 434  
             }, new StreamConsumer()
 435  
             {
 436  
 
 437  0
                 public void consumeLine( final String line )
 438  
                 {
 439  0
                     if ( verbose )
 440  
                     {
 441  0
                         getLog().info( line );
 442  
                     }
 443  
                     else
 444  
                     {
 445  0
                         getLog().debug( line );
 446  
                     }
 447  0
                 }
 448  
 
 449  
             }, new StreamConsumer()
 450  
             {
 451  
 
 452  0
                 public void consumeLine( final String line )
 453  
                 {
 454  0
                     getLog().warn( line );
 455  0
                 }
 456  
 
 457  
             } );
 458  
 
 459  0
             if ( result != 0 )
 460  
             {
 461  0
                 throw new MojoExecutionException( getMessage( "failure", getCommandlineInfo( commandLine ),
 462  
                                                               new Integer( result ) ) );
 463  
             }
 464  
         }
 465  0
         catch ( CommandLineException e )
 466  
         {
 467  0
             throw new MojoExecutionException( getMessage( "commandLineException", getCommandlineInfo( commandLine ) ),
 468  
                                               e );
 469  0
         }
 470  0
     }
 471  
 
 472  
     /**
 473  
      * Locates the executable for the jarsigner tool.
 474  
      * 
 475  
      * @return The executable of the jarsigner tool, never <code>null<code>.
 476  
      */
 477  
     private String getExecutable()
 478  
     {
 479  0
         String command = "jarsigner" + ( Os.isFamily( Os.FAMILY_WINDOWS ) ? ".exe" : "" );
 480  
 
 481  0
         String executable =
 482  
             findExecutable( command, System.getProperty( "java.home" ), new String[] { "../bin", "bin", "../sh" } );
 483  
 
 484  0
         if ( executable == null )
 485  
         {
 486  
             try
 487  
             {
 488  0
                 Properties env = CommandLineUtils.getSystemEnvVars();
 489  
 
 490  0
                 String[] variables = { "JDK_HOME", "JAVA_HOME" };
 491  
 
 492  0
                 for ( int i = 0; i < variables.length && executable == null; i++ )
 493  
                 {
 494  0
                     executable =
 495  
                         findExecutable( command, env.getProperty( variables[i] ), new String[] { "bin", "sh" } );
 496  
                 }
 497  
             }
 498  0
             catch ( IOException e )
 499  
             {
 500  0
                 if ( getLog().isDebugEnabled() )
 501  
                 {
 502  0
                     getLog().warn( "Failed to retrieve environment variables, cannot search for " + command, e );
 503  
                 }
 504  
                 else
 505  
                 {
 506  0
                     getLog().warn( "Failed to retrieve environment variables, cannot search for " + command );
 507  
                 }
 508  0
             }
 509  
         }
 510  
 
 511  0
         if ( executable == null )
 512  
         {
 513  0
             executable = command;
 514  
         }
 515  
 
 516  0
         return executable;
 517  
     }
 518  
 
 519  
     /**
 520  
      * Finds the specified command in any of the given sub directories of the specified JDK/JRE home directory.
 521  
      * 
 522  
      * @param command The command to find, must not be <code>null</code>.
 523  
      * @param homeDir The home directory to search in, may be <code>null</code>.
 524  
      * @param subDirs The sub directories of the home directory to search in, must not be <code>null</code>.
 525  
      * @return The (absolute) path to the command if found, <code>null</code> otherwise.
 526  
      */
 527  
     private String findExecutable( String command, String homeDir, String[] subDirs )
 528  
     {
 529  0
         if ( StringUtils.isNotEmpty( homeDir ) )
 530  
         {
 531  0
             for ( int i = 0; i < subDirs.length; i++ )
 532  
             {
 533  0
                 File file = new File( new File( homeDir, subDirs[i] ), command );
 534  
 
 535  0
                 if ( file.isFile() )
 536  
                 {
 537  0
                     return file.getAbsolutePath();
 538  
                 }
 539  
             }
 540  
         }
 541  
 
 542  0
         return null;
 543  
     }
 544  
 
 545  
     /**
 546  
      * Gets a message for a given key from the resource bundle backing the implementation.
 547  
      *
 548  
      * @param key The key of the message to return.
 549  
      * @param args Arguments to format the message with or {@code null}.
 550  
      *
 551  
      * @return The message with key {@code key} from the resource bundle backing the implementation.
 552  
      *
 553  
      * @throws NullPointerException if {@code key} is {@code null}.
 554  
      * @throws java.util.MissingResourceException if there is no message available matching {@code key} or accessing
 555  
      * the resource bundle fails.
 556  
      */
 557  
     private String getMessage( final String key, final Object[] args )
 558  
     {
 559  0
         if ( key == null )
 560  
         {
 561  0
             throw new NullPointerException( "key" );
 562  
         }
 563  
 
 564  0
         return new MessageFormat( ResourceBundle.getBundle( "jarsigner" ).getString( key ) ).format( args );
 565  
     }
 566  
 
 567  
     private String getMessage( final String key )
 568  
     {
 569  0
         return getMessage( key, null );
 570  
     }
 571  
 
 572  
     private String getMessage( final String key, final Object arg )
 573  
     {
 574  0
         return getMessage( key, new Object[] { arg } );
 575  
     }
 576  
 
 577  
     private String getMessage( final String key, final Object arg1, final Object arg2 )
 578  
     {
 579  0
         return getMessage( key, new Object[] { arg1, arg2 } );
 580  
     }
 581  
 
 582  
 }