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