Skip to content

UNOMI-920: Improve debugging with YAML-based toString() for core API types#768

Open
sergehuber wants to merge 25 commits into
masterfrom
UNOMI-920-yaml-tostring
Open

UNOMI-920: Improve debugging with YAML-based toString() for core API types#768
sergehuber wants to merge 25 commits into
masterfrom
UNOMI-920-yaml-tostring

Conversation

@sergehuber
Copy link
Copy Markdown
Contributor

Note: This PR replaces #754, which was accidentally closed when its base branch (UNOMI-921-itests-es-docker) was deleted after being merged. All commits, reviews, and fixes from #754 are preserved in this branch — see that PR for the full inline review thread.


JIRA: https://issues.apache.org/jira/browse/UNOMI-920

Why this change

Core Unomi API objects are deeply nested and interconnected — conditions recurse, rules bundle conditions and actions, metadata types carry parameters, and real graphs can contain cycles (for example when inspecting profile ↔ event ↔ session). In many places, toString() is missing, falls back to Object.toString(), or uses ad-hoc concatenation that is hard to read in logs and breakpoints. That slows down live debugging, operational triage, and support, because engineers cannot see structure and field names at a glance.

YAML-formatted output gives indentation, named fields, and readable nesting, which makes logged or inspected values far easier to scan than opaque object identities or flat string blobs. Together with explicit circular-reference markers, we get safe, repeatable dumps suitable for logs and copy/paste into issues or docs.

What changed

  • Introduce YamlUtils (SnakeYaml) with YamlConvertible, YamlMapBuilder, identity-based cycle detection, optional depth limits, and $ref: circular where the same instance reappears.
  • Implement toYaml / toString on key Item / MetadataItem types (including Condition, ConditionType, Action, ActionType, Rule, Segment, Goal, Scoring, ScoringElement, Parameter, Metadata, etc.).
  • Add YamlUtilsTest for the helper API and representative usage.

Supporting changes

  • unomi-api: SnakeYAML + test dependencies; mockito-core version managed in unomi-bom (mockito.version).
  • RESTParameter: defaultValue as Object to match Parameter#getDefaultValue().
  • RulesServiceImpl: avoid null defaultValue before splitting tracked-condition parameter strings.
  • unomi-itests: pin awaitility to 3.1.6 for Karaf/OSGi (Hamcrest 1.3 bundle), Karaf itests logback exclusions, Hamcrest bundle test scope (aligned with unomi-3-dev).

Review history (from #754)

All 8 Copilot review comments were addressed in commit bcf5a6898:

# File Issue Resolution
1 YamlUtils.java Circular-reference tracking used HashSet (equality-based), risking false "already visited" hits on types that override equals by content Added YamlUtils.newIdentityVisitedSet() (IdentityHashMap-backed); all implementations switched to it; javadoc updated; test added
2 Condition.java deepCopy() had no cycle guard — self-referential graphs would cause StackOverflowError deepCopy() now uses an IdentityHashMap visited set and throws IllegalStateException with a clear message on cycle detection
3 Condition.java Null guard on getParameter() implied parameterValues can be null, but containsParameter/setParameter/equals/hashCode would NPE setParameterValues(null) normalised to empty HashMap; containsParameter/setParameter made null-safe; tests added
4 Parameter.java Max-depth fallback used a "validation" key — copy/paste artifact producing misleading YAML Fixed to use standard keys (id, type, multivalued, defaultValue) with "<max depth exceeded>" placeholder
5 Parameter.java serialVersionUID changed — could break cross-version Java deserialization Acknowledged: Unomi stores via JSON/OpenSearch, not Java serialization; added class-level comment documenting this
6 Item.java Same serialVersionUID concern for the widely-used Item base class Same rationale and comment as above
7 pom.xml Added <slf4j.version> property that was not referenced anywhere Now wired to slf4j-api and slf4j-simple in unomi-api/pom.xml with a clarifying comment
8 YamlUtilsTest.java Double-brace initialisation creates anonymous inner classes that can capture enclosing instances Replaced with explicit nested HashMap instances

sergehuber and others added 25 commits May 5, 2026 17:17
- Add YamlUtils (SnakeYaml) with YamlConvertible, YamlMapBuilder, circular
  reference detection (identity-based visited sets), and recursion depth limits.
- Implement YAML-backed toString()/toYaml() on core API types extending Item /
  MetadataItem (Condition, ConditionType, Action, ActionType, Rule, Segment,
  Goal, Scoring, ScoringElement, Parameter, Metadata, etc.).
- Add YamlUtilsTest coverage for builder, toYamlValue, and representative rules.

Build and integration alignment:
- unomi-api: snakeyaml + test dependencies; manage mockito-core version in BOM.
- RESTParameter: use Object for defaultValue to match Parameter#getDefaultValue().
- RulesServiceImpl: avoid NPE when tracked parameter defaultValue is null before split.
- itests: override awaitility to 3.1.6 for OSGi (Hamcrest 1.3 bundle); Karaf
  itests common logback exclusions; hamcrest bundle scope test.
Run mikepenz/action-junit-report after every integration matrix leg
(if: always), with update_check and per-matrix check_name, so a green
re-run replaces the stale red JUnit check. Use fail_on_failure=false and
require_tests=false; continue-on-error so missing XML cannot fail the job.
Use identity-based visited sets for circular-reference detection, harden
Condition deepCopy and parameter handling, fix Parameter max-depth YAML,
wire slf4j.version in unomi-api, and expand unit test coverage.
Replace the com.github.alexcojocaru:elasticsearch-maven-plugin (binary
download + forked JVM) with io.fabric8:docker-maven-plugin so the
elasticsearch profile of itests runs ES in a Docker container, mirroring
how the opensearch profile already runs OpenSearch.

itests/pom.xml (elasticsearch profile)
* Add an <elasticsearch.port>9400</elasticsearch.port> property and pass
  it through the failsafe systemPropertyVariables so tests resolve the
  HTTP port from a single source.
* Replace the elasticsearch-maven-plugin block with a docker-maven-plugin
  block that starts/stops a docker.elastic.co/elasticsearch/elasticsearch
  container, binds target/snapshots_repository to /tmp/snapshots_repository,
  and waits on the HTTP port before the integration-test phase.
* Add a chmod -R ugo+rwx on snapshots_repository in the antrun unzip step:
  the ES container runs as UID 1000, so on Linux CI the bind-mounted
  snapshot repo otherwise hits access_denied during repository verify.

pom.xml (root)
* Declare <docker-maven-plugin.version>0.48.0</docker-maven-plugin.version>
  and add the pluginManagement entry so the elasticsearch profile (and
  any future user of the plugin) inherits a single version.

This is phase A0 of the PR #757 stack split (see
docs/PR-757-stack-extraction-tracker.md). It is intentionally small and
self-contained: no test/code changes, only the test infrastructure switch.
…icsearch in integration tests

Implements the core configuration switch from UNOMI-921:
https://issues.apache.org/jira/browse/UNOMI-921

Replace the com.github.alexcojocaru:elasticsearch-maven-plugin (binary
download + forked JVM) with io.fabric8:docker-maven-plugin in the
elasticsearch profile of itests, mirroring how the opensearch profile
already runs OpenSearch in a Docker container.

itests/pom.xml (elasticsearch profile)
* Add an <elasticsearch.port>9400</elasticsearch.port> property and pass
  it through the failsafe systemPropertyVariables so tests resolve the
  HTTP port from a single source (unchanged from the previous 9400).
* Replace the elasticsearch-maven-plugin block with a docker-maven-plugin
  block that runs docker.elastic.co/elasticsearch/elasticsearch:${elasticsearch.test.version},
  binds target/snapshots_repository to /tmp/snapshots_repository, and
  waits on the HTTP port before the integration-test phase. Heap aligned
  to 8GB (-Xms8g -Xmx8g) to match the OpenSearch configuration and the
  ES 9 recommendation. Discovery=single-node, replicas=0, xpack.ml and
  xpack.security disabled.
* Container lifecycle matches OpenSearch exactly: pre-integration-test
  runs stop+remove then start (with showLogs); post-integration-test
  runs stop only -- container is kept around for inspection.
* Add a chmod -R ugo+rwx on snapshots_repository in the antrun unzip step:
  the ES container runs as UID 1000, so on Linux CI the bind-mounted
  snapshot repo otherwise hits access_denied during repository verify.

pom.xml (root)
* Declare <docker-maven-plugin.version>0.48.0</docker-maven-plugin.version>
  and add the pluginManagement entry so the elasticsearch profile (and
  any future user of the plugin) inherits a single version.

Scope kept minimal for the PR #757 stack split: only the test
infrastructure switch lives here. The follow-up UNOMI-921 acceptance
items below ship in the platform PR (P) once it lands:

* Remove BaseIT.fixDefaultTemplateIfNeeded() and the call in
  checkSearchEngine() (no longer needed with Docker).
* Migrate16xToCurrentVersionIT: replace hardcoded
  ES_BASE_URL = "http://localhost:9400" with dynamic getSearchPort().
* Drop the comments referring to the elasticsearch-maven-plugin
  template-override workaround.

See docs/PR-757-stack-extraction-tracker.md for the full split plan and
how this PR fits in the stack.
Non-interactive prompts when CI, GITHUB_ACTIONS, or BUILD_NON_INTERACTIVE is set.
Add --ci (no Karaf, no Maven build cache) and MAVEN_EXTRA_OPTS for matrix ports.
Replace fixed sleeps in GraphQLListIT and poll after patch refresh in PatchIT.
Poll profile properties after events in PropertiesUpdateActionIT.
Align CI with local developer workflow; pass matrix ports via MAVEN_EXTRA_OPTS.
…icsearch in integration tests

Implements the core configuration switch from UNOMI-921:
https://issues.apache.org/jira/browse/UNOMI-921

Replace the com.github.alexcojocaru:elasticsearch-maven-plugin (binary
download + forked JVM) with io.fabric8:docker-maven-plugin in the
elasticsearch profile of itests, mirroring how the opensearch profile
already runs OpenSearch in a Docker container.

itests/pom.xml (elasticsearch profile)
* Add an <elasticsearch.port>9400</elasticsearch.port> property and pass
  it through the failsafe systemPropertyVariables so tests resolve the
  HTTP port from a single source (unchanged from the previous 9400).
* Replace the elasticsearch-maven-plugin block with a docker-maven-plugin
  block that runs docker.elastic.co/elasticsearch/elasticsearch:${elasticsearch.test.version},
  binds target/snapshots_repository to /tmp/snapshots_repository, and
  waits on the HTTP port before the integration-test phase. Heap aligned
  to 8GB (-Xms8g -Xmx8g) to match the OpenSearch configuration and the
  ES 9 recommendation. Discovery=single-node, replicas=0, xpack.ml and
  xpack.security disabled.
* Container lifecycle matches OpenSearch exactly: pre-integration-test
  runs stop+remove then start (with showLogs); post-integration-test
  runs stop only -- container is kept around for inspection.
* Add a chmod -R ugo+rwx on snapshots_repository in the antrun unzip step:
  the ES container runs as UID 1000, so on Linux CI the bind-mounted
  snapshot repo otherwise hits access_denied during repository verify.

pom.xml (root)
* Declare <docker-maven-plugin.version>0.48.0</docker-maven-plugin.version>
  and add the pluginManagement entry so the elasticsearch profile (and
  any future user of the plugin) inherits a single version.

Scope kept minimal for the PR #757 stack split: only the test
infrastructure switch lives here. The follow-up UNOMI-921 acceptance
items below ship in the platform PR (P) once it lands:

* Remove BaseIT.fixDefaultTemplateIfNeeded() and the call in
  checkSearchEngine() (no longer needed with Docker).
* Migrate16xToCurrentVersionIT: replace hardcoded
  ES_BASE_URL = "http://localhost:9400" with dynamic getSearchPort().
* Drop the comments referring to the elasticsearch-maven-plugin
  template-override workaround.

See docs/PR-757-stack-extraction-tracker.md for the full split plan and
how this PR fits in the stack.
# Conflicts:
#	build.sh
#	itests/src/test/java/org/apache/unomi/itests/PropertiesUpdateActionIT.java
#	itests/src/test/java/org/apache/unomi/itests/graphql/GraphQLListIT.java
# Conflicts:
#	build.sh
#	itests/pom.xml
#	itests/src/test/java/org/apache/unomi/itests/PropertiesUpdateActionIT.java
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant