Skip to content

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 of default, info or debug. Defaults to maven's global setting; debug is also enabled by maven's -X switch.
  • skip: skips the goal. Defaults to true for projects with pom packaging, 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 specific jdk toolchain, 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 of META-INF), so that the ServiceFinder can locate the implementation at runtime, both in modular and classpath mode, or
  • writes its analysis result to the analyze directory 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 for test-analyze).
  • filesets: explicit file sets to process instead of sourceDir.
  • analyzeDir (wurbel.analyzeDir): where the analysis results are written. Defaults to ${project.build.directory}/analyze (.../test-analyze for test-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 implementing java.util.function.Function<String,String> with a no-args constructor. If the name starts with @, the converter is looked up via META-INF/services instead 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, overriding sourceDir.
  • 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 same groupId that 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:

  1. tentackle:analyze (and tentackle:test-analyze) in generate-sources — produces service descriptors and analysis metadata.
  2. The wurbelizer (wurbelizer-maven-plugin) in generate-sources — consumes the analysis metadata and weaves generated code into the guarded regions of the sources.
  3. 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