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.resources;
20  
21  import java.io.File;
22  import java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.Collections;
25  import java.util.LinkedHashSet;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Properties;
29  
30  import org.apache.commons.lang3.StringUtils;
31  import org.apache.maven.execution.MavenSession;
32  import org.apache.maven.model.Resource;
33  import org.apache.maven.plugin.AbstractMojo;
34  import org.apache.maven.plugin.MojoExecutionException;
35  import org.apache.maven.plugins.annotations.Component;
36  import org.apache.maven.plugins.annotations.LifecyclePhase;
37  import org.apache.maven.plugins.annotations.Mojo;
38  import org.apache.maven.plugins.annotations.Parameter;
39  import org.apache.maven.project.MavenProject;
40  import org.apache.maven.shared.filtering.MavenFilteringException;
41  import org.apache.maven.shared.filtering.MavenResourcesExecution;
42  import org.apache.maven.shared.filtering.MavenResourcesFiltering;
43  
44  /**
45   * Copy resources for the main source code to the main output directory. Always uses the project.build.resources element
46   * to specify the resources to copy.
47   *
48   * @author <a href="michal.maczka@dimatics.com">Michal Maczka</a>
49   * @author <a href="mailto:jason@maven.org">Jason van Zyl</a>
50   * @author Andreas Hoheneder
51   * @author William Ferguson
52   */
53  @Mojo(name = "resources", defaultPhase = LifecyclePhase.PROCESS_RESOURCES, requiresProject = true, threadSafe = true)
54  public class ResourcesMojo extends AbstractMojo {
55  
56      /**
57       * The character encoding to use when reading and writing filtered resources.
58       */
59      @Parameter(defaultValue = "${project.build.sourceEncoding}")
60      protected String encoding;
61  
62      /**
63       * The character encoding to use when reading and writing filtered properties files.
64       * If not specified, it will default to the value of the "encoding" parameter.
65       *
66       * @since 3.2.0
67       */
68      @Parameter
69      protected String propertiesEncoding;
70  
71      /**
72       * The output directory into which to copy the resources.
73       */
74      @Parameter(defaultValue = "${project.build.outputDirectory}", required = true)
75      private File outputDirectory;
76  
77      /**
78       * The list of resources we want to transfer.
79       */
80      @Parameter(defaultValue = "${project.resources}", required = true, readonly = true)
81      private List<Resource> resources;
82  
83      /**
84       *
85       */
86      @Parameter(defaultValue = "${project}", readonly = true, required = true)
87      protected MavenProject project;
88  
89      /**
90       * The list of additional filter properties files to be used along with System and project properties, which would
91       * be used for the filtering.
92       *
93       * @see ResourcesMojo#filters
94       * @since 2.4
95       */
96      @Parameter(defaultValue = "${project.build.filters}", readonly = true)
97      protected List<String> buildFilters;
98  
99      /**
100      * <p>
101      * The list of extra filter properties files to be used along with System properties, project properties, and filter
102      * properties files specified in the POM build/filters section, which should be used for the filtering during the
103      * current mojo execution.</p>
104      * <p>
105      * Normally, these will be configured from a plugin's execution section, to provide a different set of filters for a
106      * particular execution. For instance, starting in Maven 2.2.0, you have the option of configuring executions with
107      * the id's <code>default-resources</code> and <code>default-testResources</code> to supply different configurations
108      * for the two different types of resources. By supplying <code>extraFilters</code> configurations, you can separate
109      * which filters are used for which type of resource.</p>
110      */
111     @Parameter
112     protected List<String> filters;
113 
114     /**
115      * If false, don't use the filters specified in the build/filters section of the POM when processing resources in
116      * this mojo execution.
117      *
118      * @see ResourcesMojo#buildFilters
119      * @see ResourcesMojo#filters
120      * @since 2.4
121      */
122     @Parameter(defaultValue = "true")
123     protected boolean useBuildFilters;
124 
125     /**
126      *
127      */
128     @Component(role = MavenResourcesFiltering.class, hint = "default")
129     protected MavenResourcesFiltering mavenResourcesFiltering;
130 
131     /**
132      *
133      */
134     @Component(role = MavenResourcesFiltering.class)
135     protected Map<String, MavenResourcesFiltering> mavenResourcesFilteringMap;
136 
137     /**
138      *
139      */
140     @Parameter(defaultValue = "${session}", readonly = true, required = true)
141     protected MavenSession session;
142 
143     /**
144      * Expressions preceded with this string won't be interpolated. Anything else preceded with this string will be
145      * passed through unchanged. For example {@code \${foo}} will be replaced with {@code ${foo}} but {@code \\${foo}}
146      * will be replaced with {@code \\value of foo}, if this parameter has been set to the backslash.
147      *
148      * @since 2.3
149      */
150     @Parameter
151     protected String escapeString;
152 
153     /**
154      * Overwrite existing files even if the destination files are newer.
155      *
156      * @since 2.3
157      */
158     @Parameter(defaultValue = "false")
159     private boolean overwrite;
160 
161     /**
162      * Copy any empty directories included in the Resources.
163      *
164      * @since 2.3
165      */
166     @Parameter(defaultValue = "false")
167     protected boolean includeEmptyDirs;
168 
169     /**
170      * Additional file extensions to not apply filtering (already defined are : jpg, jpeg, gif, bmp, png)
171      *
172      * @since 2.3
173      */
174     @Parameter
175     protected List<String> nonFilteredFileExtensions;
176 
177     /**
178      * Whether to escape backslashes and colons in windows-style paths.
179      *
180      * @since 2.4
181      */
182     @Parameter(defaultValue = "true")
183     protected boolean escapeWindowsPaths;
184 
185     /**
186      * <p>
187      * Set of delimiters for expressions to filter within the resources. These delimiters are specified in the form
188      * {@code beginToken*endToken}. If no {@code *} is given, the delimiter is assumed to be the same for start and end.
189      * </p>
190      * <p>
191      * So, the default filtering delimiters might be specified as:
192      * </p>
193      *
194      * <pre>
195      * &lt;delimiters&gt;
196      *   &lt;delimiter&gt;${*}&lt;/delimiter&gt;
197      *   &lt;delimiter&gt;@&lt;/delimiter&gt;
198      * &lt;/delimiters&gt;
199      * </pre>
200      * <p>
201      * Since the {@code @} delimiter is the same on both ends, we don't need to specify {@code @*@} (though we can).
202      * </p>
203      *
204      * @since 2.4
205      */
206     @Parameter
207     protected LinkedHashSet<String> delimiters;
208 
209     /**
210      * Use default delimiters in addition to custom delimiters, if any.
211      *
212      * @since 2.4
213      */
214     @Parameter(defaultValue = "true")
215     protected boolean useDefaultDelimiters;
216 
217     /**
218      * By default files like {@code .gitignore}, {@code .cvsignore} etc. are excluded which means they will not being
219      * copied. If you need them for a particular reason you can do that by settings this to {@code false}. This means
220      * all files like the following will be copied.
221      * <ul>
222      * <li>Misc: &#42;&#42;/&#42;~, &#42;&#42;/#&#42;#, &#42;&#42;/.#&#42;, &#42;&#42;/%&#42;%, &#42;&#42;/._&#42;</li>
223      * <li>CVS: &#42;&#42;/CVS, &#42;&#42;/CVS/&#42;&#42;, &#42;&#42;/.cvsignore</li>
224      * <li>RCS: &#42;&#42;/RCS, &#42;&#42;/RCS/&#42;&#42;</li>
225      * <li>SCCS: &#42;&#42;/SCCS, &#42;&#42;/SCCS/&#42;&#42;</li>
226      * <li>VSSercer: &#42;&#42;/vssver.scc</li>
227      * <li>MKS: &#42;&#42;/project.pj</li>
228      * <li>SVN: &#42;&#42;/.svn, &#42;&#42;/.svn/&#42;&#42;</li>
229      * <li>GNU: &#42;&#42;/.arch-ids, &#42;&#42;/.arch-ids/&#42;&#42;</li>
230      * <li>Bazaar: &#42;&#42;/.bzr, &#42;&#42;/.bzr/&#42;&#42;</li>
231      * <li>SurroundSCM: &#42;&#42;/.MySCMServerInfo</li>
232      * <li>Mac: &#42;&#42;/.DS_Store</li>
233      * <li>Serena Dimension: &#42;&#42;/.metadata, &#42;&#42;/.metadata/&#42;&#42;</li>
234      * <li>Mercurial: &#42;&#42;/.hg, &#42;&#42;/.hg/&#42;&#42;</li>
235      * <li>Git: &#42;&#42;/.git, &#42;&#42;/.git/&#42;&#42;</li>
236      * <li>Bitkeeper: &#42;&#42;/BitKeeper, &#42;&#42;/BitKeeper/&#42;&#42;, &#42;&#42;/ChangeSet,
237      * &#42;&#42;/ChangeSet/&#42;&#42;</li>
238      * <li>Darcs: &#42;&#42;/_darcs, &#42;&#42;/_darcs/&#42;&#42;, &#42;&#42;/.darcsrepo,
239      * &#42;&#42;/.darcsrepo/&#42;&#42;&#42;&#42;/-darcs-backup&#42;, &#42;&#42;/.darcs-temp-mail
240      * </ul>
241      *
242      * @since 3.0.0
243      */
244     @Parameter(defaultValue = "true")
245     protected boolean addDefaultExcludes;
246 
247     /**
248      * <p>
249      * List of plexus components hint which implements
250      * {@link MavenResourcesFiltering#filterResources(MavenResourcesExecution)}. They will be executed after the
251      * resources copying/filtering.
252      * </p>
253      *
254      * @since 2.4
255      */
256     @Parameter
257     private List<String> mavenFilteringHints;
258 
259     /**
260      * @since 2.4
261      */
262     private List<MavenResourcesFiltering> mavenFilteringComponents = new ArrayList<>();
263 
264     /**
265      * stop searching endToken at the end of line
266      *
267      * @since 2.5
268      */
269     @Parameter(defaultValue = "false")
270     private boolean supportMultiLineFiltering;
271 
272     /**
273      * Support filtering of filenames folders etc.
274      *
275      * @since 3.0.0
276      */
277     @Parameter(defaultValue = "false")
278     private boolean fileNameFiltering;
279 
280     /**
281      * You can skip the execution of the plugin if you need to. Its use is NOT RECOMMENDED, but quite convenient on
282      * occasion.
283      *
284      * @since 3.0.0
285      */
286     @Parameter(property = "maven.resources.skip", defaultValue = "false")
287     private boolean skip;
288 
289     /**
290      * {@inheritDoc}
291      */
292     public void execute() throws MojoExecutionException {
293         if (isSkip()) {
294             getLog().info("Skipping the execution.");
295             return;
296         }
297 
298         if (StringUtils.isBlank(encoding) && isFilteringEnabled(getResources())) {
299             getLog().warn("File encoding has not been set, using platform encoding "
300                     + System.getProperty("file.encoding")
301                     + ". Build is platform dependent!");
302             getLog().warn("See https://maven.apache.org/general.html#encoding-warning");
303         }
304 
305         try {
306             List<String> combinedFilters = getCombinedFiltersList();
307 
308             MavenResourcesExecution mavenResourcesExecution = new MavenResourcesExecution(
309                     getResources(),
310                     getOutputDirectory(),
311                     project,
312                     encoding,
313                     combinedFilters,
314                     Collections.emptyList(),
315                     session);
316 
317             mavenResourcesExecution.setEscapeWindowsPaths(escapeWindowsPaths);
318 
319             // never include project build filters in this call, since we've already accounted for the POM build filters
320             // above, in getCombinedFiltersList().
321             mavenResourcesExecution.setInjectProjectBuildFilters(false);
322 
323             mavenResourcesExecution.setEscapeString(escapeString);
324             mavenResourcesExecution.setOverwrite(overwrite);
325             mavenResourcesExecution.setIncludeEmptyDirs(includeEmptyDirs);
326             mavenResourcesExecution.setSupportMultiLineFiltering(supportMultiLineFiltering);
327             mavenResourcesExecution.setFilterFilenames(fileNameFiltering);
328             mavenResourcesExecution.setAddDefaultExcludes(addDefaultExcludes);
329 
330             // Handle subject of MRESOURCES-99
331             Properties additionalProperties = addSeveralSpecialProperties();
332             mavenResourcesExecution.setAdditionalProperties(additionalProperties);
333 
334             // if these are NOT set, just use the defaults, which are '${*}' and '@'.
335             mavenResourcesExecution.setDelimiters(delimiters, useDefaultDelimiters);
336 
337             // Handle MRESOURCES-171
338             mavenResourcesExecution.setPropertiesEncoding(propertiesEncoding);
339 
340             if (nonFilteredFileExtensions != null) {
341                 mavenResourcesExecution.setNonFilteredFileExtensions(nonFilteredFileExtensions);
342             }
343             mavenResourcesFiltering.filterResources(mavenResourcesExecution);
344 
345             executeUserFilterComponents(mavenResourcesExecution);
346         } catch (MavenFilteringException e) {
347             throw new MojoExecutionException(e.getMessage(), e);
348         }
349     }
350 
351     /**
352      * This solves https://issues.apache.org/jira/browse/MRESOURCES-99.<br/>
353      * BUT:<br/>
354      * This should be done different than defining those properties a second time, cause they have already being defined
355      * in Maven Model Builder (package org.apache.maven.model.interpolation) via BuildTimestampValueSource. But those
356      * can't be found in the context which can be got from the maven core.<br/>
357      * A solution could be to put those values into the context by Maven core so they are accessible everywhere. (I'm
358      * not sure if this is a good idea). Better ideas are always welcome.
359      * <p>
360      * The problem at the moment is that maven core handles usage of properties and replacements in
361      * the model, but does not the resource filtering which needed some of the properties.
362      *
363      * @return the new instance with those properties.
364      */
365     private Properties addSeveralSpecialProperties() {
366         String timeStamp = new MavenBuildTimestamp().formattedTimestamp();
367         Properties additionalProperties = new Properties();
368         additionalProperties.put("maven.build.timestamp", timeStamp);
369         if (project.getBasedir() != null) {
370             additionalProperties.put(
371                     "project.baseUri",
372                     project.getBasedir().getAbsoluteFile().toURI().toString());
373         }
374 
375         return additionalProperties;
376     }
377 
378     /**
379      * @param mavenResourcesExecution {@link MavenResourcesExecution}
380      * @throws MojoExecutionException  in case of wrong lookup.
381      * @throws MavenFilteringException in case of failure.
382      * @since 2.5
383      */
384     protected void executeUserFilterComponents(MavenResourcesExecution mavenResourcesExecution)
385             throws MojoExecutionException, MavenFilteringException {
386 
387         if (mavenFilteringHints != null) {
388             for (String hint : mavenFilteringHints) {
389                 MavenResourcesFiltering userFilterComponent = mavenResourcesFilteringMap.get(hint);
390                 if (userFilterComponent != null) {
391                     getLog().debug("added user filter component with hint: " + hint);
392                     mavenFilteringComponents.add(userFilterComponent);
393                 } else {
394                     throw new MojoExecutionException(
395                             "User filter with hint `" + hint + "` requested, but not present. Discovered filters are: "
396                                     + mavenResourcesFilteringMap.keySet());
397                 }
398             }
399         } else {
400             getLog().debug("no user filter components");
401         }
402 
403         if (mavenFilteringComponents != null && !mavenFilteringComponents.isEmpty()) {
404             getLog().debug("execute user filters");
405             for (MavenResourcesFiltering filter : mavenFilteringComponents) {
406                 filter.filterResources(mavenResourcesExecution);
407             }
408         }
409     }
410 
411     /**
412      * @return The combined filters.
413      */
414     protected List<String> getCombinedFiltersList() {
415         if (filters == null || filters.isEmpty()) {
416             return useBuildFilters ? buildFilters : null;
417         } else {
418             List<String> result = new ArrayList<>();
419 
420             if (useBuildFilters && buildFilters != null && !buildFilters.isEmpty()) {
421                 result.addAll(buildFilters);
422             }
423 
424             result.addAll(filters);
425 
426             return result;
427         }
428     }
429 
430     /**
431      * Determines whether filtering has been enabled for any resource.
432      *
433      * @param theResources The set of resources to check for filtering, may be <code>null</code>.
434      * @return <code>true</code> if at least one resource uses filtering, <code>false</code> otherwise.
435      */
436     private boolean isFilteringEnabled(Collection<Resource> theResources) {
437         if (theResources != null) {
438             for (Resource resource : theResources) {
439                 if (resource.isFiltering()) {
440                     return true;
441                 }
442             }
443         }
444         return false;
445     }
446 
447     /**
448      * @return {@link #resources}
449      */
450     public List<Resource> getResources() {
451         return resources;
452     }
453 
454     /**
455      * @param resources set {@link #resources}
456      */
457     public void setResources(List<Resource> resources) {
458         this.resources = resources;
459     }
460 
461     /**
462      * @return {@link #outputDirectory}
463      */
464     public File getOutputDirectory() {
465         return outputDirectory;
466     }
467 
468     /**
469      * @param outputDirectory the output folder.
470      */
471     public void setOutputDirectory(File outputDirectory) {
472         this.outputDirectory = outputDirectory;
473     }
474 
475     /**
476      * @return {@link #overwrite}
477      */
478     public boolean isOverwrite() {
479         return overwrite;
480     }
481 
482     /**
483      * @param overwrite true to overwrite false otherwise.
484      */
485     public void setOverwrite(boolean overwrite) {
486         this.overwrite = overwrite;
487     }
488 
489     /**
490      * @return {@link #includeEmptyDirs}
491      */
492     public boolean isIncludeEmptyDirs() {
493         return includeEmptyDirs;
494     }
495 
496     /**
497      * @param includeEmptyDirs true/false.
498      */
499     public void setIncludeEmptyDirs(boolean includeEmptyDirs) {
500         this.includeEmptyDirs = includeEmptyDirs;
501     }
502 
503     /**
504      * @return {@link #filters}
505      */
506     public List<String> getFilters() {
507         return filters;
508     }
509 
510     /**
511      * @param filters The filters to use.
512      */
513     public void setFilters(List<String> filters) {
514         this.filters = filters;
515     }
516 
517     /**
518      * @return {@link #delimiters}
519      */
520     public LinkedHashSet<String> getDelimiters() {
521         return delimiters;
522     }
523 
524     /**
525      * @param delimiters The delimiters to use.
526      */
527     public void setDelimiters(LinkedHashSet<String> delimiters) {
528         this.delimiters = delimiters;
529     }
530 
531     /**
532      * @return {@link #useDefaultDelimiters}
533      */
534     public boolean isUseDefaultDelimiters() {
535         return useDefaultDelimiters;
536     }
537 
538     /**
539      * @param useDefaultDelimiters true to use {@code ${*}}
540      */
541     public void setUseDefaultDelimiters(boolean useDefaultDelimiters) {
542         this.useDefaultDelimiters = useDefaultDelimiters;
543     }
544 
545     /**
546      * @return {@link #skip}
547      */
548     public boolean isSkip() {
549         return skip;
550     }
551 }