View Javadoc
1   package org.apache.maven.plugins.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$
57   */
58  @Mojo( name = "rar", threadSafe = true, defaultPhase = LifecyclePhase.PACKAGE,
59         requiresDependencyResolution = ResolutionScope.TEST )
60  public class RarMojo
61      extends AbstractMojo
62  {
63      private 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( defaultValue = "true" )
82      private Boolean includeJar;
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( defaultValue = "${project.build.finalName}", required = true, readonly = 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 = "maven.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 LinkedHashSet<String> delimiters;
214 
215     /**
216      * <p>
217      * The list of extra filter properties files to be used along with System properties,
218      * project properties, and filter properties files specified in the POM build/filters section,
219      * which should be used for the filtering during the current mojo execution.
220      * </p>
221      * <p>
222      * Normally, these will be configured from a plugin's execution section, to provide a different
223      * set of filters for a particular execution. For instance, starting in Maven 2.2.0, you have the
224      * option of configuring executions with the id's <code>default-resources</code> and
225      * <code>default-testResources</code> to supply different configurations for the two
226      * different types of resources. By supplying <code>extraFilters</code> configurations, you
227      * can separate which filters are used for which type of resource.
228      * </p>
229      *
230      * @since 2.3
231      */
232     @Parameter
233     protected List<String> filters;
234 
235     /**
236      * Additional file extensions to not apply filtering (already defined are : jpg, jpeg, gif, bmp, png)
237      *
238      * @since 2.3
239      */
240     @Parameter
241     protected List<String> nonFilteredFileExtensions;
242 
243     /**
244      * extra resource to include in rar archive
245      *
246      * @since 2.3
247      */
248     @Parameter
249     protected List<RarResource> rarResources;
250 
251 
252     /**
253      * Whether or not warn if the <code>ra.xml</code> file is missing. Set to <code>false</code>
254      * if you want you RAR built without a <code>ra.xml</code> file.
255      * This may be useful if you are building against JCA 1.6 or later.
256      *
257      * @since 2.3
258      */
259     @Parameter( property = "maven.rar.warnOnMissingRaXml", defaultValue = "true" )
260     protected boolean warnOnMissingRaXml;
261 
262     /**
263      * To skip execution of the rar mojo.
264      *
265      * @since 2.4
266      */
267     @Parameter( property = "maven.rar.skip" )
268     private boolean skip;
269 
270     /**
271      * The maven project.
272      */
273     @Parameter( defaultValue = "${project}", readonly = true, required = true )
274     private MavenProject project;
275 
276     /**
277      * Timestamp for reproducible output archive entries, either formatted as ISO 8601
278      * <code>yyyy-MM-dd'T'HH:mm:ssXXX</code> or as an int representing seconds since the epoch (like
279      * <a href="https://reproducible-builds.org/docs/source-date-epoch/">SOURCE_DATE_EPOCH</a>).
280      *
281      * @since 3.0.0
282      */
283     @Parameter( defaultValue = "${project.build.outputTimestamp}" )
284     private String outputTimestamp;
285 
286     /**
287      * The Jar archiver.
288      */
289     @Component( role = Archiver.class, hint = "jar" )
290     private JarArchiver jarArchiver;
291 
292     /**
293      * @since 2.3
294      */
295     @Component( role = MavenResourcesFiltering.class, hint = "default" )
296     protected MavenResourcesFiltering mavenResourcesFiltering;
297 
298     /**
299      * @since 2.4
300      */
301     @Component
302     private MavenProjectHelper projectHelper;
303 
304     private File buildDir;
305 
306 
307     /** {@inheritDoc} */
308     public void execute()
309         throws MojoExecutionException
310     {
311 
312         if ( skip )
313         {
314             getLog().info( "Skipping rar generation." );
315             return;
316         }
317 
318         // Check if jar file is there and if requested, copy it
319         try
320         {
321             if ( includeJar )
322             {
323                 File generatedJarFile = new File( outputDirectory, finalName + ".jar" );
324                 if ( generatedJarFile.exists() )
325                 {
326                     getLog().info( "Including generated jar file[" + generatedJarFile.getName() + "]" );
327                     FileUtils.copyFileToDirectory( generatedJarFile, getBuildDir() );
328                 }
329             }
330         }
331         catch ( IOException e )
332         {
333             throw new MojoExecutionException( "Error copying generated Jar file", e );
334         }
335 
336         // Copy dependencies
337         try
338         {
339             Set<Artifact> artifacts = project.getArtifacts();
340             for ( Artifact artifact : artifacts )
341             {
342 
343                 ScopeArtifactFilter filter = new ScopeArtifactFilter( Artifact.SCOPE_RUNTIME );
344                 if ( !artifact.isOptional() && filter.include( artifact )
345                     && artifact.getArtifactHandler().isAddedToClasspath() )
346                 {
347                     getLog().info( "Copying artifact[" + artifact.getGroupId() + ", " + artifact.getId() + ", "
348                                        + artifact.getScope() + "]" );
349                     FileUtils.copyFileToDirectory( artifact.getFile(), getBuildDir() );
350                 }
351             }
352         }
353         catch ( IOException e )
354         {
355             throw new MojoExecutionException( "Error copying RAR dependencies", e );
356         }
357 
358         resourceHandling();
359 
360         // Include custom manifest if necessary
361         try
362         {
363             includeCustomRaXmlFile();
364         }
365         catch ( IOException e )
366         {
367             throw new MojoExecutionException( "Error copying ra.xml file", e );
368         }
369 
370         // Check if connector deployment descriptor is there
371         File ddFile = new File( getBuildDir(), RA_XML_URI );
372         if ( !ddFile.exists() && warnOnMissingRaXml )
373         {
374             getLog().warn( "Connector deployment descriptor: " + ddFile.getAbsolutePath() + " does not exist." );
375         }
376 
377         File rarFile = getRarFile( outputDirectory, finalName, classifier );
378         try
379         {
380             MavenArchiver archiver = new MavenArchiver();
381             archiver.setArchiver( jarArchiver );
382             archiver.setCreatedBy( "Maven RAR Plugin", "org.apache.maven.plugins", "maven-rar-plugin" );
383             archiver.setOutputFile( rarFile );
384 
385             // configure for Reproducible Builds based on outputTimestamp value
386             archiver.configureReproducible( outputTimestamp );
387 
388             // Include custom manifest if necessary
389             includeCustomManifestFile();
390 
391             archiver.getArchiver().addDirectory( getBuildDir() );
392             archiver.createArchive( session, project, archive );
393         }
394         catch ( Exception e )
395         {
396             throw new MojoExecutionException( "Error assembling RAR", e );
397         }
398 
399         if ( classifier != null )
400         {
401             projectHelper.attachArtifact( project, "rar", classifier, rarFile );
402         }
403         else
404         {
405             project.getArtifact().setFile( rarFile );
406         }
407     }
408 
409     private void resourceHandling()
410         throws MojoExecutionException
411     {
412         Resource resource = new Resource();
413         resource.setDirectory( rarSourceDirectory.getAbsolutePath() );
414         resource.setTargetPath( getBuildDir().getAbsolutePath() );
415         resource.setFiltering( filterRarSourceDirectory );
416 
417         List<Resource> resources = new ArrayList<>();
418         resources.add( resource );
419 
420         if ( rarResources != null && !rarResources.isEmpty() )
421         {
422             resources.addAll( rarResources );
423         }
424 
425         MavenResourcesExecution mavenResourcesExecution =
426             new MavenResourcesExecution( resources, getBuildDir(), project, encoding, filters,
427                                          Collections.<String>emptyList(), session );
428 
429         mavenResourcesExecution.setEscapeWindowsPaths( escapeWindowsPaths );
430 
431         // never include project build filters in this call, since we've already accounted for the POM build filters
432         // above, in getCombinedFiltersList().
433         mavenResourcesExecution.setInjectProjectBuildFilters( false );
434 
435         mavenResourcesExecution.setEscapeString( escapeString );
436         mavenResourcesExecution.setOverwrite( overwrite );
437         mavenResourcesExecution.setIncludeEmptyDirs( includeEmptyDirs );
438         mavenResourcesExecution.setSupportMultiLineFiltering( supportMultiLineFiltering );
439         mavenResourcesExecution.setDelimiters( delimiters, useDefaultDelimiters );
440 
441         if ( nonFilteredFileExtensions != null )
442         {
443             mavenResourcesExecution.setNonFilteredFileExtensions( nonFilteredFileExtensions );
444         }
445         try
446         {
447             mavenResourcesFiltering.filterResources( mavenResourcesExecution );
448         }
449         catch ( MavenFilteringException e )
450         {
451             throw new MojoExecutionException( "Error copying RAR resources", e );
452         }
453     }
454 
455     /**
456      * @return The buildDir.
457      */
458     protected File getBuildDir()
459     {
460         if ( buildDir == null )
461         {
462             buildDir = new File( workDirectory );
463         }
464         return buildDir;
465     }
466 
467     /**
468      * @param basedir The basedir.
469      * @param finalName The finalName.
470      * @param classifier The classifier.
471      * @return the resulting file which contains classifier.
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 }