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.packaging;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.util.Objects;
24  
25  import org.apache.maven.model.Resource;
26  import org.apache.maven.plugin.MojoExecutionException;
27  import org.apache.maven.plugin.MojoFailureException;
28  import org.apache.maven.plugins.war.Overlay;
29  import org.apache.maven.plugins.war.util.PathSet;
30  import org.apache.maven.shared.filtering.MavenFilteringException;
31  import org.codehaus.plexus.util.DirectoryScanner;
32  import org.codehaus.plexus.util.StringUtils;
33  
34  /**
35   * Handles the project own resources, that is:
36   * <ul>
37   * <li>The list of web resources, if any</li>
38   * <li>The content of the webapp directory if it exists</li>
39   * <li>The custom deployment descriptor(s), if any</li>
40   * <li>The content of the classes directory if it exists</li>
41   * <li>The dependencies of the project</li>
42   * </ul>
43   *
44   * @author Stephane Nicoll
45   */
46  public class WarProjectPackagingTask extends AbstractWarPackagingTask {
47      private final Resource[] webResources;
48  
49      private final File webXml;
50  
51      private final File containerConfigXML;
52  
53      private final String id;
54  
55      private Overlay currentProjectOverlay;
56  
57      /**
58       * @param webResources {@link #webResources}
59       * @param webXml {@link #webXml}
60       * @param containerConfigXml {@link #containerConfigXML}
61       * @param currentProjectOverlay {@link #currentProjectOverlay}
62       */
63      public WarProjectPackagingTask(
64              Resource[] webResources, File webXml, File containerConfigXml, Overlay currentProjectOverlay) {
65          if (webResources != null) {
66              this.webResources = webResources;
67          } else {
68              this.webResources = new Resource[0];
69          }
70          this.webXml = webXml;
71          this.containerConfigXML = containerConfigXml;
72          this.currentProjectOverlay = currentProjectOverlay;
73          this.id = currentProjectOverlay.getId();
74      }
75  
76      @Override
77      public void performPackaging(WarPackagingContext context) throws MojoExecutionException, MojoFailureException {
78          context.getLog().info("Processing war project");
79  
80          // Prepare the INF directories
81          File webinfDir = new File(context.getWebappDirectory(), WEB_INF_PATH);
82          webinfDir.mkdirs();
83          File metainfDir = new File(context.getWebappDirectory(), META_INF_PATH);
84          metainfDir.mkdirs();
85  
86          handleWebResources(context);
87  
88          handleWebAppSourceDirectory(context);
89  
90          // Debug mode: dump the path set for the current build
91          PathSet pathSet = context.getWebappStructure().getStructure("currentBuild");
92          context.getLog().debug("Dump of the current build pathSet content -->");
93          for (String path : pathSet) {
94              context.getLog().debug(path);
95          }
96          context.getLog().debug("-- end of dump --");
97  
98          handleDeploymentDescriptors(context, webinfDir, metainfDir, context.isFailOnMissingWebXml());
99  
100         handleClassesDirectory(context);
101 
102         handleArtifacts(context);
103 
104         if (!context.getWebappDirectory().mkdirs()) {
105             context.deleteOutdatedResources();
106         }
107     }
108 
109     /**
110      * Handles the web resources.
111      *
112      * @param context the packaging context
113      * @throws MojoExecutionException if a resource could not be copied
114      */
115     protected void handleWebResources(WarPackagingContext context) throws MojoExecutionException {
116         for (Resource resource : webResources) {
117 
118             // MWAR-246
119             if (resource.getDirectory() == null) {
120                 throw new MojoExecutionException("The <directory> tag is missing from the <resource> tag.");
121             }
122 
123             if (!(new File(resource.getDirectory())).isAbsolute()) {
124                 resource.setDirectory(context.getProject().getBasedir() + File.separator + resource.getDirectory());
125             }
126 
127             // Make sure that the resource directory is not the same as the webappDirectory
128             if (!resource.getDirectory().equals(context.getWebappDirectory().getPath())) {
129 
130                 try {
131                     copyResources(context, resource);
132                 } catch (IOException e) {
133                     throw new MojoExecutionException("Could not copy resource [" + resource.getDirectory() + "]", e);
134                 }
135             }
136         }
137     }
138 
139     /**
140      * Handles the webapp sources.
141      *
142      * @param context the packaging context
143      * @throws MojoExecutionException if the sources could not be copied
144      */
145     protected void handleWebAppSourceDirectory(WarPackagingContext context) throws MojoExecutionException {
146         // CHECKSTYLE_OFF: LineLength
147         if (!context.getWebappSourceDirectory().exists()) {
148             context.getLog().debug("webapp sources directory does not exist - skipping.");
149         } else if (!context.getWebappSourceDirectory()
150                 .getAbsolutePath()
151                 .equals(context.getWebappDirectory().getPath())) {
152             context.getLog().info("Copying webapp resources [" + context.getWebappSourceDirectory() + "]");
153             final PathSet sources = getFilesToIncludes(
154                     context.getWebappSourceDirectory(), context.getWebappSourceIncludes(),
155                     context.getWebappSourceExcludes(), context.isWebappSourceIncludeEmptyDirectories());
156 
157             try {
158                 copyFiles(id, context, context.getWebappSourceDirectory(), sources, false);
159             } catch (IOException e) {
160                 throw new MojoExecutionException(
161                         "Could not copy webapp sources ["
162                                 + context.getWebappDirectory().getAbsolutePath() + "]",
163                         e);
164             }
165         }
166         // CHECKSTYLE_ON: LineLength
167     }
168 
169     /**
170      * Handles the webapp artifacts.
171      *
172      * @param context the packaging context
173      * @throws MojoExecutionException if the artifacts could not be packaged
174      */
175     protected void handleArtifacts(WarPackagingContext context) throws MojoExecutionException {
176         ArtifactsPackagingTask task =
177                 new ArtifactsPackagingTask(context.getProject().getArtifacts(), currentProjectOverlay);
178         task.performPackaging(context);
179     }
180 
181     /**
182      * Handles the webapp classes.
183      *
184      * @param context the packaging context
185      * @throws MojoExecutionException if the classes could not be packaged
186      */
187     protected void handleClassesDirectory(WarPackagingContext context) throws MojoExecutionException {
188         ClassesPackagingTask task = new ClassesPackagingTask(currentProjectOverlay);
189         task.performPackaging(context);
190     }
191 
192     /**
193      * Handles the deployment descriptors, if specified. Note that the behavior here is slightly different since the
194      * customized entry always win, even if an overlay has already packaged a web.xml previously.
195      *
196      * @param context the packaging context
197      * @param webinfDir the web-inf directory
198      * @param metainfDir the meta-inf directory
199      * @param failOnMissingWebXml if build should fail if web.xml is not found
200      * @throws MojoFailureException if the web.xml is specified but does not exist and failOnMissingWebXml is true
201      * @throws MojoExecutionException if an error occurred while copying the descriptors
202      */
203     protected void handleDeploymentDescriptors(
204             WarPackagingContext context, File webinfDir, File metainfDir, Boolean failOnMissingWebXml)
205             throws MojoFailureException, MojoExecutionException {
206         try {
207             if (webXml != null && StringUtils.isNotEmpty(webXml.getName())) {
208                 if (!webXml.exists() && (failOnMissingWebXml == null || Boolean.TRUE.equals(failOnMissingWebXml))) {
209                     throw new MojoFailureException("The specified web.xml file '" + webXml + "' does not exist");
210                 }
211 
212                 // Making sure that it won't get overlayed
213                 context.getWebappStructure().registerFileForced(id, WEB_INF_PATH + "/web.xml");
214 
215                 if (context.isFilteringDeploymentDescriptors()) {
216                     context.getMavenFileFilter()
217                             .copyFile(
218                                     webXml,
219                                     new File(webinfDir, "web.xml"),
220                                     true,
221                                     context.getFilterWrappers(),
222                                     getEncoding(webXml));
223                 } else {
224                     copyFile(context, webXml, new File(webinfDir, "web.xml"), "WEB-INF/web.xml", true);
225                 }
226             } else {
227                 // the webXml can be the default one
228                 File defaultWebXml = new File(context.getWebappSourceDirectory(), WEB_INF_PATH + "/web.xml");
229                 // if exists we can filter it
230                 if (defaultWebXml.exists() && context.isFilteringDeploymentDescriptors()) {
231                     context.getWebappStructure().registerFile(id, WEB_INF_PATH + "/web.xml");
232                     context.getMavenFileFilter()
233                             .copyFile(
234                                     defaultWebXml,
235                                     new File(webinfDir, "web.xml"),
236                                     true,
237                                     context.getFilterWrappers(),
238                                     getEncoding(defaultWebXml));
239                 }
240             }
241 
242             if (containerConfigXML != null && StringUtils.isNotEmpty(containerConfigXML.getName())) {
243                 String xmlFileName = containerConfigXML.getName();
244 
245                 context.getWebappStructure().registerFileForced(id, META_INF_PATH + "/" + xmlFileName);
246 
247                 if (context.isFilteringDeploymentDescriptors()) {
248                     context.getMavenFileFilter()
249                             .copyFile(
250                                     containerConfigXML,
251                                     new File(metainfDir, xmlFileName),
252                                     true,
253                                     context.getFilterWrappers(),
254                                     getEncoding(containerConfigXML));
255                 } else {
256                     copyFile(
257                             context,
258                             containerConfigXML,
259                             new File(metainfDir, xmlFileName),
260                             "META-INF/" + xmlFileName,
261                             true);
262                 }
263             }
264         } catch (IOException e) {
265             if (failOnMissingWebXml == null || Boolean.TRUE.equals(failOnMissingWebXml)) {
266                 throw new MojoExecutionException("Failed to copy deployment descriptor", e);
267             }
268         } catch (MavenFilteringException e) {
269             throw new MojoExecutionException("Failed to copy deployment descriptor", e);
270         }
271     }
272 
273     /**
274      * Copies webapp webResources from the specified directory.
275      *
276      * @param context the WAR packaging context to use
277      * @param resource the resource to copy
278      * @throws IOException if an error occurred while copying the resources
279      * @throws MojoExecutionException if an error occurred while retrieving the filter properties
280      */
281     public void copyResources(WarPackagingContext context, Resource resource)
282             throws IOException, MojoExecutionException {
283         if (!context.getWebappDirectory().exists()) {
284             context.getLog()
285                     .warn("Not copying webapp webResources [" + resource.getDirectory()
286                             + "]: webapp directory ["
287                             + context.getWebappDirectory().getAbsolutePath()
288                             + "] does not exist!");
289         }
290 
291         context.getLog()
292                 .info("Copying webapp webResources [" + resource.getDirectory() + "] to ["
293                         + context.getWebappDirectory().getAbsolutePath() + "]");
294         String[] fileNames = getFilesToCopy(resource);
295         for (String fileName : fileNames) {
296             String targetFileName = fileName;
297             if (resource.getTargetPath() != null) {
298                 // TODO make sure this thing is 100% safe
299                 // MWAR-129 if targetPath is only a dot <targetPath>.</targetPath> or ./
300                 // and the Resource is in a part of the warSourceDirectory the file from sources will override this
301                 // that's we don't have to add the targetPath yep not nice but works
302                 if (!Objects.equals(".", resource.getTargetPath()) && !Objects.equals("./", resource.getTargetPath())) {
303                     targetFileName = resource.getTargetPath() + File.separator + targetFileName;
304                 }
305             }
306             if (resource.isFiltering() && !context.isNonFilteredExtension(fileName)) {
307                 copyFilteredFile(id, context, new File(resource.getDirectory(), fileName), targetFileName);
308             } else {
309                 copyFile(id, context, new File(resource.getDirectory(), fileName), targetFileName);
310             }
311         }
312     }
313 
314     /**
315      * Returns a list of filenames that should be copied over to the destination directory.
316      *
317      * @param resource the resource to be scanned
318      * @return the array of filenames, relative to the sourceDir
319      */
320     private String[] getFilesToCopy(Resource resource) {
321         // CHECKSTYLE_OFF: LineLength
322         DirectoryScanner scanner = new DirectoryScanner();
323         scanner.setBasedir(resource.getDirectory());
324         if (resource.getIncludes() != null && !resource.getIncludes().isEmpty()) {
325             scanner.setIncludes(resource.getIncludes()
326                     .toArray(new String[resource.getIncludes().size()]));
327         } else {
328             scanner.setIncludes(DEFAULT_INCLUDES);
329         }
330         if (resource.getExcludes() != null && !resource.getExcludes().isEmpty()) {
331             scanner.setExcludes(resource.getExcludes()
332                     .toArray(new String[resource.getExcludes().size()]));
333         }
334 
335         scanner.addDefaultExcludes();
336 
337         scanner.scan();
338 
339         return scanner.getIncludedFiles();
340         // CHECKSTYLE_ON: LineLength
341     }
342 }