| /* |
| * 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. |
| */ |
| package org.apache.maven.plugins.war; |
| |
| 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; |
| import org.apache.maven.artifact.DependencyResolutionRequiredException; |
| import org.apache.maven.plugin.MojoExecutionException; |
| import org.apache.maven.plugin.MojoFailureException; |
| import org.apache.maven.plugins.annotations.Component; |
| import org.apache.maven.plugins.annotations.LifecyclePhase; |
| import org.apache.maven.plugins.annotations.Mojo; |
| import org.apache.maven.plugins.annotations.Parameter; |
| import org.apache.maven.plugins.annotations.ResolutionScope; |
| import org.apache.maven.plugins.war.util.ClassesPackager; |
| import org.apache.maven.project.MavenProjectHelper; |
| import org.codehaus.plexus.archiver.Archiver; |
| import org.codehaus.plexus.archiver.ArchiverException; |
| import org.codehaus.plexus.archiver.jar.ManifestException; |
| import org.codehaus.plexus.archiver.war.WarArchiver; |
| import org.codehaus.plexus.util.FileUtils; |
| import org.codehaus.plexus.util.StringUtils; |
| |
| /** |
| * Build a WAR file. |
| * |
| * @author <a href="evenisse@apache.org">Emmanuel Venisse</a> |
| */ |
| @Mojo( |
| name = "war", |
| defaultPhase = LifecyclePhase.PACKAGE, |
| threadSafe = true, |
| requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME) |
| public class WarMojo extends AbstractWarMojo { |
| /** |
| * The directory for the generated WAR. |
| */ |
| @Parameter(defaultValue = "${project.build.directory}", required = true) |
| private String outputDirectory; |
| |
| /** |
| * The name of the generated WAR. |
| */ |
| @Parameter(defaultValue = "${project.build.finalName}", required = true, readonly = true) |
| private String warName; |
| |
| /** |
| * Classifier to add to the generated WAR. If given, the artifact will be an attachment instead. The classifier will |
| * not be applied to the JAR file of the project - only to the WAR file. |
| */ |
| @Parameter |
| private String classifier; |
| |
| /** |
| * The comma separated list of tokens to exclude from the WAR before packaging. This option may be used to implement |
| * the skinny WAR use case. Note that you can use the Java Regular Expressions engine to include and exclude |
| * specific pattern using the expression %regex[]. Hint: read the about (?!Pattern). |
| * |
| * @since 2.1-alpha-2 |
| */ |
| @Parameter |
| private String packagingExcludes; |
| |
| /** |
| * The comma separated list of tokens to include in the WAR before packaging. By default everything is included. |
| * This option may be used to implement the skinny WAR use case. Note that you can use the Java Regular Expressions |
| * engine to include and exclude specific pattern using the expression %regex[]. |
| * |
| * @since 2.1-beta-1 |
| */ |
| @Parameter |
| private String packagingIncludes; |
| |
| /** |
| * The WAR archiver. |
| */ |
| @Component(role = Archiver.class, hint = "war") |
| private WarArchiver warArchiver; |
| |
| /** |
| */ |
| @Component |
| private MavenProjectHelper projectHelper; |
| |
| /** |
| * Whether this is the main artifact being built. Set to <code>false</code> if you don't want to install or deploy |
| * it to the local repository instead of the default one in an execution. |
| */ |
| @Parameter(defaultValue = "true") |
| private boolean primaryArtifact; |
| |
| /** |
| * Whether classes (that is the content of the WEB-INF/classes directory) should be attached to the project as an |
| * additional artifact. |
| * <p> |
| * By default the classifier for the additional artifact is 'classes'. You can change it with the |
| * <code><![CDATA[<classesClassifier>someclassifier</classesClassifier>]]></code> parameter. |
| * </p> |
| * <p> |
| * If this parameter true, another project can depend on the classes by writing something like: |
| * |
| * <pre> |
| * <![CDATA[<dependency> |
| * <groupId>myGroup</groupId> |
| * <artifactId>myArtifact</artifactId> |
| * <version>myVersion</myVersion> |
| * <classifier>classes</classifier> |
| * </dependency>]]> |
| * </pre> |
| * </p> |
| * |
| * @since 2.1-alpha-2 |
| */ |
| @Parameter(defaultValue = "false") |
| private boolean attachClasses; |
| |
| /** |
| * The classifier to use for the attached classes artifact. |
| * |
| * @since 2.1-alpha-2 |
| */ |
| @Parameter(defaultValue = "classes") |
| private String classesClassifier; |
| |
| /** |
| * You can skip the execution of the plugin if you need to. Its use is NOT RECOMMENDED, but quite convenient on |
| * occasion. |
| * |
| * @since 3.0.0 |
| */ |
| @Parameter(property = "maven.war.skip", defaultValue = "false") |
| private boolean skip; |
| |
| // ---------------------------------------------------------------------- |
| // Implementation |
| // ---------------------------------------------------------------------- |
| |
| /** |
| * Executes the WarMojo on the current project. |
| * |
| * @throws MojoExecutionException if an error occurred while building the webapp |
| * @throws MojoFailureException if an error. |
| */ |
| @Override |
| public void execute() throws MojoExecutionException, MojoFailureException { |
| |
| if (isSkip()) { |
| getLog().info("Skipping the execution."); |
| return; |
| } |
| |
| File warFile = getTargetWarFile(); |
| |
| try { |
| performPackaging(warFile); |
| } catch (DependencyResolutionRequiredException | ArchiverException e) { |
| throw new MojoExecutionException("Error assembling WAR: " + e.getMessage(), e); |
| } catch (ManifestException | IOException e) { |
| throw new MojoExecutionException("Error assembling WAR", e); |
| } |
| } |
| |
| /** |
| * Generates the webapp according to the {@code mode} attribute. |
| * |
| * @param warFile the target WAR file |
| * @throws IOException if an error occurred while copying files |
| * @throws ArchiverException if the archive could not be created |
| * @throws ManifestException if the manifest could not be created |
| * @throws DependencyResolutionRequiredException if an error occurred while resolving the dependencies |
| * @throws MojoExecutionException if the execution failed |
| * @throws MojoFailureException if a fatal exception occurred |
| */ |
| private void performPackaging(File warFile) |
| throws IOException, ManifestException, DependencyResolutionRequiredException, MojoExecutionException, |
| MojoFailureException { |
| getLog().info("Packaging webapp"); |
| |
| buildExplodedWebapp(getWebappDirectory()); |
| |
| MavenArchiver archiver = new MavenArchiver(); |
| |
| archiver.setArchiver(warArchiver); |
| |
| archiver.setCreatedBy("Maven WAR Plugin", "org.apache.maven.plugins", "maven-war-plugin"); |
| |
| archiver.setOutputFile(warFile); |
| |
| // configure for Reproducible Builds based on outputTimestamp value |
| archiver.configureReproducible(outputTimestamp); |
| |
| getLog().debug("Excluding " + Arrays.asList(getPackagingExcludes()) + " from the generated webapp archive."); |
| getLog().debug("Including " + Arrays.asList(getPackagingIncludes()) + " in the generated webapp archive."); |
| |
| warArchiver.addDirectory(getWebappDirectory(), getPackagingIncludes(), getPackagingExcludes()); |
| |
| final File webXmlFile = new File(getWebappDirectory(), "WEB-INF/web.xml"); |
| if (webXmlFile.exists()) { |
| warArchiver.setWebxml(webXmlFile); |
| } |
| |
| warArchiver.setRecompressAddedZips(isRecompressZippedFiles()); |
| |
| warArchiver.setIncludeEmptyDirs(isIncludeEmptyDirectories()); |
| |
| if (Boolean.FALSE.equals(failOnMissingWebXml) |
| || (failOnMissingWebXml == null && isProjectUsingAtLeastServlet30())) { |
| getLog().debug("Build won't fail if web.xml file is missing."); |
| warArchiver.setExpectWebXml(false); |
| } |
| |
| // create archive |
| archiver.createArchive(getSession(), getProject(), getArchive()); |
| |
| // create the classes to be attached if necessary |
| if (isAttachClasses()) { |
| if (isArchiveClasses() && getJarArchiver().getDestFile() != null) { |
| // special handling in case of archived classes: MWAR-240 |
| File targetClassesFile = getTargetClassesFile(); |
| FileUtils.copyFile(getJarArchiver().getDestFile(), targetClassesFile); |
| projectHelper.attachArtifact(getProject(), "jar", getClassesClassifier(), targetClassesFile); |
| } else { |
| ClassesPackager packager = new ClassesPackager(); |
| final File classesDirectory = packager.getClassesDirectory(getWebappDirectory()); |
| if (classesDirectory.exists()) { |
| getLog().info("Packaging classes"); |
| packager.packageClasses( |
| classesDirectory, |
| getTargetClassesFile(), |
| getJarArchiver(), |
| getSession(), |
| getProject(), |
| getArchive(), |
| outputTimestamp); |
| projectHelper.attachArtifact(getProject(), "jar", getClassesClassifier(), getTargetClassesFile()); |
| } |
| } |
| } |
| |
| if (this.classifier != null) { |
| projectHelper.attachArtifact(getProject(), "war", this.classifier, warFile); |
| } else { |
| Artifact artifact = getProject().getArtifact(); |
| if (primaryArtifact) { |
| artifact.setFile(warFile); |
| } else if (artifact.getFile() == null || artifact.getFile().isDirectory()) { |
| artifact.setFile(warFile); |
| } |
| } |
| } |
| |
| /** |
| * Determines if the current Maven project being built uses the Servlet 3.0 API (JSR 315) |
| * or Jakarta Servlet API. |
| * 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> |
| * or <code>jakarta.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 or Jakarta Servlet 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(); |
| } |
| URLClassLoader loader = new URLClassLoader(urls, Thread.currentThread().getContextClassLoader()); |
| try { |
| return hasWebServletAnnotationClassInClasspath(loader); |
| } finally { |
| try { |
| loader.close(); |
| } catch (IOException ex) { |
| // ignore |
| } |
| } |
| } |
| |
| private static boolean hasWebServletAnnotationClassInClasspath(ClassLoader loader) { |
| return hasClassInClasspath(loader, "javax.servlet.annotation.WebServlet") |
| || hasClassInClasspath(loader, "jakarta.servlet.annotation.WebServlet"); |
| } |
| |
| private static boolean hasClassInClasspath(ClassLoader loader, String clazz) { |
| try { |
| Class.forName(clazz, false, loader); |
| return true; |
| } catch (ClassNotFoundException e) { |
| return false; |
| } |
| } |
| |
| /** |
| * @param basedir The basedir |
| * @param finalName The finalName |
| * @param classifier The classifier. |
| * @param type The type. |
| * @return {@link File} |
| */ |
| protected static File getTargetFile(File basedir, String finalName, String classifier, String type) { |
| if (classifier == null) { |
| classifier = ""; |
| } else if (classifier.trim().length() > 0 && !classifier.startsWith("-")) { |
| classifier = "-" + classifier; |
| } |
| |
| return new File(basedir, finalName + classifier + "." + type); |
| } |
| |
| /** |
| * @return The war {@link File} |
| */ |
| protected File getTargetWarFile() { |
| return getTargetFile(new File(getOutputDirectory()), getWarName(), getClassifier(), "war"); |
| } |
| |
| /** |
| * @return The target class {@link File} |
| */ |
| protected File getTargetClassesFile() { |
| return getTargetFile(new File(getOutputDirectory()), getWarName(), getClassesClassifier(), "jar"); |
| } |
| |
| // Getters and Setters |
| |
| /** |
| * @return {@link #classifier} |
| */ |
| public String getClassifier() { |
| return classifier; |
| } |
| |
| /** |
| * @param classifier {@link #classifier} |
| */ |
| public void setClassifier(String classifier) { |
| this.classifier = classifier; |
| } |
| |
| /** |
| * @return The package excludes. |
| */ |
| public String[] getPackagingExcludes() { |
| if (packagingExcludes == null || packagingExcludes.isEmpty()) { |
| return new String[0]; |
| } else { |
| return StringUtils.split(packagingExcludes, ","); |
| } |
| } |
| |
| /** |
| * @param packagingExcludes {@link #packagingExcludes} |
| */ |
| public void setPackagingExcludes(String packagingExcludes) { |
| this.packagingExcludes = packagingExcludes; |
| } |
| |
| /** |
| * @return The packaging includes. |
| */ |
| public String[] getPackagingIncludes() { |
| if (packagingIncludes == null || packagingIncludes.isEmpty()) { |
| return new String[] {"**"}; |
| } else { |
| return StringUtils.split(packagingIncludes, ","); |
| } |
| } |
| |
| /** |
| * @param packagingIncludes {@link #packagingIncludes} |
| */ |
| public void setPackagingIncludes(String packagingIncludes) { |
| this.packagingIncludes = packagingIncludes; |
| } |
| |
| /** |
| * @return {@link #outputDirectory} |
| */ |
| public String getOutputDirectory() { |
| return outputDirectory; |
| } |
| |
| /** |
| * @param outputDirectory {@link #outputDirectory} |
| */ |
| public void setOutputDirectory(String outputDirectory) { |
| this.outputDirectory = outputDirectory; |
| } |
| |
| /** |
| * @return {@link #warName} |
| */ |
| public String getWarName() { |
| return warName; |
| } |
| |
| /** |
| * @param warName {@link #warName} |
| */ |
| public void setWarName(String warName) { |
| this.warName = warName; |
| } |
| |
| /** |
| * @return {@link #warArchiver} |
| */ |
| public WarArchiver getWarArchiver() { |
| return warArchiver; |
| } |
| |
| /** |
| * @param warArchiver {@link #warArchiver} |
| */ |
| public void setWarArchiver(WarArchiver warArchiver) { |
| this.warArchiver = warArchiver; |
| } |
| |
| /** |
| * @return {@link #projectHelper} |
| */ |
| public MavenProjectHelper getProjectHelper() { |
| return projectHelper; |
| } |
| |
| /** |
| * @param projectHelper {@link #projectHelper} |
| */ |
| public void setProjectHelper(MavenProjectHelper projectHelper) { |
| this.projectHelper = projectHelper; |
| } |
| |
| /** |
| * @return {@link #primaryArtifact} |
| */ |
| public boolean isPrimaryArtifact() { |
| return primaryArtifact; |
| } |
| |
| /** |
| * @param primaryArtifact {@link #primaryArtifact} |
| */ |
| public void setPrimaryArtifact(boolean primaryArtifact) { |
| this.primaryArtifact = primaryArtifact; |
| } |
| |
| /** |
| * @return {@link #attachClasses} |
| */ |
| public boolean isAttachClasses() { |
| return attachClasses; |
| } |
| |
| /** |
| * @param attachClasses {@link #attachClasses} |
| */ |
| public void setAttachClasses(boolean attachClasses) { |
| this.attachClasses = attachClasses; |
| } |
| |
| /** |
| * @return {@link #classesClassifier} |
| */ |
| public String getClassesClassifier() { |
| return classesClassifier; |
| } |
| |
| /** |
| * @param classesClassifier {@link #classesClassifier} |
| */ |
| public void setClassesClassifier(String classesClassifier) { |
| this.classesClassifier = classesClassifier; |
| } |
| |
| /** |
| * @return {@link #failOnMissingWebXml} |
| */ |
| public boolean isFailOnMissingWebXml() { |
| return failOnMissingWebXml; |
| } |
| |
| /** |
| * @param failOnMissingWebXml {@link #failOnMissingWebXml} |
| */ |
| public void setFailOnMissingWebXml(boolean failOnMissingWebXml) { |
| this.failOnMissingWebXml = failOnMissingWebXml; |
| } |
| |
| /** |
| * Skip the mojo run |
| * @return {@link #skip} |
| */ |
| public boolean isSkip() { |
| return skip; |
| } |
| } |