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