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