Unofficial Gradle Plugin for DataNucleus JPA and JDO Provider.
This is a follow-up to a blog post talking about performing build-time enhancement with both DataNucleus and Gradle Ant Tasks.
Heavily inspired by the official DataNucleus Maven Plugin, this one defines a Gradle plugin for introducing DataNucleus specific tasks and capabilities into an end-user Gradle Project build.
Currently the only capabilities added are for bytecode enhancement of the user domain model and schema operations, although other capabilities are planned.
Getting Started
This plugin is published to the Gradle Plugin Portal.
- Build script snippet for plugins DSL for Gradle 2.1 and later:
Grab via Gradle, by applying the plugin (and configure it) in your build.gradle
:
plugins {
id "org.rm3l.datanucleus-gradle-plugin" version "2.0.0"
}
- Build script snippet for use in older Gradle versions or where dynamic configuration is required:
buildscript {
repositories {
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath "org.rm3l:datanucleus-gradle-plugin:2.0.0"
}
}
apply plugin: "org.rm3l.datanucleus-gradle-plugin"
Note that this plugin automatically applies the Gradle JavaPlugin, as this is a prerequisite here.
Tasks
This plugin aims at supporting the same set of operations provided by the official DataNucleus Maven Plugin:
- Bytecode Enhancement
- SchemaTool
Applying this plugin automatically applies the Java Plugin (if not already the case) and adds the following tasks to your project:
- Enhancement tasks
enhance
: to enhance classes from the main source set. Run automatically during the build since theclasses
task depends on it.testEnhance
: to enhance classes from the test source set. Run automatically during the build since thetestClasses
task depends on it.enhanceCheck
: to check the main classes for enhancement statustestEnhanceCheck
: to check the test classes for enhancement status
- Schema Tool tasks
createDatabase
: to create the specified database (catalog/schema) if the datastore supports that operationdeleteDatabase
: to delete the specified database (catalog.schema) if the datastore supports that operationcreateDatabaseTables
: to create all database tables required for the classes defined by the input datadeleteDatabaseTables
: to delete all database tables required for the classes defined by the input datavalidateDatabaseTables
: to validate all database tables required for the classes defined by the input datadeleteThenCreateDatabaseTables
: delete all database tables required for the classes defined by the input data, then create the tablesdbinfo
: provide detailed information about the database, its limits and datatypes support. Only for RDBMS currentlyschemainfo
: provide detailed information about the database schema. Only for RDBMS currently
You can see the exhaustive list of tasks by issuing the following command from the root of your project:
./gradlew tasks
To see the runtime options of a particular tasks (say createDatabaseTables
):
./gradlew -q help --task createDatabaseTables
Bytecode Enhancement
A noteworthy behavior of most JPA providers is to “enhance” the domain JPA classes. This technique, also known as weaving, allows to modify the resulting bytecode of your domain classes, in order to essentially add the following capabilities:
- lazy state initialization
- object dirty state tracking, i.e. the ability to track object updates (including collections/maps mutations), and translate such updates into JPQL DML queries, which are translated into database-specific SQL queries
- automatic bi-directional mapping, i.e., ensuring that both sides of a relationship are set properly
- optionally, performance optimizations
Some providers have chosen to require all domain classes to be enhanced before any use. This means that enhancement has to be done beforehand at build time, or at any time between compile time and packaging time. It is still possible to do it at run-time, but this requires using an appropriate ClassLoader to make sure the enhanced classes are effectively being used.
On the other hand, other JPA providers do not make enhancement a mandatory prerequisite, and can do it on-the-fly at run-time. They still allow to perform enhancement at build time, but this may not be the default behavior.
Performing bytecode enhancement at build time clearly has a performance benefit over the use of slow proxies or reflection that might be done at run-time.
This plugin supports build-time enhancement by providing Gradle tasks wrapped around the official DataNucleus enhancer. The benefit is that the enhanced classes are what gets added to the final built Jar artifact.
To use the plugin in your Gradle project, after applying it as depicted above, you need to configure it, e.g.:
datanucleus {
enhance {
api 'JPA'
persistenceUnitName 'myPersistenceUnit'
//... other options are possible
}
//
testEnhance { //'testEnhance' task has exactly the same options as the 'enhance' one above
api 'JPA'
persistenceUnitName 'myTestPersistenceUnit'
//...
}
}
This provides you with the following set of additional tasks:
enhance
: to enhance classes from the main source set. Run automatically during the build since theclasses
task depends on it.testEnhance
: to enhance classes from the test source set. Run automatically during the build since thetestClasses
task depends on it.enhanceCheck
: to check the main classes for enhancement statustestEnhanceCheck
: to check the test classes for enhancement status
All those tasks support the same set of enhancement options as in the official datanucleus-maven-plugin, i.e.:
Property | Default value | Description |
---|---|---|
persistenceUnitName |
- | Name of the persistence-unit to enhance. Mandatory |
log4jConfiguration |
- | Config file location for Log4J (if using it) |
jdkLogConfiguration |
- | Config file location for JDK1.4 logging (if using it) |
api |
JDO |
API to enhance to (JDO or JPA ). Mandatory |
verbose |
false |
Verbose output? |
quiet |
false |
No output? |
targetDirectory |
- | Where the enhanced classes are written (default is to overwrite them) |
generatePK |
true |
Generate a PK class (of name {MyClass}_PK ) for cases where there are multiple PK fields yet no IdClass is defined. |
generateConstructor |
true |
Generate a default constructor if not defined for the class being enhanced. |
detachListener |
false |
Whether to enhance classes to make use of a detach listener for attempts to access an un-detached field. |
ignoreMetaDataForMissingClasses |
false |
Whether to ignore when we have metadata specified for classes that are not found (e.g in orm.xml) |
skip |
false |
Whether to skip execution |
Note that by default, the classes
task
is automatically marked as depending on the enhance
task, so that the latter is automatically run when you run a build.
Similarly, the testClasses
task
is automatically marked as depending on the testEnhance
task.
This way, your resulting artifacts will contain the enhanced classes.
You can also perform enhancement at runtime by running the task and passing its various options. For example, below is the help menu of the enhance
task:
❯ ./gradlew -q help --task enhance
Detailed task information for enhance
Path
:enhance
Type
EnhanceTask (org.rm3l.datanucleus.gradle.tasks.enhance.EnhanceTask)
Options
--api API to enhance to (JDO or JPA). Mandatory. Default is JDO.
Available values are:
JDO
JPA
--detach-listener Whether to enhance classes to make use of a detach listener for attempts to access an un-detached field.
--generate-constructor Whether to generate a default constructor if not defined for the class being enhanced.
--generate-pk Whether to generate a PK class (of name {MyClass}_PK) for cases where there are multiple PK fields yet no IdClass is defined.
--ignore-metadata-for-missing-classes Whether to ignore when we have metadata specified for classes that are not found (e.g in orm.xml)
--jdk-log-conf Config file location for JDK logging (if using it)
--log4j-conf Config file location for Log4J (if using it)
--persistence-unit-name Name of the persistence-unit to enhance. Mandatory
--quiet Whether to be quiet or not
--skip Whether to skip execution
--target-directory Where the enhanced classes are written (default is to overwrite them)
--verbose Whether to be verbose or not
Description
Performs enhancement of the main classes.
Group
DataNucleus Enhancement
So for example, we can issue the following command to enhance classes part of a given persistence unit (named ‘myPersistenceUnit’):
./gradlew enhance --api JPA --persistence-unit-name myPersistenceUnit
SchemaTool
This plugin works hand-by-hand with DataNucleus SchemaTool, which currently works with RDBMS, HBase, Excel, OOXML, ODF, MongoDB, Cassandra datastores.
To use the plugin in your Gradle project, after applying it as depicted above, you need to configure it, e.g.:
datanucleus {
schemaTool {
api 'JPA'
persistenceUnitName 'myPersistenceUnit'
//... other options are possible
}
}
Configuring the DSL provides you with the following set of SchemaTool tasks:
createDatabase
: to create the specified database (catalog/schema) if the datastore supports that operationdeleteDatabase
: to delete the specified database (catalog.schema) if the datastore supports that operationcreateDatabaseTables
: to create all database tables required for the classes defined by the input datadeleteDatabaseTables
: to delete all database tables required for the classes defined by the input datavalidateDatabaseTables
: to validate all database tables required for the classes defined by the input datadeleteThenCreateDatabaseTables
: delete all database tables required for the classes defined by the input data, then create the tablesdbinfo
: provide detailed information about the database, its limits and datatypes support. Only for RDBMS currentlyschemainfo
: provide detailed information about the database schema. Only for RDBMS currently
All those tasks support the same set of options as in the official DataNucleus Maven Plugin, i.e.:
Property | Default value | Description |
---|---|---|
persistenceUnitName |
- | Name of the persistence-unit to enhance. Mandatory |
log4jConfiguration |
- | Config file location for Log4J (if using it) |
jdkLogConfiguration |
- | Config file location for JDK1.4 logging (if using it) |
api |
JDO |
API to enhance to (JDO or JPA ). Mandatory |
catalogName |
- | Name of the catalog. Mandatory when using createDatabase or deleteDatabase options |
schemaName |
- | Name of the schema. Mandatory when using createDatabase or deleteDatabase options |
verbose |
false |
Verbose output? |
quiet |
false |
No output? |
completeDdl |
false |
Whether to generate DDL including things that already exist? (for RDBMS) |
ddlFile |
- | Name of an output file to dump any DDL to (for RDBMS) |
ignoreMetaDataForMissingClasses |
false |
Whether to ignore when we have metadata specified for classes that are not found (e.g in orm.xml) |
skip |
false |
Whether to skip execution |
Like for the Enhancement tasks depicted above, you can also perform SchemaTool operations at runtime by running the task and passing its various options.
For example, below is the help menu of the createDatabaseTables
task:
./gradlew -q help --task createDatabaseTables
Detailed task information for createDatabaseTables
Path
:createDatabaseTables
Type
CreateDatabaseTablesTask (org.rm3l.datanucleus.gradle.tasks.schematool.CreateDatabaseTablesTask)
Options
--api API to enhance to (JDO or JPA). Mandatory. Default is JDO.
Available values are:
JDO
JPA
--catalog-name Catalog Name
--complete-ddl Whether to consider complete DDL or not
--ddl-file Path to DDL file
--ignore-metadata-for-missing-classes Whether to ignore when we have metadata specified for classes that are not found (e.g in orm.xml)
--jdk-log-conf Config file location for JDK logging (if using it)
--log4j-conf Config file location for Log4J (if using it)
--persistence-unit-name Name of the persistence-unit to enhance. Mandatory
--schema-name Schema Name
--skip Whether to skip execution
--verbose Whether to be verbose or not
Description
Creates all database tables required for the classes defined by the input data.
Group
DataNucleus SchemaTool
Contribution Guidelines
Contributions and issue reporting are more than welcome. So to help out, do feel free to fork this repo and open up a pull request. I’ll review and merge your changes as quickly as possible.
You can use GitHub issues to report bugs. However, please make sure your description is clear enough and has sufficient instructions to be able to reproduce the issue.
Source Code Layout
Source Code is organized as much as possible per the official Gradle conventions, as follows:
buildSrc
: the actual code of the Plugin, along with its own unit, integration and functional testssample-jdo
: sample JDO project serving as a reference project that can be used to test and play with the pluginsample-jpa
: sample JPA project serving as a reference project that can be used to test and play with the pluginsample-jpa-multiproject
: sample multi-module Gradle project serving as a reference project that can be used to test and play with the plugin
Please note that Jacoco coverage metrics displayed here are reported against the plugin code solely.
Building from source
This can be built as a standard Gradle Project, by issuing the following command:
./gradlew build
This command automatically builds the plugin code (from the buildSrc
folder) and then continues with the sample project.
Publishing the plugin
All releases of this plugin ought to be published to the Gradle Plugin Portal.
To publish the plugin, you must have the appropriate rights on the Gradle Plugin Portal.
Additionally, the key and secret credentials for publishing are recommended to be put on your
local machine in your ${HOME}/.gradle/gradle.properties
, which should contain the following two properties:
gradle.publish.key
: the API key for publishing, which you can grab from the Gradle Plugin Portal “API Keys” sectiongradle.publish.secret
: the API secret for publishing, which you can grab from the Gradle Plugin Portal “API Keys” section
Publishing the plugin is then as easy as calling the publishPlugins
task from the buildSrc
folder:
../gradlew publishPlugins
Alternatively, you may publish the plugin right from the command-line:
../gradlew publishPlugins -Pgradle.publish.key=<apiKey> -Pgradle.publish.secret=<apiSecret>
Do not forget to make and push the corresponding tag afterwards if needed.
Credits / Inspiration
Developed by
- Armel Soro
License
The MIT License (MIT)
Copyright (c) 2018-2021 Armel Soro
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.