TomEE Maven plugin got recently a new feature allowing to use javascript (or groovy) to customize the TomEE instance you build using tomee:build goal.
Let’s see why and how to use this feature…
Why scripting instead of using all the customization provided by TomEE? They are several reasons for that but the most common will be:
- you read the configuration from a particular place to match your company/project standards
- you need to access external data to customize properly TomEE
- you need a feature which is not yet provided by TomEE
- you use a custom TomEE layout
- you need to access some custom logic to handle security (ciphering etc…)
- probably more since with scripting you have access to the whole JVM features 😉
The central point of the feature is to wire the JSR223 into TomEE Maven plugin. This is done using inline scripts in jsCustomizer(s) tag:
<plugin> <groupId>org.apache.tomee.maven</groupId> <artifactId>tomee-maven-plugin</artifactId> <version>7.0.0-SNAPSHOT</version> <configuration> <skipCurrentProject>true</skipCurrentProject> <jsCustomizers> <jsCustomizer> <![CDATA[ // here you script ]]> </jsCustomizer> </jsCustomizers> </configuration> </plugin>
Side note: CDATA allows to not care about special XML characters escaping writing the script.
A Sample
To illustrate it we’ll create a sample adding hibernate to TomEE and removing OpenJPA.
We’ll split out script in three main parts:
- the configuration (which libraries to add/remove)
- the utility (how to add/remove a library)
- the execution (linking both utility and configuration parts)
The configuration
We’ll keep the configuration easy and just store the list of coordinates to add and the list of prefix to remove:
var Config = { added: [ [ 'org.hibernate', 'hibernate-core', '5.0.2.Final' ], [ 'org.hibernate.common', 'hibernate-commons-annotations', '5.0.0.Final' ], [ 'org.jboss.logging', 'jboss-logging', '3.3.0.Final' ], [ 'org.javassist', 'javassist', '3.18.1-GA' ], [ 'antlr', 'antlr', '2.7.7' ], [ 'org.jboss', 'jandex', '1.2.2.Final' ], [ 'dom4j', 'dom4j', '1.6.1' ] ], removed: [ 'openjpa' ] };
Side note: keeping the configuration at the top of your script is a little tip to ensure it stays easy to update ;).
The utility part
We don’t need much as utility excepted:
- being able to resolve a dependency with maven from its coordinates
- being able to delete a file
- being able to copy a file
Since our script will use Nashorn we can just reuse java logic for the filesystem abstraction but how to handle dependency resolution? TomEE Maven Plugin provides out of the box a resolver
binding allowing to use maven to retrieve a dependency. It has several signatures (with type, with classifier etc..) but we only need the simplest one with the groupId, artifactId and version.
var Libs = (function () { var File = Java.type('java.io.File'); var Files = Java.type('java.nio.file.Files'); var StandardCopyOption = Java.type('java.nio.file.StandardCopyOption'); return { cp: function (coordinates) { // resolver.resolve(...coordinates...) and catalinaBase are provided and first one uses maven resolver 🙂 var resolved = resolver.resolve(coordinates[0], coordinates[1], coordinates[2]); Files.copy(resolved.toPath(), new File(catalinaBase, 'lib/' + resolved.getName()).toPath(), StandardCopyOption.REPLACE_EXISTING); }, rm: function (prefix) { var files = new File(catalinaBase, 'lib').listFiles(); for each (file in files) { // nashorn extension if (file.getName().indexOf(prefix) == 0) { file.delete(); } } } }; })();
Note: it is an immediately invoked function to hide java types used internally in the implementation. You can make it simpler if you desire and just move this code in the execution part.
If you know Files and File API there is nothing clever in this code which means it is easy to write and maintain even for a Java developpers. If not you can replace jsCustomizer
by groovyCustomizer
and add groovy-all
as a plugin dependency and you can just use groovy instead of javascript.
Execution
The execution is quite trivial since we just loop over our configurations and call Libs
method to execute the tasks:
for each (coordinate in Config.added) { Libs.cp(coordinate); } for each (prefix in Config.removed) { Libs.rm(prefix); }
Note: this for each syntax is specific to Nashorn.
All together
Just to make copy/paste easy I put there a whole pom.xml creating a TomEE with hibernate:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>demo</groupId> <artifactId>demo</artifactId> <version>1.0-SNAPSHOT</version> <build> <plugins> <plugin> <groupId>org.apache.tomee.maven</groupId> <artifactId>tomee-maven-plugin</artifactId> <version>7.0.0-SNAPSHOT</version> <configuration> <skipCurrentProject>true</skipCurrentProject> <jsCustomizers> <jsCustomizer> <![CDATA[ // put the config at the beginning of the script to update it quickly later if needed var Config = { added: [ [ 'org.hibernate', 'hibernate-core', '5.0.2.Final' ], [ 'org.hibernate.common', 'hibernate-commons-annotations', '5.0.0.Final' ], [ 'org.jboss.logging', 'jboss-logging', '3.3.0.Final' ], [ 'org.javassist', 'javassist', '3.18.1-GA' ], [ 'antlr', 'antlr', '2.7.7' ], [ 'org.jboss', 'jandex', '1.2.2.Final' ], [ 'dom4j', 'dom4j', '1.6.1' ] ], removed: [ 'openjpa' ] }; // utility function to add/rm a file resolved from maven to tomee/lib var Libs = (function () { var File = Java.type('java.io.File'); var Files = Java.type('java.nio.file.Files'); var StandardCopyOption = Java.type('java.nio.file.StandardCopyOption'); return { cp: function (coordinates) { // resolver.resolve(...coordinates...) and catalinaBase are provided and first one uses maven resolver 🙂 var resolved = resolver.resolve(coordinates[0], coordinates[1], coordinates[2]); Files.copy(resolved.toPath(), new File(catalinaBase, 'lib/' + resolved.getName()).toPath(), StandardCopyOption.REPLACE_EXISTING); }, rm: function (prefix) { var files = new File(catalinaBase, 'lib').listFiles(); for each (file in files) { // nashorn extension if (file.getName().indexOf(prefix) == 0) { file.delete(); } } } }; })(); // now just make the Config meet the utility methods (Libs) for each (coordinate in Config.added) { Libs.cp(coordinate); } for each (prefix in Config.removed) { Libs.rm(prefix); } ]]> </jsCustomizer> </jsCustomizers> </configuration> </plugin> </plugins> </build> </project>
Using skipCurrentProject
allows to build a custom TomEE without applications.