Generating Elements in a persistence.xml File

Summary

The list-entity-classnames goal scans the test classpath, looking for JPA entities, mapped superclasses, embeddables and id classes. When it finds them, it installs the list of their names under one or more property names that are then available throughout the rest of the Maven build for filtering. The value of these properties is customizable and may be formatted in various ways.

Additionally, the list-entity-classnames goal can generate a .properties file in (usually) target/generated-test-sources/jpa-maven-plugin named (usually) entityClassnames.properties containing these properties. What you do with this file is up to you. This example shows the most common thing to do with this file and is divided into two parts.

The first part shows the mechanics of generating the .properties file. The second part shows how one might make use of this .properties file and Maven filtering.

Generating the .properties file

Generate the file using defaults

To use the defaults, which are sensible for most simple persistence units, just do this:

<plugin>
  <groupId>com.edugility</groupId>
  <artifactId>jpa-maven-plugin</artifactId>
  <version>3-SNAPSHOT</version>
  <executions>
    <execution>
      <id>Generate entityClassnames.properties</id>
      <goals>
        <goal>list-entity-classnames</goal>
      </goals>
    </execution>
  </executions>
</plugin>

This will generate an entityClassnames.properties file in ${project.build.directory}/generated-test-sources/jpa-maven-plugin that looks something like this:

entityClassnames = com.foobar.MyFirstEntity</class>\n<class>com.foobar.MySecondEntity</class>\n<class>com.foobar.MyThirdEntity

Customization

To customize this goal, make sure to supply a <configuration> element in your <execution> element or directly beneath your <plugin> element. See the Guide to Configuring Plug-ins for more general information.

Customizing what is scanned

By default, the list-entity-classnames goal scans the test classpath, which is normally that set up by the Maven Surefire plugin. This behavior can be customized by changing the set of URLs that is scanned.

To change the set of URLs that is scanned, add a <URLs> element to your configuration:

<plugin>
  <groupId>com.edugility</groupId>
  <artifactId>jpa-maven-plugin</artifactId>
  <version>3-SNAPSHOT</version>
  <executions>
    <execution>
      <id>Generate entityClassnames.properties</id>
      <goals>
        <goal>list-entity-classnames</goal>
      </goals>
      <configuration>
        <URLs>
          <URL>file:///foobar</URL>
          <URL>file:${project.build.outputDirectory}</URL>
        <URLs>
      </configuration>
    </execution>
  </executions>
</plugin>

Remember that you can (and probably should) use Maven variables here (like ${project.build.outputDirectory} or ${settings.localRepository}).

Also, remember that dependent .jar files (dependency artifacts) may contain some entities you need. By default these .jars are included in the test classpath.

There is usually very little reason, if any, to customize the set of URLs being scanned.

Customizing how the scan takes place

The jpa-maven-plugin makes use of the Scannotation library to locate classes that have certain annotations applied to them. You can customize the mechanism by which this scanning takes place by specifying the implementation of the AnnotationDB instance that performs the scanning:

<plugin>
  <groupId>com.edugility</groupId>
  <artifactId>jpa-maven-plugin</artifactId>
  <version>3-SNAPSHOT</version>
  <executions>
    <execution>
      <id>Generate entityClassnames.properties</id>
      <goals>
        <goal>list-entity-classnames</goal>
      </goals>
      <configuration>
        <db>
          <ignoredPackages>
            <ignoredPackage>junit</ignoredPackage>
            <ignoredPackage>java</ignoredPackage>
            <ignoredPackage>javax</ignoredPackage>
            <ignoredPackage>org.apache.maven</ignoredPackage>
            <ignoredPackage>org.apache.plexus</ignoredPackage>
          </ignoredPackages>
        </db>
      </configuration>
    </execution>
  </executions>
</plugin>

This customization relies on the rather sophisticated configuration mechanism brought to Maven by Plexus. Specifically, when a plugin parameter is an object, and not, say, a String or boolean, you can specify the implementation you would like Maven to use instead. You can configure that implementation using XML tags that represent its Java bean properties. So in the above example, we've caused the setIgnoredPackages method to be called on the AnnotationDB instance represented by the <db> parameter. It follows that you can customize any public property of the AnnotationDB used to perform annotation scanning.

Customize the .properties file contents
Changing the defaultPropertyName

You can change the default property name:

<plugin>
  <groupId>com.edugility</groupId>
  <artifactId>jpa-maven-plugin</artifactId>
  <version>3-SNAPSHOT</version>
  <executions>
    <execution>
      <id>Generate entityClassnames.properties</id>
      <goals>
        <goal>list-entity-classnames</goal>
      </goals>
      <configuration>
        <defaultPropertyName>allMyClasses</defaultPropertyName>
      </configuration>
    </execution>
  </executions>
</plugin>

The .properties file contents would now look like this:

allMyClasses = com.foobar.MyFirstEntity</class>\n<class>com.foobar.MySecondEntity</class>\n<class>com.foobar.MyThirdEntity
Changing how packages are allocated to property names

In the case of multiple persistence units you might find that you want certain classes listed under different persistence units. To accomplish this, your first step needs to be customizing the way that auto-discovered persistent classes are allocated to property names.

By default, all classes are allocated to the property named by the value of the defaultPropertyName parameter. Technically, this value is the name of a property to use when no other property name could be determined.

To do this property name allocation, you need to customize the internal Map used to index property names by package fragments. Here, for example, is a configuration that uses package fragments to list classes under different property keys. The keys to the map are package fragments, not simple strings. That means that com.foobar is a valid property key (assuming there is a com.foobar package defined somewhere) but com.foob is not (assuming that there is not a com.foob defined somewhere).

The search is carried out from most specific package fragment to most general:

<plugin>
  <groupId>com.edugility</groupId>
  <artifactId>jpa-maven-plugin</artifactId>
  <version>3-SNAPSHOT</version>
  <executions>
    <execution>
      <id>Generate entityClassnames.properties</id>
      <goals>
        <goal>list-entity-classnames</goal>
      </goals>
      <configuration>
        <defaultPropertyName>dumpingGroundClasses</defaultPropertyName>
        <propertyNames>
          <com.foobar.animal>animalClasses</com.foobar.animal>
          <com.foobar.people>peopleClasses</com.foobar.people>
          <com.foobar.vehicle>vehicleClasses</com.foobar.vehicle>
          <com.foobar>generalClasses</com.foobar>
          <com>otherClasses</com>
      </configuration>
    </execution>
  </executions>
</plugin>

The .properties file contents would look something like this:

dumpingGroundClasses = Rock.class</class>\n<class>org.fugly.FuglyEntity

animalClasses = com.foobar.animal.Chicken</class>\n<class>com.foobar.animal.Cow

peopleClasses = com.foobar.people.Person

vehicleClasses = com.foobar.vehicle.Truck</class>\n<class>com.foobar.vehicle.Car

otherClasses = com.bizbaw.SomeEntity
Changing the prefix and suffix

You may have noticed the <class> and </class> fragments decorating the property values. These are the prefix and suffix used to further add to the individual class names. Obviously whether you customize or eliminate these values depends on what you want to do with the .properties file. The defaults exist for the most common case of wanting to use this goal in conjunction with a persistence.xml file and Maven filtering (more on that later).

To change the prefix, provide the <prefix> parameter and the <suffix> with a value:

<plugin>
  <groupId>com.edugility</groupId>
  <artifactId>jpa-maven-plugin</artifactId>
  <version>3-SNAPSHOT</version>
  <executions>
    <execution>
      <id>Generate entityClassnames.properties</id>
      <goals>
        <goal>list-entity-classnames</goal>
      </goals>
      <configuration>
        <prefix>'Property name: '</prefix>
        <suffix>'
'</suffix>
      </configuration>
    </execution>
  </executions>
</plugin>

The contents of the .properties file will now look (somewhat uselessly) like this:

entityClassnames = com.foobar.simple.jpa.SimpleEntity\nProperty name \:com.edugility.simple.jpa.SimpleMappedSuperclass

You may notice the single quotes (') in the <prefix> and <suffix> elements. Due to http://jira.codehaus.org/browse/MODELLO-256, leading and trailing whitespace in Maven plugin configuration elements is ignored.

The list-entity-classnames goal works around this severe bug by stripping (matching) leading quotation marks (single or double) that occur as the first and last characters in the <prefix>, <suffix>, <firstItemPrefix> and <lastItemSuffix> elements. This allows you to "protect" whitespace. (Please note that the usual standard mechanisms (CDATA, xml:space attribute, etc.) do not work.) If there are no such quotation marks, or if the stripQuotes parameter is set to false, then no substitution is performed.

Changing the first and last prefix and suffix elements

In the example above, the first element, com.foobar.simple.jpa.SimpleEntity, does not have a prefix. You can set this prefix as well, and you can set the last item's suffix, too:

<plugin>
  <groupId>com.edugility</groupId>
  <artifactId>jpa-maven-plugin</artifactId>
  <version>3-SNAPSHOT</version>
  <executions>
    <execution>
      <id>Generate entityClassnames.properties</id>
      <goals>
        <goal>list-entity-classnames</goal>
      </goals>
      <configuration>
        <firstItemPrefix>'Here are the properties! Property name: '</firstItemPrefix>
        <prefix>'Property name: '</prefix>
        <suffix>'
'</suffix>
        <lastItemSuffix>'
And that's a wrap!'</lastItemSuffix>
      </configuration>
    </execution>
  </executions>
</plugin>

This will produce:

entityClassnames = Here are the properties! Property name\: com.foobar.simple.jpa.SimpleEntity\nProperty name \:com.edugility.simple.jpa.SimpleMappedSuperclass\nAnd that's a wrap!

As you might expect, the firstItemPrefix and lastItemSuffix govern the "wrapper" prefix and suffix--the textual decorations that are applied to the list as a whole.

In practice, you will rarely change any of these parameters--at least for JPA testing purposes.

Using the .properties file for Maven filtering

Create a src/test/resources/META-INF/persistence.xml file

Once you have a .properties file, the most productive thing to do with it is to use it to fill out the persistent class list that has to appear in your META-INF/persistence.xml file that you use for testing.

First, create this file in your src/test/resources directory. It should look something like this (modify what you need to; the important part is the ${entityClassnames} reference):

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
             xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  <persistence-unit name="test" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>

    <class>${entityClassnames}</class>

    <properties>
      <property name="javax.persistence.jdbc.driver" value="${testDatabaseDriverClass}" />
      <property name="javax.persistence.jdbc.url" value="${testDatabaseConnectionURL}" />
      <property name="javax.persistence.jdbc.user" value="${testDatabaseUser}" />
      <property name="javax.persistence.jdbc.password" value="${testDatabasePassword}" />

      <property name="hibernate.default_schema" value="${testDatabaseSchema}" />
      <property name="hibernate.id.new_generator_mappings" value="true"/>
      <property name="hibernate.show_sql" value="${hibernateShowSql}" />
      <property name="hibernate.format_sql" value="true" />
    </properties>
  </persistence-unit>
</persistence>

Please note that this is the persistence.xml file you'll use for testing, not the one you'll distribute with your application.

If you were to run mvn process-test-classes at this point, nothing very exciting would happen. Your .properties file would be generated, but it wouldn't be used in any way.

The Maven resource plugin's resources:resources goal needs to have all its filters present at the time it is invoked, or it fails. Since we can't even produce our .properties file until at least the process-test-classes phase, we can't meet this condition. So filtering using the preconfigured project resources is out of the question.

That's OK, though, because the Maven resources plugin also has a copy-resources goal. That goal is fully configurable. So we will make use of it, and invoke it after we've generated our .properties file. Then we'll set it up so that it copies and filters our META-INF/persistence.xml file to where it should go, and it will be as though our list of entity classnames was just another Maven property that we used for filtering.

To start with, we need to exclude our test persistence.xml from standard Maven resource copying. Edit your pom.xml so that the <build> section's <testResources> element looks like this:

  <testResources>
    <testResource>
      <directory>src/test/resources</directory>
      <filtering>true</filtering> <!-- if you want filtering on by default, obviously -->
      <excludes>
        <exclude>META-INF/persistence.xml</exclude>
      </excludes>
    </testResource>
  </testResources>

Next, we need to explicitly use the maven-resources-plugin, so add a stanza for that in your <build><plugins> area:

  <build>
    <plugins>

      <!-- various plugins, including the jpa-maven-plugin -->

      <!--
           ORDER IS IMPORTANT! The jpa-maven-plugin must come before
           the XML below.
      -->

      <plugin>
        <artifactId>maven-resources-plugin</artifactId>
        <version>2.5</version>
        <executions>
          <execution>
            <id>Copy persistence.xml filtered with generated entityClassnames.properties file</id>
            <phase>process-test-classes</phase>
            <goals>
              <goal>copy-resources</goal>
            </goals>
            <configuration>
              <filters>
                <filter>${project.build.directory}/generated-test-sources/jpa-maven-plugin/entityClassnames.properties</filter>
              </filters>
              <outputDirectory>${project.build.testOutputDirectory}/META-INF</outputDirectory>
              <resources>
                <resource>
                  <filtering>true</filtering>
                  <directory>src/test/resources/META-INF</directory>
                  <includes>
                    <include>persistence.xml</include>
                  </includes>
                </resource>
              </resources>
            </configuration>
          </execution>
        </executions>
      </plugin>

    </plugins>
  </build>

This bit of XML says that during the process-test-classes phase, Maven should copy the src/test/resources/META-INF/persistence.xml to target/test-classes/META-INF/persistence.xml and should filter it along the way with the contents of the entityClassnames.properties file we just generated.

A note on plugin ordering: Maven plugins within the same phase execute in the order that they are declared. So because the maven-resources-plugin goal we've configured above uses the output of the jpa-maven-plugin's list-entity-classnames goal, the configuration stanzas for the jpa-maven-plugin must appear first in your pom.xml. Alternatively, we could have configured the copy-resources goal to bind to the test phase, but that might run after the maven-surefire-plugin--which is going to run all our tests, after all--during the test phase. It's easier to put them both into test-classes and just to ensure that they're in the right order.