Skip to content

Commit 0158ef2

Browse files
committed
docs(adr): ADR-0012 — remove the OSGi runtime variant from the Java port
Captures the rationale for the OSGi removal that landed in 3595d58: the 2026 ecosystem shift (Spring Boot / Quarkus / Micronaut absorbing OSGi's mid-2010s share; Java 9 JPMS displacing the modularity story; adoption concentrated in legacy Eclipse RCP / Karaf / industrial IoT), the alignment with ADR-0001 (build-time type binding, not runtime reflection) and ADR-0004 (provider-based registration), the three alternatives considered (keep + add Pax Exam tests; manifest-only without runtime; opt-in adapter module), and the consequences for the affected (small) OSGi-consumer audience. Also: refresh spec/decisions/README.md index — it was missing ADRs 0008-0011 already on disk. Now lists 0001-0012. Cross-link from server/java/MIGRATION.md § "OSGi support removed" and from the 7.0.1-SNAPSHOT entry in server/java/RELEASE_NOTES.md.
1 parent b78e201 commit 0158ef2

4 files changed

Lines changed: 87 additions & 2 deletions

File tree

server/java/MIGRATION.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ The `metaobjects-dynamic-core` module from 6.x is gone — its `CoreObjectsMetaD
3232

3333
### OSGi support removed
3434

35-
The OSGi runtime variant (`OSGIServiceRegistry`, `BundleLifecycleManager`, the `maven-bundle-plugin` packaging on every reactor module) was dropped in 7.0.1. The artifacts are now plain JARs.
35+
The OSGi runtime variant (`OSGIServiceRegistry`, `BundleLifecycleManager`, the `maven-bundle-plugin` packaging on every reactor module) was dropped in 7.0.1. The artifacts are now plain JARs. Decision rationale: [ADR-0012](../../spec/decisions/ADR-0012-remove-osgi-runtime-variant-java.md).
3636

3737
Consumers running inside an OSGi container can still wrap MetaObjects' JARs with `bnd` / `pax-url` to produce bundles with the appropriate manifest headers — the code itself works in any classloader environment. What changes:
3838

server/java/RELEASE_NOTES.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Apache 2.0. See [LICENSE](LICENSE).
1212

1313
Post-7.0.0 cleanup line:
1414

15-
- **OSGi runtime variant removed.** `OSGIServiceRegistry`, `BundleLifecycleManager`, and the `maven-bundle-plugin` packaging on every reactor module are gone. All artifacts now ship as plain JARs. The `ServiceRegistry` interface drops the OSGi-specific methods; `StandardServiceRegistry` is the only implementation. See [MIGRATION.md](MIGRATION.md#osgi-support-removed) for the migration path (OSGi consumers can wrap our JARs with `bnd` / `pax-url`).
15+
- **OSGi runtime variant removed.** `OSGIServiceRegistry`, `BundleLifecycleManager`, and the `maven-bundle-plugin` packaging on every reactor module are gone. All artifacts now ship as plain JARs. The `ServiceRegistry` interface drops the OSGi-specific methods; `StandardServiceRegistry` is the only implementation. Decision rationale: [ADR-0012](../../spec/decisions/ADR-0012-remove-osgi-runtime-variant-java.md). Migration path: [MIGRATION.md](MIGRATION.md#osgi-support-removed) (OSGi consumers can wrap our JARs with `bnd` / `pax-url`).
1616
- **`metaobjects-dynamic-core` folded into `metaobjects-metadata`.** Its 89-LoC `CoreObjectsMetaDataProvider` now ships inside the metadata jar; the module is removed from the reactor. Reactor module count: 14 → 13.
1717
- **`archetype` and `examples` directories deleted.** They had been out of the reactor since 7.0.0; carrying them as scaffold source risked bit-rot.
1818
- **Programmatic provider registration.** `MetaDataRegistry.compose(Collection<MetaDataTypeProvider>)` and `MetaDataRegistry.registerProviders(Collection<...>)` mirror the cross-port pattern already shipped in TypeScript (`composeRegistry(providers)`) and Python (`compose_registry(providers)`). Strict cross-port error contract: duplicate provider id throws `MetaDataException` with `ERR_PROVIDER_DUPLICATE_ID`; missing dependency throws `ERR_PROVIDER_MISSING_DEPENDENCY`; dependency cycle throws `ERR_PROVIDER_DEPENDENCY_CYCLE`. The ServiceLoader auto-discovery path on `MetaDataRegistry.getInstance()` continues to work unchanged (warn-and-continue semantics, for runtime robustness).
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# ADR-0012 — Remove the OSGi runtime variant from the Java port
2+
3+
**Status:** Accepted — 2026-05-28
4+
**Applies to:** Java port + Kotlin facade (Kotlin runs on the JVM via `metadata-ktx` and `codegen-kotlin`; both inherit the registry from Java).
5+
**Related:** ADR-0001 (cross-language type binding — same anti-reflection argument that motivates this), ADR-0004 (provider-based type registration — the path that survives), `server/java/MIGRATION.md` § "OSGi support removed".
6+
7+
## Context
8+
9+
The Java port shipped a parallel **OSGi runtime variant** alongside the standard `java.util.ServiceLoader`-backed `ServiceRegistry` from the earliest 6.x releases. Concretely, this meant:
10+
11+
- A `ServiceRegistry` interface with OSGi-specific methods (`isOSGIEnvironment`, `onBundleEvent(Object)`, `cleanupForBundle(Object)`, `isBundleLifecycleActive()`, `getBundleLifecycleStatus()`).
12+
- An `OSGIServiceRegistry` implementation (450 LoC) that wrapped a `BundleContext` and tracked services across bundles.
13+
- A `BundleLifecycleManager` (322 LoC) that handled `Bundle` start/stop events and kept the registry consistent across bundle lifecycles.
14+
- A `ServiceRegistryFactory` that reflected on `org.osgi.framework.BundleContext` / `FrameworkUtil` at runtime to decide whether to construct the OSGi variant or the standard one.
15+
- `<packaging>bundle</packaging>` on all reactor modules, with per-module `org.apache.felix:maven-bundle-plugin` configuration generating OSGi manifest headers (`Bundle-SymbolicName`, `Export-Package`, `Import-Package`).
16+
- `WeakReference`-keyed cache strategies inside `HybridCache`, `MetaData`, and `StandardServiceRegistry` justified in javadoc as "OSGi-compatible" patterns to survive bundle reload.
17+
- A test-only `MockBundle` / `MockBundleContext` / `ServiceReferenceLeakTest` infrastructure to exercise the lifecycle paths.
18+
19+
The OSGi variant added genuine value when industrial Eclipse RCP and Apache Karaf consumers were a meaningful share of the audience. By 2026 the cost-benefit had shifted.
20+
21+
## What changed in the ecosystem
22+
23+
Two independent signals converged:
24+
25+
**The Java framework market consolidated away from OSGi.** Surveys and adoption indices through 2026 show Spring Boot, Quarkus, and Micronaut absorbing the cloud-native and microservices share that mid-2010s OSGi proponents anticipated. OSGi adoption today is concentrated in legacy Eclipse RCP, Apache Karaf, and a niche slice of industrial-IoT projects where hot-swappable bundle deployment is a hard requirement.
26+
27+
**Java 9 JPMS partly displaced OSGi's modularity story.** Strong encapsulation of `module-info.java` removed one of OSGi's main differentiators for greenfield projects that wanted package-level visibility control without the bundle lifecycle complexity.
28+
29+
In parallel, the MetaObjects project itself moved on from "Java with an optional OSGi posture":
30+
31+
- The 7.0.0 `server/java/README.md` (the post-consolidation rewrite) did not mention OSGi at all — the framing is "Java port of the cross-language standard, published to Maven Central as plain Maven artifacts."
32+
- ADR-0001 (cross-language type binding) committed the standard to **build-time type binding via providers**, not runtime reflection. The provider model that ADR-0004 ratified is ClassLoader-friendly out of the box and does not depend on OSGi's bundle-scoped service registry to be portable.
33+
- The five-port conformance corpora (metamodel, render, persistence, api-contract, verify) were specified to be runtime-environment-agnostic; nothing in the cross-port contract presupposes OSGi.
34+
35+
## Decision
36+
37+
**Remove the OSGi runtime variant from the Java port.** Ship plain JAR artifacts only. `StandardServiceRegistry` (Java `ServiceLoader`-backed) becomes the sole `ServiceRegistry` implementation. The `ServiceRegistry` interface drops its OSGi-specific methods.
38+
39+
Concretely, in the 7.0.1-SNAPSHOT cleanup line:
40+
41+
- Delete `metadata/src/main/java/com/metaobjects/registry/osgi/` (`OSGIServiceRegistry`, `BundleLifecycleManager`).
42+
- Delete the OSGi test infrastructure (`MockBundle`, `MockBundleContext`, `ServiceReferenceLeakTest`).
43+
- Drop the OSGi methods from `ServiceRegistry`: `isOSGIEnvironment`, `onBundleEvent(Object)`, `cleanupForBundle(Object)`, `isBundleLifecycleActive()`, `getBundleLifecycleStatus()`.
44+
- Simplify `ServiceRegistryFactory.create()` to return a `StandardServiceRegistry` — no reflection-based OSGi detection, no `createOSGI(BundleContext)` factory.
45+
- Remove the `org.apache.felix:maven-bundle-plugin` block from the parent pom `<pluginManagement>` and from each module's plugin overrides.
46+
- Remove the OSGi properties from the parent pom (`bundle.plugin.version`, `osgi.export`, `osgi.import`, `osgi.private`, `osgi.embed`, `osgi.embed.dir`, `osgi.activator`, `osgi.dynamic`).
47+
- Switch `<packaging>bundle</packaging>``<packaging>jar</packaging>` on every reactor module that declared it.
48+
- Update javadoc on `MetaData`, `HybridCache`, `MetaDataLoaderRegistry`, `StandardServiceRegistry` to drop OSGi-specific framing while keeping the `WeakReference` patterns themselves — those patterns are general ClassLoader-leak prevention, not OSGi-specific.
49+
50+
## What this is *not*
51+
52+
This ADR does not claim that the OSGi variant was wrong in 6.x. The decision is purely about **what the Java port should carry forward at 7.0.x given the 2026 ecosystem.** A consumer running inside an OSGi container today can still wrap MetaObjects' plain JARs with `bnd` or `pax-url` to produce bundles with appropriate manifest headers — the code itself works in any classloader environment. The change is: MetaObjects no longer ships native OSGi metadata or a bundle-aware registry, and the test gate no longer exercises OSGi-specific code paths.
53+
54+
## Alternatives considered
55+
56+
**Alternative 1 — Keep OSGi support and add integration tests.** Pax Exam or BND test-bundle infrastructure exists for verifying OSGi-deployed code. The MetaObjects test suite never had it; the OSGi paths were unit-tested via `MockBundle` fakes that did not catch real bundle-lifecycle drift. A real OSGi gate would have cost a meaningful CI investment (Pax Exam launches a container per test class) on top of the ~770 LoC of production code already being maintained. Rejected as not worth the cost given the current consumer mix.
57+
58+
**Alternative 2 — Keep the bundle manifest headers but drop the runtime variant.** A middle path: emit `Bundle-SymbolicName` / `Export-Package` in the manifest via `bnd-maven-plugin`, but drop `OSGIServiceRegistry` and the bundle lifecycle code. This would let JARs continue to be deployable to an OSGi container without the bundle wrapper step. Rejected because (a) the consumer reach of this would be very narrow — anyone willing to deploy MetaObjects to an OSGi container in 2026 is also capable of running a `bnd` wrap step — and (b) maintaining the manifest contract (correct exports for every public package) is real ongoing work, and getting it subtly wrong is worse than not shipping it.
59+
60+
**Alternative 3 — Move OSGi support to an opt-in adapter module.** Ship `metaobjects-metadata-osgi` alongside `metaobjects-metadata`. Rejected because the OSGi variant's complexity was concentrated in the lifecycle code that needs to weave through the *core* registry, not in a thin adapter on top of it. An honest opt-in module would replicate or reach into core internals — either is worse than just dropping the path.
61+
62+
## Consequences
63+
64+
**+** Removes ~770 LoC of production OSGi code, ~600 LoC of OSGi test scaffolding, ~50 lines of OSGi-specific parent-pom configuration, and 7 per-module bundle plugin blocks. The metadata module's surface area drops meaningfully — `ServiceRegistry` is a tighter, more cross-port-comprehensible interface, and `StandardServiceRegistry` is THE registry rather than "the default registry."
65+
66+
**+** Aligns the Java port's framing with what the 7.0.0 README and the cross-port standard pitch describe — plain JARs published to Maven Central, no special runtime posture. Reduces the gap between the documented surface and the actual surface.
67+
68+
**+** Removes a quietly-failing test path. The OSGi unit tests passed against `MockBundle` fakes that did not exercise a real OSGi container; the gate was approximately ceremonial. Removing it acknowledges the test coverage was thinner than its file count implied.
69+
70+
**+** The Kotlin facade (`metadata-ktx`, `omdb-ktx`) and Spring auto-configuration (`core-spring`) become simpler, because their javadoc and integration story no longer have to address "and also OSGi" caveats.
71+
72+
**** OSGi consumers who depended on the native variant must wrap MetaObjects JARs with `bnd` / `pax-url` themselves. The MIGRATION.md "OSGi support removed" section documents the path, but the friction is real for the affected (small) audience.
73+
74+
**** Loses optionality. If OSGi adoption rebounds for a reason we cannot anticipate, restoring native support would mean re-introducing the lifecycle code and re-establishing a real OSGi gate. The git history preserves the 6.x implementation, so this is recoverable but not cheap.
75+
76+
**** A small audience of consumers who used the OSGi support purely for the *bundle manifest headers* (Export-Package etc.) inside an OSGi container loses those headers from MetaObjects-built JARs. They must now produce bundles via wrap rather than consuming the artifacts directly.
77+
78+
## Cross-port footprint
79+
80+
This ADR is **Java-scoped** (Kotlin inherits via the metadata-ktx facade). The other ports (TypeScript, Python, C#) have no parallel concept to remove — none of them shipped OSGi-equivalent runtime variants, and their packaging stories (npm, PyPI, NuGet) do not have an OSGi analog. The cross-port `MetaDataTypeProvider` / `composeRegistry` contract is unchanged and stays the canonical extension surface across all five ports.

spec/decisions/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,8 @@ Write one when a decision is **cross-cutting** (affects more than one language p
2525
- [ADR-0005 — Object representation: entity/value semantics + binding-resolved representation (OO ports)](ADR-0005-object-representation-binding.md)*Accepted*
2626
- [ADR-0006 — AI-first YAML authoring (sigil-free YAML; JSON stays canonical)](ADR-0006-ai-first-yaml-authoring.md)*Proposed*
2727
- [ADR-0007 — Source metatype v2: storage-paradigm subtypes, logical names, multi-source, per-subtype physical addresses](ADR-0007-source-v2-paradigm-subtypes-multisource.md)*Proposed*
28+
- [ADR-0008 — Parameter-passing for generated repo helpers](ADR-0008-parameter-passing-generated-repo-helpers.md)*Accepted*
29+
- [ADR-0009 — Loader error envelope + source-on-node](ADR-0009-loader-error-envelope-and-source-on-node.md)*Accepted*
30+
- [ADR-0010 — Per-port parser-on-receipt codegen for `template.output`](ADR-0010-template-output-parser-codegen.md)*Accepted*
31+
- [ADR-0011 — `template.toolcall` as a core MO subtype](ADR-0011-template-toolcall-as-core-subtype.md)*Accepted*
32+
- [ADR-0012 — Remove the OSGi runtime variant from the Java port](ADR-0012-remove-osgi-runtime-variant-java.md)*Accepted*

0 commit comments

Comments
 (0)