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