Kohei Nozaki's blog 

Building environment specific artifacts with classifier


Posted on Friday Mar 20, 2015 at 03:09PM in Maven


Consider following multi-module Maven project:

  • classifier: The parent & aggregator project

  • persistence: A jar project which holds JPA entities and a persistence descriptor (persistence.xml)

  • web: A war project which depends on persistence project

persistence project contains following persistence descriptor:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
    <persistence-unit name="myPU">
        <jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
        <properties>
            <property name="javax.persistence.schema-generation.database.action"
                      value="${javax.persistence.schema-generation.database.action}"/>
        </properties>
    </persistence-unit>
</persistence>

I need to set javax.persistence.schema-generation.database.action property as drop-and-create for development environment, but none for production environment. in such case, using profiles and filtering might be a solution, but its shortcoming is that we can hold only one (among environment specific builds) artifact in the local repository because these builds has same coordinate. it may bring unexpected result such as deploying development build to the production environment by some accident. in such case, using classifier would be a better solution.

Preparation

First, let’s enable resource filtering in persistence project.

<build>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <filtering>true</filtering>
        </resource>
    </resources>

    <plugins>
        <plugin>
            <artifactId>maven-resources-plugin</artifactId>
            <executions>
                <!-- Filter and copy resources under src/main/resources into target/classes (default location) -->
                <execution>
                    <id>default-resources</id>
                    <phase>process-resources</phase>
                    <goals>
                        <goal>resources</goal>
                    </goals>
                    <configuration>
                        <filters>
                            <filter>${basedir}/filters/dev.properties</filter>
                        </filters>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

Then put filters/dev.properties:

javax.persistence.schema-generation.database.action=drop-and-create

Set a dependency in web project:

<dependencies>
    <dependency>
        <groupId>org.nailedtothex.examples.classifier</groupId>
        <artifactId>persistence</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
</dependencies>

Add configurations for producing artifacts for production

Now we can make artifacts that holds filtered persistence.xml for development environment. next, let’s add configurations for producing artifacts for production environment with prod classifier.

Put following profile definition into persistence project to make the project to produce both of development and production (with prod classifier) artifacts:

<profile>
    <id>prod</id>
    <properties>
        <filteredResources>target/filtered-classes</filteredResources>
    </properties>
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-resources-plugin</artifactId>
                <executions>
                    <!-- Filter and copy resources under src/main/resources into target/filtered-classes/prod -->
                    <execution>
                        <id>prod-resources</id>
                        <phase>process-resources</phase>
                        <goals>
                            <goal>resources</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${filteredResources}/prod</outputDirectory>
                            <filters>
                                <filter>${basedir}/filters/prod.properties</filter>
                            </filters>
                        </configuration>
                    </execution>
                    <!-- Copy classes under target/classes into target/filtered-classes/prod -->
                    <!-- Existing files will not be overwritten. -->
                    <!-- see http://maven.apache.org/plugins/maven-resources-plugin/resources-mojo.html#overwrite -->
                    <execution>
                        <id>copy-classes-prod</id>
                        <phase>process-classes</phase>
                        <goals>
                            <goal>copy-resources</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${filteredResources}/prod</outputDirectory>
                            <resources>
                                <resource>
                                    <directory>${project.build.outputDirectory}</directory>
                                    <filtering>false</filtering>
                                </resource>
                            </resources>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <artifactId>maven-jar-plugin</artifactId>
                <executions>
                    <!-- Create the production jar with files inside target/filtered-classes/prod  -->
                    <execution>
                        <id>prod-jar</id>
                        <phase>package</phase>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                        <configuration>
                            <classifier>prod</classifier>
                            <classesDirectory>${filteredResources}/prod</classesDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</profile>

Also put filters/prod.properties:

javax.persistence.schema-generation.database.action=none

Issue following command:

$ mvn clean install -P prod

Result:

...
[INFO] --- maven-jar-plugin:2.6:jar (default-jar) @ persistence ---
[INFO] Building jar: /Users/kyle/src/classifier/persistence/target/persistence-1.0-SNAPSHOT.jar
[INFO]
[INFO] --- maven-jar-plugin:2.6:jar (prod-jar) @ persistence ---
[INFO] Building jar: /Users/kyle/src/classifier/persistence/target/persistence-1.0-SNAPSHOT-prod.jar
[INFO]
[INFO] --- maven-install-plugin:2.4:install (default-install) @ persistence ---
[INFO] Installing /Users/kyle/src/classifier/persistence/target/persistence-1.0-SNAPSHOT.jar to /Users/kyle/.m2/repository/org/nailedtothex/examples/classifier/persistence/1.0-SNAPSHOT/persistence-1.0-SNAPSHOT.jar
[INFO] Installing /Users/kyle/src/classifier/persistence/pom.xml to /Users/kyle/.m2/repository/org/nailedtothex/examples/classifier/persistence/1.0-SNAPSHOT/persistence-1.0-SNAPSHOT.pom
[INFO] Installing /Users/kyle/src/classifier/persistence/target/persistence-1.0-SNAPSHOT-prod.jar to /Users/kyle/.m2/repository/org/nailedtothex/examples/classifier/persistence/1.0-SNAPSHOT/persistence-1.0-SNAPSHOT-prod.jar
...

You can see both of artifacts are installed as expected:

$ unzip -p /Users/kyle/.m2/repository/org/nailedtothex/examples/classifier/persistence/1.0-SNAPSHOT/persistence-1.0-SNAPSHOT.jar META-INF/persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
    <persistence-unit name="myPU">
        <jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
        <properties>
            <property name="javax.persistence.schema-generation.database.action"
                      value="drop-and-create"/>
        </properties>
    </persistence-unit>
</persistence>

$ unzip -p /Users/kyle/.m2/repository/org/nailedtothex/examples/classifier/persistence/1.0-SNAPSHOT/persistence-1.0-SNAPSHOT-prod.jar META-INF/persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
    <persistence-unit name="myPU">
        <jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
        <properties>
            <property name="javax.persistence.schema-generation.database.action"
                      value="none"/>
        </properties>
    </persistence-unit>
</persistence>

So what is needed for web project? put following profile as well:

<profile>
    <id>prod</id>
    <dependencies>
        <dependency>
            <groupId>org.nailedtothex.examples.classifier</groupId>
            <artifactId>persistence</artifactId>
            <version>1.0-SNAPSHOT</version>
            <classifier>prod</classifier>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-war-plugin</artifactId>
                <executions>
                    <execution>
                        <id>default-war</id>
                        <phase>package</phase>
                        <goals>
                            <goal>war</goal>
                        </goals>
                        <configuration>
                            <packagingExcludes>WEB-INF/lib/persistence-1.0-SNAPSHOT-prod.jar</packagingExcludes>
                        </configuration>
                    </execution>
                    <execution>
                        <id>prod-war</id>
                        <phase>package</phase>
                        <goals>
                            <goal>war</goal>
                        </goals>
                        <configuration>
                            <classifier>prod</classifier>
                            <packagingExcludes>WEB-INF/lib/persistence-1.0-SNAPSHOT.jar</packagingExcludes>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</profile>

Then you will get both of artifacts installed after issuing mvn clean install -P prod as follows:

$ unzip -l /Users/kyle/.m2/repository/org/nailedtothex/examples/classifier/web/1.0-SNAPSHOT/web-1.0-SNAPSHOT.war | grep persistence
     3521  03-20-15 14:25   WEB-INF/lib/persistence-1.0-SNAPSHOT.jar
$ unzip -l /Users/kyle/.m2/repository/org/nailedtothex/examples/classifier/web/1.0-SNAPSHOT/web-1.0-SNAPSHOT-prod.war | grep persistence
     3513  03-20-15 14:25   WEB-INF/lib/persistence-1.0-SNAPSHOT-prod.jar