View Javadoc
1   package org.codehaus.mojo.appassembler;
2   
3   /*
4    * The MIT License
5    *
6    * Copyright (c) 2006-2012, The Codehaus
7    *
8    * Permission is hereby granted, free of charge, to any person obtaining a copy of
9    * this software and associated documentation files (the "Software"), to deal in
10   * the Software without restriction, including without limitation the rights to
11   * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12   * of the Software, and to permit persons to whom the Software is furnished to do
13   * so, subject to the following conditions:
14   *
15   * The above copyright notice and this permission notice shall be included in all
16   * copies or substantial portions of the Software.
17   *
18   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24   * SOFTWARE.
25   */
26  
27  import java.io.File;
28  import java.util.ArrayList;
29  import java.util.Arrays;
30  import java.util.Collections;
31  import java.util.HashSet;
32  import java.util.Iterator;
33  import java.util.List;
34  import java.util.Map;
35  import java.util.Set;
36  import java.util.StringTokenizer;
37  
38  import org.apache.maven.artifact.Artifact;
39  import org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout;
40  import org.apache.maven.plugin.MojoExecutionException;
41  import org.apache.maven.plugin.MojoFailureException;
42  import org.apache.maven.plugins.annotations.LifecyclePhase;
43  import org.apache.maven.plugins.annotations.Mojo;
44  import org.apache.maven.plugins.annotations.Parameter;
45  import org.apache.maven.plugins.annotations.ResolutionScope;
46  import org.codehaus.mojo.appassembler.daemon.DaemonGenerationRequest;
47  import org.codehaus.mojo.appassembler.daemon.DaemonGeneratorException;
48  import org.codehaus.mojo.appassembler.daemon.script.Platform;
49  import org.codehaus.mojo.appassembler.model.Classpath;
50  import org.codehaus.mojo.appassembler.model.Dependency;
51  import org.codehaus.mojo.appassembler.model.Directory;
52  import org.codehaus.mojo.appassembler.model.JvmSettings;
53  import org.codehaus.mojo.appassembler.util.DependencyFactory;
54  import org.codehaus.plexus.util.StringUtils;
55  
56  // @deprecated Use the generate-daemons goal instead
57  
58  /**
59   * Assembles the artifacts and generates bin scripts for the configured applications
60   *
61   * @author <a href="mailto:kristian.nordal@gmail.com">Kristian Nordal</a>
62   * @version $Id$
63   */
64  @Mojo( name = "assemble", defaultPhase = LifecyclePhase.PACKAGE, requiresDependencyResolution = ResolutionScope.RUNTIME, threadSafe = true )
65  public class AssembleMojo
66      extends AbstractScriptGeneratorMojo
67  {
68      // -----------------------------------------------------------------------
69      // Parameters
70      // -----------------------------------------------------------------------
71  
72      /**
73       * The directory that will be used to assemble the artifacts in and place the bin scripts.
74       */
75      @Parameter( property = "assembleDirectory", defaultValue = "${project.build.directory}/appassembler", required = true )
76      private File assembleDirectory;
77  
78      /**
79       * The file extensions to use for bin files. The file extensions are stored in a Map that uses the platform name as
80       * key. To change the file extension for Unix bin files to ".sh" use this configuration:
81       *
82       * <pre>
83       *          &lt;binFileExtensions&gt;
84       *            &lt;unix&gt;.sh&lt;/unix&gt;
85       *          &lt;/binFileExtensions&gt;
86       * </pre>
87       *
88       * @since 1.1
89       */
90      @Parameter
91      protected Map<String, String> binFileExtensions;
92  
93      /**
94       * Define the name of binary folder.
95       *
96       * @since 1.2
97       */
98      @Parameter( defaultValue = "bin" )
99      private String binFolder;
100 
101     /**
102      * Extra arguments that will be given to the JVM verbatim. If you define JvmSettings on the
103      * {@link Program#setJvmSettings(JvmSettings)} level this part will be overwritten by the given parameters on
104      * program level. Otherwise if {@link Program#setJvmSettings(JvmSettings)} is not given these settings will be used
105      * instead. This can be used to define some default values whereas by using the
106      * {@link Program#setJvmSettings(JvmSettings)} to overwrite the default settings. This is only valid for the
107      * extraJvmArguments not for the rest of the {@link JvmSettings#}. Since 1.2 it's possible to use place holders
108      * <code>@BASEDIR@</code> and <code>@REPO@</code> which will be expanded based on the platform for which the
109      * appropriate scripts will be generated.
110      */
111     @Parameter
112     private String extraJvmArguments;
113 
114     /**
115      * If the <code>configurationDirectory</code> (<code>etc</code> by default) should be included in the beginning of
116      * the classpath in the generated bin files.
117      */
118     @Parameter( defaultValue = "true" )
119     private boolean includeConfigurationDirectoryInClasspath;
120 
121     /**
122      * The default platforms the plugin will generate bin files for. Configure with string values - "all"(default/empty)
123      * | "windows" | "unix".
124      */
125     @Parameter
126     private Set<String> platforms;
127 
128     /**
129      * The set of Programs that bin files will be generated for.
130      */
131     @Parameter( required = true )
132     private Set<Program> programs;
133 
134     /**
135      * This can be used to put the project artifact as the first entry in the classpath after the configuration folder (
136      * <code>etc</code> by default). The default behavior is to have the project artifact at the last position in
137      * classpath.
138      *
139      * @since 1.2.1
140      */
141     @Parameter( defaultValue = "false" )
142     private boolean projectArtifactFirstInClassPath;
143 
144     /**
145      * Path (relative to <code>assembleDirectory</code>) of the desired output repository.
146      */
147     @Parameter( defaultValue = "repo" )
148     private String repositoryName;
149 
150     /**
151      * Show console window when execute this application. When false, the generated java command runs in background.
152      * This works best for Swing application where the command line invocation is not blocked.
153      */
154     @Parameter( defaultValue = "true" )
155     private boolean showConsoleWindow;
156 
157     /**
158      * The following can be used to use all project dependencies instead of the default behavior which represents
159      * <code>runtime</code> dependencies only.
160      *
161      * @since 1.2.3
162      */
163     @Parameter( defaultValue = "false" )
164     private boolean useAllProjectDependencies;
165 
166     // -----------------------------------------------------------------------
167     // Components
168     // -----------------------------------------------------------------------
169 
170     // ----------------------------------------------------------------------
171     // CONSTANTS
172     // ----------------------------------------------------------------------
173 
174     private static final Set<String> VALID_PLATFORMS =
175         Collections.unmodifiableSet( new HashSet<String>( Arrays.asList( new String[] { "unix", "windows" } ) ) );
176 
177     // ----------------------------------------------------------------------
178     // Validate
179     // ----------------------------------------------------------------------
180 
181     private void validate( Set<String> defaultPlatforms )
182         throws MojoFailureException, MojoExecutionException
183     {
184         // ----------------------------------------------------------------------
185         // Validate Programs
186         // ----------------------------------------------------------------------
187 
188         ArrayList<String> programNames = new ArrayList<String>();
189 
190         for ( Program program : programs )
191         {
192             if ( program.getName() != null )
193             {
194                 program.setId( program.getName() );
195                 getLog().warn( "The usage of program name (" + program.getName()
196                                    + ") is deprecated. Please use program.id instead." );
197             }
198 
199             if ( program.getMainClass() == null || program.getMainClass().trim().equals( "" ) )
200             {
201                 throw new MojoFailureException( "Missing main class in Program configuration" );
202             }
203 
204             // FIXME: After migration to Java 1.5 the following check could be
205             // done simpler!
206             if ( !programNames.contains( program.getId() ) )
207             {
208                 programNames.add( program.getId() );
209             }
210             else
211             {
212                 throw new MojoFailureException( "The program id: " + program.getId() + " exists more than once!" );
213             }
214 
215             // platforms
216             program.setPlatforms( validatePlatforms( program.getPlatforms(), defaultPlatforms ) );
217         }
218 
219     }
220 
221     // ----------------------------------------------------------------------
222     // Execute
223     // ----------------------------------------------------------------------
224 
225     public void checkDeprecatedParameterAndFailIfOneOfThemIsUsed()
226         throws MojoExecutionException
227     {
228     }
229 
230     /**
231      * calling from Maven.
232      *
233      * @see org.apache.maven.plugin.AbstractMojo#execute()
234      * @throws {@link MojoExecutionException}
235      * @throws {@link MojoFailureException}
236      */
237     public void execute()
238         throws MojoExecutionException, MojoFailureException
239     {
240         Set<String> defaultPlatforms = validatePlatforms( platforms, VALID_PLATFORMS );
241 
242         checkDeprecatedParameterAndFailIfOneOfThemIsUsed();
243 
244         // validate input and set defaults
245         validate( defaultPlatforms );
246 
247         if ( useWildcardClassPath && !repositoryLayout.equalsIgnoreCase( "flat" ) )
248         {
249             throw new MojoExecutionException( "useWildcardClassPath works only in "
250                 + "combination with repositoryLayout flat." );
251         }
252 
253         // Set the extensions for bin files for the different platforms
254         setBinFileExtensions();
255 
256         ArtifactRepositoryLayout artifactRepositoryLayout = getArtifactRepositoryLayout();
257 
258         if ( useAllProjectDependencies )
259         {
260             // TODO: This should be made different. We have to think about using
261             // a default ArtifactFilter
262             Set dependencyArtifacts = mavenProject.getDependencyArtifacts();
263             artifacts = new ArrayList<Artifact>();
264             for ( Iterator it = dependencyArtifacts.iterator(); it.hasNext(); )
265             {
266                 Artifact artifact = (Artifact) it.next();
267                 artifacts.add( artifact );
268             }
269         }
270 
271         // ----------------------------------------------------------------------
272         // Install dependencies in the new repository
273         // ----------------------------------------------------------------------
274         super.installDependencies( assembleDirectory.getAbsolutePath(), repositoryName );
275 
276         // ----------------------------------------------------------------------
277         // Setup
278         // ----------------------------------------------------------------------
279 
280         setUpWorkingArea();
281 
282         // ----------------------------------------------------------------------
283         // Create bin files
284         // ----------------------------------------------------------------------
285 
286         for ( Program program : programs )
287         {
288             if ( program.getName() != null )
289             {
290                 program.setId( program.getName() );
291             }
292 
293             Set<String> validatedPlatforms = validatePlatforms( program.getPlatforms(), defaultPlatforms );
294 
295             for ( String platform : validatedPlatforms )
296             {
297                 // TODO: seems like a bug in the generator that the request is
298                 // modified
299                 org.codehaus.mojo.appassembler.model.Daemon daemon =
300                     programToDaemon( program, artifactRepositoryLayout );
301                 DaemonGenerationRequest request =
302                     new DaemonGenerationRequest( daemon, mavenProject, localRepository, assembleDirectory, binFolder );
303                 request.setStubDaemon( request.getDaemon() );
304 
305                 request.setPlatform( platform );
306 
307                 try
308                 {
309                     daemonGeneratorService.generateDaemon( request );
310                 }
311                 catch ( DaemonGeneratorException e )
312                 {
313                     throw new MojoExecutionException( "Error while generating script for the program '"
314                         + program.getId() + "' for the platform '" + platform + "': " + e.getMessage(), e );
315                 }
316             }
317         }
318 
319         // ----------------------------------------------------------------------
320         // Copy configuration directory
321         // ----------------------------------------------------------------------
322 
323         if ( this.preAssembleDirectory != null && this.preAssembleDirectory.isDirectory() )
324         {
325             doCopyPreAssembleDirectory( assembleDirectory.getAbsolutePath() );
326         }
327 
328         if ( this.copyConfigurationDirectory && configurationSourceDirectory.isDirectory() )
329         {
330             doCopyConfigurationDirectory( assembleDirectory.getAbsolutePath() );
331         }
332 
333         // ----------------------------------------------------------------------
334         // Create logs and temp dirs if specified
335         // ----------------------------------------------------------------------
336 
337         doCreateExtraDirectories( assembleDirectory );
338     }
339 
340     private org.codehaus.mojo.appassembler.model.Daemon programToDaemon( Program program,
341                                                                          ArtifactRepositoryLayout artifactRepositoryLayout )
342     {
343         org.codehaus.mojo.appassembler.model.Daemon daemon = new org.codehaus.mojo.appassembler.model.Daemon();
344 
345         if ( program.getName() == null )
346         {
347             daemon.setId( program.getId() );
348         }
349         else
350         {
351             daemon.setId( program.getName() );
352         }
353         daemon.setMainClass( program.getMainClass() );
354         daemon.setShowConsoleWindow( showConsoleWindow );
355         daemon.setCommandLineArguments( program.getCommandLineArguments() );
356 
357         if ( program.getLicenseHeaderFile() != null )
358         {
359             getLog().debug( "Using the program specific license header. :" + program.getLicenseHeaderFile() );
360             daemon.setLicenseHeaderFile( program.getLicenseHeaderFile().getPath() );
361         }
362         else
363         {
364             getLog().debug( "Using the global defined license header. :" + licenseHeaderFile );
365 
366             if ( licenseHeaderFile != null )
367             {
368                 daemon.setLicenseHeaderFile( this.licenseHeaderFile.getAbsolutePath() );
369             }
370             else
371             {
372                 daemon.setLicenseHeaderFile( null );
373             }
374         }
375 
376         List<Directory> directories = new ArrayList<Directory>();
377 
378         if ( includeConfigurationDirectoryInClasspath )
379         {
380             Directory directory = new Directory();
381             directory.setRelativePath( configurationDirectory );
382             directories.add( directory );
383         }
384 
385         if ( daemon.getClasspath() == null )
386         {
387             daemon.setClasspath( new Classpath() );
388         }
389 
390         daemon.getClasspath().setDirectories( directories );
391 
392         daemon.setRepositoryName( repositoryName );
393 
394         daemon.setEndorsedDir( endorsedDir );
395 
396         List<Dependency> dependencies = new ArrayList<Dependency>();
397 
398         // TODO: This should be done in a more elegant way for 2.0
399         // TODO: Check if the classpath wildcard could be used for Daemons as well?
400 
401         if ( useWildcardClassPath )
402         {
403             Dependency dependency = new Dependency();
404             dependency.setGroupId( "" );
405             dependency.setArtifactId( "" );
406             dependency.setVersion( "" );
407             dependency.setRelativePath( "*" );
408             dependencies.add( dependency );
409         }
410         else
411         {
412             List<Artifact> classPathArtifacts = new ArrayList<Artifact>();
413 
414             if ( projectArtifactFirstInClassPath )
415             {
416                 classPathArtifacts.add( projectArtifact );
417                 classPathArtifacts.addAll( artifacts );
418             }
419             else
420             {
421                 classPathArtifacts.addAll( artifacts );
422                 classPathArtifacts.add( projectArtifact );
423             }
424 
425             for ( Artifact artifact : classPathArtifacts )
426             {
427                 dependencies.add( DependencyFactory.create( artifact, artifactRepositoryLayout,
428                                                             this.useTimestampInSnapshotFileName, outputFileNameMapping ) );
429             }
430 
431         }
432 
433         daemon.getClasspath().setDependencies( dependencies );
434 
435         daemon.setJvmSettings( convertToJvmSettingsWithDefaultHandling( program ) );
436 
437         daemon.setEnvironmentSetupFileName( this.environmentSetupFileName );
438 
439         if ( this.unixScriptTemplate != null )
440         {
441             daemon.setUnixScriptTemplate( unixScriptTemplate );
442         }
443         if ( this.windowsScriptTemplate != null )
444         {
445             daemon.setWindowsScriptTemplate( windowsScriptTemplate );
446         }
447 
448         return daemon;
449     }
450 
451     private JvmSettings convertToJvmSettingsWithDefaultHandling( Program program )
452     {
453         JvmSettings jvmSettings = new JvmSettings();
454 
455         if ( program.getJvmSettings() != null )
456         {
457             // Some kind of settings done on per program base so they take
458             // precendence.
459             jvmSettings = program.getJvmSettings();
460         }
461         else
462         {
463             // No settings in the program done so we use the default behaviour
464             if ( StringUtils.isNotBlank( this.extraJvmArguments ) )
465             {
466                 jvmSettings.setExtraArguments( parseTokens( this.extraJvmArguments ) );
467             }
468         }
469 
470         return jvmSettings;
471     }
472 
473     // ----------------------------------------------------------------------
474     // Set up the assemble environment
475     // ----------------------------------------------------------------------
476 
477     private void setUpWorkingArea()
478         throws MojoFailureException
479     {
480         // create (if necessary) directory for bin files
481         File binDir = new File( assembleDirectory.getAbsolutePath(), binFolder.toString() );
482 
483         if ( !binDir.exists() )
484         {
485 
486             boolean success = binDir.mkdirs();
487 
488             if ( !success )
489             {
490                 throw new MojoFailureException( "Failed to create directory for bin files." );
491             }
492         }
493     }
494 
495     private Set<String> validatePlatforms( Set<String> platformsToValidate, Set<String> defaultPlatforms )
496         throws MojoFailureException
497     {
498         if ( platformsToValidate == null )
499         {
500             return defaultPlatforms;
501         }
502 
503         if ( platformsToValidate.size() == 1 && platformsToValidate.iterator().next().equals( "all" ) )
504         {
505             return VALID_PLATFORMS;
506         }
507 
508         if ( !VALID_PLATFORMS.containsAll( platformsToValidate ) )
509         {
510             throw new MojoFailureException( "Non-valid default platform declared, supported types are: "
511                 + VALID_PLATFORMS );
512         }
513 
514         return platformsToValidate;
515     }
516 
517     /**
518      * This will tokenize the given argument or give the extraJvmArguments back if the given argument is empty.
519      *
520      * @param arg The argument to parse.
521      * @return List of arguments.
522      */
523     public static List<String> parseTokens( String arg )
524     {
525         List<String> extraJvmArguments = new ArrayList<String>();
526 
527         if ( StringUtils.isEmpty( arg ) )
528         {
529             return extraJvmArguments;
530         }
531 
532         StringTokenizer tokenizer = new StringTokenizer( arg );
533 
534         String argument = null;
535 
536         while ( tokenizer.hasMoreTokens() )
537         {
538             String token = tokenizer.nextToken();
539 
540             if ( argument != null )
541             {
542                 if ( token.length() == 0 )
543                 {
544                     // ignore it
545                     continue;
546                 }
547 
548                 int length = token.length();
549 
550                 if ( token.charAt( length - 1 ) == '\"' )
551                 {
552                     extraJvmArguments.add( argument + " " + token.substring( 0, length - 1 ) );
553                     argument = null;
554                 }
555                 else
556                 {
557                     argument += " " + token;
558                 }
559             }
560             else
561             {
562                 // If the token starts with a ", save it
563                 if ( token.charAt( 0 ) == '\"' )
564                 {
565                     argument = token.substring( 1 );
566                 }
567                 else
568                 {
569                     extraJvmArguments.add( token );
570                 }
571             }
572         }
573 
574         return extraJvmArguments;
575     }
576 
577     /**
578      * Set the extensions for bin files for the supported platforms. The values are taken from the Mojo's
579      * <code>binFileExtensions</code> parameter.
580      */
581     private void setBinFileExtensions()
582         throws MojoFailureException
583     {
584         if ( binFileExtensions != null )
585         {
586             for ( String platformName : binFileExtensions.keySet() )
587             {
588                 if ( !VALID_PLATFORMS.contains( platformName ) )
589                 {
590                     getLog().warn( "Bin file extension configured for a non-valid platform (" + platformName
591                                        + "), supported platforms are: " + VALID_PLATFORMS );
592                 }
593                 else
594                 {
595                     try
596                     {
597                         Platform platform = Platform.getInstance( platformName );
598                         platform.setBinFileExtension( binFileExtensions.get( platformName ) );
599                     }
600                     catch ( DaemonGeneratorException e )
601                     {
602                         getLog().warn( "Unable to set the bin file extension for " + platformName, e );
603                     }
604                 }
605             }
606         }
607     }
608 
609 }