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.CollectionUtils;
23  import org.apache.commons.lang.StringUtils;
24  import org.apache.maven.artifact.Artifact;
25  import org.apache.maven.artifact.resolver.filter.AndArtifactFilter;
26  import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
27  import org.apache.maven.artifact.resolver.filter.ExcludesArtifactFilter;
28  import org.apache.maven.artifact.resolver.filter.IncludesArtifactFilter;
29  import org.apache.maven.plugin.MojoExecutionException;
30  import org.apache.maven.plugins.annotations.Component;
31  import org.apache.maven.plugins.annotations.Parameter;
32  import org.apache.maven.project.MavenProjectHelper;
33  import org.codehaus.mojo.webstart.generator.ExtensionGenerator;
34  import org.codehaus.mojo.webstart.generator.ExtensionGeneratorConfig;
35  import org.codehaus.mojo.webstart.generator.Generator;
36  import org.codehaus.mojo.webstart.generator.GeneratorConfig;
37  import org.codehaus.mojo.webstart.generator.GeneratorTechnicalConfig;
38  import org.codehaus.mojo.webstart.util.IOUtil;
39  
40  import java.io.File;
41  import java.net.URL;
42  import java.util.ArrayList;
43  import java.util.Arrays;
44  import java.util.Collection;
45  import java.util.HashMap;
46  import java.util.List;
47  import java.util.Map;
48  
49  /**
50   * @author <a href="jerome@coffeebreaks.org">Jerome Lacoste</a>
51   * @version $Id$
52   *          TODO how to propagate the -X argument to enable verbose?
53   *          TODO initialize the jnlp alias and dname.o from pom.artifactId and pom.organization.name
54   */
55  public abstract class AbstractJnlpMojo
56      extends AbstractBaseJnlpMojo
57  {
58      // ----------------------------------------------------------------------
59      // Constants
60      // ----------------------------------------------------------------------
61  
62      /**
63       * Name of the built in jnlp template to use if none given.
64       */
65      private static final String BUILT_IN_JNLP_TEMPLATE_FILENAME = "default-jnlp-template.vm";
66  
67      /**
68       * Name of the default jnlp template to use if user define it in the default template directory.
69       */
70      private static final String JNLP_TEMPLATE_FILENAME = "template.vm";
71  
72      /**
73       * Name of the built in extension template to use if none is given.
74       */
75      private static final String BUILT_IN_EXTENSION_TEMPLATE_FILENAME = "default-jnlp-extension-template.vm";
76  
77      /**
78       * Name of the default jnlp extension template to use if user define it in the default template directory.
79       */
80      private static final String EXTENSION_TEMPLATE_FILENAME = "extension-template.vm";
81  
82      // ----------------------------------------------------------------------
83      // Mojo Parameters
84      // ----------------------------------------------------------------------
85  
86      /**
87       * Represents the configuration element that specifies which of the current
88       * project's dependencies will be included or excluded from the resources element
89       * in the generated JNLP file.
90       */
91      public static class Dependencies
92      {
93  
94          private List<String> includes;
95  
96          private List<String> excludes;
97  
98          public List<String> getIncludes()
99          {
100             return includes;
101         }
102 
103         public void setIncludes( List<String> includes )
104         {
105             this.includes = includes;
106         }
107 
108         public List<String> getExcludes()
109         {
110             return excludes;
111         }
112 
113         public void setExcludes( List<String> excludes )
114         {
115             this.excludes = excludes;
116         }
117     }
118 
119     /**
120      * Flag to create the archive or not.
121      *
122      * @since 1.0-beta-2
123      */
124     @Parameter( property = "jnlp.makeArchive", defaultValue = "true" )
125     private boolean makeArchive;
126 
127     /**
128      * Flag to attach the archive or not to the project's build.
129      *
130      * @since 1.0-beta-2
131      */
132     @Parameter( property = "jnlp.attachArchive", defaultValue = "true" )
133     private boolean attachArchive;
134 
135     /**
136      * The path of the archive to generate if {@link #makeArchive} flag is on.
137      *
138      * @since 1.0-beta-4
139      */
140     @Parameter( property = "jnlp.archive", defaultValue = "${project.build.directory}/${project.build.finalName}.zip" )
141     private File archive;
142 
143     /**
144      * The jnlp configuration element.
145      */
146     @Parameter
147     private JnlpConfig jnlp;
148 
149     /**
150      * [optional] extensions configuration.
151      *
152      * @since 1.0-beta-2
153      */
154     @Parameter
155     private List<JnlpExtension> jnlpExtensions;
156 
157     /**
158      * [optional] transitive dependencies filter - if omitted, the plugin will include all transitive dependencies.
159      * Provided and test scope dependencies are always excluded.
160      */
161     @Parameter
162     private Dependencies dependencies;
163 
164     /**
165      * A placeholder for an obsoleted configuration element.
166      * <p/>
167      * This dummy parameter is here to force the plugin configuration to fail in case one
168      * didn't properly migrate from 1.0-alpha-1 to 1.0-alpha-2 configuration.
169      * <p/>
170      * It will be removed before 1.0.
171      */
172     @Parameter
173     private String keystore;
174 
175     /**
176      */
177     @Parameter( defaultValue = "${basedir}", readonly = true, required = true )
178     private File basedir;
179 
180     /**
181      * When set to true, this flag indicates that a version attribute should
182      * be output in each of the jar resource elements in the generated
183      * JNLP file.
184      * <p/>
185      * <strong>Note: </strong> since version 1.0-beta-5 we use the version download protocol optimization (see
186      * http://docs.oracle.com/javase/tutorial/deployment/deploymentInDepth/avoidingUnnecessaryUpdateChecks.html).
187      */
188     @Parameter( property = "jnlp.outputJarVersions", defaultValue = "false" )
189     private boolean outputJarVersions;
190 
191     // ----------------------------------------------------------------------
192     // Components
193     // ----------------------------------------------------------------------
194 
195     /**
196      * The project helper used to attach the artifact produced by this plugin to the project.
197      */
198     @Component
199     private MavenProjectHelper projectHelper;
200 
201     // ----------------------------------------------------------------------
202     // Fields
203     // ----------------------------------------------------------------------
204 
205     /**
206      * the artifacts packaged in the webstart app
207      */
208     private List<Artifact> packagedJnlpArtifacts = new ArrayList<Artifact>();
209 
210     /**
211      * the artifacts associated to each jnlp extension
212      */
213     private Map<JnlpExtension, List<Artifact>> extensionsJnlpArtifacts = new HashMap<JnlpExtension, List<Artifact>>();
214 
215     private Artifact artifactWithMainClass;
216 
217     // ----------------------------------------------------------------------
218     // Mojo Implementation
219     // ----------------------------------------------------------------------
220 
221     /**
222      * {@inheritDoc}
223      */
224     public void execute()
225         throws MojoExecutionException
226     {
227 
228         boolean withExtensions = CollectionUtils.isNotEmpty( jnlpExtensions );
229 
230         if ( withExtensions )
231         {
232             prepareExtensions();
233             findDefaultJnlpExtensionTemplateURL();
234         }
235 
236         checkInput();
237 
238         getLog().debug( "using work directory " + getWorkDirectory() );
239         getLog().debug( "using library directory " + getLibDirectory() );
240 
241         IOUtil ioUtil = getIoUtil();
242 
243         // ---
244         // prepare layout
245         // ---
246 
247         ioUtil.makeDirectoryIfNecessary( getWorkDirectory() );
248         ioUtil.makeDirectoryIfNecessary( getLibDirectory() );
249 
250         try
251         {
252             ioUtil.copyResources( getResourcesDirectory(), getWorkDirectory() );
253 
254             artifactWithMainClass = null;
255 
256             processDependencies();
257 
258             if ( jnlp.isRequireMainClass() && artifactWithMainClass == null )
259             {
260                 throw new MojoExecutionException(
261                     "didn't find artifact with main class: " + jnlp.getMainClass() + ". Did you specify it? " );
262             }
263 
264             if ( withExtensions )
265             {
266                 processExtensionsDependencies();
267             }
268 
269             // ---
270             // Process native libs (FIXME)
271             // ---
272 
273             processNativeLibs();
274 
275             if ( ( isPack200() || getSign() != null ) && getLog().isDebugEnabled() )
276             {
277                 logCollection(
278                     "Some dependencies may be skipped. Here's the list of the artifacts that should be signed/packed: ",
279                     getModifiedJnlpArtifacts() );
280             }
281 
282             // ---
283             // Process collected jars
284             // ---
285 
286             signOrRenameJars();
287 
288             // ---
289             // Generate jnlp file
290             // ---
291 
292             generateJnlpFile( getWorkDirectory() );
293 
294             // ---
295             // Generate jnlp extension files
296             // ---
297 
298             if ( withExtensions )
299             {
300                 generateJnlpExtensionsFile( getWorkDirectory() );
301             }
302 
303             // ---
304             // Generate archive file if required
305             // ---
306 
307             if ( makeArchive )
308             {
309                 // package the zip. Note this is very simple. Look at the JarMojo which does more things.
310                 // we should perhaps package as a war when inside a project with war packaging ?
311 
312                 ioUtil.makeDirectoryIfNecessary( archive.getParentFile() );
313 
314                 ioUtil.deleteFile( archive );
315 
316                 verboseLog( "Will create archive at location: " + archive );
317 
318                 ioUtil.createArchive( getWorkDirectory(), archive );
319 
320                 if ( attachArchive )
321                 {
322                     // maven 2 version 2.0.1 method
323                     projectHelper.attachArtifact( getProject(), "zip", archive );
324                 }
325             }
326         }
327         catch ( MojoExecutionException e )
328         {
329             throw e;
330         }
331         catch ( Exception e )
332         {
333             throw new MojoExecutionException( "Failure to run the plugin: ", e );
334         }
335     }
336 
337     // ----------------------------------------------------------------------
338     // Protected Methods
339     // ----------------------------------------------------------------------
340 
341     protected JnlpConfig getJnlp()
342     {
343         return jnlp;
344     }
345 
346     protected Dependencies getDependencies()
347     {
348         return this.dependencies;
349     }
350 
351     // ----------------------------------------------------------------------
352     // Private Methods
353     // ----------------------------------------------------------------------
354 
355     void checkJnlpConfig()
356         throws MojoExecutionException
357     {
358         JnlpFileType type = jnlp.getType();
359         if ( type == null )
360         {
361             throw new MojoExecutionException( "jnlp must define a default jnlp type file to generate (among " +
362                                                   Arrays.toString( JnlpFileType.values() ) + " )." );
363         }
364         if ( !type.isRequireMainClass() && StringUtils.isNotBlank( jnlp.getMainClass() ) )
365         {
366             getLog().warn( "Jnlp file of type '" + type +
367                                "' does not support mainClass, value will not be accessible in template." );
368             jnlp.setMainClass( null );
369         }
370     }
371     /**
372      * Detects improper includes/excludes configuration.
373      *
374      * @throws MojoExecutionException if at least one of the specified includes or excludes matches no artifact,
375      *                                false otherwise
376      */
377     void checkDependencies()
378         throws MojoExecutionException
379     {
380         if ( dependencies == null )
381         {
382             return;
383         }
384 
385         boolean failed = false;
386 
387         Collection<Artifact> artifacts = getProject().getArtifacts();
388 
389         getLog().debug( "artifacts: " + artifacts.size() );
390 
391         if ( dependencies.getIncludes() != null && !dependencies.getIncludes().isEmpty() )
392         {
393             failed = checkDependencies( dependencies.getIncludes(), artifacts );
394         }
395         if ( dependencies.getExcludes() != null && !dependencies.getExcludes().isEmpty() )
396         {
397             failed = checkDependencies( dependencies.getExcludes(), artifacts ) || failed;
398         }
399 
400         if ( failed )
401         {
402             throw new MojoExecutionException(
403                 "At least one specified dependency is incorrect. Review your project configuration." );
404         }
405     }
406 
407     /**
408      * @param patterns  list of patterns to test over artifacts
409      * @param artifacts collection of artifacts to check
410      * @return true if at least one of the pattern in the list matches no artifact, false otherwise
411      */
412     private boolean checkDependencies( List<String> patterns, Collection<Artifact> artifacts )
413     {
414         if ( dependencies == null )
415         {
416             return false;
417         }
418 
419         boolean failed = false;
420         for ( String pattern : patterns )
421         {
422             failed = ensurePatternMatchesAtLeastOneArtifact( pattern, artifacts ) || failed;
423         }
424         return failed;
425     }
426 
427     /**
428      * @param pattern   pattern to test over artifacts
429      * @param artifacts collection of artifacts to check
430      * @return true if filter matches no artifact, false otherwise *
431      */
432     private boolean ensurePatternMatchesAtLeastOneArtifact( String pattern, Collection<Artifact> artifacts )
433     {
434         List<String> onePatternList = new ArrayList<String>();
435         onePatternList.add( pattern );
436         ArtifactFilter filter = new IncludesArtifactFilter( onePatternList );
437 
438         boolean noMatch = true;
439         for ( Artifact artifact : artifacts )
440         {
441             getLog().debug( "checking pattern: " + pattern + " against " + artifact );
442 
443             if ( filter.include( artifact ) )
444             {
445                 noMatch = false;
446                 break;
447             }
448         }
449         if ( noMatch )
450         {
451             getLog().error( "pattern: " + pattern + " doesn't match any artifact." );
452         }
453         return noMatch;
454     }
455 
456     /**
457      * Iterate through all the top level and transitive dependencies declared in the project and
458      * collect all the runtime scope dependencies for inclusion in the .zip and signing.
459      *
460      * @throws MojoExecutionException if could not process dependencies
461      */
462     private void processDependencies()
463         throws MojoExecutionException
464     {
465 
466         processDependency( getProject().getArtifact() );
467 
468         AndArtifactFilter filter = new AndArtifactFilter();
469         // filter.add( new ScopeArtifactFilter( dependencySet.getScope() ) );
470 
471         if ( dependencies != null && dependencies.getIncludes() != null && !dependencies.getIncludes().isEmpty() )
472         {
473             filter.add( new IncludesArtifactFilter( dependencies.getIncludes() ) );
474         }
475         if ( dependencies != null && dependencies.getExcludes() != null && !dependencies.getExcludes().isEmpty() )
476         {
477             filter.add( new ExcludesArtifactFilter( dependencies.getExcludes() ) );
478         }
479 
480         Collection<Artifact> artifacts =
481             isExcludeTransitive() ? getProject().getDependencyArtifacts() : getProject().getArtifacts();
482 
483         for ( Artifact artifact : artifacts )
484         {
485             if ( filter.include( artifact ) )
486             {
487                 processDependency( artifact );
488             }
489         }
490     }
491 
492     private void processDependency( Artifact artifact )
493         throws MojoExecutionException
494     {
495         // TODO: scope handler
496         // Include runtime and compile time libraries
497         if ( !Artifact.SCOPE_SYSTEM.equals( artifact.getScope() ) &&
498             !Artifact.SCOPE_PROVIDED.equals( artifact.getScope() ) &&
499             !Artifact.SCOPE_TEST.equals( artifact.getScope() ) )
500         {
501             String type = artifact.getType();
502             if ( "jar".equals( type ) || "ejb-client".equals( type ) )
503             {
504 
505                 // FIXME when signed, we should update the manifest.
506                 // see http://www.mail-archive.com/turbine-maven-dev@jakarta.apache.org/msg08081.html
507                 // and maven1: maven-plugins/jnlp/src/main/org/apache/maven/jnlp/UpdateManifest.java
508                 // or shouldn't we?  See MOJO-7 comment end of October.
509                 final File toCopy = artifact.getFile();
510 
511                 if ( toCopy == null )
512                 {
513                     getLog().error( "artifact with no file: " + artifact );
514                     getLog().error( "artifact download url: " + artifact.getDownloadUrl() );
515                     getLog().error( "artifact repository: " + artifact.getRepository() );
516                     getLog().error( "artifact repository: " + artifact.getVersion() );
517                     throw new IllegalStateException(
518                         "artifact " + artifact + " has no matching file, why? Check the logs..." );
519                 }
520 
521                 String name =
522                     getDependencyFilenameStrategy().getDependencyFilename( artifact, outputJarVersions, isUseUniqueVersions() );
523 
524                 boolean copied = copyJarAsUnprocessedToDirectoryIfNecessary( toCopy, getLibDirectory(), name );
525 
526                 if ( copied )
527                 {
528 
529                     getModifiedJnlpArtifacts().add( name.substring( 0, name.lastIndexOf( '.' ) ) );
530 
531                 }
532 
533                 packagedJnlpArtifacts.add( artifact );
534 
535                 if ( jnlp.isRequireMainClass() )
536                 {
537 
538                     // try to find if this dependency contains the main class
539                     boolean containsMainClass =
540                         getArtifactUtil().artifactContainsClass( artifact, jnlp.getMainClass() );
541 
542                     if ( containsMainClass )
543                     {
544                         if ( artifactWithMainClass == null )
545                         {
546                             artifactWithMainClass = artifact;
547                             getLog().debug(
548                                 "Found main jar. Artifact " + artifactWithMainClass + " contains the main class: " +
549                                     jnlp.getMainClass() );
550                         }
551                         else
552                         {
553                             getLog().warn(
554                                 "artifact " + artifact + " also contains the main class: " + jnlp.getMainClass() +
555                                     ". IGNORED." );
556                         }
557                     }
558                 }
559 
560             }
561             else
562             // FIXME how do we deal with native libs?
563             // we should probably identify them and package inside jars that we timestamp like the native lib
564             // to avoid repackaging every time. What are the types of the native libs?
565             {
566                 verboseLog( "Skipping artifact of type " + type + " for " + getLibDirectory().getName() );
567             }
568             // END COPY
569         }
570         else
571         {
572             verboseLog( "Skipping artifact of scope " + artifact.getScope() + " for " + getLibDirectory().getName() );
573         }
574     }
575 
576     private void generateJnlpFile( File outputDirectory )
577         throws MojoExecutionException
578     {
579         // ---
580         // get output file
581         // ---
582 
583         if ( StringUtils.isBlank( jnlp.getOutputFile() ) )
584         {
585             getLog().debug( "Jnlp output file name not specified. Using default output file name: launch.jnlp." );
586             jnlp.setOutputFile( "launch.jnlp" );
587         }
588         File jnlpOutputFile = new File( outputDirectory, jnlp.getOutputFile() );
589 
590         // ---
591         // get template directory
592         // ---
593 
594         File templateDirectory;
595 
596         if ( StringUtils.isNotBlank( jnlp.getInputTemplateResourcePath() ) )
597         {
598             templateDirectory = new File( jnlp.getInputTemplateResourcePath() );
599             getLog().debug( "Use jnlp directory : " + templateDirectory );
600         }
601         else
602         {
603             // use default template directory
604             templateDirectory = getTemplateDirectory();
605             getLog().debug( "Use default template directory : " + templateDirectory );
606         }
607 
608         // ---
609         // get template filename
610         // ---
611 
612         if ( StringUtils.isBlank( jnlp.getInputTemplate() ) )
613         {
614             getLog().debug( "Jnlp template file name not specified. Checking if default output file name exists: " +
615                                 JNLP_TEMPLATE_FILENAME );
616 
617             File templateFile = new File( templateDirectory, JNLP_TEMPLATE_FILENAME );
618 
619             if ( templateFile.isFile() )
620             {
621                 jnlp.setInputTemplate( JNLP_TEMPLATE_FILENAME );
622             }
623             else
624             {
625                 getLog().debug( "Jnlp template file not found in default location. Using inbuilt one." );
626             }
627         }
628         else
629         {
630             File templateFile = new File( templateDirectory, jnlp.getInputTemplate() );
631 
632             if ( !templateFile.isFile() )
633             {
634                 throw new MojoExecutionException(
635                     "The specified JNLP template does not exist: [" + templateFile + "]" );
636             }
637         }
638         String templateFileName = jnlp.getInputTemplate();
639 
640         GeneratorTechnicalConfig generatorTechnicalConfig =
641             new GeneratorTechnicalConfig( getProject(), templateDirectory, jnlp.getType().getDefaultTemplateName(),
642                                           jnlpOutputFile, templateFileName, jnlp.getMainClass(),
643                                           getWebstartJarURLForVelocity(), getEncoding() );
644 
645         GeneratorConfig generatorConfig =
646             new GeneratorConfig( getLibPath(), isPack200(), outputJarVersions, isUseUniqueVersions(), artifactWithMainClass,
647                                  getDependencyFilenameStrategy(), packagedJnlpArtifacts, jnlpExtensions, getCodebase(),
648                                  jnlp );
649 
650         Generator jnlpGenerator = new Generator( getLog(), generatorTechnicalConfig, generatorConfig );
651 
652         try
653         {
654             jnlpGenerator.generate();
655         }
656         catch ( Exception e )
657         {
658             getLog().debug( e.toString() );
659             throw new MojoExecutionException( "Could not generate the JNLP deployment descriptor", e );
660         }
661     }
662 
663     private void processNativeLibs()
664     {
665         /*
666             for( Iterator it = getNativeLibs().iterator(); it.hasNext(); ) {
667                 Artifact artifact = ;
668                 Artifact copiedArtifact =
669 
670                 // similar to what we do for jars, except that we must pack them into jar instead of copying.
671                 // them
672                     File nativeLib = artifact.getFile()
673                     if(! nativeLib.endsWith( ".jar" ) ){
674                         getLog().debug("Wrapping native library " + artifact + " into jar." );
675                         File nativeLibJar = new File( applicationFolder, xxx + ".jar");
676                         Jar jarTask = new Jar();
677                         jarTask.setDestFile( nativeLib );
678                         jarTask.setBasedir( basedir );
679                         jarTask.setIncludes( nativeLib );
680                         jarTask.execute();
681 
682                         nativeLibJar.setLastModified( nativeLib.lastModified() );
683 
684                         copiedArtifact = new ....
685                     } else {
686                         getLog().debug( "Copying native lib " + artifact );
687                         copyFileToDirectory( artifact.getFile(), applicationFolder );
688 
689                         copiedArtifact = artifact;
690                     }
691                     copiedNativeArtifacts.add( copiedArtifact );
692                 }
693             }
694             */
695     }
696 
697     private void logCollection( final String prefix, final Collection collection )
698     {
699         getLog().debug( prefix + " " + collection );
700         if ( collection == null )
701         {
702             return;
703         }
704         for ( Object aCollection : collection )
705         {
706             getLog().debug( prefix + aCollection );
707         }
708     }
709 
710     private void checkInput()
711         throws MojoExecutionException
712     {
713 
714         getLog().debug( "basedir " + this.basedir );
715         getLog().debug( "gzip " + isGzip() );
716         getLog().debug( "pack200 " + isPack200() );
717         getLog().debug( "project " + this.getProject() );
718         getLog().debug( "verbose " + isVerbose() );
719 
720         checkJnlpConfig();
721         checkDependencyFilenameStrategy();
722         checkDependencies();
723 
724         findDefaultTemplateURL(jnlp.getType());
725 
726         if ( jnlp != null && jnlp.getResources() != null )
727         {
728             throw new MojoExecutionException(
729                 "The <jnlp><resources> configuration element is obsolete. Use <resourcesDirectory> instead." );
730         }
731 
732         // FIXME
733         /*
734         if ( !"pom".equals( getProject().getPackaging() ) ) {
735            throw new MojoExecutionException( "'" + getProject().getPackaging() + "' packaging unsupported. Use 'pom'" );
736         }
737         */
738     }
739 
740     private void checkExtension( JnlpExtension extension )
741         throws MojoExecutionException
742     {
743         if ( StringUtils.isEmpty( extension.getName() ) )
744         {
745             throw new MojoExecutionException( "JnlpExtension name is mandatory. Review your project configuration." );
746         }
747         if ( StringUtils.isEmpty( extension.getVendor() ) )
748         {
749             throw new MojoExecutionException( "JnlpExtension vendor is mandatory. Review your project configuration." );
750         }
751         if ( StringUtils.isEmpty( extension.getTitle() ) )
752         {
753             throw new MojoExecutionException( "JnlpExtension name is title. Review your project configuration." );
754         }
755         if ( extension.getIncludes() == null || extension.getIncludes().isEmpty() )
756         {
757             throw new MojoExecutionException(
758                 "JnlpExtension need at least one include artifact. Review your project configuration." );
759         }
760     }
761 
762     protected URL findDefaultJnlpExtensionTemplateURL()
763     {
764         return getClass().getClassLoader().getResource( "default-jnlp-extension-template.vm" );
765     }
766 
767     /**
768      * Prepare extensions.
769      * <p/>
770      * Copy all includes of all extensions as to be excluded.
771      *
772      * @throws MojoExecutionException if could not prepare extensions
773      */
774     private void prepareExtensions()
775         throws MojoExecutionException
776     {
777         List<String> includes = new ArrayList<String>();
778         for ( JnlpExtension extension : jnlpExtensions )
779         {
780             // Check extensions (mandatory name, title and vendor and at least one include)
781 
782             checkExtension( extension );
783 
784             for ( String o : extension.getIncludes() )
785             {
786                 includes.add( o.trim() );
787             }
788 
789             if ( StringUtils.isEmpty( extension.getOutputFile() ) )
790             {
791                 String name = extension.getName() + ".jnlp";
792                 verboseLog(
793                     "Jnlp extension output file name not specified. Using default output file name: " + name + "." );
794                 extension.setOutputFile( name );
795             }
796         }
797         // copy all includes libs fro extensions to be exclude from the mojo
798         // treatments (extensions by nature are already signed)
799         if ( dependencies == null )
800         {
801             dependencies = new Dependencies();
802         }
803 
804         if ( dependencies.getExcludes() == null )
805         {
806             dependencies.setExcludes( new ArrayList<String>() );
807         }
808 
809         dependencies.getExcludes().addAll( includes );
810     }
811 
812     /**
813      * Iterate through all the extensions dependencies declared in the project and
814      * collect all the runtime scope dependencies for inclusion in the .zip and just
815      * copy them to the lib directory.
816      * <p/>
817      * TODO, should check that all dependencies are well signed with the same
818      * extension with the same signer.
819      *
820      * @throws MojoExecutionException
821      */
822     private void processExtensionsDependencies()
823         throws MojoExecutionException
824     {
825 
826         Collection<Artifact> artifacts =
827             isExcludeTransitive() ? getProject().getDependencyArtifacts() : getProject().getArtifacts();
828 
829         for ( JnlpExtension extension : jnlpExtensions )
830         {
831             ArtifactFilter filter = new IncludesArtifactFilter( extension.getIncludes() );
832 
833             for ( Artifact artifact : artifacts )
834             {
835                 if ( filter.include( artifact ) )
836                 {
837                     processExtensionDependency( extension, artifact );
838                 }
839             }
840         }
841     }
842 
843     private void processExtensionDependency( JnlpExtension extension, Artifact artifact )
844         throws MojoExecutionException
845     {
846         // TODO: scope handler
847         // Include runtime and compile time libraries
848         if ( !Artifact.SCOPE_SYSTEM.equals( artifact.getScope() ) &&
849             !Artifact.SCOPE_PROVIDED.equals( artifact.getScope() ) &&
850             !Artifact.SCOPE_TEST.equals( artifact.getScope() ) )
851         {
852             String type = artifact.getType();
853             if ( "jar".equals( type ) || "ejb-client".equals( type ) )
854             {
855 
856                 // FIXME when signed, we should update the manifest.
857                 // see http://www.mail-archive.com/turbine-maven-dev@jakarta.apache.org/msg08081.html
858                 // and maven1: maven-plugins/jnlp/src/main/org/apache/maven/jnlp/UpdateManifest.java
859                 // or shouldn't we?  See MOJO-7 comment end of October.
860                 final File toCopy = artifact.getFile();
861 
862                 if ( toCopy == null )
863                 {
864                     getLog().error( "artifact with no file: " + artifact );
865                     getLog().error( "artifact download url: " + artifact.getDownloadUrl() );
866                     getLog().error( "artifact repository: " + artifact.getRepository() );
867                     getLog().error( "artifact repository: " + artifact.getVersion() );
868                     throw new IllegalStateException(
869                         "artifact " + artifact + " has no matching file, why? Check the logs..." );
870                 }
871 
872                 // check jar is signed
873                 boolean jarSigned = isJarSigned( toCopy );
874                 if ( !jarSigned )
875                 {
876                     throw new IllegalStateException(
877                         "artifact " + artifact + " must be signed as part of an extension.." );
878                 }
879 
880                 String targetFilename =
881                     getDependencyFilenameStrategy().getDependencyFilename( artifact, outputJarVersions, isUseUniqueVersions() );
882 
883                 File targetFile = new File( getLibDirectory(), targetFilename );
884                 boolean copied = getIoUtil().shouldCopyFile( toCopy, targetFile );
885 
886                 if ( copied )
887                 {
888                     getIoUtil().copyFile( toCopy, targetFile );
889                     verboseLog( "copy extension artifact " + toCopy );
890                 }
891                 else
892                 {
893                     verboseLog( "already up to date artifact " + toCopy );
894                 }
895 
896                 // save the artifact dependency for the extension
897 
898                 List<Artifact> deps = extensionsJnlpArtifacts.get( extension );
899                 if ( deps == null )
900                 {
901                     deps = new ArrayList<Artifact>();
902                     extensionsJnlpArtifacts.put( extension, deps );
903                 }
904                 deps.add( artifact );
905             }
906             else
907             // FIXME how do we deal with native libs?
908             // we should probably identify them and package inside jars that we timestamp like the native lib
909             // to avoid repackaging every time. What are the types of the native libs?
910             {
911                 verboseLog( "Skipping artifact of type " + type + " for " + getLibDirectory().getName() );
912             }
913             // END COPY
914         }
915         else
916         {
917             verboseLog( "Skipping artifact of scope " + artifact.getScope() + " for " + getLibDirectory().getName() );
918         }
919     }
920 
921     private void generateJnlpExtensionsFile( File outputDirectory )
922         throws MojoExecutionException
923     {
924         for ( JnlpExtension jnlpExtension : jnlpExtensions )
925         {
926             generateJnlpExtensionFile( outputDirectory, jnlpExtension );
927         }
928     }
929 
930     private void generateJnlpExtensionFile( File outputDirectory, JnlpExtension extension )
931         throws MojoExecutionException
932     {
933 
934         // ---
935         // get output file
936         // ---
937 
938         File jnlpOutputFile = new File( outputDirectory, extension.getOutputFile() );
939 
940         // ---
941         // get template directory
942         // ---
943 
944         File templateDirectory;
945 
946         if ( StringUtils.isNotBlank( extension.getInputTemplateResourcePath() ) )
947         {
948             // if user overrides the input template resource path
949             templateDirectory = new File( extension.getInputTemplateResourcePath() );
950         }
951         else
952         {
953 
954             // use default template directory
955             templateDirectory = getTemplateDirectory();
956             getLog().debug( "Use default jnlp directory : " + templateDirectory );
957         }
958 
959         // ---
960         // get template filename
961         // ---
962 
963         if ( StringUtils.isBlank( extension.getInputTemplate() ) )
964         {
965             getLog().debug(
966                 "Jnlp extension template file name not specified. Checking if default output file name exists: " +
967                     EXTENSION_TEMPLATE_FILENAME );
968 
969             File templateFile = new File( templateDirectory, EXTENSION_TEMPLATE_FILENAME );
970 
971             if ( templateFile.isFile() )
972             {
973                 extension.setInputTemplate( EXTENSION_TEMPLATE_FILENAME );
974             }
975             else
976             {
977                 getLog().debug( "Jnlp extension template file not found in default location. Using inbuilt one." );
978             }
979         }
980         else
981         {
982             File templateFile = new File( templateDirectory, extension.getInputTemplate() );
983 
984             if ( !templateFile.isFile() )
985             {
986                 throw new MojoExecutionException(
987                     "The specified JNLP extension template does not exist: [" + templateFile + "]" );
988             }
989         }
990         String templateFileName = extension.getInputTemplate();
991 
992         GeneratorTechnicalConfig generatorTechnicalConfig =
993             new GeneratorTechnicalConfig( getProject(), templateDirectory, BUILT_IN_EXTENSION_TEMPLATE_FILENAME,
994                                           jnlpOutputFile, templateFileName, getJnlp().getMainClass(),
995                                           getWebstartJarURLForVelocity(), getEncoding() );
996 
997         ExtensionGeneratorConfig extensionGeneratorConfig =
998             new ExtensionGeneratorConfig( getLibPath(), isPack200(), outputJarVersions, isUseUniqueVersions(),
999                                           artifactWithMainClass, getDependencyFilenameStrategy(),
1000                                           extensionsJnlpArtifacts, getCodebase(), extension );
1001         ExtensionGenerator jnlpGenerator =
1002             new ExtensionGenerator( getLog(), generatorTechnicalConfig, extensionGeneratorConfig );
1003 
1004 //        jnlpGenerator.setExtraConfig( new ExtensionGeneratorExtraConfig( extension, getCodebase() ) );
1005 
1006         try
1007         {
1008             jnlpGenerator.generate();
1009         }
1010         catch ( Exception e )
1011         {
1012             getLog().debug( e.toString() );
1013             throw new MojoExecutionException( "Could not generate the JNLP deployment descriptor", e );
1014         }
1015     }
1016 
1017 }
1018