mavenmaven-war-pluginmaven-resources-plugin

How to modify the contents or replace a file which resides in webapp directory of a Maven webapp project


I've a Maven webapp project. It has a default directory structure as below -

 |-- pom.xml
 `-- src
     `-- main
         |-- java
         |   `-- com
         |       `-- example
         |           `-- projects
         |               `-- SampleAction.java
         |-- resources
         |   `-- images
         |       `-- sampleimage.jpg
         `-- webapp
             |-- WEB-INF
             |   `-- web.xml
             |-- index.jsp
             `-- jsp
                 `-- websource.jsp

The WAR file it generates has the structure as below which is again based on defaults -

  |-- META-INF
  |   |-- MANIFEST.MF
  |   `-- maven
  |       `-- com.example.projects
  |           `-- documentedproject
  |               |-- pom.properties
  |               `-- pom.xml
  |-- WEB-INF
  |   |-- classes
  |   |   |-- com
  |   |   |   `-- example
  |   |   |       `-- projects
  |   |   |           `-- SampleAction.class
  |   |   `-- images
  |   |       `-- sampleimage.jpg
  |   `-- web.xml
  |-- index.jsp
  `-- jsp
      `-- websource.jsp

I want to rename web-prod.xml to web.xml, which reside under webapp directory, before packaging into WAR. Now the catch is - contents of this directory are copied into target/<finalName> by the maven-war-plugin only during the packaging phase and immediately the WAR is generated.
Since webapp contents are copied by default by maven-war-plugin, other plugins like maven-compiler-plugin and maven-resources-plugin which come into picture early, don't have any role in modifying the said file.

pom.xml

<build>
    ...
    <plugins>
        ...
        <plugin>
            <artifactId>maven-antrun-plugin</artifactId>
            <executions>
                <execution>
                    <id>replace-descriptor</id>
                    <phase>compile</phase>
                    <goals>
                        <goal>run</goal>
                    </goals>
                    <configuration>
                        <target>
                            <delete file="${project.build.directory}/${project.finalName}/WEB-INF/web.xml" />
                            <move file="${project.build.directory}/${project.finalName}/WEB-INF/web-prod.xml" tofile="${project.build.directory}/${project.finalName}/WEB-INF/web.xml"/>
                        </target>
                    </configuration>
                </execution>
            </executions>
        </plugin>
        <plugin>
            <artifactId>maven-war-plugin</artifactId>
            <configuration>
                <archiveClasses>true</archiveClasses>
                <attachClasses>true</attachClasses>
                <warSourceIncludes>WEB-INF/**</warSourceIncludes>
                <packagingExcludes>WEB-INF/classes</packagingExcludes>
                <webResources>
                    <resource>
                        <directory>src/main/resources</directory>
                        <targetPath>WEB-INF/classes</targetPath>
                        <includes>
                            ...
                        </includes>
                    </resource>
                    <resource>
                        <directory>${project.build.directory}/classes</directory>
                        <includes>
                            ...
                        </includes>
                        <targetPath>WEB-INF/classes</targetPath>
                    </resource>
                </webResources>
            </configuration>
        </plugin>
    </plugins>
</build>

web.xml

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" metadata-complete="true" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
    <display-name>NSWeb</display-name>
    ...
    <filter>
        <filter-name>corsFilter</filter-name>
        <filter-class>com.example.core.security.filter.CORSFilter</filter-class>
    </filter>
    ...
</web-app>

web-prod.xml

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" metadata-complete="true" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
    <display-name>NSWeb</display-name>
    ...
    <filter>
        <filter-name>corsFilter</filter-name>
        <filter-class>com.example.core.security.filter.CORSFilter</filter-class>
        <init-param>
            <description>A comma separated list of allowed origins. Note: An '*' cannot be used for an allowed origin when using credentials.</description>
            <param-name>cors.enabled</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
            ...
        </init-param>
        <init-param>
            ...
        </init-param>
        <init-param>
            ...
        </init-param>
        ...
    </filter>
    ...
</web-app>

Solution

  • Thanks eis for pointing me to the filtering feature!
    Earlier I maintained two different web.xml files and was trying to replace one with the other since there was a difference of around 40 lines of block between those two files. I could achieve the end result by modifying the same and NOT by replacing it with the other. Here's the solution explained stepwise:

    1. Have a single web.xml file with a placeholder to be replaced with the block.
    2. Enable resource filtering for maven-war-plugin and use the profiles.
    3. Have two different properties files to store the target specific values for the placeholder.

    web.xml

    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" metadata-complete="true" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
        <display-name>NSWeb</display-name>
        ...
        <filter>
            <filter-name>corsFilter</filter-name>
            <filter-class>com.example.core.security.filter.CORSFilter</filter-class>
            @web.xml.cors.config@
        </filter>
        ...
    </web-app>
    

    pom.xml

    <build>
        ...
        <filters>
            <filter>src/main/resources/place-holders-${targetenv}.properties</filter>
        </filters>
        <plugins>
            
            <plugin>
                <artifactId>maven-war-plugin</artifactId>
                <configuration>
                    ...
                    <webResources>
                        ...
                        <resource>
                            <directory>${basedir}/src/main/webapp/WEB-INF</directory>
                            <filtering>true</filtering>
                            <targetPath>WEB-INF</targetPath>
                            <includes>
                                <include>**/web.xml</include>
                            </includes>
                        </resource>
                    </webResources>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <profiles>
        <profile>
            <id>dev</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <properties>
                <targetenv>dev</targetenv>
            </properties>
        </profile>
        <profile>
            <id>prod</id>
            <properties>
                <targetenv>prod</targetenv>
            </properties>
        </profile>
    </profiles>
    

    place-holders-dev.properties [just a blank/empty space for dev]

    web.xml.cors.config= 
    

    place-holders-prod.properties

    web.xml.cors.config=<init-param>\
    \n\t\t\t<description>A comma separated list of allowed origins. Note An '*' cannot be used for an allowed origin when using credentials.</description>\
    \n\t\t\t<param-name>cors.enabled</param-name>\
    \n\t\t\t<param-value>true</param-value>\
    \n\t\t</init-param>\
    \n\t\t<init-param>\
            ...
    \n\t\t</init-param>\
    \n\t\t<init-param>\
            ...
    \n\t\t</init-param>\
            ...
            ...