blob: 032267145ca3aa91c92356a6326486a2672ec142 [file] [view]
<!--
SPDX-License-Identifier: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
# Apache Unomi — CLAUDE.md
Apache Unomi is a Java/OSGi customer data platform running on Apache Karaf. Multi-module Maven
reactor; runtime code is packaged as OSGi bundles. **New code uses OSGi Declarative Services**
(`@Component`, `@Reference`, `@Activate`). Legacy modules still ship Blueprint XML
(`OSGI-INF/blueprint/blueprint.xml`) during migration — do not add new Blueprint files; see
`CODING_GUIDELINES.md` and `manual/.../writing-plugins.adoc`.
## Dual persistence backends — the #1 thing to know
Every persistence-related feature must be implemented **twice**, once per search engine, and the
two implementations must behave identically:
- `persistence-elasticsearch/core` — `ElasticSearchPersistenceServiceImpl.java` (Elasticsearch, ILM
for rollover, `co.elastic.clients.elasticsearch.*` client)
- `persistence-opensearch/core` — `OpenSearchPersistenceServiceImpl.java` (OpenSearch, ISM for
rollover, `org.opensearch.client.opensearch.*` client)
The two client libraries look similar (OpenSearch's Java client is a fork of Elastic's) but their
typed builder APIs differ in real, non-obvious ways — e.g. ES's `numberOfShards(String)` vs
OpenSearch's `numberOfShards(Integer)`. **Do not port code between the two by pattern-matching
alone — verify actual method signatures for the specific client version in use** (`mvn
dependency:tree` + decompile if needed; there is no live cluster available in most dev sandboxes to
test against directly).
ISM (OpenSearch) and ILM (ES) are conceptually parallel but not identical: ISM policies attach to
indices either via an explicit `plugins.index_state_management.policy_id` index setting or via the
policy's own `ism_template.index_patterns` matching at index-creation time — prefer the explicit
`policy_id` setting (set via `IndexSettings.customSettings(...)`, since there's no typed builder
field for it) over template pattern-matching, which is fragile (a past bug: the pattern list was
built from raw item-type names like `"event"` that never matched real index names like
`"context-event-000001"`).
**Composable index template priority**: `BaseIT` registers an IT-only catch-all template
(`unomi-it-zero-replicas`, pattern `*`, priority `1`) to force zero replicas on single-node test
clusters. Any real Unomi index template must use a priority well above that (convention: `100`) or
template registration fails outright — composable templates reject ambiguous same-priority
overlapping patterns rather than merging them.
**Single-node test clusters legitimately stay `yellow` forever** (no second node to host replicas).
Code that blocks on `HealthStatus.Green` will hang/fail in ITs. OpenSearch's persistence service has
a `minimalClusterState` config + `getHealthStatus(minimalClusterState)` helper for this — reuse it.
**Elasticsearch's persistence service has no equivalent wired in** (only the separate
`extensions/healthcheck` bundle knows about yellow-tolerance) — this is a known, unaddressed parity
gap, not an oversight to silently "fix" by inventing new config without discussing it with the user
first.
## Build
- Root `pom.xml` aggregates ~40 modules. Key ones: `api`, `common`, `persistence-spi`,
`persistence-elasticsearch`, `persistence-opensearch`, `services`, `rest`, `graphql`, `wab`,
`kar` (Karaf feature repo), `itests`.
- `./build.sh` is the canonical local build entrypoint (aligns with CI). Prefer it over ad hoc
`mvn` invocations when validating a full build.
- The `itests` module only joins the reactor under the `integration-tests` (or `ci-build-itests`)
profile — `mvn -pl itests ...` alone will fail with "module not found in reactor" unless that
profile is active.
- Karaf `karaf-maven-plugin:verify` failures on unrelated modules (e.g. stale
`target/classes` "(Is a directory)" errors) are usually stale local build state, not real
regressions — don't chase them as if they were caused by your change without checking first.
## Integration tests (`itests/`)
- PaxExam + Karaf, `PerSuite` reactor strategy: **one shared Karaf container/cluster runs the
entire suite**, not one per test class. Tests must clean up after themselves (`@After`) or they
pollute later tests' assumptions.
- **`itests/pom.xml`'s failsafe config only includes `**/*AllITs.java`** — individual `*IT.java`
classes are silently never run unless explicitly registered in `AllITs.java`'s `@SuiteClasses`
list. This is easy to forget when adding a new IT.
- `BaseIT.java` is the shared base class: search-engine-agnostic HTTP helpers
(`createSearchEngineHttpClient()`, `getSearchEngineBaseUrl()`), `keepTrying(...)` for
poll-with-timeout assertions, and Karaf container config (`etc/custom.system.properties`
overrides) shared by ES and OpenSearch runs. IT-only config differences between the two engines
(e.g. `rollover.maxDocs`) must be set for **both** `org.apache.unomi.elasticsearch.*` and
`org.apache.unomi.opensearch.*` properties, or one engine silently runs untested.
- `itests/kt.sh` is a helper for driving/inspecting a running IT Karaf instance (`kt.sh t` to run,
`kt.sh l` to view the log) — see that script and any `itests/README*` for the current test-dev
workflow rather than assuming.
- Run engine-specific ITs via the search-engine system property; check `BaseIT` /
`AllITs`/`build.sh` for the exact flag rather than guessing.
## Working with subagents / cost awareness
Verifying an unfamiliar client library's exact API surface via jar decompilation (`javap`) is a
legitimate but expensive way to work when no live test environment is available — it burns a lot of
tool calls. When a fix's correctness can't be confirmed without actually running the code (e.g.
against a live Elasticsearch/OpenSearch container), prefer asking the user to run it and paste back
real output over exhaustively re-deriving behavior from bytecode.
## Memory
Session-persistent project memory for this repo lives outside the working tree (Claude's own memory
system) — check it for prior investigation before re-deriving things like client API quirks,
known-flaky tests, or in-progress uncommitted work state.