View Javadoc
1   package org.apache.maven.plugins.war;
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.net.MalformedURLException;
25  import java.net.URL;
26  import java.net.URLClassLoader;
27  import java.util.Arrays;
28  import java.util.List;
29  
30  import org.apache.maven.archiver.MavenArchiver;
31  import org.apache.maven.artifact.Artifact;
32  import org.apache.maven.artifact.DependencyResolutionRequiredException;
33  import org.apache.maven.plugin.MojoExecutionException;
34  import org.apache.maven.plugin.MojoFailureException;
35  import org.apache.maven.plugins.annotations.Component;
36  import org.apache.maven.plugins.annotations.LifecyclePhase;
37  import org.apache.maven.plugins.annotations.Mojo;
38  import org.apache.maven.plugins.annotations.Parameter;
39  import org.apache.maven.plugins.annotations.ResolutionScope;
40  import org.apache.maven.plugins.war.util.ClassesPackager;
41  import org.apache.maven.project.MavenProjectHelper;
42  import org.codehaus.plexus.archiver.Archiver;
43  import org.codehaus.plexus.archiver.ArchiverException;
44  import org.codehaus.plexus.archiver.jar.ManifestException;
45  import org.codehaus.plexus.archiver.war.WarArchiver;
46  import org.codehaus.plexus.util.FileUtils;
47  import org.codehaus.plexus.util.StringUtils;
48  
49  /**
50   * Build a WAR file.
51   *
52   * @author <a href="evenisse@apache.org">Emmanuel Venisse</a>
53   */
54  @Mojo( name = "war", defaultPhase = LifecyclePhase.PACKAGE, threadSafe = true,
55                  requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME )
56  public class WarMojo
57      extends AbstractWarMojo
58  {
59      /**
60       * The directory for the generated WAR.
61       */
62      @Parameter( defaultValue = "${project.build.directory}", required = true )
63      private String outputDirectory;
64  
65      /**
66       * The name of the generated WAR.
67       */
68      @Parameter( defaultValue = "${project.build.finalName}", required = true, readonly = true )
69      private String warName;
70  
71      /**
72       * Classifier to add to the generated WAR. If given, the artifact will be an attachment instead. The classifier will
73       * not be applied to the JAR file of the project - only to the WAR file.
74       */
75      @Parameter
76      private String classifier;
77  
78      /**
79       * The comma separated list of tokens to exclude from the WAR before packaging. This option may be used to implement
80       * the skinny WAR use case. Note that you can use the Java Regular Expressions engine to include and exclude
81       * specific pattern using the expression %regex[]. Hint: read the about (?!Pattern).
82       *
83       * @since 2.1-alpha-2
84       */
85      @Parameter
86      private String packagingExcludes;
87  
88      /**
89       * The comma separated list of tokens to include in the WAR before packaging. By default everything is included.
90       * This option may be used to implement the skinny WAR use case. Note that you can use the Java Regular Expressions
91       * engine to include and exclude specific pattern using the expression %regex[].
92       *
93       * @since 2.1-beta-1
94       */
95      @Parameter
96      private String packagingIncludes;
97  
98      /**
99       * The WAR archiver.
100      */
101     @Component( role = Archiver.class, hint = "war" )
102     private WarArchiver warArchiver;
103 
104     /**
105      */
106     @Component
107     private MavenProjectHelper projectHelper;
108 
109     /**
110      * Whether this is the main artifact being built. Set to <code>false</code> if you don't want to install or deploy
111      * it to the local repository instead of the default one in an execution.
112      */
113     @Parameter( defaultValue = "true" )
114     private boolean primaryArtifact;
115 
116     /**
117      * Whether classes (that is the content of the WEB-INF/classes directory) should be attached to the project as an
118      * additional artifact.
119      * <p>
120      * By default the classifier for the additional artifact is 'classes'. You can change it with the
121      * <code><![CDATA[<classesClassifier>someclassifier</classesClassifier>]]></code> parameter.
122      * </p>
123      * <p>
124      * If this parameter true, another project can depend on the classes by writing something like:
125      *
126      * <pre>
127      * <![CDATA[<dependency>
128      *   <groupId>myGroup</groupId>
129      *   <artifactId>myArtifact</artifactId>
130      *   <version>myVersion</myVersion>
131      *   <classifier>classes</classifier>
132      * </dependency>]]>
133      * </pre>
134      * </p>
135      *
136      * @since 2.1-alpha-2
137      */
138     @Parameter( defaultValue = "false" )
139     private boolean attachClasses;
140 
141     /**
142      * The classifier to use for the attached classes artifact.
143      *
144      * @since 2.1-alpha-2
145      */
146     @Parameter( defaultValue = "classes" )
147     private String classesClassifier;
148 
149     /**
150      * You can skip the execution of the plugin if you need to. Its use is NOT RECOMMENDED, but quite convenient on
151      * occasion.
152      *
153      * @since 3.0.0
154      */
155     @Parameter( property = "maven.war.skip", defaultValue = "false" )
156     private boolean skip;
157 
158     // ----------------------------------------------------------------------
159     // Implementation
160     // ----------------------------------------------------------------------
161 
162     /**
163      * Executes the WarMojo on the current project.
164      *
165      * @throws MojoExecutionException if an error occurred while building the webapp
166      * @throws MojoFailureException if an error.
167      */
168     @Override
169     public void execute()
170         throws MojoExecutionException, MojoFailureException
171     {
172 
173         if ( isSkip() )
174         {
175             getLog().info( "Skipping the execution." );
176             return;
177         }
178 
179         File warFile = getTargetWarFile();
180 
181         try
182         {
183             performPackaging( warFile );
184         }
185         catch ( DependencyResolutionRequiredException | ArchiverException e )
186         {
187             throw new MojoExecutionException( "Error assembling WAR: " + e.getMessage(), e );
188         }
189         catch ( ManifestException | IOException e )
190         {
191             throw new MojoExecutionException( "Error assembling WAR", e );
192         }
193     }
194 
195     /**
196      * Generates the webapp according to the <tt>mode</tt> attribute.
197      *
198      * @param warFile the target WAR file
199      * @throws IOException if an error occurred while copying files
200      * @throws ArchiverException if the archive could not be created
201      * @throws ManifestException if the manifest could not be created
202      * @throws DependencyResolutionRequiredException if an error occurred while resolving the dependencies
203      * @throws MojoExecutionException if the execution failed
204      * @throws MojoFailureException if a fatal exception occurred
205      */
206     private void performPackaging( File warFile )
207         throws IOException, ManifestException, DependencyResolutionRequiredException, MojoExecutionException,
208         MojoFailureException
209     {
210         getLog().info( "Packaging webapp" );
211 
212         buildExplodedWebapp( getWebappDirectory() );
213 
214         MavenArchiver archiver = new MavenArchiver();
215 
216         archiver.setArchiver( warArchiver );
217 
218         archiver.setCreatedBy( "Maven WAR Plugin", "org.apache.maven.plugins", "maven-war-plugin" );
219 
220         archiver.setOutputFile( warFile );
221 
222         // configure for Reproducible Builds based on outputTimestamp value
223         archiver.configureReproducible( outputTimestamp );
224 
225         getLog().debug( "Excluding " + Arrays.asList( getPackagingExcludes() )
226             + " from the generated webapp archive." );
227         getLog().debug( "Including " + Arrays.asList( getPackagingIncludes() ) + " in the generated webapp archive." );
228 
229         warArchiver.addDirectory( getWebappDirectory(), getPackagingIncludes(), getPackagingExcludes() );
230 
231         final File webXmlFile = new File( getWebappDirectory(), "WEB-INF/web.xml" );
232         if ( webXmlFile.exists() )
233         {
234             warArchiver.setWebxml( webXmlFile );
235         }
236 
237         warArchiver.setRecompressAddedZips( isRecompressZippedFiles() );
238 
239         warArchiver.setIncludeEmptyDirs( isIncludeEmptyDirectories() );
240 
241         if ( Boolean.FALSE.equals( failOnMissingWebXml )
242             || ( failOnMissingWebXml == null && isProjectUsingAtLeastServlet30() ) )
243         {
244             getLog().debug( "Build won't fail if web.xml file is missing." );
245             warArchiver.setExpectWebXml( false );
246         }
247 
248         // create archive
249         archiver.createArchive( getSession(), getProject(), getArchive() );
250 
251         // create the classes to be attached if necessary
252         if ( isAttachClasses() )
253         {
254             if ( isArchiveClasses() && getJarArchiver().getDestFile() != null )
255             {
256                 // special handling in case of archived classes: MWAR-240
257                 File targetClassesFile = getTargetClassesFile();
258                 FileUtils.copyFile( getJarArchiver().getDestFile(), targetClassesFile );
259                 projectHelper.attachArtifact( getProject(), "jar", getClassesClassifier(), targetClassesFile );
260             }
261             else
262             {
263                 ClassesPackager packager = new ClassesPackager();
264                 final File classesDirectory = packager.getClassesDirectory( getWebappDirectory() );
265                 if ( classesDirectory.exists() )
266                 {
267                     getLog().info( "Packaging classes" );
268                     packager.packageClasses( classesDirectory, getTargetClassesFile(), getJarArchiver(), getSession(),
269                                              getProject(), getArchive(), outputTimestamp );
270                     projectHelper.attachArtifact( getProject(), "jar", getClassesClassifier(), getTargetClassesFile() );
271                 }
272             }
273         }
274 
275         if ( this.classifier != null )
276         {
277             projectHelper.attachArtifact( getProject(), "war", this.classifier, warFile );
278         }
279         else
280         {
281             Artifact artifact = getProject().getArtifact();
282             if ( primaryArtifact )
283             {
284                 artifact.setFile( warFile );
285             }
286             else if ( artifact.getFile() == null || artifact.getFile().isDirectory() )
287             {
288                 artifact.setFile( warFile );
289             }
290         }
291     }
292 
293     /**
294      * Determines if the current Maven project being built uses the Servlet 3.0 API (JSR 315)
295      * or Jakarta Servlet API.
296      * If it does then the <code>web.xml</code> file can be omitted.
297      * <p>
298      * This is done by checking if the interface <code>javax.servlet.annotation.WebServlet</code>
299      * or <code>jakarta.servlet.annotation.WebServlet</code> is in the compile-time
300      * dependencies (which includes provided dependencies) of the Maven project.
301      *
302      * @return <code>true</code> if the project being built depends on Servlet 3.0 API or Jakarta Servlet API,
303      *         <code>false</code> otherwise.
304      * @throws DependencyResolutionRequiredException if the compile elements can't be resolved.
305      * @throws MalformedURLException if the path to a dependency file can't be transformed to a URL.
306      */
307     private boolean isProjectUsingAtLeastServlet30()
308         throws DependencyResolutionRequiredException, MalformedURLException
309     {
310         List<String> classpathElements = getProject().getCompileClasspathElements();
311         URL[] urls = new URL[classpathElements.size()];
312         for ( int i = 0; i < urls.length; i++ )
313         {
314             urls[i] = new File( classpathElements.get( i ) ).toURI().toURL();
315         }
316         ClassLoader loader = new URLClassLoader( urls, Thread.currentThread().getContextClassLoader() );
317 
318         return hasWebServletAnnotationClassInClasspath( loader );
319     }
320 
321     private static boolean hasWebServletAnnotationClassInClasspath( ClassLoader loader )
322     {
323         return hasClassInClasspath( loader, "javax.servlet.annotation.WebServlet" )
324                 || hasClassInClasspath( loader, "jakarta.servlet.annotation.WebServlet" );
325     }
326 
327     private static boolean hasClassInClasspath( ClassLoader loader, String clazz )
328     {
329         try
330         {
331             Class.forName( clazz, false, loader );
332             return true;
333         }
334         catch ( ClassNotFoundException e )
335         {
336             return false;
337         }
338     }
339 
340     /**
341      * @param basedir The basedir
342      * @param finalName The finalName
343      * @param classifier The classifier.
344      * @param type The type.
345      * @return {@link File}
346      */
347     protected static File getTargetFile( File basedir, String finalName, String classifier, String type )
348     {
349         if ( classifier == null )
350         {
351             classifier = "";
352         }
353         else if ( classifier.trim().length() > 0 && !classifier.startsWith( "-" ) )
354         {
355             classifier = "-" + classifier;
356         }
357 
358         return new File( basedir, finalName + classifier + "." + type );
359     }
360 
361     /**
362      * @return The war {@link File}
363      */
364     protected File getTargetWarFile()
365     {
366         return getTargetFile( new File( getOutputDirectory() ), getWarName(), getClassifier(), "war" );
367 
368     }
369 
370     /**
371      * @return The target class {@link File}
372      */
373     protected File getTargetClassesFile()
374     {
375         return getTargetFile( new File( getOutputDirectory() ), getWarName(), getClassesClassifier(), "jar" );
376     }
377 
378     // Getters and Setters
379 
380     /**
381      * @return {@link #classifier}
382      */
383     public String getClassifier()
384     {
385         return classifier;
386     }
387 
388     /**
389      * @param classifier {@link #classifier}
390      */
391     public void setClassifier( String classifier )
392     {
393         this.classifier = classifier;
394     }
395 
396     /**
397      * @return The package excludes.
398      */
399     public String[] getPackagingExcludes()
400     {
401         if ( StringUtils.isEmpty( packagingExcludes ) )
402         {
403             return new String[0];
404         }
405         else
406         {
407             return StringUtils.split( packagingExcludes, "," );
408         }
409     }
410 
411     /**
412      * @param packagingExcludes {@link #packagingExcludes}
413      */
414     public void setPackagingExcludes( String packagingExcludes )
415     {
416         this.packagingExcludes = packagingExcludes;
417     }
418 
419     /**
420      * @return The packaging includes.
421      */
422     public String[] getPackagingIncludes()
423     {
424         if ( StringUtils.isEmpty( packagingIncludes ) )
425         {
426             return new String[] { "**" };
427         }
428         else
429         {
430             return StringUtils.split( packagingIncludes, "," );
431         }
432     }
433 
434     /**
435      * @param packagingIncludes {@link #packagingIncludes}
436      */
437     public void setPackagingIncludes( String packagingIncludes )
438     {
439         this.packagingIncludes = packagingIncludes;
440     }
441 
442     /**
443      * @return {@link #outputDirectory}
444      */
445     public String getOutputDirectory()
446     {
447         return outputDirectory;
448     }
449 
450     /**
451      * @param outputDirectory {@link #outputDirectory}
452      */
453     public void setOutputDirectory( String outputDirectory )
454     {
455         this.outputDirectory = outputDirectory;
456     }
457 
458     /**
459      * @return {@link #warName}
460      */
461     public String getWarName()
462     {
463         return warName;
464     }
465 
466     /**
467      * @param warName {@link #warName}
468      */
469     public void setWarName( String warName )
470     {
471         this.warName = warName;
472     }
473 
474     /**
475      * @return {@link #warArchiver}
476      */
477     public WarArchiver getWarArchiver()
478     {
479         return warArchiver;
480     }
481 
482     /**
483      * @param warArchiver {@link #warArchiver}
484      */
485     public void setWarArchiver( WarArchiver warArchiver )
486     {
487         this.warArchiver = warArchiver;
488     }
489 
490     /**
491      * @return {@link #projectHelper}
492      */
493     public MavenProjectHelper getProjectHelper()
494     {
495         return projectHelper;
496     }
497 
498     /**
499      * @param projectHelper {@link #projectHelper}
500      */
501     public void setProjectHelper( MavenProjectHelper projectHelper )
502     {
503         this.projectHelper = projectHelper;
504     }
505 
506     /**
507      * @return {@link #primaryArtifact}
508      */
509     public boolean isPrimaryArtifact()
510     {
511         return primaryArtifact;
512     }
513 
514     /**
515      * @param primaryArtifact {@link #primaryArtifact}
516      */
517     public void setPrimaryArtifact( boolean primaryArtifact )
518     {
519         this.primaryArtifact = primaryArtifact;
520     }
521 
522     /**
523      * @return {@link #attachClasses}
524      */
525     public boolean isAttachClasses()
526     {
527         return attachClasses;
528     }
529 
530     /**
531      * @param attachClasses {@link #attachClasses}
532      */
533     public void setAttachClasses( boolean attachClasses )
534     {
535         this.attachClasses = attachClasses;
536     }
537 
538     /**
539      * @return {@link #classesClassifier}
540      */
541     public String getClassesClassifier()
542     {
543         return classesClassifier;
544     }
545 
546     /**
547      * @param classesClassifier {@link #classesClassifier}
548      */
549     public void setClassesClassifier( String classesClassifier )
550     {
551         this.classesClassifier = classesClassifier;
552     }
553 
554     /**
555      * @return {@link #failOnMissingWebXml}
556      */
557     public boolean isFailOnMissingWebXml()
558     {
559         return failOnMissingWebXml;
560     }
561 
562     /**
563      * @param failOnMissingWebXml {@link #failOnMissingWebXml}
564      */
565     public void setFailOnMissingWebXml( boolean failOnMissingWebXml )
566     {
567         this.failOnMissingWebXml = failOnMissingWebXml;
568     }
569 
570     public boolean isSkip()
571     {
572         return skip;
573     }
574 }