View Javadoc
1   package org.codehaus.mojo.webstart;
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.collections.MapUtils;
23  import org.apache.commons.lang.StringUtils;
24  import org.apache.maven.artifact.repository.ArtifactRepository;
25  import org.apache.maven.plugin.AbstractMojo;
26  import org.apache.maven.plugin.MojoExecutionException;
27  import org.apache.maven.plugins.annotations.Component;
28  import org.apache.maven.plugins.annotations.Parameter;
29  import org.apache.maven.project.MavenProject;
30  import org.codehaus.mojo.webstart.dependency.filenaming.DependencyFilenameStrategy;
31  import org.codehaus.mojo.webstart.pack200.Pack200Config;
32  import org.codehaus.mojo.webstart.pack200.Pack200Tool;
33  import org.codehaus.mojo.webstart.sign.SignConfig;
34  import org.codehaus.mojo.webstart.sign.SignTool;
35  import org.codehaus.mojo.webstart.util.ArtifactUtil;
36  import org.codehaus.mojo.webstart.util.IOUtil;
37  import org.codehaus.mojo.webstart.util.JarUtil;
38  import org.sonatype.plexus.components.sec.dispatcher.SecDispatcher;
39  
40  import java.io.File;
41  import java.io.FileFilter;
42  import java.io.IOException;
43  import java.net.MalformedURLException;
44  import java.net.URL;
45  import java.net.URLClassLoader;
46  import java.util.ArrayList;
47  import java.util.List;
48  import java.util.Map;
49  
50  /**
51   * The superclass for all JNLP generating MOJOs.
52   *
53   * @author Kevin Stembridge
54   * @author $LastChangedBy$
55   * @version $Revision$
56   * @since 28 May 2007
57   */
58  public abstract class AbstractBaseJnlpMojo
59      extends AbstractMojo
60  {
61      // ----------------------------------------------------------------------
62      // Constants
63      // ----------------------------------------------------------------------
64  
65      private static final String DEFAULT_RESOURCES_DIR = "src/main/jnlp/resources";
66  
67      /**
68       * unprocessed files (that will be signed) are prefixed with this
69       */
70      private static final String UNPROCESSED_PREFIX = "unprocessed_";
71  
72      /**
73       * Suffix extension of a jar file.
74       */
75      public static final String JAR_SUFFIX = ".jar";
76  
77      // ----------------------------------------------------------------------
78      // Mojo Parameters
79      // ----------------------------------------------------------------------
80  
81      /**
82       * Local repository.
83       */
84      @Parameter( defaultValue = "${localRepository}", required = true, readonly = true )
85      private ArtifactRepository localRepository;
86  
87      /**
88       * The collection of remote artifact repositories.
89       */
90      @Parameter( defaultValue = "${project.remoteArtifactRepositories}", required = true, readonly = true )
91      private List<ArtifactRepository> remoteRepositories;
92  
93      /**
94       * The directory in which files will be stored prior to processing.
95       */
96      @Parameter( property = "jnlp.workDirectory", defaultValue = "${project.build.directory}/jnlp", required = true )
97      private File workDirectory;
98  
99      /**
100      * The path where the libraries are placed within the jnlp structure.
101      */
102     @Parameter( property = "jnlp.libPath", defaultValue = "" )
103     protected String libPath;
104 
105     /**
106      * The location of the directory (relative or absolute) containing non-jar resources that
107      * are to be included in the JNLP bundle.
108      */
109     @Parameter( property = "jnlp.resourcesDirectory" )
110     private File resourcesDirectory;
111 
112     /**
113      * The location where the JNLP Velocity template files are stored.
114      */
115     @Parameter( property = "jnlp.templateDirectory", defaultValue = "${project.basedir}/src/main/jnlp", required = true )
116     private File templateDirectory;
117 
118     /**
119      * The Pack200 Config.
120      *
121      * @since 1.0-beta-4
122      */
123     @Parameter
124     private Pack200Config pack200;
125 
126     /**
127      * The Sign Config.
128      */
129     @Parameter
130     private SignConfig sign;
131 
132     /**
133      * Indicates whether or not gzip archives will be created for each of the jar
134      * files included in the webstart bundle.
135      */
136     @Parameter( property = "jnlp.gzip", defaultValue = "false" )
137     private boolean gzip;
138 
139     /**
140      * Enable verbose output.
141      */
142     @Parameter( property = "webstart.verbose", alias = "verbose", defaultValue = "false" )
143     private boolean verbose;
144 
145     /**
146      * Set to true to exclude all transitive dependencies.
147      *
148      * @parameter
149      */
150     @Parameter( property = "jnlp.excludeTransitive" )
151     private boolean excludeTransitive;
152 
153     /**
154      * The code base to use on the generated jnlp files.
155      *
156      * @since 1.0-beta-2
157      */
158     @Parameter( property = "jnlp.codebase", defaultValue = "${project.url}/jnlp" )
159     private String codebase;
160 
161     /**
162      * Encoding used to read and write jnlp files.
163      * <p/>
164      * <strong>Note:</strong> If this property is not defined, then will use a default value {@code utf-8}.
165      *
166      * @since 1.0-beta-2
167      */
168     @Parameter( property = "jnlp.encoding", defaultValue = "${project.build.sourceEncoding}" )
169     private String encoding;
170 
171     /**
172      * Define whether to remove existing signatures.
173      */
174     @Parameter( property = "jnlp.unsign", alias = "unsign", defaultValue = "false" )
175     private boolean unsignAlreadySignedJars;
176 
177     /**
178      * To authorize or not to unsign some already signed jar.
179      * <p/>
180      * If set to false and the {@code unsign} parameter is set to {@code true} then the build will fail if there is
181      * a jar to unsign, to avoid this use then the extension jnlp component.
182      *
183      * @since 1.0-beta-2
184      */
185     @Parameter( property = "jnlp.canUnsign", defaultValue = "true" )
186     private boolean canUnsign;
187 
188     /**
189      * To update manifest entries of all jar resources.
190      * <p/>
191      * Since jdk 1.7u45, you need to add some entries to be able to open jnlp files in High security level.
192      * See http://www.oracle.com/technetwork/java/javase/7u45-relnotes-2016950.html
193      * <p/>
194      * <strong>Note:</strong> Won't affect any already signed jar resources if you configuration does not authorize it.
195      * <p/>
196      * See parameters {@link #unsignAlreadySignedJars} and {@link #canUnsign}.
197      *
198      * @since 1.0-beta-4
199      */
200     @Parameter
201     private Map<String, String> updateManifestEntries;
202 
203     /**
204      * Compile class-path elements used to search for the keystore
205      * (if kestore location was prefixed by {@code classpath:}).
206      *
207      * @since 1.0-beta-4
208      */
209     @Parameter( defaultValue = "${project.compileClasspathElements}", required = true, readonly = true )
210     private List<?> compileClassPath;
211 
212     /**
213      * Naming strategy for dependencies of a jnlp application.
214      *
215      * The strategy purpose is to transform the name of the dependency file.
216      *
217      * The actual authorized values are:
218      * <ul>
219      *     <li><strong>simple</strong>: artifactId[-classifier]-version.jar</li>
220      *     <li><strong>full</strong>: groupId-artifactId[-classifier]-version.jar</li>
221      * </ul>
222      *
223      * Default value is {@code full} which avoid any colision of naming.
224      *
225      * @since 1.0-beta-5
226      */
227     @Parameter( property = "jnlp.filenameMapping", defaultValue = "simple", required = true)
228     private String filenameMapping;
229 
230     /**
231      * Use unique version for any snapshot dependency, or just use the {@code -SNAPSHOT} version suffix.
232      *
233      * @since 1.0-beta-7
234      */
235     @Parameter( property = "jnlp.useUniqueVersions", defaultValue = "false" )
236     private boolean useUniqueVersions;
237 
238     // ----------------------------------------------------------------------
239     // Components
240     // ----------------------------------------------------------------------
241 
242     /**
243      * Sign tool.
244      */
245     @Component
246     private SignTool signTool;
247 
248     /**
249      * All available pack200 tools.
250      * <p/>
251      * We use a plexus list injection instead of a direct component injection since for a jre 1.4, we will at the
252      * moment have no implementation of this tool.
253      * <p/>
254      * Later in the execute of mojo, we will check if at least one implementation is available if required.
255      *
256      * @since 1.0-beta-2
257      */
258     @Component( role = Pack200Tool.class )
259     private Pack200Tool pack200Tool;
260 
261     /**
262      * Artifact helper.
263      *
264      * @since 1.0-beta-4
265      */
266     @Component
267     private ArtifactUtil artifactUtil;
268 
269     /**
270      * io helper.
271      *
272      * @since 1.0-beta-4
273      */
274     @Component
275     private IOUtil ioUtil;
276 
277     /**
278      * Jar util.
279      *
280      * @since 1.0-beta-4
281      */
282     @Component( hint = "default" )
283     private JarUtil jarUtil;
284 
285     /**
286      * All dependency filename strategy indexed by theire role-hint.
287      *
288      * @since 1.0-beta-5
289      */
290     @Component( role = DependencyFilenameStrategy.class )
291     private Map<String, DependencyFilenameStrategy> dependencyFilenameStrategyMap;
292 
293     /**
294      * @since 1.0-beta-7
295      */
296     @Component(hint = "mng-4384")
297     private SecDispatcher securityDispatcher;
298 
299     // ----------------------------------------------------------------------
300     // Fields
301     // ----------------------------------------------------------------------
302 
303     /**
304      * List of detected modified artifacts (will then re-apply stuff on them).
305      */
306     private final List<String> modifiedJnlpArtifacts = new ArrayList<String>();
307 
308     // the jars to sign and pack are selected if they are prefixed by UNPROCESSED_PREFIX.
309     // as the plugin copies the new versions locally before signing/packing them
310     // we just need to see if the plugin copied a new version
311     // We achieve that by only filtering files modified after the plugin was started
312     // Note: if other files (the pom, the keystore config) have changed, one needs to clean
313     private final FileFilter unprocessedJarFileFilter;
314 
315     /**
316      * Filter of processed jar files.
317      */
318     private final FileFilter processedJarFileFilter;
319 
320     /**
321      * Filter of jar files that need to be pack200.
322      */
323     private final FileFilter unprocessedPack200FileFilter;
324 
325     /**
326      * The dependency filename strategy.
327      */
328     private DependencyFilenameStrategy dependencyFilenameStrategy;
329 
330     /**
331      * Creates a new {@code AbstractBaseJnlpMojo}.
332      */
333     public AbstractBaseJnlpMojo()
334     {
335 
336         processedJarFileFilter = new FileFilter()
337         {
338             /**
339              * {@inheritDoc}
340              */
341             public boolean accept( File pathname )
342             {
343                 return pathname.isFile() && pathname.getName().endsWith( JAR_SUFFIX ) &&
344                     !pathname.getName().startsWith( UNPROCESSED_PREFIX );
345             }
346         };
347 
348         unprocessedJarFileFilter = new FileFilter()
349         {
350             /**
351              * {@inheritDoc}
352              */
353             public boolean accept( File pathname )
354             {
355                 return pathname.isFile() && pathname.getName().startsWith( UNPROCESSED_PREFIX ) &&
356                     pathname.getName().endsWith( JAR_SUFFIX );
357             }
358         };
359 
360         unprocessedPack200FileFilter = new FileFilter()
361         {
362             /**
363              * {@inheritDoc}
364              */
365             public boolean accept( File pathname )
366             {
367                 return pathname.isFile() && pathname.getName().startsWith( UNPROCESSED_PREFIX ) &&
368                     ( pathname.getName().endsWith( JAR_SUFFIX + Pack200Tool.PACK_GZ_EXTENSION ) ||
369                         pathname.getName().endsWith( JAR_SUFFIX + Pack200Tool.PACK_EXTENSION ) );
370             }
371         };
372     }
373 
374     // ----------------------------------------------------------------------
375     // Public Methods
376     // ----------------------------------------------------------------------
377 
378     public abstract MavenProject getProject();
379 
380     /**
381      * Returns the library path. This is ths subpath within the working directory, where the libraries are placed.
382      * If the path is not configured it is <code>null</code>.
383      *
384      * @return the library path or <code>null</code> if not configured.
385      */
386     public String getLibPath()
387     {
388         if ( StringUtils.isBlank( libPath ) )
389         {
390             return null;
391         }
392         return libPath;
393     }
394 
395     /**
396      * Returns the flag that indicates whether or not jar resources
397      * will be compressed using pack200.
398      *
399      * @return Returns the value of the pack200.enabled field.
400      */
401     public boolean isPack200()
402     {
403         return pack200 != null && pack200.isEnabled();
404     }
405 
406     /**
407      * Returns the files to be passed without pack200 compression.
408      *
409      * @return Returns the list value of the pack200.passFiles.
410      */
411     public List<String> getPack200PassFiles()
412     {
413         return pack200 == null ? null : pack200.getPassFiles();
414     }
415 
416     // ----------------------------------------------------------------------
417     // Protected Methods
418     // ----------------------------------------------------------------------
419 
420     /**
421      * Returns the working directory. This is the directory in which files and resources
422      * will be placed in order to be processed prior to packaging.
423      *
424      * @return Returns the value of the workDirectory field.
425      */
426     protected File getWorkDirectory()
427     {
428         return workDirectory;
429     }
430 
431     /**
432      * Returns the library directory. If not libPath is configured, the working directory is returned.
433      *
434      * @return Returns the value of the libraryDirectory field.
435      */
436     protected File getLibDirectory()
437     {
438         if ( getLibPath() != null )
439         {
440             return new File( getWorkDirectory(), getLibPath() );
441         }
442         return getWorkDirectory();
443     }
444 
445     /**
446      * Returns the location of the directory containing
447      * non-jar resources that are to be included in the JNLP bundle.
448      *
449      * @return Returns the value of the resourcesDirectory field, never null.
450      */
451     protected File getResourcesDirectory()
452     {
453 
454         if ( resourcesDirectory == null )
455         {
456             resourcesDirectory = new File( getProject().getBasedir(), DEFAULT_RESOURCES_DIR );
457         }
458 
459         return resourcesDirectory;
460 
461     }
462 
463     /**
464      * Returns the file handle to the directory containing the Velocity templates for the JNLP
465      * files to be generated.
466      *
467      * @return Returns the value of the templateDirectory field.
468      */
469     protected File getTemplateDirectory()
470     {
471         return templateDirectory;
472     }
473 
474     /**
475      * Returns the local artifact repository.
476      *
477      * @return Returns the value of the localRepository field.
478      */
479     protected ArtifactRepository getLocalRepository()
480     {
481         return localRepository;
482     }
483 
484     /**
485      * Returns the collection of remote artifact repositories for the current
486      * Maven project.
487      *
488      * @return Returns the value of the remoteRepositories field.
489      */
490     protected List<ArtifactRepository> getRemoteRepositories()
491     {
492         return remoteRepositories;
493     }
494 
495     /**
496      * Returns jar signing configuration element.
497      *
498      * @return Returns the value of the sign field.
499      */
500     protected SignConfig getSign()
501     {
502         return sign;
503     }
504 
505     /**
506      * Returns the code base to inject in the generated jnlp.
507      *
508      * @return Returns the value of codebase field.
509      */
510     protected String getCodebase()
511     {
512         return codebase;
513     }
514 
515     /**
516      * Returns the flag that indicates whether or not a gzip should be
517      * created for each jar resource.
518      *
519      * @return Returns the value of the gzip field.
520      */
521     protected boolean isGzip()
522     {
523         return gzip;
524     }
525 
526     /**
527      * Returns the flag that indicates whether or not to provide verbose output.
528      *
529      * @return Returns the value of the verbose field.
530      */
531     protected boolean isVerbose()
532     {
533         return verbose;
534     }
535 
536     /**
537      * Returns the flag that indicates whether or not all transitive dependencies will be excluded
538      * from the generated JNLP bundle.
539      *
540      * @return Returns the value of the excludeTransitive field.
541      */
542     protected boolean isExcludeTransitive()
543     {
544         return this.excludeTransitive;
545     }
546 
547     /**
548      * Returns the collection of artifacts that have been modified
549      * since the last time this mojo was run.
550      *
551      * @return Returns the value of the modifiedJnlpArtifacts field.
552      */
553     protected List<String> getModifiedJnlpArtifacts()
554     {
555         return modifiedJnlpArtifacts;
556     }
557 
558     /**
559      * @return the mojo encoding to use to write files.
560      */
561     protected String getEncoding()
562     {
563         if ( StringUtils.isEmpty( encoding ) )
564         {
565             encoding = "utf-8";
566             getLog().warn( "No encoding defined, will use the default one : " + encoding );
567         }
568         return encoding;
569     }
570 
571     protected DependencyFilenameStrategy getDependencyFilenameStrategy()
572     {
573         if ( dependencyFilenameStrategy == null )
574         {
575             dependencyFilenameStrategy = dependencyFilenameStrategyMap.get(filenameMapping );
576         }
577         return dependencyFilenameStrategy;
578     }
579 
580     protected boolean isUseUniqueVersions()
581     {
582         return useUniqueVersions;
583     }
584 
585     protected void  checkDependencyFilenameStrategy()
586         throws MojoExecutionException
587     {
588         if ( getDependencyFilenameStrategy() == null )
589         {
590 
591             dependencyFilenameStrategy = dependencyFilenameStrategyMap.get(filenameMapping );
592             if (dependencyFilenameStrategy==null) {
593                 throw new MojoExecutionException( "Could not find filenameMapping named '"+filenameMapping+"', use one of the following one: "+ dependencyFilenameStrategyMap.keySet());
594             }
595         }
596     }
597 
598     /**
599      * Conditionally copy the jar file into the target directory.
600      * The operation is not performed when a signed target file exists and is up to date.
601      * The signed target file name is taken from the <code>sourceFile</code> name.E
602      * The unsigned target file name is taken from the <code>sourceFile</code> name prefixed with UNPROCESSED_PREFIX.
603      * TODO this is confusing if the sourceFile is already signed. By unsigned we really mean 'unsignedbyus'
604      *
605      * @param sourceFile      source file to copy
606      * @param targetDirectory location of the target directory where to copy file
607      * @param targetFilename  [optional] to change the target filename to use (if {@code null} will
608      *                        use the sourceFile name).
609      * @return <code>true</code> when the file was copied, <code>false</code> otherwise.
610      * @throws IllegalArgumentException if sourceFile is <code>null</code> or
611      *                                  <code>sourceFile.getName()</code> is <code>null</code>
612      * @throws MojoExecutionException   if an error occurs attempting to copy the file.
613      */
614     protected boolean copyJarAsUnprocessedToDirectoryIfNecessary( File sourceFile, File targetDirectory,
615                                                                   String targetFilename )
616         throws MojoExecutionException
617     {
618 
619         if ( sourceFile == null )
620         {
621             throw new IllegalArgumentException( "sourceFile is null" );
622         }
623 
624         if ( targetFilename == null )
625         {
626             targetFilename = sourceFile.getName();
627         }
628 
629         File signedTargetFile = new File( targetDirectory, targetFilename );
630 
631         File unsignedTargetFile = toUnprocessFile( targetDirectory, targetFilename );
632 
633         boolean shouldCopy =
634             !signedTargetFile.exists() || ( signedTargetFile.lastModified() < sourceFile.lastModified() );
635 
636         shouldCopy &=
637             ( !unsignedTargetFile.exists() || ( unsignedTargetFile.lastModified() < sourceFile.lastModified() ) );
638 
639         if ( shouldCopy )
640         {
641             getIoUtil().copyFile( sourceFile, unsignedTargetFile );
642 
643         }
644         else
645         {
646             getLog().debug(
647                 "Source file hasn't changed. Do not reprocess " + signedTargetFile + " with " + sourceFile + "." );
648         }
649 
650         return shouldCopy;
651     }
652 
653 
654     /**
655      * If sign is enabled, sign the jars, otherwise rename them into final jars
656      *
657      * @throws MojoExecutionException if can not sign or rename jars
658      */
659     protected void signOrRenameJars()
660         throws MojoExecutionException
661     {
662 
663         if ( sign != null )
664         {
665             try
666             {
667                 ClassLoader loader = getCompileClassLoader();
668                 sign.init( getWorkDirectory(), getLog().isDebugEnabled(), signTool, securityDispatcher, loader );
669             }
670             catch ( MalformedURLException e )
671             {
672                 throw new MojoExecutionException( "Could not create classloader", e );
673             }
674 
675             if ( unsignAlreadySignedJars )
676             {
677                 removeExistingSignatures( getLibDirectory() );
678             }
679 
680             if ( isPack200() )
681             {
682 
683                 //TODO tchemit  use a temporary directory to pack-unpack
684 
685                 // http://java.sun.com/j2se/1.5.0/docs/guide/deployment/deployment-guide/pack200.html
686                 // we need to pack then unpack the files before signing them
687                 unpackJars( getLibDirectory() );
688 
689                 // As out current Pack200 ant tasks don't give us the ability to use a temporary area for
690                 // creating those temporary packing, we have to delete the temporary files.
691                 ioUtil.deleteFiles( getLibDirectory(), unprocessedPack200FileFilter );
692                 // specs says that one should do it twice when there are unsigned jars??
693                 // Pack200.unpackJars( applicationDirectory, updatedPack200FileFilter );
694             }
695 
696             if ( MapUtils.isNotEmpty( updateManifestEntries ) )
697             {
698                 updateManifestEntries( getLibDirectory() );
699             }
700 
701             int signedJars = signJars( getLibDirectory() );
702 
703             if ( signedJars != getModifiedJnlpArtifacts().size() )
704             {
705                 throw new IllegalStateException(
706                     "The number of signed artifacts (" + signedJars + ") differ from the number of modified " +
707                         "artifacts (" + getModifiedJnlpArtifacts().size() + "). Implementation error" );
708             }
709 
710         }
711         else
712         {
713             makeUnprocessedFilesFinal( getLibDirectory() );
714         }
715 
716         if ( isPack200() )
717         {
718             verboseLog( "-- Pack jars" );
719             pack200Jars( getLibDirectory(), processedJarFileFilter );
720         }
721     }
722 
723 
724     protected void pack200Jars( File directory, FileFilter filter )
725         throws MojoExecutionException
726     {
727         try
728         {
729             getPack200Tool().packJars( directory, filter, isGzip(), getPack200PassFiles() );
730         }
731         catch ( IOException e )
732         {
733             throw new MojoExecutionException( "Could not pack200 jars: ", e );
734         }
735     }
736 
737     protected URL findDefaultTemplateURL(JnlpFileType fileType)
738     {
739         return getClass().getClassLoader().getResource( fileType.getDefaultTemplateName() );
740     }
741 
742     /**
743      * @return something of the form jar:file:..../webstart-maven-plugin-.....jar!/
744      */
745     protected String getWebstartJarURLForVelocity()
746     {
747         String url = findDefaultTemplateURL(JnlpFileType.application).toString();
748         return url.substring( 0, url.indexOf( "!" ) + 2 );
749     }
750 
751     protected boolean isJarSigned( File jarFile )
752         throws MojoExecutionException
753     {
754 
755         return signTool.isJarSigned( jarFile );
756     }
757 
758     protected ArtifactUtil getArtifactUtil()
759     {
760         return artifactUtil;
761     }
762 
763     protected IOUtil getIoUtil()
764     {
765         return ioUtil;
766     }
767 
768     protected Pack200Tool getPack200Tool()
769     {
770         return pack200Tool;
771     }
772 
773     /**
774      * Log as info when verbose or info is enabled, as debug otherwise.
775      *
776      * @param msg the message to display
777      */
778     protected void verboseLog( String msg )
779     {
780         if ( isVerbose() )
781         {
782             getLog().info( msg );
783         }
784         else
785         {
786             getLog().debug( msg );
787         }
788     }
789 
790     // ----------------------------------------------------------------------
791     // Private Methods
792     // ----------------------------------------------------------------------
793 
794     private void unpackJars( File directory )
795         throws MojoExecutionException
796     {
797         getLog().info( "-- Unpack jars before sign operation " );
798 
799         verboseLog(
800             "see http://docs.oracle.com/javase/7/docs/technotes/guides/deployment/deployment-guide/pack200.html" );
801 
802         // pack
803         pack200Jars( directory, unprocessedJarFileFilter );
804 
805         // then unpack
806         try
807         {
808             getPack200Tool().unpackJars( directory, unprocessedPack200FileFilter );
809         }
810         catch ( IOException e )
811         {
812             throw new MojoExecutionException( "Could not unpack200 jars: ", e );
813         }
814     }
815 
816     private int makeUnprocessedFilesFinal( File directory )
817         throws MojoExecutionException
818     {
819         File[] jarFiles = directory.listFiles( unprocessedJarFileFilter );
820 
821         getLog().debug(
822             "makeUnprocessedFilesFinal in " + directory + " found " + jarFiles.length + " file(s) to rename" );
823 
824         if ( jarFiles.length == 0 )
825         {
826             return 0;
827         }
828 
829         for ( File unprocessedJarFile : jarFiles )
830         {
831 
832             File finalJar = toProcessFile( unprocessedJarFile );
833 
834             ioUtil.deleteFile( finalJar );
835 
836             ioUtil.renameTo( unprocessedJarFile, finalJar );
837         }
838         return jarFiles.length;
839     }
840 
841     /**
842      * @param directory location of directory where to update manifest entries jars
843      * @throws MojoExecutionException if can not update manifest entries jars
844      */
845     private void updateManifestEntries( File directory )
846         throws MojoExecutionException
847     {
848 
849         File[] jarFiles = directory.listFiles( unprocessedJarFileFilter );
850 
851         getLog().info( "-- Update manifest entries" );
852         getLog().debug( "updateManifestEntries in " + directory + " found " + jarFiles.length + " jar(s) to treat" );
853 
854         if ( jarFiles.length == 0 )
855         {
856             return;
857         }
858 
859         for ( File unprocessedJarFile : jarFiles )
860         {
861             verboseLog( "Update manifest " + toProcessFile( unprocessedJarFile ).getName() );
862 
863             jarUtil.updateManifestEntries( unprocessedJarFile, updateManifestEntries );
864         }
865     }
866 
867     /**
868      * @param directory location of directory where to sign jars
869      * @return the number of signed jars
870      * @throws MojoExecutionException if can not sign jars
871      */
872     private int signJars( File directory )
873         throws MojoExecutionException
874     {
875 
876         File[] jarFiles = directory.listFiles( unprocessedJarFileFilter );
877 
878         getLog().info( "-- Sign jars" );
879         getLog().debug( "signJars in " + directory + " found " + jarFiles.length + " jar(s) to sign" );
880 
881         if ( jarFiles.length == 0 )
882         {
883             return 0;
884         }
885 
886         boolean signVerify = sign.isVerify();
887 
888         for ( File unprocessedJarFile : jarFiles )
889         {
890 
891             File signedJar = toProcessFile( unprocessedJarFile );
892             ioUtil.deleteFile( signedJar );
893 
894             verboseLog( "Sign " + signedJar.getName() );
895             signTool.sign( sign, unprocessedJarFile, signedJar );
896 
897             getLog().debug( "lastModified signedJar:" + signedJar.lastModified() + " unprocessed signed Jar:" +
898                                 unprocessedJarFile.lastModified() );
899 
900             if ( signVerify )
901             {
902                 verboseLog( "Verify signature of " + signedJar.getName() );
903                 signTool.verify( sign, signedJar, isVerbose() );
904             }
905             // remove unprocessed files
906             // TODO wouldn't have to do that if we copied the unprocessed jar files in a temporary area
907             ioUtil.deleteFile( unprocessedJarFile );
908         }
909 
910         return jarFiles.length;
911     }
912 
913     /**
914      * Removes the signature of the files in the specified directory which satisfy the
915      * specified filter.
916      *
917      * @param workDirectory working directory used to unsign jars
918      * @return the number of unsigned jars
919      * @throws MojoExecutionException if could not remove signatures
920      */
921     private int removeExistingSignatures( File workDirectory )
922         throws MojoExecutionException
923     {
924         getLog().info( "-- Remove existing signatures" );
925 
926         // cleanup tempDir if exists
927         File tempDir = new File( workDirectory, "temp_extracted_jars" );
928         ioUtil.removeDirectory( tempDir );
929 
930         // recreate temp dir
931         ioUtil.makeDirectoryIfNecessary( tempDir );
932 
933         // process jars
934         File[] jarFiles = workDirectory.listFiles( unprocessedJarFileFilter );
935 
936         for ( File jarFile : jarFiles )
937         {
938             if ( isJarSigned( jarFile ) )
939             {
940                 if ( !canUnsign )
941                 {
942                     throw new MojoExecutionException(
943                         "neverUnsignAlreadySignedJar is set to true and a jar file [" + jarFile +
944                             " was asked to be unsign,\n please prefer use in this case an extension for " +
945                             "signed jars or not set to true the neverUnsignAlreadySignedJar parameter, Make " +
946                             "your choice:)" );
947                 }
948                 verboseLog( "Remove signature " + toProcessFile( jarFile ).getName() );
949 
950                 signTool.unsign( jarFile, isVerbose() );
951             }
952             else
953             {
954                 verboseLog( "Skip not signed " + toProcessFile( jarFile ).getName() );
955             }
956         }
957 
958         // cleanup tempDir
959         ioUtil.removeDirectory( tempDir );
960 
961         return jarFiles.length; // FIXME this is wrong. Not all jars are signed.
962     }
963 
964     private ClassLoader getCompileClassLoader()
965         throws MalformedURLException
966     {
967         URL[] urls = new URL[compileClassPath.size()];
968         for ( int i = 0; i < urls.length; i++ )
969         {
970             String spec = compileClassPath.get( i ).toString();
971             URL url = new File( spec ).toURI().toURL();
972             urls[i] = url;
973         }
974         return new URLClassLoader( urls );
975     }
976 
977     private File toUnprocessFile( File targetDirectory, String sourceName )
978     {
979         if ( sourceName.startsWith( UNPROCESSED_PREFIX ) )
980         {
981             throw new IllegalStateException( sourceName + " does start with " + UNPROCESSED_PREFIX );
982         }
983         String targetFilename = UNPROCESSED_PREFIX + sourceName;
984         return new File( targetDirectory, targetFilename );
985     }
986 
987     private File toProcessFile( File source )
988     {
989         if ( !source.getName().startsWith( UNPROCESSED_PREFIX ) )
990         {
991             throw new IllegalStateException( source.getName() + " does not start with " + UNPROCESSED_PREFIX );
992         }
993         String targetFilename = source.getName().substring( UNPROCESSED_PREFIX.length() );
994         return new File( source.getParentFile(), targetFilename );
995     }
996 
997 }