Tentackle Maven Plugin
The Tentackle Maven Plugin¶
The tentackle-maven-plugin is the central build-time helper of the Tentackle framework.
It bridges the gap between the Java sources of an application and the metadata Tentackle needs at
runtime. Most importantly, it drives the Service and Configuration API:
it scans the sources for Tentackle annotations and generates the META-INF service descriptors
that make services discoverable in both modular (JPMS) and classpath environments
(see Service and Configuration API).
Besides this core duty the plugin provides a handful of convenience goals to manage dependency
versions, derive computed maven properties, and generate BeanInfo manifests.
All goals share the prefix tentackle, so they can be invoked directly from the command line, e.g.
mvn tentackle:versions.
Prerequisites¶
The plugin requires the same JDK and Maven versions as the rest of the framework (JDK >= 25, Maven >= 3.9.0). It is platform independent.
Overview of Goals¶
| Goal | Default phase | Purpose |
|---|---|---|
analyze |
generate-sources |
Process Tentackle annotations in the main sources and generate service descriptors. |
test-analyze |
generate-test-sources |
Same as analyze, but for the test sources. |
properties |
generate-resources |
Derive a maven property from another property by running it through a converter. |
beaninfo |
generate-resources |
Build a JavaBean manifest from the *BeanInfo.java files of the project. |
versions |
validate (aggregator) |
List the versions of all 3rd-party dependencies as ready-to-paste XML properties. |
plugin-versions |
validate (aggregator) |
List the versions of all maven plugins as ready-to-paste XML properties. |
help |
— | Print plugin usage information (generated by the maven-plugin-plugin). |
Common Configuration¶
All goals inherit a small set of common parameters:
verbosity: one ofdefault,infoordebug. Defaults to maven's global setting; debug is also enabled by maven's-Xswitch.skip: skips the goal. Defaults totruefor projects withpompackaging, so the plugin can be configured once in a parent pom without acting on aggregator modules.charset: the encoding used to read and write files. Defaults to${project.build.sourceEncoding}.jdkToolchain: selects a specificjdktoolchain, overriding the one chosen by the maven-toolchain-plugin (only relevant for goals that run external tools).
The analyze Goal¶
This is the goal you will use in almost every Tentackle module. It runs an annotation processor over
the project's sources before the wurbelizer and the java compiler run, picking up all annotations
that are themselves annotated with org.tentackle.common.Analyze — most prominently @Service and
@MappedService.
For each such annotation the corresponding handler either:
- writes a service descriptor under
META-INF/services(or a Tentackle-specific subdirectory ofMETA-INF), so that theServiceFindercan locate the implementation at runtime, both in modular and classpath mode, or - writes its analysis result to the
analyzedirectory and/or places it on the heap to be picked up later by a wurblet during code generation.
Because Tentackle's service mapping is based on META-INF rather than on the JPMS
uses/provides declarations and java.util.ServiceLoader, this generation step is what makes the
same artifact work unchanged on the module path and on the classpath.
A typical configuration registers both the main and the test variant:
<plugin>
<groupId>org.tentackle</groupId>
<artifactId>tentackle-maven-plugin</artifactId>
<version>${project.version}</version>
<executions>
<execution>
<id>analyze</id>
<goals>
<goal>analyze</goal>
</goals>
</execution>
<execution>
<id>test-analyze</id>
<goals>
<goal>test-analyze</goal>
</goals>
</execution>
</executions>
<configuration>
<verbosity>info</verbosity>
<showCompileOutput>true</showCompileOutput>
<index>META-INF/RESOURCE-INDEX.LIST</index>
</configuration>
</plugin>
Important parameters:
sourceDir(wurbel.sourceDir): the sources to process. Defaults to${project.build.sourceDirectory}(resp. the test source directory fortest-analyze).filesets: explicit file sets to process instead ofsourceDir.analyzeDir(wurbel.analyzeDir): where the analysis results are written. Defaults to${project.build.directory}/analyze(.../test-analyzefortest-analyze). The wurbelizer reads the same directory, so the two plugins stay in sync.servicesDir(tentackle.serviceDir): where the generated service descriptors are written. Defaults to${project.build.directory}/generated-resources/services. This directory is added as a resource root, so the descriptors end up in the final artifact.index: if set, an additional index file listing all generated service resources is created (e.g.META-INF/RESOURCE-INDEX.LIST). This index speeds up service discovery in modular runtimes.showCompileOutput(tentackle.showCompileOutput): show the output of the internal compilation used during analysis.
The goal is incremental: sources whose analysis output is newer than the source file are skipped.
If the analysis compiler reports errors, a marker file is written to the analyze directory so that
subsequent build phases know the metadata may be incomplete.
Note: because the annotation processor compiles the sources internally, modules that define their own annotation processors usually disable annotation processing in the
maven-compiler-plugin(-proc:none) to avoid a chicken-and-egg problem.
The test-analyze Goal¶
Identical to analyze, but bound to generate-test-sources and operating on the test sources and
test classpath. It writes to ${project.build.directory}/test-analyze and
${project.build.directory}/generated-test-resources/services. Use it whenever your tests rely on
Tentackle services that are defined in test sources.
The properties Goal¶
Generates a maven property by passing the value of an input (typically another property) through a converter. The classic use case is encrypting a database password at build time so the plaintext never lands in the artifact:
<plugin>
<groupId>org.tentackle</groupId>
<artifactId>tentackle-maven-plugin</artifactId>
<version>${project.version}</version>
<executions>
<execution>
<goals>
<goal>properties</goal>
</goals>
</execution>
</executions>
<configuration>
<propertyDescriptors>
<propertyDescriptor>
<input>${dbPasswd}</input>
<converter>@org.tentackle.common.Cryptor</converter>
<property>encryptedPasswd</property>
</propertyDescriptor>
</propertyDescriptors>
</configuration>
</plugin>
Each propertyDescriptor has three elements:
input: the value to convert (any maven expression is allowed).converter: the fully qualified class name of a stateless singleton implementingjava.util.function.Function<String,String>with a no-args constructor. If the name starts with@, the converter is looked up viaMETA-INF/servicesinstead of being instantiated directly by its class name.property: the name of the generated maven property, which becomes available to the rest of the build.
Because the converter is loaded by the plugin, its artifact must be on the plugin's classpath — add
it to the plugin's <dependencies>. Converters are cached and therefore must be stateless.
The beaninfo Goal¶
Scans the sources for files ending in BeanInfo.java and generates a MANIFEST.MF that marks the
matching classes as JavaBeans (Java-Bean: True). This manifest is written to
${project.build.directory}/generated-resources/manifest/META-INF by default.
sourceDir(tentackle.sourceDir): the sources to scan, defaults to${project.build.sourceDirectory}.filesets: explicit file sets, overridingsourceDir.manifestDirectory(tentackle.manifestDirectory): the output directory for the manifest.
For every XxxBeanInfo.java a matching Xxx.java must exist or the build fails. Classes whose name
contains Abstract are skipped — those BeanInfo files are assumed to only carry shared defaults.
The versions Goal¶
An aggregator goal that walks the dependency graph of the whole reactor and prints the versions of
all 3rd-party dependencies as XML, ready to be pasted into the <properties> section of a pom. The
property names follow the pattern version.groupIdInCamelCase.artifactIdInCamelCase:
mvn -Dscope=compile tentackle:versions
...
[INFO] versions of 3rd-party dependencies for scope 'compile':
<version.orgOpenjfx.javafxBase>13.0.1</version.orgOpenjfx.javafxBase>
<version.orgOpenjfx.javafxControls>13.0.1</version.orgOpenjfx.javafxControls>
<version.orgSlf4j.slf4jApi>1.7.28</version.orgSlf4j.slf4jApi>
...
The artifacts produced by the reactor itself are excluded, so only true 3rd-party dependencies are listed. Parameters:
scope(scope): restrict the listing to a single dependency scope (compile,runtime, …). If omitted, dependencies of all scopes are listed.outputFile(outputFile): also write the result to a file. If the name ends with.properties, it is written in properties-file format, otherwise as XML.groupVersions(groupVersions): collapse artifacts of the samegroupIdthat share a version into a single shared group property (see the goal's javadoc for the exact semantics of positive and negative values).
The whole project must have been built successfully before invoking this goal, so that all versions can be resolved.
The plugin-versions Goal¶
The counterpart to versions for build plugins. It lists the versions of all maven plugins used
across the reactor, again as XML properties ready to be centralized in a parent pom:
mvn tentackle:plugin-versions
...
<version.orgApacheMavenPlugins.mavenCompilerPlugin>3.8.1</version.orgApacheMavenPlugins.mavenCompilerPlugin>
<version.orgApacheMavenPlugins.mavenJarPlugin>3.1.2</version.orgApacheMavenPlugins.mavenJarPlugin>
...
It shares the outputFile and groupVersions parameters with versions.
How It Fits Into the Build¶
For a normal Tentackle module, the build-time tool chain runs in this order:
tentackle:analyze(andtentackle:test-analyze) ingenerate-sources— produces service descriptors and analysis metadata.- The wurbelizer (
wurbelizer-maven-plugin) ingenerate-sources— consumes the analysis metadata and weaves generated code into the guarded regions of the sources. - The java compiler in
compile.
This ordering is why analyze and the wurbelizer share the same analyzeDir: the first produces the
metadata the second consumes.
Further Reading¶
- Service and Configuration API
- Wurbelizer — the code generation engine
- Plugin reference (generated)