Skip to content

Tentackle Common — The Foundation Module

Overview and Motivation

The tentackle-common module is the root of the entire Tentackle stack. Its package-info sums it up in one line — "Common classes for build- and runtime" — and that dual role is the key to understanding it. Unlike every other module, tentackle-common is used in two completely different contexts:

  1. At runtime, by every Tentackle application, as the lowest-level utility and SPI layer.
  2. At build time, by the Tentackle Maven plugins and the Wurbelizer, which need the same annotations, naming rules, money/date types, and the service-discovery primitives while generating code and analyzing annotations.

Because it must work everywhere, tentackle-common is deliberately dependency-free: it requires only java.sql and java.compiler from the JDK and nothing else from Tentackle. Everything else in the framework depends, directly or transitively, on this module.

Its single most important responsibility is the Service and Configuration API — Tentackle's replacement for java.util.ServiceLoader that powers dependency injection, component replacement, and build-time annotation analysis throughout the framework. That subsystem is large enough to have its own document; this page gives the whole-module picture and points to it.

See also: services.md — the in-depth guide to the @Service / @MappedService annotations, the ServiceFinder/ServiceFactory, build-time @Analyze processing, the ModuleHook mechanism, and OSGi.

Design principles

  • Zero framework dependencies. Nothing in Tentackle sits below this module, so it cannot depend on anything in Tentackle. It leans only on the JDK.
  • Build-time and runtime. Types here are shared by the running application and by the Maven plugins/wurblets that build it, which guarantees that, e.g., a BMoney or a @ClassId means the same thing in both worlds.
  • Annotation-driven, not reflection-at-startup. Component discovery is based on annotations analyzed at build time and recorded under META-INF, avoiding classpath scanning and minimizing startup cost.
  • Classpath and module-path. All mechanisms work identically in traditional classpath mode and in JPMS modular (including jlink'd) mode, via the ModuleHook indirection.

Key Concepts

Service and Configuration API

This is the heart of the module and is documented fully in services.md. In brief:

  • Service / ServiceName / MappedService — the annotations that register an implementation (a simple service in META-INF/services, or a mapped service in META-INF/mapped-services that relates a property → class → value).
  • ServiceFinder / DefaultServiceFinder / ServiceFinderKey — locate service configurations, preserving module-dependency order (the first URL belongs to the topmost module; the classpath comes last).
  • ServiceFactory — creates services and finders; a richer replacement for ServiceLoader (any configuration type, per-service classloaders, modular plus non-modular). It is itself loadable via ServiceLoader and therefore replaceable.
  • Analyze / AnnotationProcessor — the meta-annotation that ties an annotation to a build-time AnalyzeHandler (named by string, so the application never depends on the handler at runtime).
  • ModuleHook / ModuleInfo / ModuleOrdinal / ModuleSorter — determine and topologically sort the module hierarchy (needed for resource bundles and for jlink'd images where module dependencies are otherwise not discoverable).
  • ClasspathFirst — override a module-path service from the classpath.
  • RemoteMethod / RecordDTO — annotations consumed by wurblets/analysis to make handwritten methods remoting-capable and to mark records for code generation.

Database-aware data types

Tentackle ships its own value types whose defining feature is stable database/serialization semantics across timezones and optional immutability. Most are usable directly as model attribute types (mapped by tentackle-sql data types).

  • BMoney — a money value derived from BigDecimal with a fixed, unchangeable scale; stored as a DOUBLE value + INTEGER scale.
  • DMoney — like BMoney, but stored as a DECIMAL (scale 0) + INTEGER scale for exact decimal storage.
  • Date — a date with database semantics: serialized as YYYYMMDD so it stays the same calendar date in every timezone (unlike java.sql.Date, which serializes epochal time).
  • Time — a time with database semantics.
  • Timestamp — a timestamp with an optional UTC flag (the model's [UTC] option) that keeps it fixed to a timezone by convention rather than converting on serialization.
  • Binary — stores a serializable object as a blob-like byte array using Java Object Serialization, but via binary-stream methods so it stays valid outside a transaction (unlike real blobs).
  • TBinary — same as Binary but uses the pluggable ObjectSerializer (by default TRIP's serializer), so the object need not implement Serializable. AbstractBinary is the shared base.
  • I18NText — a multilingual string usable as a model attribute (org.tentackle.sql.datatypes.I18NTextType); implements CharSequence/Comparable for the current locale.
  • Freezable — the interface behind the "frozen" (made-immutable) capability of the date/time and binary types. See freezable.md for why it exists and how it makes old-school mutable types like java.util.Date safe as entity attributes.

Object serialization

ObjectSerializer abstracts generic object serialization; DefaultObjectSerializer is the JOS-based default. This is the seam that lets TBinary (and other framework code) serialize via TRIP instead of plain Java serialization when tentackle-core is present.

Security and configuration

  • Cryptor — a simple symmetric en/decryptor. An application provides its own @Service(Cryptor.class) subclass with a confidential salt/passphrase; it is used to encrypt passwords in memory, during client/server login, and in backend.properties.
  • EncryptedProperties — a Properties variant where values starting with ~ are decrypted via the Cryptor (with \ escaping and optional case-insensitive keys). This is the configuration format used for backend connection settings.
  • Settings — global framework settings.
  • Constants — shared build- and runtime constants.
  • Version — Tentackle version information.

Internationalization and resource bundles

Resource-bundle handling differs between classpath and modular mode, so the framework routes it through its own factory:

Helper utilities

A set of final static-method helpers and small utilities used throughout build and runtime:

External tools

  • ToolFinder — locates external executables.
  • ToolRunner — runs external tools (used by build tooling, e.g., jlink/jpackage style invocations).

Exceptions


Package Layout

Package Contents
org.tentackle.common Everything: the service/configuration API, the database-aware data types, object serialization, the Cryptor/EncryptedProperties security layer, i18n/bundle support, the helper utilities, external-tool support and the base exceptions.
org.tentackle.common.service The Hook (ModuleHook) for this module.

How It Fits Together

tentackle-common underpins the whole framework in two directions:

   Build time                              Runtime
   ──────────                              ───────
   tentackle-maven-plugin   ┐         ┌  every Tentackle module
   tentackle-sql-maven-…    ├─ use ──►│  (core, session, sql, database,
   Wurbelizer wurblets      ┘         │   pdo, domain, persistence, fx, …)
        │                             │
        └──────► tentackle-common ◄───┘
                 (annotations, ServiceFinder, money/date types,
                  naming rules, Cryptor, helpers — no deps)
  • At build time, the Maven plugins read the @Analyze-meta-annotated annotations (@Service, @ClassId, @TableName, …) from source, run their AnalyzeHandlers, and emit configuration files under META-INF. The same money/date/naming types are available, so the generated code is consistent.
  • At runtime, application code obtains implementations through ServiceFactory.createService(...) / ServiceFactory.getServiceFinder(), which read those META-INF files in module-dependency order — working in both classpath and modular mode thanks to the ModuleHooks every module provides.

A minimal application only needs a dependency on tentackle-common plus the tentackle-maven-plugin to use the service API; higher-level utilities (e.g., the reflection-based class mappers) additionally require tentackle-core.


Module Dependencies

tentackle-common is the bottom of the stack:

  • It requires transitive only java.sql and java.compiler from the JDK — and nothing from Tentackle.
  • It uses org.tentackle.common.ModuleHook (to discover other modules' META-INF contributions) and org.tentackle.common.ServiceFactory (so an application can replace the factory itself).
  • It opens org.tentackle.common to org.tentackle.core for TRIP serialization.
  • It provides its own ModuleHook (org.tentackle.common.service.Hook).
  • Every other Tentackle module depends on it, directly or transitively.
  • Services / ServiceFinder — the in-depth guide to the service and configuration API summarized above.
  • Freezable — why the date/time and binary value types are freezable, and how that protects entity attributes from aliasing mutation.
  • Tentackle SQL — maps the data types here (BMoney, DMoney, Date, Timestamp, I18NText, …) to database columns.
  • Session — uses EncryptedProperties/Cryptor for connection configuration and credentials.
  • PDO — the PDO pattern wired together by the service API.