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