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