Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,30 @@ Each of `--auth` and `--ssl`, independently, made a multi-node PoppyDB replica s

### Added

#### `morphium-jakarta-data` — optional Jakarta Data 1.0 runtime module
A new optional module, `morphium-jakarta-data`, brings a [Jakarta Data 1.0](https://jakarta.ee/specifications/data/1.0/)
provider implementation on top of Morphium's existing query engine: `@Repository`-based
`CrudRepository`/`MorphiumRepository` interfaces with query derivation from method names
(`findByCategory`, `countByStatus`, `deleteByX`, `And`/`Or`/`Between`/`In`/`Like`/`OrderBy`
and the rest of the standard keyword set), JDQL via `@Query` (including `GROUP BY`/`HAVING`
aggregates compiled into a Morphium aggregation pipeline), `@Find`/`@Delete` with explicit
`@By` parameter binding, offset pagination (`Page<T>`) and cursor/keyset pagination
(`CursoredPage<T>`), and both static (`@OrderBy`) and dynamic (`Sort`/`Order`) sorting. The
module depends on Morphium core and on `jakarta.data:jakarta.data-api`; the dependency
direction is strictly one-way — core has no knowledge of Jakarta Data and no dependency on
this module, so an application declaring only `de.caluga:morphium` does not get
`jakarta.data-api` on its classpath and none of these annotations or types become available.
Building the reactor with `-DskipExtensions` produces a core-only build (core + PoppyDB, no
extension modules) exactly as before this change. `morphium-jakarta-data` is deliberately
framework-agnostic — plain Java classes with zero dependencies on Quarkus, Spring, or any DI
container — because it is meant to be consumed transitively by framework integrations, not
added directly by most applications: `quarkus-morphium` (build-time Gizmo bytecode
generation) and `spring-boot-morphium` (JDK dynamic proxies) build on top of this module and
will follow in subsequent PRs. The code originates from
[Bardioc1977/morphium-jakarta-data](https://github.com/Bardioc1977/morphium-jakarta-data),
which is being archived now that its content has moved into the main Morphium repository.
See [Jakarta Data](docs/jakarta-data.md).

#### PoppyDB: `--users-file` — declarative user provisioning (bootstrap, upsert, version-gated)
Builds on user replication: `--rootUser`/`--rootPassword` only ever provisioned one admin user,
so any real application user set still had to be created by hand (a shell script running
Expand Down
10 changes: 10 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,16 @@ Morphium includes a complete in-memory MongoDB-compatible implementation for tes
## Reference
- **[API Reference](./api-reference.md)** - Complete API documentation with examples

## Extensions (Optional Modules)
Morphium's core module (`de.caluga:morphium`) is fully self-contained and does not need
any of the following. These are additional, opt-in modules built on top of the core:
- **[Jakarta Data](./jakarta-data.md)** - Optional module implementing the Jakarta Data
1.0 specification on top of Morphium's query engine (repository pattern, `@Repository`)
- Query derivation from method names, JDQL (`@Query`), `@Find`/`@Delete` with `@By`
- Offset and cursor pagination (`Page<T>`, `CursoredPage<T>`), dynamic and static sorting
- Zero dependency from the core: build with `-DskipExtensions` for a core-only artifact;
framework integrations for Quarkus and Spring Boot build on top of this module

Minimum requirements
- Java 21+
- MongoDB 5.0+
Expand Down
530 changes: 530 additions & 0 deletions docs/jakarta-data.md

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ nav:
- Messaging Implementations: howtos/messaging-implementations.md
- SSL/TLS Connections: ssl-tls.md
- Developer Guide: developer-guide.md
- Extensions:
# Placeholder: Quarkus- und Spring-Boot-Integrationsseiten folgen in späteren Wellen (M4, M5).
- Jakarta Data: jakarta-data.md
- Reference:
- API Reference: api-reference.md
- Configuration: configuration-reference.md
Expand Down
29 changes: 29 additions & 0 deletions morphium-jakarta-data/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [Unreleased]

### Changed

#### Integrated as a module of the Morphium multi-module project
`morphium-jakarta-data` is no longer a standalone Maven project with its own release cycle. It is now built as a module of the Morphium multi-module reactor (`morphium-parent`), lives in the `morphium-jakarta-data/` directory of the [sboesebeck/morphium](https://github.com/sboesebeck/morphium) repository, and is versioned in lockstep with Morphium core. The artifact coordinates changed from `de.caluga:morphium-jakarta-data:1.1.0` (standalone) to `de.caluga:morphium-jakarta-data:<morphium-version>` (currently `6.2.6-SNAPSHOT`). The groupId is unchanged. Existing users pinning `1.1.0`/`1.1.0-SNAPSHOT` (or the earlier `1.0.0-SNAPSHOT` line) need to bump the dependency version to match the Morphium core version they use, and should expect the artifact to be built from the Morphium reactor going forward — this repository is archived once the migration completes. No source-level API changes are part of this move; only the build/versioning model changed.

## [1.1.0-SNAPSHOT] (superseded — see [Unreleased])

This heading previously read `[Unreleased] - 1.0.0-SNAPSHOT`, which no longer reflected reality: the module had already moved past `1.0.0-SNAPSHOT` to `1.1.0-SNAPSHOT` as a standalone project before the integration into Morphium made a fixed pre-1.0 standalone version number moot altogether. The entries below are kept for history; going forward, changes are tracked under `[Unreleased]` above and, once released, under the Morphium version they ship with.

### Added
- Framework-agnostic Jakarta Data 1.0 runtime for Morphium ODM
- `AbstractMorphiumRepository` base class with full CRUD implementation
- `MorphiumRepository` extended interface (distinct, direct Morphium/Query access)
- Query derivation from method names: `findBy*`, `countBy*`, `existsBy*`, `deleteBy*`
- Supported operators: equals, greaterThan, lessThan, like, in, between, not, and, or
- JDQL parsing via `@Query` annotation
- `@Find` / `@Delete` with `@By` parameter binding
- Pagination support: `Page<T>`, `CursoredPage<T>`, `PageRequest`
- Sorting: `Sort<T>`, `Order<T>`, `@OrderBy`
- Stream and async return types: `Stream<T>`, `CompletionStage<T>`
- `RepositoryMetadata` for entity type, ID type, and collection name resolution
141 changes: 141 additions & 0 deletions morphium-jakarta-data/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# Morphium Jakarta Data

An optional module of [Morphium](https://github.com/sboesebeck/morphium), the MongoDB ODM and messaging framework for Java 21+. This module provides a framework-agnostic [Jakarta Data 1.0](https://jakarta.ee/specifications/data/1.0/) runtime — repository implementation, query derivation, JDQL parsing, pagination, and sorting — on top of Morphium.

## What this module is and is not

`morphium-jakarta-data` is the shared implementation layer that turns Jakarta Data repository interfaces into Morphium queries. It has **zero framework dependencies**: only Morphium core and the Jakarta Data API.

Application code typically does **not** depend on this module directly. Instead, it goes through a framework integration:

| Framework | Module | Repository generation |
|-----------|--------|------------------------|
| Quarkus | `quarkus-morphium` | Gizmo bytecode generation (build-time) |
| Spring Boot | `spring-boot-morphium` | JDK dynamic proxies (runtime) |

This module exists as a separate artifact so the ~2400 lines of query derivation, JDQL parsing, pagination, and result-type handling are implemented once and shared, instead of being duplicated between the Quarkus and Spring Boot adapters.

The direct target audience for this module is anyone building their **own** framework integration — Micronaut, Helidon, plain Jakarta EE, or a hand-rolled repository wiring in plain Java. If that is not your situation, use `quarkus-morphium` or `spring-boot-morphium` instead and treat this module as an implementation detail.

## Optionality

Morphium core (`de.caluga:morphium`) does **not** depend on this module. Projects that only pull in `de.caluga:morphium` get the ODM, driver, caching, and messaging — but no `jakarta.data-api` dependency and no repository support. Jakarta Data support is opt-in by adding `morphium-jakarta-data` (directly, or transitively via one of the framework integrations).

## Features

- `CrudRepository<T, K>` and `MorphiumRepository<T, K>` base interfaces
- Query derivation from method names: `findBy*`, `countBy*`, `existsBy*`, `deleteBy*`
- Supported operators: equals, greaterThan, lessThan, like, in, between, not, and, or
- JDQL (Jakarta Data Query Language) support via `@Query` annotation
- `@Find` / `@Delete` with `@By` parameter binding
- Pagination: `Page<T>`, `CursoredPage<T>`, `PageRequest`
- Sorting: `Sort<T>`, `Order<T>`, `@OrderBy`
- Stream and async return types: `Stream<T>`, `CompletionStage<T>`
- `RepositoryMetadata` for entity type, ID type, and collection name resolution

## Maven Dependency

```xml
<dependency>
<groupId>de.caluga</groupId>
<artifactId>morphium-jakarta-data</artifactId>
<version>${project.version}</version> <!-- currently 6.2.6-SNAPSHOT -->
</dependency>
```

The version tracks Morphium's version lockstep — `morphium-jakarta-data` is released alongside `morphium` core with the same version number, not independently.

## Architecture

```
morphium-jakarta-data
de.caluga.morphium.data
AbstractMorphiumRepository Core CRUD implementation (protected setMorphium)
MorphiumRepository Extended repository interface (distinct, query access)
RepositoryMetadata Entity type, ID type, collection name metadata
QueryDescriptor Parsed query representation (field, operator, value)
MethodNameParser Parses findByXxx method names into QueryDescriptors
JdqlParser / JdqlQuery JDQL (Jakarta Data Query Language) parsing
QueryMethodBridge Executes derived queries (findBy*, countBy*, deleteBy*)
JdqlMethodBridge Executes @Query JDQL methods
FindMethodBridge Executes @Find / @Delete annotated methods
QueryExecutor Low-level Morphium query execution
QueryResultHelper Result type adaptation (List, Stream, Page, Optional)
CursorHelper Cursor-based pagination support
SortMapper Maps Jakarta Data Sort/Order to Morphium sort
MorphiumPage Page/CursoredPage implementation
```

### Processing chain

A repository method call is resolved through a fixed pipeline, regardless of which bridge parses it:

```
Repository method call
-> MethodNameParser (findBy*/countBy*/...) or JdqlParser (@Query / JDQL)
-> QueryDescriptor (parsed field/operator/value/sort representation)
-> QueryExecutor (builds and runs the Morphium Query<T>)
-> QueryResultHelper (adapts the raw result to the declared return type)
-> return type (T, Optional<T>, List<T>, Stream<T>, Page<T>, CursoredPage<T>, CompletionStage<T>, ...)
```

`@Find` / `@Delete` methods go through `FindMethodBridge` instead of `MethodNameParser`, but join the same `QueryDescriptor` → `QueryExecutor` → `QueryResultHelper` chain from that point on.

The key design point is `AbstractMorphiumRepository.setMorphium(Morphium)` being `protected` — framework subclasses override it to bridge their injection mechanism:
- Quarkus: `@Inject` + `@PostConstruct`
- Spring Boot: public setter called by `FactoryBean`

## Building your own framework integration

To wire a new framework to this module, extend `AbstractMorphiumRepository<T, K>` for each repository interface and call `setMorphium(Morphium)` once a `Morphium` instance is available from your framework's dependency injection (or from plain code). The repository interface methods delegate to the `doXxx()` methods already implemented on `AbstractMorphiumRepository`; for query-derivation and JDQL methods not covered by the base class, dispatch through `QueryMethodBridge` / `JdqlMethodBridge` / `FindMethodBridge` as needed.

Minimal example without any framework, wiring a repository by hand:

```java
import de.caluga.morphium.Morphium;
import de.caluga.morphium.data.AbstractMorphiumRepository;
import de.caluga.morphium.data.RepositoryMetadata;

public class PersonRepositoryImpl extends AbstractMorphiumRepository<Person, String>
implements PersonRepository {

public PersonRepositoryImpl(Morphium morphium) {
super(new RepositoryMetadata(Person.class, String.class, "id"));
setMorphium(morphium);
}

@Override
public Optional<Person> findById(String id) {
return doFindById(id);
}

@Override
public List<Person> findAll() {
return doFindAll().toList();
}
Comment on lines +112 to +115

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail
rg -n -P -A8 -B4 '\b(findAll|doFindAll)\s*\(' \
  morphium-jakarta-data/src/main/java \
  docs/jakarta-data.md \
  morphium-jakarta-data/README.md

Repository: Bardioc1977/morphium

Length of output: 6539


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "== README relevant section =="
sed -n '1,140p' morphium-jakarta-data/README.md | cat -n

echo
echo "== docs jakarta-data relevant sections =="
sed -n '430,475p' docs/jakarta-data.md | cat -n
sed -n '1,80p' docs/jakarta-data.md | cat -n

echo
echo "== AbstractMorphiumRepository relevant methods =="
sed -n '130,165p' morphium-jakarta-data/src/main/java/de/caluga/morphium/data/AbstractMorphiumRepository.java | cat -n
sed -n '400,430p' morphium-jakarta-data/src/main/java/de/caluga/morphium/data/AbstractMorphiumRepository.java | cat -n

Repository: Bardioc1977/morphium

Length of output: 17693


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "== docs repository interface definition =="
sed -n '72,86p' docs/jakarta-data.md | cat -n

echo
echo "== docs product repository implementation =="
sed -n '365,420p' docs/jakarta-data.md | cat -n

echo
echo "== AbstractMorphiumRepository class declaration/imports =="
sed -n '1,80p' morphium-jakarta-data/src/main/java/de/caluga/morphium/data/AbstractMorphiumRepository.java | cat -n

echo
echo "== PersonRepository declaration nearby == "
sed -n '92,118p' morphium-jakarta-data/README.md | cat -n

Repository: Bardioc1977/morphium

Length of output: 8399


🌐 Web query:

Jakarta Data 1.0 BasicRepository findAll Stream<T> signature CrudRepository

💡 Result:

In Jakarta Data 1.0, the BasicRepository<T, K> interface defines the findAll() method with the following signature [1]: @Find Stream findAll; This method retrieves all persistent entities of the specified type from the database and returns a Stream<T> [1][2]. It is important to note that this method will never return null, though it may throw an UnsupportedOperationException if the underlying database (such as certain Key-Value or Wide-Column stores) is not capable of performing the findAll operation [1][3]. The CrudRepository<T, K> interface extends BasicRepository<T, K> [4][5]. Consequently, any repository interface that extends CrudRepository inherits this findAll() method signature [4].

Citations:


Return Stream<Person> for findAll() in the example.

PersonRepositoryImpl claims @Override, but @Find Stream<T> findAll() is the Jakarta Data contract. A List<Person> method is an overload, not an override, so any generated/proxied findAll() call is left unimplemented.

Proposed contract-aligned example
-    public List<Person> findAll() {
-        return doFindAll().toList();
+    public Stream<Person> findAll() {
+        return doFindAll();

Add java.util.stream.Stream to the omitted imports.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Override
public List<Person> findAll() {
return doFindAll().toList();
}
`@Override`
public Stream<Person> findAll() {
return doFindAll();
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@morphium-jakarta-data/README.md` around lines 112 - 115, Update the
PersonRepositoryImpl findAll() example to return Stream<Person> instead of
List<Person>, preserving the doFindAll() result flow with the appropriate stream
return. Add the omitted java.util.stream.Stream import so the method correctly
overrides the Jakarta Data contract.

}
```

`setMorphium(Morphium)` is `protected`, so it can only be called from within the class hierarchy — subclasses either widen its visibility (as Spring Boot's public setter does) or call it internally from a constructor/lifecycle callback (as the example above and the Quarkus `@PostConstruct` integration do).

## Building

This module is part of the Morphium multi-module Maven build. Build it from the root of the `morphium` repository:

```bash
mvn -pl morphium-jakarta-data -am verify
```

`-am` (also-make) ensures `morphium-core` is built first if it is not already up to date in the reactor.

## Requirements

| Requirement | Version |
|-------------|---------|
| Java | 21+ |
| Morphium | same version (lockstep) |
| Jakarta Data API | 1.0 |

## License

This module is licensed under the same terms as the Morphium project (Apache License 2.0). There is no separate license file for this module — the license is defined at the repository root of the Morphium project.
76 changes: 76 additions & 0 deletions morphium-jakarta-data/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?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>
<!-- ENTSCHEIDUNG-OFFEN D1: Lockstep mit Morphium-Version. Bei Variante A (eigene Versionslinie) hier <version> und <morphium.version> ergänzen. -->
<parent>
<groupId>de.caluga</groupId>
<artifactId>morphium-parent</artifactId>
<version>6.3.0-SNAPSHOT</version>
</parent>
<artifactId>morphium-jakarta-data</artifactId>
<packaging>jar</packaging>
<name>Morphium Jakarta Data</name>
<description>Framework-agnostic Jakarta Data runtime for Morphium ODM</description>
<dependencies>
<dependency>
<groupId>de.caluga</groupId>
<artifactId>morphium</artifactId>
<version>${project.version}</version>
</dependency>
<!-- jakarta.data.version and dependencyManagement now inherited from morphium-parent -->
<dependency>
<groupId>jakarta.data</groupId>
<artifactId>jakarta.data-api</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<!-- Test -->
<!-- M2: prüfen ob nach morphium-parent -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/main/java</sourceDirectory>
<testSourceDirectory>src/test/java</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Loading