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