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