View Javadoc
1   package org.apache.maven.plugin.rar;
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.maven.archiver.MavenArchiveConfiguration;
23  import org.apache.maven.archiver.MavenArchiver;
24  import org.apache.maven.artifact.Artifact;
25  import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter;
26  import org.apache.maven.execution.MavenSession;
27  import org.apache.maven.model.Resource;
28  import org.apache.maven.plugin.AbstractMojo;
29  import org.apache.maven.plugin.MojoExecutionException;
30  import org.apache.maven.plugins.annotations.Component;
31  import org.apache.maven.plugins.annotations.LifecyclePhase;
32  import org.apache.maven.plugins.annotations.Mojo;
33  import org.apache.maven.plugins.annotations.Parameter;
34  import org.apache.maven.plugins.annotations.ResolutionScope;
35  import org.apache.maven.project.MavenProject;
36  import org.apache.maven.project.MavenProjectHelper;
37  import org.apache.maven.shared.filtering.MavenFilteringException;
38  import org.apache.maven.shared.filtering.MavenResourcesExecution;
39  import org.apache.maven.shared.filtering.MavenResourcesFiltering;
40  import org.codehaus.plexus.archiver.Archiver;
41  import org.codehaus.plexus.archiver.jar.JarArchiver;
42  import org.codehaus.plexus.util.FileUtils;
43  
44  import java.io.File;
45  import java.io.IOException;
46  import java.util.ArrayList;
47  import java.util.Collections;
48  import java.util.LinkedHashSet;
49  import java.util.List;
50  import java.util.Set;
51  
52  /**
53   * Builds J2EE Resource Adapter Archive (RAR) files.
54   *
55   * @author <a href="stephane.nicoll@gmail.com">Stephane Nicoll</a>
56   * @version $Id: RarMojo.java 1617130 2014-08-10 15:58:34Z khmarbaise $
57   */
58  @Mojo( name = "rar", threadSafe = true, defaultPhase = LifecyclePhase.PACKAGE,
59         requiresDependencyResolution = ResolutionScope.TEST )
60  public class RarMojo
61      extends AbstractMojo
62  {
63      public static final String RA_XML_URI = "META-INF/ra.xml";
64  
65      /**
66       * Single directory for extra files to include in the RAR.
67       */
68      @Parameter( defaultValue = "${basedir}/src/main/rar", required = true )
69      private File rarSourceDirectory;
70  
71      /**
72       * The location of the ra.xml file to be used within the rar file.
73       */
74      @Parameter( defaultValue = "${basedir}/src/main/rar/META-INF/ra.xml" )
75      private File raXmlFile;
76  
77      /**
78       * Specify if the generated jar file of this project should be
79       * included in the rar file ; default is true.
80       */
81      @Parameter
82      private Boolean includeJar = Boolean.TRUE;
83  
84      /**
85       * The location of the manifest file to be used within the rar file.
86       */
87      @Parameter( defaultValue = "${basedir}/src/main/rar/META-INF/MANIFEST.MF" )
88      private File manifestFile;
89  
90      /**
91       * Directory that resources are copied to during the build.
92       */
93      @Parameter( defaultValue = "${project.build.directory}/${project.build.finalName}", required = true )
94      private String workDirectory;
95  
96      /**
97       * The directory for the generated RAR.
98       */
99      @Parameter( defaultValue = "${project.build.directory}", required = true )
100     private File outputDirectory;
101 
102     /**
103      * The name of the RAR file to generate.
104      */
105     @Parameter( alias = "rarName", defaultValue = "${project.build.finalName}", required = true )
106     private String finalName;
107 
108     /**
109      * Classifier to add to the artifact generated. If given, the artifact will be attached.
110      *
111      * If this is not given, it will merely be written to the output directory
112      * according to the finalName.
113      *
114      * @since 2.4
115      */
116     @Parameter( property = "maven.rar.classifier", defaultValue = "" )
117     private String classifier;
118 
119     /**
120      * The archive configuration to use.
121      * See <a href="http://maven.apache.org/shared/maven-archiver/index.html">Maven Archiver Reference</a>.
122      */
123     @Parameter
124     private MavenArchiveConfiguration archive = new MavenArchiveConfiguration();
125 
126     /**
127      * allow filtering of link{rarSourceDirectory}
128      *
129      * @since 2.3
130      */
131     @Parameter( property = "rar.filterRarSourceDirectory", defaultValue = "false" )
132     private boolean filterRarSourceDirectory;
133 
134     /**
135      * @since 2.3
136      */
137     @Parameter( defaultValue = "${session}", required = true, readonly = true )
138     protected MavenSession session;
139 
140     /**
141      * @since 2.3
142      */
143     @Parameter( property = "encoding", defaultValue = "${project.build.sourceEncoding}" )
144     protected String encoding;
145 
146     /**
147      * Whether to escape backslashes and colons in windows-style paths.
148      *
149      * @since 2.3
150      */
151     @Parameter( property = "maven.resources.escapeWindowsPaths", defaultValue = "true" )
152     protected boolean escapeWindowsPaths;
153 
154     /**
155      * Expression preceded with the String won't be interpolated
156      * \${foo} will be replaced with ${foo}
157      *
158      * @since 2.3
159      */
160     @Parameter( property = "maven.resources.escapeString" )
161     protected String escapeString;
162 
163     /**
164      * Overwrite existing files even if the destination files are newer.
165      *
166      * @since 2.3
167      */
168     @Parameter( property = "maven.resources.overwrite", defaultValue = "false" )
169     private boolean overwrite;
170 
171     /**
172      * Copy any empty directories included in the Resources.
173      *
174      * @since 2.3
175      */
176     @Parameter( property = "maven.resources.includeEmptyDirs", defaultValue = "false" )
177     protected boolean includeEmptyDirs;
178 
179     /**
180      * stop searching endToken at the end of line
181      *
182      * @since 2.3
183      */
184     @Parameter( property = "maven.resources.supportMultiLineFiltering", defaultValue = "false" )
185     private boolean supportMultiLineFiltering;
186 
187     /**
188      * @since 2.3
189      */
190     @Parameter( defaultValue = "true" )
191     protected boolean useDefaultDelimiters;
192 
193     /**
194      * <p>
195      * Set of delimiters for expressions to filter within the resources. These delimiters are specified in the
196      * form 'beginToken*endToken'. If no '*' is given, the delimiter is assumed to be the same for start and end.
197      * </p><p>
198      * So, the default filtering delimiters might be specified as:
199      * </p>
200      * <pre>
201      * &lt;delimiters&gt;
202      *   &lt;delimiter&gt;${*}&lt/delimiter&gt;
203      *   &lt;delimiter&gt;@&lt/delimiter&gt;
204      * &lt;/delimiters&gt;
205      * </pre>
206      * <p>
207      * Since the '@' delimiter is the same on both ends, we don't need to specify '@*@' (though we can).
208      * </p>
209      *
210      * @since 2.3
211      */
212     @Parameter
213     protected List<String> delimiters;
214 
215     /**
216      * The list of extra filter properties files to be used along with System properties,
217      * project properties, and filter properties files specified in the POM build/filters section,
218      * which should be used for the filtering during the current mojo execution.
219      * <br/>
220      * Normally, these will be configured from a plugin's execution section, to provide a different
221      * set of filters for a particular execution. For instance, starting in Maven 2.2.0, you have the
222      * option of configuring executions with the id's <code>default-resources</code> and
223      * <code>default-testResources</code> to supply different configurations for the two
224      * different types of resources. By supplying <code>extraFilters</code> configurations, you
225      * can separate which filters are used for which type of resource.
226      *
227      * @since 2.3
228      */
229     @Parameter
230     protected List<String> filters;
231 
232     /**
233      * Additional file extensions to not apply filtering (already defined are : jpg, jpeg, gif, bmp, png)
234      *
235      * @since 2.3
236      */
237     @Parameter
238     protected List<String> nonFilteredFileExtensions;
239 
240     /**
241      * extra resource to include in rar archive
242      *
243      * @since 2.3
244      */
245     @Parameter
246     protected List<RarResource> rarResources;
247 
248 
249     /**
250      * Whether or not warn if the <code>ra.xml</code> file is missing. Set to <code>false</code>
251      * if you want you RAR built without a <code>ra.xml</code> file.
252      * This may be useful if you are building against JCA 1.6 or later.
253      *
254      * @since 2.3
255      */
256     @Parameter( property = "warnOnMissingRaXml", defaultValue = "true" )
257     protected boolean warnOnMissingRaXml = true;
258 
259     /**
260      * To skip execution of the rar mojo.
261      *
262      * @since 2.4
263      */
264     @Parameter( property = "maven.rar.skip" )
265     private boolean skip;
266 
267     /**
268      * The maven project.
269      */
270     @Parameter( defaultValue = "${project}", readonly = true, required = true )
271     private MavenProject project;
272 
273     /**
274      * The Jar archiver.
275      */
276     @Component( role = Archiver.class, hint = "jar" )
277     private JarArchiver jarArchiver;
278 
279     /**
280      * @since 2.3
281      */
282     @Component( role = MavenResourcesFiltering.class, hint = "default" )
283     protected MavenResourcesFiltering mavenResourcesFiltering;
284 
285     /**
286      * @since 2.4
287      */
288     @Component
289     private MavenProjectHelper projectHelper;
290 
291     private File buildDir;
292 
293 
294     public void execute()
295         throws MojoExecutionException
296     {
297 
298         if ( skip )
299         {
300             getLog().info( "Skipping rar generation." );
301             return;
302         }
303 
304         getLog().debug( " ======= RarMojo settings =======" );
305         getLog().debug( "rarSourceDirectory[" + rarSourceDirectory + "]" );
306         getLog().debug( "manifestFile[" + manifestFile + "]" );
307         getLog().debug( "raXmlFile[" + raXmlFile + "]" );
308         getLog().debug( "workDirectory[" + workDirectory + "]" );
309         getLog().debug( "outputDirectory[" + outputDirectory + "]" );
310         getLog().debug( "finalName[" + finalName + "]" );
311         getLog().debug( "classifier[" + classifier + "]" );
312 
313         // Check if jar file is there and if requested, copy it
314         try
315         {
316             if (includeJar)
317             {
318                 File generatedJarFile = new File( outputDirectory, finalName + ".jar" );
319                 if ( generatedJarFile.exists() )
320                 {
321                     getLog().info( "Including generated jar file[" + generatedJarFile.getName() + "]" );
322                     FileUtils.copyFileToDirectory( generatedJarFile, getBuildDir() );
323                 }
324             }
325         }
326         catch ( IOException e )
327         {
328             throw new MojoExecutionException( "Error copying generated Jar file", e );
329         }
330 
331         // Copy dependencies
332         try
333         {
334             @SuppressWarnings("unchecked")
335             Set<Artifact> artifacts = project.getArtifacts();
336             for (Artifact artifact : artifacts) {
337 
338                 ScopeArtifactFilter filter = new ScopeArtifactFilter(Artifact.SCOPE_RUNTIME);
339                 if (!artifact.isOptional() && filter.include(artifact)
340                         && artifact.getArtifactHandler().isAddedToClasspath()) {
341                     getLog().info("Copying artifact[" + artifact.getGroupId() + ", " + artifact.getId() + ", "
342                             + artifact.getScope() + "]");
343                     FileUtils.copyFileToDirectory(artifact.getFile(), getBuildDir());
344                 }
345             }
346         }
347         catch ( IOException e )
348         {
349             throw new MojoExecutionException( "Error copying RAR dependencies", e );
350         }
351 
352         Resource resource = new Resource();
353         resource.setDirectory( rarSourceDirectory.getAbsolutePath() );
354         resource.setTargetPath( getBuildDir().getAbsolutePath() );
355         resource.setFiltering( filterRarSourceDirectory );
356 
357         List<Resource> resources = new ArrayList<Resource>();
358         resources.add( resource );
359 
360         if ( rarResources != null && !rarResources.isEmpty() )
361         {
362             resources.addAll( rarResources );
363         }
364 
365         MavenResourcesExecution mavenResourcesExecution =
366             new MavenResourcesExecution( resources, getBuildDir(), project, encoding, filters,
367                                          Collections.<String>emptyList(), session );
368 
369         mavenResourcesExecution.setEscapeWindowsPaths( escapeWindowsPaths );
370 
371         // never include project build filters in this call, since we've already accounted for the POM build filters
372         // above, in getCombinedFiltersList().
373         mavenResourcesExecution.setInjectProjectBuildFilters( false );
374 
375         mavenResourcesExecution.setEscapeString( escapeString );
376         mavenResourcesExecution.setOverwrite( overwrite );
377         mavenResourcesExecution.setIncludeEmptyDirs( includeEmptyDirs );
378         mavenResourcesExecution.setSupportMultiLineFiltering( supportMultiLineFiltering );
379 
380         // if these are NOT set, just use the defaults, which are '${*}' and '@'.
381         if ( delimiters != null && !delimiters.isEmpty() )
382         {
383             LinkedHashSet<String> delims = new LinkedHashSet<String>();
384             if ( useDefaultDelimiters )
385             {
386                 //noinspection unchecked
387                 delims.addAll( mavenResourcesExecution.getDelimiters() );
388             }
389 
390             for ( String delim : delimiters )
391             {
392                 if ( delim == null )
393                 {
394                     // FIXME: ${filter:*} could also trigger this condition. Need a better long-term solution.
395                     delims.add( "${*}" );
396                 }
397                 else
398                 {
399                     delims.add( delim );
400                 }
401             }
402 
403             mavenResourcesExecution.setDelimiters( delims );
404         }
405 
406         if ( nonFilteredFileExtensions != null )
407         {
408             mavenResourcesExecution.setNonFilteredFileExtensions( nonFilteredFileExtensions );
409         }
410         try
411         {
412             mavenResourcesFiltering.filterResources( mavenResourcesExecution );
413         }
414         catch ( MavenFilteringException e )
415         {
416             throw new MojoExecutionException( "Error copying RAR resources", e );
417         }
418 
419         // Include custom manifest if necessary
420         try
421         {
422             includeCustomRaXmlFile();
423         }
424         catch ( IOException e )
425         {
426             throw new MojoExecutionException( "Error copying ra.xml file", e );
427         }
428 
429         // Check if connector deployment descriptor is there
430         File ddFile = new File( getBuildDir(), RA_XML_URI );
431         if ( !ddFile.exists() && warnOnMissingRaXml )
432         {
433             getLog().warn( "Connector deployment descriptor: " + ddFile.getAbsolutePath() + " does not exist." );
434         }
435 
436         File rarFile = getRarFile(outputDirectory, finalName, classifier);
437         try
438         {
439             MavenArchiver archiver = new MavenArchiver();
440             archiver.setArchiver( jarArchiver );
441             archiver.setOutputFile( rarFile );
442 
443             // Include custom manifest if necessary
444             includeCustomManifestFile();
445 
446             archiver.getArchiver().addDirectory( getBuildDir() );
447             archiver.createArchive( session, project, archive );
448         }
449         catch ( Exception e )
450         {
451             throw new MojoExecutionException( "Error assembling RAR", e );
452         }
453 
454         if ( classifier != null )
455         {
456             projectHelper.attachArtifact( project, "rar", classifier, rarFile );
457         }
458         else
459         {
460             project.getArtifact().setFile( rarFile );
461         }
462     }
463 
464     protected File getBuildDir()
465     {
466         if ( buildDir == null )
467         {
468             buildDir = new File( workDirectory );
469         }
470         return buildDir;
471     }
472 
473     protected static File getRarFile( File basedir, String finalName, String classifier )
474     {
475         if ( classifier == null )
476         {
477             classifier = "";
478         }
479         else if ( classifier.trim().length() > 0 && !classifier.startsWith( "-" ) )
480         {
481             classifier = "-" + classifier;
482         }
483 
484         return new File( basedir, finalName + classifier + ".rar" );
485     }
486 
487     private void includeCustomManifestFile()
488         throws IOException
489     {
490         File customManifestFile = manifestFile;
491         if ( !customManifestFile.exists() )
492         {
493             getLog().info( "Could not find manifest file: " + manifestFile + " - Generating one" );
494         }
495         else
496         {
497             getLog().info( "Including custom manifest file[" + customManifestFile + "]" );
498             archive.setManifestFile( customManifestFile );
499             File metaInfDir = new File( getBuildDir(), "META-INF" );
500             FileUtils.copyFileToDirectory( customManifestFile, metaInfDir );
501         }
502     }
503 
504     private void includeCustomRaXmlFile()
505         throws IOException
506     {
507         if ( raXmlFile == null )
508         {
509             return;
510         }
511         File raXml = raXmlFile;
512         if ( raXml.exists() )
513         {
514             getLog().info( "Using ra.xml " + raXmlFile );
515             File metaInfDir = new File( getBuildDir(), "META-INF" );
516             FileUtils.copyFileToDirectory( raXml, metaInfDir );
517         }
518     }
519 }