Skip to content

[MNG-8547] Expose repository events through the Maven API - #13011

Open
goutamadwant wants to merge 9 commits into
apache:masterfrom
goutamadwant:feature/mng-8547-repository-events
Open

[MNG-8547] Expose repository events through the Maven API#13011
goutamadwant wants to merge 9 commits into
apache:masterfrom
goutamadwant:feature/mng-8547-repository-events

Conversation

@goutamadwant

@goutamadwant goutamadwant commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes #10677

Description

Expose repository operations through Maven-owned API types without leaking Resolver types into the public API. The bridge covers all 19 repository event types and maps artifacts, metadata, repositories, paths, exceptions, and request traces.

New API types (22 files, +1908/−49):

  • RepositoryEvent / RepositoryEventType / RepositoryListener — typed repository event family (19 event types: artifact/metadata install, deploy, resolve, download, etc.)
  • ExecutionEvent / ExecutionEventType / ExecutionListener — typed execution event family with noun-style accessors
  • RepositoryMetadata — immutable Maven API wrapper for repository metadata (noun-style accessors)

Event hierarchy:

  • Event is the shared base interface with session() accessor
  • Deprecated bridge methods on Event (getType(), getSession(), getProject(), getMojoExecution(), getException()) are preserved for backward compatibility; getType() documents that it throws UnsupportedOperationException for non-ExecutionEvent instances
  • Listener remains @FunctionalInterface for backward compatibility; existing lambdas continue to receive execution events unchanged

Listener registration:

  • Unified Session.registerListener(Listener) / unregisterListener(Listener) / getListeners() path
  • Typed listeners (ExecutionListener, RepositoryListener) receive only their event-family callbacks; a single listener can implement both
  • Thread-safe registration shared across Maven sessions backed by the same Resolver session

Implementation:

  • DefaultRepositoryEvent maps Resolver RepositoryEvent to the Maven API, converting artifacts, metadata, repositories, paths, exceptions, and request traces
  • MavenRepositoryListener bridges Resolver repository events to registered RepositoryListener instances
  • DefaultEvent and EventSpyImpl updated for the new hierarchy
  • AbstractSession updated for unified listener registration
  • DefaultRepositorySystemSessionFactory wires the repository listener bridge
  • Listener failures are isolated (do not break the build)

Tests

  • EventSpyImplTest (337 lines): all 17 execution callbacks, legacy listener lambdas, noun accessors, combined listeners, repository-only listeners, single delivery without duplicate legacy callback, registration/removal through derived sessions, concurrent registration, immutable collection views, null arguments, listener failure isolation
  • MavenRepositoryListenerTest (347 lines): all 19 repository event types, event-field conversion, metadata mapping with noun-style accessors, request traces, immutable metadata properties
  • DefaultRepositorySystemSessionFactoryTest (41 lines): repository listener bridge wiring

Following this checklist to help us incorporate your contribution quickly and easily:

  • This pull request addresses one issue without unrelated changes.

  • The description explains what the pull request does, how, and why.

  • Each commit has a meaningful subject and body.

  • Unit tests cover the behavioral changes.

  • Reactor unit tests and basic verification checks pass.

  • The complete Core IT suite passes.

  • I hereby declare this contribution to be licenced under the Apache License Version 2.0, January 2004

Add Maven API repository event and listener types and bridge all Resolver repository callbacks without exposing Resolver types.

Keep registration scoped to the underlying Resolver session and cover event mapping, listener isolation, and derived-session behavior.

Signed-off-by: goutamadwant <workwithgoutam@gmail.com>
@gnodet gnodet added the enhancement New feature or request label Sep 2, 2026
@gnodet gnodet added this to the 4.1.0 milestone Sep 2, 2026

@gnodet gnodet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Well-implemented feature exposing Maven Resolver repository events through the public Maven API. The event model, listener lifecycle, and bridge from Resolver to Maven are clean. Comprehensive test coverage (19 event types, isolation, concurrency, lambda ambiguity guard). One low-severity finding noted below.

This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.

Claude Code on behalf of Guillaume Nodet


@Override
public Collection<RepositoryListener> getRepositoryListeners() {
return null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Low: getRepositoryListeners() returns null, but the Session interface (added in this same PR) declares it @Nonnull with Javadoc "never null". The real implementation in AbstractSession correctly returns an unmodifiable collection.

Consider returning List.of() or Collections.emptyList() here to honor the contract:

Suggested change
return null;
return List.of();

(The pre-existing getListeners() above has the same null-return anti-pattern, but no need to extend it to new methods.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

thanks @gnodet - fixed it.

gnodet added a commit to gnodet/maven that referenced this pull request Sep 2, 2026
Return an empty immutable collection from SessionStub instead of null, matching the non-null Session API contract.

Signed-off-by: goutamadwant <workwithgoutam@gmail.com>

@gnodet gnodet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Solid implementation. The bridge architecture (Resolver → Maven API) is clean, thread safety is correct (CopyOnWriteArrayList in session data, listener isolation via catch-and-log), and test coverage is comprehensive (all 19 event types, failure isolation, concurrency, lambda ambiguity guard, derived-session sharing).

The previous review finding (SessionStub returning null for getRepositoryListeners()) has been addressed.

Two documentation gaps in the public API:

  1. RepositoryListener — all 19 callback methods lack Javadoc. This is a @Consumer API that plugin developers will implement. Each method should at minimum describe when it fires (e.g., "Called when an artifact resolution operation has started" vs "Called after an artifact has been resolved from a repository, whether successfully or with a failure"). Without this, consumers must reverse-engineer semantics from the Resolver docs or the enum names.

  2. RepositoryMetadata — all 7 getter methods lack Javadoc. At minimum, getType() (which returns the metadata filename, not a MIME type) and getNature() need clarification for users unfamiliar with Resolver internals.

RepositoryEventType enum constants would also benefit from brief descriptions, but the names are reasonably self-documenting so this is lower priority.

These are documentation-only issues — the implementation itself is correct.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

public interface RepositoryListener {

default void artifactDescriptorInvalid(@Nonnull RepositoryEvent event) {}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All 19 callback methods lack Javadoc. For a @Consumer public API, each method should describe when it fires and what the event contains. For example:

/**
 * Called when an artifact descriptor could not be parsed.
 *
 * @param event the event details; {@link RepositoryEvent#getArtifact()} identifies the artifact,
 *              {@link RepositoryEvent#getException()} describes the parse failure
 */
default void artifactDescriptorInvalid(@Nonnull RepositoryEvent event) {}

Without this, consumers must reverse-engineer semantics from Resolver documentation.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@gnodet Addressed in cac2b6f1f5. Added Javadocs for all 19 callbacks describing when each event fires and the relevant event details.

@Nonnull
String getArtifactId();

@Nonnull

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All getter methods lack Javadoc. At minimum, getType() needs clarification — it returns the metadata filename (e.g., "maven-metadata.xml"), not a MIME type or content descriptor. getNature() should explain the RELEASE/SNAPSHOT/RELEASE_OR_SNAPSHOT semantics.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added Javadocs for all RepositoryMetadata accessors, including clarification that getType() returns the metadata filename and getNature() describes release/snapshot applicability.

Describe when repository listener callbacks fire and clarify the values exposed by repository metadata.
@goutamadwant

Copy link
Copy Markdown
Contributor Author

@gnodet addressed all review comments. let me know. thanks!

@gnodet

gnodet commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

API Design Suggestion: Event/Listener Hierarchy

The current PR introduces RepositoryListener / RepositoryEvent as standalone types with separate registration methods on Session. This works, but it creates an inconsistency with the existing Listener / Event API (build lifecycle events) — two parallel, unrelated listener systems on the same Session with different design patterns:

  • Listener uses @FunctionalInterface single-dispatch (onEvent(Event)) — consumers must switch on EventType
  • RepositoryListener uses typed default callbacks (artifactDownloading(), etc.) — much better ergonomics

Proposed hierarchy for 4.1.0: introduce Event and Listener as base marker types, with ExecutionEvent/ExecutionListener (build lifecycle) and RepositoryEvent/RepositoryListener (repository operations) as typed specializations.

Event hierarchy

@Experimental @Immutable
public interface Event {
    @Nonnull Session session();
}

@Experimental @Immutable
public interface ExecutionEvent extends Event {
    @Nonnull ExecutionEventType type();
    @Nonnull Optional<Project> project();
    @Nonnull Optional<MojoExecution> mojoExecution();
    @Nonnull Optional<Exception> exception();
}

@Experimental @Immutable
public interface RepositoryEvent extends Event {
    @Nonnull RepositoryEventType type();
    @Nonnull Optional<Artifact> artifact();
    @Nonnull Optional<RepositoryMetadata> metadata();
    @Nonnull Optional<Path> path();
    @Nonnull Optional<Repository> repository();
    @Nonnull Optional<Exception> exception();
    @Nonnull List<Exception> exceptions();
    @Nonnull Optional<RequestTrace> trace();
}

Listener hierarchy

@Experimental @Consumer
public interface Listener {
    /** @deprecated Implement ExecutionListener or RepositoryListener instead. */
    @Deprecated
    default void onEvent(@Nonnull Event event) {}
}

@Experimental @Consumer
public interface ExecutionListener extends Listener {
    default void sessionStarted(@Nonnull ExecutionEvent event) {}
    default void sessionEnded(@Nonnull ExecutionEvent event) {}
    default void projectDiscoveryStarted(@Nonnull ExecutionEvent event) {}
    default void projectStarted(@Nonnull ExecutionEvent event) {}
    default void projectSucceeded(@Nonnull ExecutionEvent event) {}
    default void projectFailed(@Nonnull ExecutionEvent event) {}
    default void projectSkipped(@Nonnull ExecutionEvent event) {}
    default void mojoStarted(@Nonnull ExecutionEvent event) {}
    default void mojoSucceeded(@Nonnull ExecutionEvent event) {}
    default void mojoFailed(@Nonnull ExecutionEvent event) {}
    default void mojoSkipped(@Nonnull ExecutionEvent event) {}
    default void forkStarted(@Nonnull ExecutionEvent event) {}
    default void forkSucceeded(@Nonnull ExecutionEvent event) {}
    default void forkFailed(@Nonnull ExecutionEvent event) {}
    default void forkedProjectStarted(@Nonnull ExecutionEvent event) {}
    default void forkedProjectSucceeded(@Nonnull ExecutionEvent event) {}
    default void forkedProjectFailed(@Nonnull ExecutionEvent event) {}
}

@Experimental @Consumer
public interface RepositoryListener extends Listener {
    default void artifactDescriptorInvalid(@Nonnull RepositoryEvent event) {}
    default void artifactDescriptorMissing(@Nonnull RepositoryEvent event) {}
    default void metadataInvalid(@Nonnull RepositoryEvent event) {}
    default void artifactResolving(@Nonnull RepositoryEvent event) {}
    default void artifactResolved(@Nonnull RepositoryEvent event) {}
    // ... etc (19 typed callbacks, as in this PR)
}

Session impact

Single registration point — no overloaded methods needed:

// Session keeps ONE set of listener methods for both types:
void registerListener(@Nonnull Listener listener);
void unregisterListener(@Nonnull Listener listener);
Collection<Listener> getListeners();

The dispatcher routes via instanceof ExecutionListener / instanceof RepositoryListener. A listener can even implement both.

Benefits

  • Uniform design — both event families use typed default callbacks, no more @FunctionalInterface single-dispatch
  • Single registration — no separate registerListener(RepositoryListener) / getRepositoryListeners() on Session
  • Extensible — future event categories (transfer, toolchain) just add XxxEvent extends Event + XxxListener extends Listener, no Session changes
  • Backward compatible — old Listener.onEvent() stays as a @Deprecated default, existing consumers keep compiling
  • Noun-style accessors on new types (consistent with Switch core API value types to noun-style accessors #13036), @Immutable events
  • EventTypeExecutionEventType for symmetry with RepositoryEventType

Route execution and repository events through one shared listener registry. Add typed execution callbacks and noun-style event accessors while preserving existing execution Event getters and Listener lambdas.

Cover all event callbacks, combined listeners, derived-session registration, concurrent updates, and failure isolation.
@goutamadwant

Copy link
Copy Markdown
Contributor Author

API Design Suggestion: Event/Listener Hierarchy

The current PR introduces RepositoryListener / RepositoryEvent as standalone types with separate registration methods on Session. This works, but it creates an inconsistency with the existing Listener / Event API (build lifecycle events) — two parallel, unrelated listener systems on the same Session with different design patterns:

  • Listener uses @FunctionalInterface single-dispatch (onEvent(Event)) — consumers must switch on EventType
  • RepositoryListener uses typed default callbacks (artifactDownloading(), etc.) — much better ergonomics

Proposed hierarchy for 4.1.0: introduce Event and Listener as base marker types, with ExecutionEvent/ExecutionListener (build lifecycle) and RepositoryEvent/RepositoryListener (repository operations) as typed specializations.

Event hierarchy

@Experimental @Immutable
public interface Event {
    @Nonnull Session session();
}

@Experimental @Immutable
public interface ExecutionEvent extends Event {
    @Nonnull ExecutionEventType type();
    @Nonnull Optional<Project> project();
    @Nonnull Optional<MojoExecution> mojoExecution();
    @Nonnull Optional<Exception> exception();
}

@Experimental @Immutable
public interface RepositoryEvent extends Event {
    @Nonnull RepositoryEventType type();
    @Nonnull Optional<Artifact> artifact();
    @Nonnull Optional<RepositoryMetadata> metadata();
    @Nonnull Optional<Path> path();
    @Nonnull Optional<Repository> repository();
    @Nonnull Optional<Exception> exception();
    @Nonnull List<Exception> exceptions();
    @Nonnull Optional<RequestTrace> trace();
}

Listener hierarchy

@Experimental @Consumer
public interface Listener {
    /** @deprecated Implement ExecutionListener or RepositoryListener instead. */
    @Deprecated
    default void onEvent(@Nonnull Event event) {}
}

@Experimental @Consumer
public interface ExecutionListener extends Listener {
    default void sessionStarted(@Nonnull ExecutionEvent event) {}
    default void sessionEnded(@Nonnull ExecutionEvent event) {}
    default void projectDiscoveryStarted(@Nonnull ExecutionEvent event) {}
    default void projectStarted(@Nonnull ExecutionEvent event) {}
    default void projectSucceeded(@Nonnull ExecutionEvent event) {}
    default void projectFailed(@Nonnull ExecutionEvent event) {}
    default void projectSkipped(@Nonnull ExecutionEvent event) {}
    default void mojoStarted(@Nonnull ExecutionEvent event) {}
    default void mojoSucceeded(@Nonnull ExecutionEvent event) {}
    default void mojoFailed(@Nonnull ExecutionEvent event) {}
    default void mojoSkipped(@Nonnull ExecutionEvent event) {}
    default void forkStarted(@Nonnull ExecutionEvent event) {}
    default void forkSucceeded(@Nonnull ExecutionEvent event) {}
    default void forkFailed(@Nonnull ExecutionEvent event) {}
    default void forkedProjectStarted(@Nonnull ExecutionEvent event) {}
    default void forkedProjectSucceeded(@Nonnull ExecutionEvent event) {}
    default void forkedProjectFailed(@Nonnull ExecutionEvent event) {}
}

@Experimental @Consumer
public interface RepositoryListener extends Listener {
    default void artifactDescriptorInvalid(@Nonnull RepositoryEvent event) {}
    default void artifactDescriptorMissing(@Nonnull RepositoryEvent event) {}
    default void metadataInvalid(@Nonnull RepositoryEvent event) {}
    default void artifactResolving(@Nonnull RepositoryEvent event) {}
    default void artifactResolved(@Nonnull RepositoryEvent event) {}
    // ... etc (19 typed callbacks, as in this PR)
}

Session impact

Single registration point — no overloaded methods needed:

// Session keeps ONE set of listener methods for both types:
void registerListener(@Nonnull Listener listener);
void unregisterListener(@Nonnull Listener listener);
Collection<Listener> getListeners();

The dispatcher routes via instanceof ExecutionListener / instanceof RepositoryListener. A listener can even implement both.

Benefits

  • Uniform design — both event families use typed default callbacks, no more @FunctionalInterface single-dispatch
  • Single registration — no separate registerListener(RepositoryListener) / getRepositoryListeners() on Session
  • Extensible — future event categories (transfer, toolchain) just add XxxEvent extends Event + XxxListener extends Listener, no Session changes
  • Backward compatible — old Listener.onEvent() stays as a @Deprecated default, existing consumers keep compiling
  • Noun-style accessors on new types (consistent with Switch core API value types to noun-style accessors #13036), @Immutable events
  • EventTypeExecutionEventType for symmetry with RepositoryEventType

@gnodet Updated to one listener registration path with typed execution and repository callbacks, including listeners implementing both interfaces. Added noun-style event accessors and ExecutionEventType.

I kept Listener functional and retained the existing Event getters: making onEvent a default would break existing lambdas, and replacing Event with a marker would remove its current methods. SessionEvent provides the shared event base, while TypedListener supplies the common default without conflicting inherited methods.

The PR description includes the full integration results and successful redirect-test retry.

Make Event the base interface for both ExecutionEvent and RepositoryEvent,
removing the SessionEvent intermediate. Drop TypedListener by moving the
dispatch logic into ExecutionListener.onEvent() and RepositoryListener.onEvent()
as default methods. This makes the dispatch self-contained in the API interfaces
and simplifies EventSpyImpl to a single listener.onEvent(event) call.

Changes:
- Event: base interface with session() + deprecated getX() compat defaults
- ExecutionEvent: extends Event (unchanged noun-style accessors)
- RepositoryEvent: extends Event (was SessionEvent)
- ExecutionListener: extends Listener, default onEvent() dispatches to typed callbacks
- RepositoryListener: extends Listener, default onEvent() dispatches to typed callbacks
- SessionEvent: deleted (redundant, Event is the base)
- TypedListener: deleted (each sub-interface provides its own default onEvent())
- EventSpyImpl: simplified to just call listener.onEvent(event)
- Listener: unchanged (@FunctionalInterface, abstract onEvent(Event))

@gnodet gnodet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All three findings from the previous review (2026-09-03) have been addressed:

  • SessionStub getRepositoryListeners() null return → fixed (returns List.of()).
  • RepositoryListener — 19 callback methods now have Javadoc.
  • RepositoryMetadatagetType() and getNature() now have clarifying Javadoc.

The new commits (unified listener hierarchy, ExecutionEvent/ExecutionListener/ExecutionEventType) are a clean improvement. Two documentation issues in the new types:

This review was generated by an AI agent, Hermès on behalf of @gnodet.

Comment thread api/maven-api-core/src/main/java/org/apache/maven/api/Event.java Outdated
Comment thread api/maven-api-core/src/main/java/org/apache/maven/api/RepositoryEvent.java Outdated

@gnodet gnodet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Both findings from the 2026-09-07 review are addressed:

  • getType() Javadoc missing @throws@throws UnsupportedOperationException if this event is not an ExecutionEvent added in 740c915e04. ✅
  • Redundant session() re-declaration in RepositoryEvent → removed in 740c915e04. ✅
  • RepositoryMetadata noun-style accessors → renamed from getXxx() to xxx() in b963221926. ✅

Two documentation issues remain in the new code:

This review was generated by an AI agent, Hermès on behalf of @gnodet.

/**
* Gets the type of the event.
*
* @return the type of the event, never {@code null}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Javadoc still inconsistent after partial fix. The @throws UnsupportedOperationException was correctly added in 740c915e04, but line 64 still says never {@code null} — which is misleading: for any Event that is not an ExecutionEvent, the method throws rather than returning. The @return description and @Nonnull both imply the method always produces a value, contradicting the @throws.

Change the @return description to scope the guarantee:

Suggested change
* @return the type of the event, never {@code null}
* @return the execution event type, never {@code null}; only meaningful when this event is an {@link ExecutionEvent}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Fixed in a8fe34a. Changed @return to: "the execution event type, never null; only meaningful when this event is an ExecutionEvent", which is consistent with the @throws and no longer implies the method always produces a value.

public interface RepositoryEvent extends Event {

/**
* Returns the kind of repository operation represented by this event.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Low: Javadoc style inconsistency across sibling types. All eight accessors in RepositoryEvent use prose-style /** Returns X */ without a @return tag. Both sibling types — ExecutionEvent and RepositoryMetadata — use the modern /** {@return X} */ single-tag form throughout. Using the same style here keeps the public API consistent and ensures @return appears in generated Javadoc output.

For example, type() should be:

Suggested change
* Returns the kind of repository operation represented by this event.
/**
* {@return the kind of repository operation represented by this event}
*/

Apply the same {@return ...} pattern to the remaining seven accessors (artifact(), metadata(), path(), repository(), exception(), exceptions(), trace()).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Fixed in a8fe34a. All eight accessors in RepositoryEvent now use the /** {\@return ...} */ single-tag form, consistent with ExecutionEvent and RepositoryMetadata.

@gnodet gnodet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All findings from previous reviews (2026-09-02, 2026-09-03, 2026-09-07, 2026-09-08) have been addressed in the latest merge commit a8fe34a4bb. The PR is in good shape.

Summary of resolved items:

  • SessionStub null return → List.of()
  • RepositoryListener Javadoc (19 callbacks) ✅
  • RepositoryMetadata Javadoc (type(), nature(), all accessors) ✅
  • getType() Javadoc @throws + scoped @return
  • Redundant session() re-declaration in RepositoryEvent → removed ✅
  • RepositoryEvent {@return} style consistency ✅
  • RepositoryMetadata noun-style accessors (getXxx()xxx()) ✅

Current state assessment — no blocking issues found.

The implementation is clean and well-structured:

  1. Event hierarchyEventExecutionEvent / RepositoryEvent with shared session() base. Backward-compatible deprecated defaults on Event that delegate properly (getType() throws for non-execution events, others return Optional.empty()). Clean.

  2. Listener dispatchExecutionListener and RepositoryListener each provide their own default onEvent() with switch dispatch to typed callbacks. No need for a TypedListener intermediary. Listener stays @FunctionalInterface. Existing lambdas continue to work. This is the right design.

  3. Registration sharing — Listeners stored in RepositorySystemSession.getData() keyed by Listener.class, so derived sessions (withRemoteRepositories, withContext) share the same CopyOnWriteArrayList. Thread-safe and consistent.

  4. Bridge wiringMavenRepositoryListener (Resolver → Maven API) correctly chains into the session factory via ChainedRepositoryListener, and ApiRunner sets it up for standalone use. EventSpyImpl handles execution events with listener failure isolation.

  5. Test coverage — comprehensive: all 17 execution and 19 repository event types, combined listeners, concurrent registration, derived-session sharing, failure isolation, lambda ambiguity guard, metadata mapping, immutable collections, and the session factory wiring test.

Two optional observations (not blocking):

  • DefaultEvent still stores EventType and converts via ExecutionEventType.valueOf(eventType.name()) in type() — this works because the enum constants have matching names, but it's a runtime coupling. Could store ExecutionEventType directly. Minor.

  • CombinedListener pattern requires explicit onEvent override — a class implementing both ExecutionListener and RepositoryListener must write onEvent to call both supers. The tests demonstrate this clearly, but the API Javadoc on Listener could mention it. Nice-to-have.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

Explain the onEvent override required when a listener implements both
ExecutionListener and RepositoryListener, with an example delegating to
both default methods.

@gnodet gnodet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

New commit (f77c6dbeb9) documents the combined listener dispatch pattern on Listener — the onEvent override required when implementing both ExecutionListener and RepositoryListener. This was the last nice-to-have from the previous review round.

Documentation is clear, correct, and includes a useful code example. No issues.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[MNG-8547] The API is missing a way to listen to repository events

2 participants