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.war;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.nio.file.FileVisitResult;
24  import java.nio.file.Files;
25  import java.nio.file.Path;
26  import java.nio.file.SimpleFileVisitor;
27  import java.nio.file.attribute.BasicFileAttributes;
28  import java.util.ArrayList;
29  import java.util.Arrays;
30  import java.util.Collection;
31  import java.util.Collections;
32  import java.util.LinkedHashSet;
33  import java.util.List;
34  
35  import org.apache.maven.archiver.MavenArchiveConfiguration;
36  import org.apache.maven.artifact.factory.ArtifactFactory;
37  import org.apache.maven.execution.MavenSession;
38  import org.apache.maven.model.Resource;
39  import org.apache.maven.plugin.AbstractMojo;
40  import org.apache.maven.plugin.MojoExecutionException;
41  import org.apache.maven.plugin.MojoFailureException;
42  import org.apache.maven.plugin.logging.Log;
43  import org.apache.maven.plugins.annotations.Component;
44  import org.apache.maven.plugins.annotations.Parameter;
45  import org.apache.maven.plugins.war.overlay.OverlayManager;
46  import org.apache.maven.plugins.war.packaging.CopyUserManifestTask;
47  import org.apache.maven.plugins.war.packaging.OverlayPackagingTask;
48  import org.apache.maven.plugins.war.packaging.WarPackagingContext;
49  import org.apache.maven.plugins.war.packaging.WarPackagingTask;
50  import org.apache.maven.plugins.war.packaging.WarProjectPackagingTask;
51  import org.apache.maven.plugins.war.util.WebappStructure;
52  import org.apache.maven.project.MavenProject;
53  import org.apache.maven.shared.filtering.FilterWrapper;
54  import org.apache.maven.shared.filtering.MavenFileFilter;
55  import org.apache.maven.shared.filtering.MavenFilteringException;
56  import org.apache.maven.shared.filtering.MavenResourcesExecution;
57  import org.apache.maven.shared.filtering.MavenResourcesFiltering;
58  import org.apache.maven.shared.utils.StringUtils;
59  import org.codehaus.plexus.archiver.Archiver;
60  import org.codehaus.plexus.archiver.jar.JarArchiver;
61  import org.codehaus.plexus.archiver.manager.ArchiverManager;
62  
63  /**
64   * Contains common jobs for WAR mojos.
65   */
66  public abstract class AbstractWarMojo extends AbstractMojo {
67      private static final String META_INF = "META-INF";
68  
69      private static final String WEB_INF = "WEB-INF";
70      /**
71       * Whether or not to fail the build if the <code>web.xml</code> file is missing. Set to <code>false</code> if you
72       * want your WAR built without a <code>web.xml</code> file. This may be useful if you are building an overlay that
73       * has no web.xml file.
74       * <p>
75       * Starting with <b>3.1.0</b>, this property defaults to <code>false</code> if the project depends on the Servlet
76       * 3.0 API or newer.
77       *
78       * @since 2.1-alpha-2
79       */
80      @Parameter
81      protected Boolean failOnMissingWebXml;
82  
83      /**
84       * The Maven project.
85       */
86      @Parameter(defaultValue = "${project}", readonly = true, required = true)
87      private MavenProject project;
88  
89      /**
90       * The directory containing compiled classes.
91       */
92      @Parameter(defaultValue = "${project.build.outputDirectory}", required = true, readonly = true)
93      private File classesDirectory;
94  
95      /**
96       * Whether a JAR file will be created for the classes in the webapp. Using this optional configuration parameter
97       * will make the compiled classes to be archived into a JAR file in <code>/WEB-INF/lib/</code> and the classes
98       * directory will then be excluded from the webapp <code>/WEB-INF/classes/</code>.
99       *
100      * @since 2.0.1
101      */
102     @Parameter(defaultValue = "false")
103     private boolean archiveClasses;
104 
105     /**
106      * The encoding to use when copying filtered web resources.
107      *
108      * @since 2.3
109      */
110     @Parameter(defaultValue = "${project.build.sourceEncoding}")
111     private String resourceEncoding;
112 
113     /**
114      * The character encoding to use when reading and writing filtered properties files.
115      * If not specified, it will default to the value of the "resourceEncoding" parameter.
116      *
117      * @since 3.4.0
118      */
119     @Parameter
120     protected String propertiesEncoding;
121 
122     /**
123      * The JAR archiver needed for archiving the classes directory into a JAR file under WEB-INF/lib.
124      */
125     @Component(role = Archiver.class, hint = "jar")
126     private JarArchiver jarArchiver;
127 
128     /**
129      * The directory where the webapp is built.
130      */
131     @Parameter(defaultValue = "${project.build.directory}/${project.build.finalName}", required = true)
132     private File webappDirectory;
133 
134     /**
135      * Single directory for extra files to include in the WAR. This is where you place your JSP files.
136      */
137     @Parameter(defaultValue = "${basedir}/src/main/webapp", required = true)
138     private File warSourceDirectory;
139 
140     /**
141      * The list of webResources we want to transfer.
142      */
143     @Parameter
144     private Resource[] webResources;
145 
146     /**
147      * Filters (property files) to include during the interpolation of the pom.xml.
148      */
149     @Parameter
150     private List<String> filters;
151 
152     /**
153      * <p>
154      * Set of delimiters for expressions to filter within the resources. These delimiters are specified in the form
155      * 'beginToken*endToken'. If no '*' is given, the delimiter is assumed to be the same for start and end.
156      * </p>
157      * <p>
158      * So, the default filtering delimiters might be specified as:
159      * </p>
160      *
161      * <pre>
162      * &lt;delimiters&gt;
163      *   &lt;delimiter&gt;${*}&lt;/delimiter&gt;
164      *   &lt;delimiter&gt;@&lt;/delimiter&gt;
165      * &lt;/delimiters&gt;
166      * </pre>
167      * <p>
168      * Since the '@' delimiter is the same on both ends, we don't need to specify '@*@' (though we can).
169      * </p>
170      *
171      * @since 3.0.0
172      */
173     @Parameter
174     private LinkedHashSet<String> delimiters;
175 
176     /**
177      * Use default delimiters in addition to custom delimiters, if any.
178      *
179      * @since 3.0.0
180      */
181     @Parameter(defaultValue = "true")
182     private boolean useDefaultDelimiters;
183 
184     /**
185      * The path to the web.xml file to use.
186      */
187     @Parameter
188     private File webXml;
189 
190     /**
191      * The path to a configuration file for the servlet container. Note that the file name may be different for
192      * different servlet containers. Apache Tomcat uses a configuration file named context.xml. The file will be copied
193      * to the META-INF directory.
194      */
195     @Parameter
196     private File containerConfigXML;
197 
198     /**
199      * Directory to unpack dependent WARs into if needed.
200      */
201     @Parameter(defaultValue = "${project.build.directory}/war/work", required = true)
202     private File workDirectory;
203 
204     /**
205      * The file name mapping to use when copying libraries and TLDs. If no file mapping is set (default) the files are
206      * copied with their standard names.
207      *
208      * @since 2.1-alpha-1
209      */
210     @Parameter
211     private String outputFileNameMapping;
212 
213     /**
214      */
215     @Component(role = ArtifactFactory.class)
216     private ArtifactFactory artifactFactory;
217 
218     /**
219      * To look up Archiver/UnArchiver implementations.
220      */
221     @Component(role = ArchiverManager.class)
222     private ArchiverManager archiverManager;
223 
224     /**
225      */
226     @Component(role = MavenFileFilter.class, hint = "default")
227     private MavenFileFilter mavenFileFilter;
228 
229     /**
230      */
231     @Component(role = MavenResourcesFiltering.class, hint = "default")
232     private MavenResourcesFiltering mavenResourcesFiltering;
233 
234     /**
235      * The comma separated list of tokens to include when copying the content of the warSourceDirectory.
236      */
237     @Parameter(defaultValue = "**")
238     private String warSourceIncludes;
239 
240     /**
241      * The comma separated list of tokens to exclude when copying the content of the warSourceDirectory.
242      */
243     @Parameter
244     private String warSourceExcludes;
245 
246     /**
247      * The comma separated list of tokens to include when doing a WAR overlay. Default is
248      * {@link org.apache.maven.plugins.war.Overlay#DEFAULT_INCLUDES}
249      *
250      */
251     @Parameter
252     private String dependentWarIncludes = StringUtils.join(Overlay.DEFAULT_INCLUDES, ",");
253 
254     /**
255      * The comma separated list of tokens to exclude when doing a WAR overlay. Default is
256      * {@link org.apache.maven.plugins.war.Overlay#DEFAULT_EXCLUDES}
257      *
258      */
259     @Parameter
260     private String dependentWarExcludes = StringUtils.join(Overlay.DEFAULT_EXCLUDES, ",");
261 
262     /**
263      * The overlays to apply. Each &lt;overlay&gt; element may contain:
264      * <ul>
265      * <li>id (defaults to {@code currentBuild})</li>
266      * <li>groupId (if this and artifactId are null, then the current project is treated as its own overlay)</li>
267      * <li>artifactId (see above)</li>
268      * <li>classifier</li>
269      * <li>type</li>
270      * <li>includes (a list of string patterns)</li>
271      * <li>excludes (a list of string patterns)</li>
272      * <li>filtered (defaults to false)</li>
273      * <li>skip (defaults to false)</li>
274      * <li>targetPath (defaults to root of webapp structure)</li>
275      * </ul>
276      *
277      * @since 2.1-alpha-1
278      */
279     @Parameter
280     private List<Overlay> overlays = new ArrayList<>();
281 
282     /**
283      * A list of file extensions that should not be filtered. <b>Will be used when filtering webResources and
284      * overlays.</b>
285      *
286      * @since 2.1-alpha-2
287      */
288     @Parameter
289     private List<String> nonFilteredFileExtensions;
290 
291     /**
292      * @since 2.1-alpha-2
293      */
294     @Parameter(defaultValue = "${session}", readonly = true, required = true)
295     private MavenSession session;
296 
297     /**
298      * To filter deployment descriptors. <b>Disabled by default.</b>
299      *
300      * @since 2.1-alpha-2
301      */
302     @Parameter(defaultValue = "false")
303     private boolean filteringDeploymentDescriptors;
304 
305     /**
306      * To escape interpolated values with Windows path <code>c:\foo\bar</code> will be replaced with
307      * <code>c:\\foo\\bar</code>.
308      *
309      * @since 2.1-alpha-2
310      */
311     @Parameter(defaultValue = "false")
312     private boolean escapedBackslashesInFilePath;
313 
314     /**
315      * Expression preceded with this String won't be interpolated. <code>\${foo}</code> will be replaced with
316      * <code>${foo}</code>.
317      *
318      * @since 2.1-beta-1
319      */
320     @Parameter
321     protected String escapeString;
322 
323     /**
324      * Indicates if zip archives (jar,zip etc) being added to the war should be compressed again. Compressing again can
325      * result in smaller archive size, but gives noticeably longer execution time.
326      *
327      * @since 2.3
328      */
329     @Parameter(defaultValue = "true")
330     private boolean recompressZippedFiles;
331 
332     /**
333      * @since 2.4
334      */
335     @Parameter(defaultValue = "false")
336     private boolean includeEmptyDirectories;
337 
338     /**
339      * Stop searching endToken at the end of line
340      *
341      * @since 2.4
342      */
343     @Parameter(defaultValue = "false")
344     private boolean supportMultiLineFiltering;
345 
346     /**
347      * use jvmChmod rather that cli chmod and forking process
348      *
349      * @since 2.4
350      */
351     @Parameter(defaultValue = "true")
352     private boolean useJvmChmod;
353 
354     /**
355      * The archive configuration to use. See <a href="http://maven.apache.org/shared/maven-archiver/index.html">Maven
356      * Archiver Reference</a>.
357      */
358     @Parameter
359     private MavenArchiveConfiguration archive = new MavenArchiveConfiguration();
360 
361     /**
362      * Timestamp for reproducible output archive entries, either formatted as ISO 8601
363      * <code>yyyy-MM-dd'T'HH:mm:ssXXX</code> or as an int representing seconds since the epoch (like
364      * <a href="https://reproducible-builds.org/docs/source-date-epoch/">SOURCE_DATE_EPOCH</a>).
365      *
366      * @since 3.3.0
367      */
368     @Parameter(defaultValue = "${project.build.outputTimestamp}")
369     protected String outputTimestamp;
370 
371     /**
372      * Path prefix for resources that will be checked against outdated content.
373      *
374      * Starting with <b>3.3.2</b>, if a value of "/" is specified the entire
375      * webappDirectory will be checked, i.e. the "/" signifies "root".
376      *
377      * @since 3.3.1
378      */
379     @Parameter(defaultValue = "WEB-INF/lib/")
380     private String outdatedCheckPath;
381 
382     private final Overlay currentProjectOverlay = Overlay.createInstance();
383 
384     /**
385      * @return The current overlay.
386      */
387     public Overlay getCurrentProjectOverlay() {
388         return currentProjectOverlay;
389     }
390 
391     /**
392      * Returns a string array of the excludes to be used when copying the content of the WAR source directory.
393      *
394      * @return an array of tokens to exclude
395      */
396     protected String[] getExcludes() {
397         List<String> excludeList = new ArrayList<>();
398         if (warSourceExcludes != null && !warSourceExcludes.isEmpty()) {
399             excludeList.addAll(Arrays.asList(StringUtils.split(warSourceExcludes, ",")));
400         }
401 
402         // if webXML is specified, omit the one in the source directory
403         if (webXml != null && StringUtils.isNotEmpty(webXml.getName())) {
404             excludeList.add("**/" + WEB_INF + "/web.xml");
405         }
406 
407         // if contextXML is specified, omit the one in the source directory
408         if (containerConfigXML != null && StringUtils.isNotEmpty(containerConfigXML.getName())) {
409             excludeList.add("**/" + META_INF + "/" + containerConfigXML.getName());
410         }
411 
412         return excludeList.toArray(new String[excludeList.size()]);
413     }
414 
415     /**
416      * Returns a string array of the includes to be used when assembling/copying the WAR.
417      *
418      * @return an array of tokens to include
419      */
420     protected String[] getIncludes() {
421         return StringUtils.split(StringUtils.defaultString(warSourceIncludes), ",");
422     }
423 
424     /**
425      * Returns a string array of the excludes to be used when adding dependent WAR as an overlay onto this WAR.
426      *
427      * @return an array of tokens to exclude
428      */
429     protected String[] getDependentWarExcludes() {
430         return StringUtils.split(StringUtils.defaultString(dependentWarExcludes), ",");
431     }
432 
433     /**
434      * Returns a string array of the includes to be used when adding dependent WARs as an overlay onto this WAR.
435      *
436      * @return an array of tokens to include
437      */
438     protected String[] getDependentWarIncludes() {
439         return StringUtils.split(StringUtils.defaultString(dependentWarIncludes), ",");
440     }
441 
442     /**
443      * @param webapplicationDirectory The web application directory.
444      * @throws MojoExecutionException In case of failure.
445      * @throws MojoFailureException In case of failure.
446      */
447     public void buildExplodedWebapp(File webapplicationDirectory) throws MojoExecutionException, MojoFailureException {
448         webapplicationDirectory.mkdirs();
449 
450         try {
451             buildWebapp(project, webapplicationDirectory);
452         } catch (IOException e) {
453             throw new MojoExecutionException("Could not build webapp", e);
454         }
455     }
456 
457     /**
458      * Builds the webapp for the specified project with the new packaging task thingy.
459      * Classes, libraries and tld files are copied to the {@code webappDirectory} during this phase.
460      *
461      * @param mavenProject the maven project
462      * @param webapplicationDirectory the target directory
463      * @throws MojoExecutionException if an error occurred while packaging the webapp
464      * @throws MojoFailureException if an unexpected error occurred while packaging the webapp
465      * @throws IOException if an error occurred while copying the files
466      */
467     public void buildWebapp(MavenProject mavenProject, File webapplicationDirectory)
468             throws MojoExecutionException, MojoFailureException, IOException {
469 
470         WebappStructure structure = new WebappStructure(mavenProject.getDependencies());
471 
472         // CHECKSTYLE_OFF: LineLength
473         final long startTime = System.currentTimeMillis();
474         getLog().info("Assembling webapp [" + mavenProject.getArtifactId() + "] in [" + webapplicationDirectory + "]");
475 
476         final OverlayManager overlayManager = new OverlayManager(
477                 overlays, mavenProject, getDependentWarIncludes(), getDependentWarExcludes(), currentProjectOverlay);
478         // CHECKSTYLE_ON: LineLength
479         List<FilterWrapper> defaultFilterWrappers;
480         try {
481             MavenResourcesExecution mavenResourcesExecution = new MavenResourcesExecution();
482             mavenResourcesExecution.setEscapeString(escapeString);
483             mavenResourcesExecution.setSupportMultiLineFiltering(supportMultiLineFiltering);
484             mavenResourcesExecution.setMavenProject(mavenProject);
485 
486             // if these are NOT set, just use the defaults, which are '${*}' and '@'.
487             mavenResourcesExecution.setDelimiters(delimiters, useDefaultDelimiters);
488 
489             if (nonFilteredFileExtensions != null) {
490                 mavenResourcesExecution.setNonFilteredFileExtensions(nonFilteredFileExtensions);
491             }
492 
493             if (filters == null) {
494                 filters = getProject().getBuild().getFilters();
495             }
496             mavenResourcesExecution.setFilters(filters);
497             mavenResourcesExecution.setEscapedBackslashesInFilePath(escapedBackslashesInFilePath);
498             mavenResourcesExecution.setMavenSession(this.session);
499             mavenResourcesExecution.setEscapeString(this.escapeString);
500             mavenResourcesExecution.setSupportMultiLineFiltering(supportMultiLineFiltering);
501 
502             defaultFilterWrappers = mavenFileFilter.getDefaultFilterWrappers(mavenResourcesExecution);
503 
504         } catch (MavenFilteringException e) {
505             getLog().error("fail to build filtering wrappers " + e.getMessage());
506             throw new MojoExecutionException(e.getMessage(), e);
507         }
508 
509         final WarPackagingContext context = new DefaultWarPackagingContext(
510                 webapplicationDirectory,
511                 structure,
512                 overlayManager,
513                 defaultFilterWrappers,
514                 getNonFilteredFileExtensions(),
515                 filteringDeploymentDescriptors,
516                 this.artifactFactory,
517                 resourceEncoding,
518                 propertiesEncoding,
519                 useJvmChmod,
520                 failOnMissingWebXml,
521                 outputTimestamp);
522 
523         final List<WarPackagingTask> packagingTasks = getPackagingTasks(overlayManager);
524 
525         for (WarPackagingTask warPackagingTask : packagingTasks) {
526             warPackagingTask.performPackaging(context);
527         }
528 
529         getLog().debug("Webapp assembled in [" + (System.currentTimeMillis() - startTime) + " msecs]");
530     }
531 
532     /**
533      * Returns a {@code List} of the {@link org.apache.maven.plugins.war.packaging.WarPackagingTask}
534      * instances to invoke to perform the packaging.
535      *
536      * @param overlayManager the overlay manager
537      * @return the list of packaging tasks
538      * @throws MojoExecutionException if the packaging tasks could not be built
539      */
540     private List<WarPackagingTask> getPackagingTasks(OverlayManager overlayManager) throws MojoExecutionException {
541         final List<WarPackagingTask> packagingTasks = new ArrayList<>();
542 
543         packagingTasks.add(new CopyUserManifestTask());
544 
545         final List<Overlay> resolvedOverlays = overlayManager.getOverlays();
546         for (Overlay overlay : resolvedOverlays) {
547             if (overlay.isCurrentProject()) {
548                 packagingTasks.add(
549                         new WarProjectPackagingTask(webResources, webXml, containerConfigXML, currentProjectOverlay));
550             } else {
551                 packagingTasks.add(new OverlayPackagingTask(overlay, currentProjectOverlay));
552             }
553         }
554         return packagingTasks;
555     }
556 
557     /**
558      * WarPackagingContext default implementation
559      */
560     private class DefaultWarPackagingContext implements WarPackagingContext {
561         private final ArtifactFactory artifactFactory;
562 
563         private final String resourceEncoding;
564 
565         private final String propertiesEncoding;
566 
567         private final WebappStructure webappStructure;
568 
569         private final File webappDirectory;
570 
571         private final OverlayManager overlayManager;
572 
573         private final List<FilterWrapper> filterWrappers;
574 
575         private List<String> nonFilteredFileExtensions;
576 
577         private boolean filteringDeploymentDescriptors;
578 
579         private boolean useJvmChmod;
580 
581         private final Boolean failOnMissingWebXml;
582 
583         private final Collection<String> outdatedResources;
584 
585         private final String outputTimestamp;
586 
587         /**
588          * @param webappDirectory The web application directory.
589          * @param webappStructure The web app structure.
590          * @param overlayManager The overlay manager.
591          * @param filterWrappers The filter wrappers
592          * @param nonFilteredFileExtensions The non filtered file extensions.
593          * @param filteringDeploymentDescriptors The filtering deployment descriptors.
594          * @param artifactFactory The artifact factory.
595          * @param resourceEncoding The resource encoding.
596          * @param propertiesEncoding The encoding to use for properties files.
597          * @param useJvmChmod use Jvm chmod or not.
598          * @param failOnMissingWebXml Flag to check whether we should ignore missing web.xml or not
599          * @param outputTimestamp the output timestamp for reproducible archive creation
600          */
601         DefaultWarPackagingContext(
602                 final File webappDirectory,
603                 final WebappStructure webappStructure,
604                 final OverlayManager overlayManager,
605                 List<FilterWrapper> filterWrappers,
606                 List<String> nonFilteredFileExtensions,
607                 boolean filteringDeploymentDescriptors,
608                 ArtifactFactory artifactFactory,
609                 String resourceEncoding,
610                 String propertiesEncoding,
611                 boolean useJvmChmod,
612                 final Boolean failOnMissingWebXml,
613                 String outputTimestamp) {
614             this.webappDirectory = webappDirectory;
615             this.webappStructure = webappStructure;
616             this.overlayManager = overlayManager;
617             this.filterWrappers = filterWrappers;
618             this.artifactFactory = artifactFactory;
619             this.filteringDeploymentDescriptors = filteringDeploymentDescriptors;
620             this.nonFilteredFileExtensions =
621                     nonFilteredFileExtensions == null ? Collections.emptyList() : nonFilteredFileExtensions;
622             this.resourceEncoding = resourceEncoding;
623             this.propertiesEncoding = propertiesEncoding;
624             // This is kinda stupid but if we loop over the current overlays and we request the path structure
625             // it will register it. This will avoid wrong warning messages in a later phase
626             for (String overlayId : overlayManager.getOverlayIds()) {
627                 webappStructure.getStructure(overlayId);
628             }
629             this.useJvmChmod = useJvmChmod;
630             this.failOnMissingWebXml = failOnMissingWebXml;
631 
632             if (!webappDirectory.exists()) {
633                 outdatedResources = Collections.emptyList();
634             } else if (getWarSourceDirectory().toPath().equals(webappDirectory.toPath())) {
635                 getLog().info("Can't detect outdated resources when running inplace goal");
636                 outdatedResources = Collections.emptyList();
637             } else if (session.getStartTime() == null) {
638                 // MWAR-439: this should never happen, but has happened in some integration context...
639                 getLog().warn("Can't detect outdated resources because unexpected session.getStartTime() == null");
640                 outdatedResources = Collections.emptyList();
641             } else {
642                 outdatedResources = new ArrayList<>();
643                 try {
644                     if ('\\' == File.separatorChar) {
645                         if (!checkAllPathsForOutdated()) {
646                             outdatedCheckPath = outdatedCheckPath.replace('/', '\\');
647                         }
648                     }
649                     Files.walkFileTree(webappDirectory.toPath(), new SimpleFileVisitor<Path>() {
650                         @Override
651                         public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
652                             if (file.toFile().lastModified()
653                                     < session.getStartTime().getTime()) {
654                                 String path = webappDirectory
655                                         .toPath()
656                                         .relativize(file)
657                                         .toString();
658                                 if (checkAllPathsForOutdated() || path.startsWith(outdatedCheckPath)) {
659                                     outdatedResources.add(path);
660                                 }
661                             }
662                             return super.visitFile(file, attrs);
663                         }
664                     });
665                 } catch (IOException e) {
666                     getLog().warn("Can't detect outdated resources", e);
667                 }
668             }
669             this.outputTimestamp = outputTimestamp;
670         }
671 
672         protected boolean checkAllPathsForOutdated() {
673             return outdatedCheckPath.equals("/");
674         }
675 
676         @Override
677         public MavenProject getProject() {
678             return project;
679         }
680 
681         @Override
682         public File getWebappDirectory() {
683             return webappDirectory;
684         }
685 
686         @Override
687         public File getClassesDirectory() {
688             return classesDirectory;
689         }
690 
691         @Override
692         public Log getLog() {
693             return AbstractWarMojo.this.getLog();
694         }
695 
696         @Override
697         public String getOutputFileNameMapping() {
698             return outputFileNameMapping;
699         }
700 
701         @Override
702         public File getWebappSourceDirectory() {
703             return warSourceDirectory;
704         }
705 
706         @Override
707         public String[] getWebappSourceIncludes() {
708             return getIncludes();
709         }
710 
711         @Override
712         public String[] getWebappSourceExcludes() {
713             return getExcludes();
714         }
715 
716         @Override
717         public boolean isWebappSourceIncludeEmptyDirectories() {
718             return includeEmptyDirectories;
719         }
720 
721         @Override
722         public boolean archiveClasses() {
723             return archiveClasses;
724         }
725 
726         @Override
727         public File getOverlaysWorkDirectory() {
728             return workDirectory;
729         }
730 
731         @Override
732         public ArchiverManager getArchiverManager() {
733             return archiverManager;
734         }
735 
736         @Override
737         public MavenArchiveConfiguration getArchive() {
738             return archive;
739         }
740 
741         @Override
742         public JarArchiver getJarArchiver() {
743             return jarArchiver;
744         }
745 
746         @Override
747         public List<String> getFilters() {
748             return filters;
749         }
750 
751         @Override
752         public WebappStructure getWebappStructure() {
753             return webappStructure;
754         }
755 
756         @Override
757         public List<String> getOwnerIds() {
758             return overlayManager.getOverlayIds();
759         }
760 
761         @Override
762         public MavenFileFilter getMavenFileFilter() {
763             return mavenFileFilter;
764         }
765 
766         @Override
767         public List<FilterWrapper> getFilterWrappers() {
768             return filterWrappers;
769         }
770 
771         @Override
772         public boolean isNonFilteredExtension(String fileName) {
773             return !mavenResourcesFiltering.filteredFileExtension(fileName, nonFilteredFileExtensions);
774         }
775 
776         @Override
777         public boolean isFilteringDeploymentDescriptors() {
778             return filteringDeploymentDescriptors;
779         }
780 
781         @Override
782         public ArtifactFactory getArtifactFactory() {
783             return this.artifactFactory;
784         }
785 
786         @Override
787         public MavenSession getSession() {
788             return session;
789         }
790 
791         @Override
792         public String getResourceEncoding() {
793             return resourceEncoding;
794         }
795 
796         @Override
797         public String getPropertiesEncoding() {
798             return propertiesEncoding;
799         }
800 
801         @Override
802         public boolean isUseJvmChmod() {
803             return useJvmChmod;
804         }
805 
806         @Override
807         public Boolean isFailOnMissingWebXml() {
808             return failOnMissingWebXml;
809         }
810 
811         @Override
812         public void addResource(String resource) {
813             outdatedResources.remove(resource.replace('/', File.separatorChar));
814         }
815 
816         @Override
817         public void deleteOutdatedResources() {
818             for (String resource : outdatedResources) {
819                 getLog().info("deleting outdated resource " + resource);
820                 new File(getWebappDirectory(), resource).delete();
821             }
822         }
823 
824         @Override
825         public String getOutputTimestamp() {
826             return outputTimestamp;
827         }
828     }
829 
830     /**
831      * @return The Maven Project.
832      */
833     public MavenProject getProject() {
834         return project;
835     }
836 
837     /**
838      * @param project The project to be set.
839      */
840     public void setProject(MavenProject project) {
841         this.project = project;
842     }
843 
844     /**
845      * @return the classes directory.
846      */
847     public File getClassesDirectory() {
848         return classesDirectory;
849     }
850 
851     /**
852      * @param classesDirectory The classes directory to be set.
853      */
854     public void setClassesDirectory(File classesDirectory) {
855         this.classesDirectory = classesDirectory;
856     }
857 
858     /**
859      * @return {@link #webappDirectory}
860      */
861     public File getWebappDirectory() {
862         return webappDirectory;
863     }
864 
865     /**
866      * @param webappDirectory The web application directory.
867      */
868     public void setWebappDirectory(File webappDirectory) {
869         this.webappDirectory = webappDirectory;
870     }
871 
872     /**
873      * @return {@link #warSourceDirectory}
874      */
875     public File getWarSourceDirectory() {
876         return warSourceDirectory;
877     }
878 
879     /**
880      * @param warSourceDirectory {@link #warSourceDirectory}
881      */
882     public void setWarSourceDirectory(File warSourceDirectory) {
883         this.warSourceDirectory = warSourceDirectory;
884     }
885 
886     /**
887      * @return The {@link #webXml}
888      */
889     public File getWebXml() {
890         return webXml;
891     }
892 
893     /**
894      * @param webXml The {@link #webXml}
895      */
896     public void setWebXml(File webXml) {
897         this.webXml = webXml;
898     }
899 
900     /**
901      * @return {@link #containerConfigXML}
902      */
903     public File getContainerConfigXML() {
904         return containerConfigXML;
905     }
906 
907     /**
908      * @param containerConfigXML {@link #containerConfigXML}
909      */
910     public void setContainerConfigXML(File containerConfigXML) {
911         this.containerConfigXML = containerConfigXML;
912     }
913 
914     /**
915      * @return {@link #outputFileNameMapping}
916      */
917     public String getOutputFileNameMapping() {
918         return outputFileNameMapping;
919     }
920 
921     /**
922      * @param outputFileNameMapping {@link #outputFileNameMapping}
923      */
924     public void setOutputFileNameMapping(String outputFileNameMapping) {
925         this.outputFileNameMapping = outputFileNameMapping;
926     }
927 
928     /**
929      * @return {@link #overlays}
930      */
931     public List<Overlay> getOverlays() {
932         return overlays;
933     }
934 
935     /**
936      * @param overlays {@link #overlays}
937      */
938     public void setOverlays(List<Overlay> overlays) {
939         this.overlays = overlays;
940     }
941 
942     /**
943      * @param overlay add {@link #overlays}.
944      */
945     public void addOverlay(Overlay overlay) {
946         overlays.add(overlay);
947     }
948 
949     /**
950      * @return {@link #archiveClasses}
951      */
952     public boolean isArchiveClasses() {
953         return archiveClasses;
954     }
955 
956     /**
957      * @param archiveClasses {@link #archiveClasses}
958      */
959     public void setArchiveClasses(boolean archiveClasses) {
960         this.archiveClasses = archiveClasses;
961     }
962 
963     /**
964      * @return {@link JarArchiver}
965      */
966     public JarArchiver getJarArchiver() {
967         return jarArchiver;
968     }
969 
970     /**
971      * @param jarArchiver {@link JarArchiver}
972      */
973     public void setJarArchiver(JarArchiver jarArchiver) {
974         this.jarArchiver = jarArchiver;
975     }
976 
977     /**
978      * @return {@link #webResources}.
979      */
980     public Resource[] getWebResources() {
981         return webResources;
982     }
983 
984     /**
985      * @param webResources {@link #webResources}.
986      */
987     public void setWebResources(Resource[] webResources) {
988         this.webResources = webResources;
989     }
990 
991     /**
992      * @return {@link #filters}
993      */
994     public List<String> getFilters() {
995         return filters;
996     }
997 
998     /**
999      * @param filters {@link #filters}
1000      */
1001     public void setFilters(List<String> filters) {
1002         this.filters = filters;
1003     }
1004 
1005     /**
1006      * @return {@link #workDirectory}
1007      */
1008     public File getWorkDirectory() {
1009         return workDirectory;
1010     }
1011 
1012     /**
1013      * @param workDirectory {@link #workDirectory}
1014      */
1015     public void setWorkDirectory(File workDirectory) {
1016         this.workDirectory = workDirectory;
1017     }
1018 
1019     /**
1020      * @return {@link #warSourceIncludes}
1021      */
1022     public String getWarSourceIncludes() {
1023         return warSourceIncludes;
1024     }
1025 
1026     /**
1027      * @param warSourceIncludes {@link #warSourceIncludes}
1028      */
1029     public void setWarSourceIncludes(String warSourceIncludes) {
1030         this.warSourceIncludes = warSourceIncludes;
1031     }
1032 
1033     /**
1034      * @return {@link #warSourceExcludes}
1035      */
1036     public String getWarSourceExcludes() {
1037         return warSourceExcludes;
1038     }
1039 
1040     /**
1041      * @param warSourceExcludes {@link #warSourceExcludes}
1042      */
1043     public void setWarSourceExcludes(String warSourceExcludes) {
1044         this.warSourceExcludes = warSourceExcludes;
1045     }
1046 
1047     /**
1048      * @return {@link #archive}
1049      */
1050     public MavenArchiveConfiguration getArchive() {
1051         return archive;
1052     }
1053 
1054     /**
1055      * @return {@link #nonFilteredFileExtensions}
1056      */
1057     public List<String> getNonFilteredFileExtensions() {
1058         return nonFilteredFileExtensions;
1059     }
1060 
1061     /**
1062      * @param nonFilteredFileExtensions {@link #nonFilteredFileExtensions}
1063      */
1064     public void setNonFilteredFileExtensions(List<String> nonFilteredFileExtensions) {
1065         this.nonFilteredFileExtensions = nonFilteredFileExtensions;
1066     }
1067 
1068     /**
1069      * @return {@link #artifactFactory}
1070      */
1071     public ArtifactFactory getArtifactFactory() {
1072         return this.artifactFactory;
1073     }
1074 
1075     /**
1076      * @param artifactFactory {@link #artifactFactory}
1077      */
1078     public void setArtifactFactory(ArtifactFactory artifactFactory) {
1079         this.artifactFactory = artifactFactory;
1080     }
1081 
1082     /**
1083      * @return {@link #session}
1084      */
1085     protected MavenSession getSession() {
1086         return this.session;
1087     }
1088 
1089     /**
1090      * @return {@link #recompressZippedFiles}
1091      */
1092     protected boolean isRecompressZippedFiles() {
1093         return recompressZippedFiles;
1094     }
1095 
1096     /**
1097      * @return {@link #includeEmptyDirectories}
1098      */
1099     protected boolean isIncludeEmptyDirectories() {
1100         return includeEmptyDirectories;
1101     }
1102 }