[MWAR-396] Check the existence of the web.xml based on the existence of particular classes

In 3.0, the default value of 'failOnMissingWebXml' was changed to be 'false'. With this fix, the default value is set to 'null' so that:
 - when the parameter is explicitly specified, it is used;
 - when it is not specified, it defaults to whether or not we can detect a Servlet 3.0 or newer environment for the web application. Such an environment does not need a web.xml since it can be written in Java.

git-svn-id: https://svn.apache.org/repos/asf/maven/plugins/trunk@1760101 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/pom.xml b/pom.xml
index 24eaa3c..1b9d883 100644
--- a/pom.xml
+++ b/pom.xml
@@ -215,6 +215,7 @@
         <configuration>
           <systemPropertyVariables>
             <project.build.directory>${project.build.directory}</project.build.directory>
+            <project.build.outputDirectory>${project.build.outputDirectory}</project.build.outputDirectory>
           </systemPropertyVariables>
         </configuration>
       </plugin>
@@ -251,6 +252,7 @@
                 <configuration>
                   <extraArtifacts>
                     <extraArtifact>javax.servlet:servlet-api:2.4:jar</extraArtifact>
+                    <extraArtifact>javax.servlet:javax.servlet-api:3.0.1:jar</extraArtifact>
                     <extraArtifact>org.apache.struts:struts-core:1.3.9:jar</extraArtifact>
                     <extraArtifact>org.codehaus.plexus:plexus-utils:1.4.7:jar:sources</extraArtifact>
                   </extraArtifacts>
diff --git a/src/it/MWAR-396_no-servlet30/invoker.properties b/src/it/MWAR-396_no-servlet30/invoker.properties
new file mode 100644
index 0000000..f2a7dfb
--- /dev/null
+++ b/src/it/MWAR-396_no-servlet30/invoker.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+invoker.buildResult = failure
diff --git a/src/it/MWAR-396_no-servlet30/pom.xml b/src/it/MWAR-396_no-servlet30/pom.xml
new file mode 100644
index 0000000..5fa6d85
--- /dev/null
+++ b/src/it/MWAR-396_no-servlet30/pom.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+<project>
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>org.apache.maven.its.war</groupId>
+  <artifactId>maven-it-mwar396</artifactId>
+  <version>1.0</version>
+  <packaging>war</packaging>
+
+  <name>Maven Integration Test :: MWAR-396</name> 
+  <description>Test that a web.xml is required when project does not depend on Servlet 3.0 or newer</description>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>javax.servlet</groupId>
+      <artifactId>servlet-api</artifactId>
+      <version>2.4</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>commons-logging</groupId>
+      <artifactId>commons-logging</artifactId>
+      <version>1.0.3</version>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>3.8.2</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+  
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-war-plugin</artifactId>
+        <version>@project.version@</version>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/src/it/MWAR-396_servlet30/pom.xml b/src/it/MWAR-396_servlet30/pom.xml
new file mode 100644
index 0000000..2244d15
--- /dev/null
+++ b/src/it/MWAR-396_servlet30/pom.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+<project>
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>org.apache.maven.its.war</groupId>
+  <artifactId>maven-it-mwar396_servlet30</artifactId>
+  <version>1.0</version>
+  <packaging>war</packaging>
+
+  <name>Maven Integration Test :: MWAR-396</name> 
+  <description>Test that no web.xml is required when project depends on Servlet 3.0 or newer</description>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>javax.servlet</groupId>
+      <artifactId>javax.servlet-api</artifactId>
+      <version>3.0.1</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>commons-logging</groupId>
+      <artifactId>commons-logging</artifactId>
+      <version>1.0.3</version>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>3.8.2</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+  
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-war-plugin</artifactId>
+        <version>@project.version@</version>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/src/it/MWAR-396_servlet30/verify.bsh b/src/it/MWAR-396_servlet30/verify.bsh
new file mode 100644
index 0000000..402a3ca
--- /dev/null
+++ b/src/it/MWAR-396_servlet30/verify.bsh
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.*;
+import java.util.*;
+import java.util.jar.*;
+import java.util.regex.*;
+
+try
+{
+    File explodedDir = new File( basedir, "target/maven-it-mwar396_servlet30-1.0" );
+    System.out.println( "Checking for existence of exploded directory " + explodedDir );
+    if ( !explodedDir.exists() )
+    {
+        System.out.println( "FAILURE! The directory " + explodedDir + " does not exist." );
+        return false;
+    }
+    
+    File webInfFile = new File( explodedDir, "WEB-INF/web.xml" );
+    if ( webInfFile.exists() )
+    {
+        System.err.println( "FAILURE! The file web.xml should not be present." );
+        return false;
+    }
+
+}
+catch( Throwable t )
+{
+    t.printStackTrace();
+    return false;
+}
+
+return true;
diff --git a/src/main/java/org/apache/maven/plugins/war/WarMojo.java b/src/main/java/org/apache/maven/plugins/war/WarMojo.java
index b68b98f..4506f4b 100644
--- a/src/main/java/org/apache/maven/plugins/war/WarMojo.java
+++ b/src/main/java/org/apache/maven/plugins/war/WarMojo.java
@@ -21,7 +21,11 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
 import java.util.Arrays;
+import java.util.List;
 
 import org.apache.maven.archiver.MavenArchiver;
 import org.apache.maven.artifact.Artifact;
@@ -49,7 +53,7 @@
  * @version $Id$
  */
 // CHECKSTYLE_OFF: LineLength
-@Mojo( name = "war", defaultPhase = LifecyclePhase.PACKAGE, threadSafe = true, requiresDependencyResolution = ResolutionScope.RUNTIME )
+@Mojo( name = "war", defaultPhase = LifecyclePhase.PACKAGE, threadSafe = true, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME )
 // CHECKSTYLE_ON: LineLength
 public class WarMojo
     extends AbstractWarMojo
@@ -113,13 +117,16 @@
 
     /**
      * Whether or not to fail the build if the <code>web.xml</code> file is missing. Set to <code>false</code> if you
-     * want you WAR built without a <code>web.xml</code> file. This may be useful if you are building an overlay that
+     * want your WAR built without a <code>web.xml</code> file. This may be useful if you are building an overlay that
      * has no web.xml file.
+     * <p>
+     * Starting with <b>3.0.1</b>, this property defaults to <code>false</code> if the project depends on the Servlet
+     * 3.0 API or newer.
      *
      * @since 2.1-alpha-2
      */
-    @Parameter( defaultValue = "false" )
-    private boolean failOnMissingWebXml;
+    @Parameter
+    private Boolean failOnMissingWebXml;
 
     /**
      * Whether classes (that is the content of the WEB-INF/classes directory) should be attached to the project as an
@@ -250,7 +257,8 @@
 
         warArchiver.setIncludeEmptyDirs( isIncludeEmptyDirectories() );
 
-        if ( !failOnMissingWebXml )
+        if ( Boolean.FALSE.equals( failOnMissingWebXml )
+            || ( failOnMissingWebXml == null && isProjectUsingAtLeastServlet30() ) )
         {
             getLog().debug( "Build won't fail if web.xml file is missing." );
             warArchiver.setExpectWebXml( false );
@@ -302,6 +310,38 @@
     }
 
     /**
+     * Determines if the current Maven project being built uses the Servlet 3.0 API (JSR 315). If it does then the
+     * <code>web.xml</code> file can be omitted.
+     * <p>
+     * This is done by checking if the interface <code>javax.servlet.annotation.WebServlet</code> is in the compile-time
+     * dependencies (which includes provided dependencies) of the Maven project.
+     * 
+     * @return <code>true</code> if the project being built depends on Servlet 3.0 API, <code>false</code> otherwise.
+     * @throws DependencyResolutionRequiredException if the compile elements can't be resolved.
+     * @throws MalformedURLException if the path to a dependency file can't be transformed to a URL.
+     */
+    private boolean isProjectUsingAtLeastServlet30()
+        throws DependencyResolutionRequiredException, MalformedURLException
+    {
+        List<String> classpathElements = getProject().getCompileClasspathElements();
+        URL[] urls = new URL[classpathElements.size()];
+        for ( int i = 0; i < urls.length; i++ )
+        {
+            urls[i] = new File( classpathElements.get( i ) ).toURI().toURL();
+        }
+        ClassLoader loader = new URLClassLoader( urls, Thread.currentThread().getContextClassLoader() );
+        try
+        {
+            Class.forName( "javax.servlet.annotation.WebServlet", false, loader );
+            return true;
+        }
+        catch ( ClassNotFoundException e )
+        {
+            return false;
+        }
+    }
+
+    /**
      * @param basedir The basedir
      * @param finalName The finalName
      * @param classifier The classifier.
diff --git a/src/test/java/org/apache/maven/plugins/war/WarMojoTest.java b/src/test/java/org/apache/maven/plugins/war/WarMojoTest.java
index 75af475..dac7474 100644
--- a/src/test/java/org/apache/maven/plugins/war/WarMojoTest.java
+++ b/src/test/java/org/apache/maven/plugins/war/WarMojoTest.java
@@ -29,9 +29,13 @@
 import java.util.jar.JarFile;
 import java.util.zip.ZipEntry;
 
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.handler.ArtifactHandler;
 import org.apache.maven.plugin.MojoExecutionException;
 import org.apache.maven.plugins.war.WarMojo;
+import org.apache.maven.plugins.war.stub.JarArtifactStub;
 import org.apache.maven.plugins.war.stub.MavenProject4CopyConstructor;
+import org.apache.maven.plugins.war.stub.MavenProjectArtifactsStub;
 import org.apache.maven.plugins.war.stub.ProjectHelperStub;
 import org.apache.maven.plugins.war.stub.WarArtifact4CCStub;
 import org.codehaus.plexus.util.IOUtil;
@@ -353,6 +357,75 @@
             // expected behaviour
         }
     }
+    
+    public void testFailOnMissingWebXmlNotSpecifiedAndServlet30Used()
+        throws Exception
+    {
+        String testId = "SimpleWarUnderServlet30";
+        MavenProjectArtifactsStub project = new MavenProjectArtifactsStub();
+        String outputDir = getTestDirectory().getAbsolutePath() + "/" + testId + "-output";
+        File webAppDirectory = new File( getTestDirectory(), testId );
+        WarArtifact4CCStub warArtifact = new WarArtifact4CCStub( getBasedir() );
+        String warName = "simple";
+        File webAppSource = createWebAppSource( testId );
+        File classesDir = createClassesDir( testId, true );
+
+        final ArtifactHandler artifactHandler = (ArtifactHandler) lookup( ArtifactHandler.ROLE, "jar" );
+        JarArtifactStub jarArtifactStub = new JarArtifactStub( getBasedir(), artifactHandler );
+        jarArtifactStub.setFile( new File( getBasedir(),
+                                           "/target/test-classes/unit/sample_wars/javax.servlet-api-3.0.1.jar" ) );
+        jarArtifactStub.setScope( Artifact.SCOPE_PROVIDED );
+        project.addArtifact( jarArtifactStub );
+
+        project.setArtifact( warArtifact );
+        project.setFile( warArtifact.getFile() );
+        this.configureMojo( mojo, new LinkedList<String>(), classesDir, webAppSource, webAppDirectory, project );
+        setVariableValueToObject( mojo, "outputDirectory", outputDir );
+        setVariableValueToObject( mojo, "warName", warName );
+
+        mojo.execute();
+
+        // validate war file
+        File expectedWarFile = new File( outputDir, "simple.war" );
+        final Map<String, JarEntry> jarContent =
+            assertJarContent( expectedWarFile,
+                              new String[] { "META-INF/MANIFEST.MF", "pansit.jsp", "org/web/app/last-exile.jsp",
+                                  "META-INF/maven/org.apache.maven.plugin.test/maven-war-plugin-test/pom.xml",
+                                  "META-INF/maven/org.apache.maven.plugin.test/maven-war-plugin-test/pom.properties" },
+                              new String[] { null, null, null, null, null } );
+
+        assertFalse( "web.xml should be missing", jarContent.containsKey( "WEB-INF/web.xml" ) );
+    }
+
+    public void testFailOnMissingWebXmlNotSpecifiedAndServlet30NotUsed()
+        throws Exception
+    {
+        String testId = "SimpleWarNotUnderServlet30";
+        MavenProjectArtifactsStub project = new MavenProjectArtifactsStub();
+        String outputDir = getTestDirectory().getAbsolutePath() + "/" + testId + "-output";
+        File webAppDirectory = new File( getTestDirectory(), testId );
+        WarArtifact4CCStub warArtifact = new WarArtifact4CCStub( getBasedir() );
+        String warName = "simple";
+        File webAppSource = createWebAppSource( testId );
+        File classesDir = createClassesDir( testId, true );
+
+        project.setArtifact( warArtifact );
+        project.setFile( warArtifact.getFile() );
+        this.configureMojo( mojo, new LinkedList<String>(), classesDir, webAppSource, webAppDirectory, project );
+        setVariableValueToObject( mojo, "outputDirectory", outputDir );
+        setVariableValueToObject( mojo, "warName", warName );
+
+        try
+        {
+            mojo.execute();
+            fail( "Building of the war isn't possible because no 'failOnMissingWebXml' policy was set and the project "
+                + "does not depend on Servlet 3.0" );
+        }
+        catch ( MojoExecutionException e )
+        {
+            // expected behaviour
+        }
+    }
 
     public void testAttachClasses()
         throws Exception
diff --git a/src/test/java/org/apache/maven/plugins/war/stub/JarArtifactStub.java b/src/test/java/org/apache/maven/plugins/war/stub/JarArtifactStub.java
index 62266ce..4b08995 100644
--- a/src/test/java/org/apache/maven/plugins/war/stub/JarArtifactStub.java
+++ b/src/test/java/org/apache/maven/plugins/war/stub/JarArtifactStub.java
@@ -37,6 +37,7 @@
 
     protected String scope;
 
+    private File file;
 
     private ArtifactHandler artifactHandler;
 
@@ -131,7 +132,16 @@
 
     public File getFile()
     {
-        return new File( basedir, "/target/test-classes/unit/sample_wars/simple.jar" );
+        if ( file == null )
+        {
+            return new File( basedir, "/target/test-classes/unit/sample_wars/simple.jar" );
+        }
+        return file;
+    }
+
+    public void setFile( File file )
+    {
+        this.file = file;
     }
 
     public ArtifactHandler getArtifactHandler()
diff --git a/src/test/java/org/apache/maven/plugins/war/stub/MavenProjectBasicStub.java b/src/test/java/org/apache/maven/plugins/war/stub/MavenProjectBasicStub.java
index efbc7fc..020760e 100644
--- a/src/test/java/org/apache/maven/plugins/war/stub/MavenProjectBasicStub.java
+++ b/src/test/java/org/apache/maven/plugins/war/stub/MavenProjectBasicStub.java
@@ -116,6 +116,7 @@
         Build build = super.getBuild();
 
         build.setDirectory( System.getProperty( "project.build.directory" ) );
+        build.setOutputDirectory( System.getProperty( "project.build.outputDirectory" ) );
 
         return build;
     }
diff --git a/src/test/resources/unit/sample_wars/javax.servlet-api-3.0.1.jar b/src/test/resources/unit/sample_wars/javax.servlet-api-3.0.1.jar
new file mode 100644
index 0000000..4e2edcc
--- /dev/null
+++ b/src/test/resources/unit/sample_wars/javax.servlet-api-3.0.1.jar
Binary files differ