From 4bda27d7fd06af7a1a02ca42b763bc4012eb00cb Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Sun, 6 Sep 2026 20:41:45 +0000 Subject: [PATCH] [MNG-8547] Introduce Event/Listener hierarchy with ExecutionEvent and ExecutionListener Introduce a base Event/Listener hierarchy to support multiple event families (execution, repository, etc.) through a single registration point on Session. - Event: minimal base interface with session() accessor - ExecutionEvent: typed execution event (extends Event) with project(), mojoExecution(), exception() accessors - ExecutionListener: typed callbacks (extends Listener) with per-event-type default methods - ExecutionEventType: renamed from EventType for clarity - EventType: kept as @Deprecated alias - Listener: refactored to base marker, onEvent() deprecated - DefaultEvent: implements ExecutionEvent with noun-style accessors - EventSpyImpl: dispatches to both legacy and typed listeners --- .../main/java/org/apache/maven/api/Event.java | 64 +++++-- .../java/org/apache/maven/api/EventType.java | 23 ++- .../org/apache/maven/api/ExecutionEvent.java | 70 ++++++++ .../apache/maven/api/ExecutionEventType.java | 50 ++++++ .../apache/maven/api/ExecutionListener.java | 158 ++++++++++++++++++ .../java/org/apache/maven/api/Listener.java | 21 ++- .../maven/execution/ExecutionEvent.java | 2 +- .../maven/internal/impl/DefaultEvent.java | 26 +-- .../maven/internal/impl/EventSpyImpl.java | 46 ++++- 9 files changed, 424 insertions(+), 36 deletions(-) create mode 100644 api/maven-api-core/src/main/java/org/apache/maven/api/ExecutionEvent.java create mode 100644 api/maven-api-core/src/main/java/org/apache/maven/api/ExecutionEventType.java create mode 100644 api/maven-api-core/src/main/java/org/apache/maven/api/ExecutionListener.java diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/Event.java b/api/maven-api-core/src/main/java/org/apache/maven/api/Event.java index 0323abd43579..005368c899cd 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/Event.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/Event.java @@ -21,54 +21,98 @@ import java.util.Optional; import org.apache.maven.api.annotations.Experimental; +import org.apache.maven.api.annotations.Immutable; import org.apache.maven.api.annotations.Nonnull; /** - * Event sent by maven during various phases of the build process. - * Such events can be listened to using {@link Listener}s objects - * registered in the {@link Session}. + * Base interface for all Maven events. + * Specific event families extend this interface to provide typed event data. + * Events can be listened to using {@link Listener} objects registered in the {@link Session}. * + * @see ExecutionEvent + * @see Listener * @since 4.0.0 */ @Experimental +@Immutable public interface Event { /** - * Gets the type of the event. + * Gets the session from which this event originates. * - * @return the type of the event, never {@code null} + * @return the current session, never {@code null} */ @Nonnull - EventType getType(); + Session session(); /** * Gets the session from which this event originates. * * @return the current session, never {@code null} + * @deprecated Use {@link #session()} instead. + */ + @Deprecated(since = "4.1.0", forRemoval = true) + @Nonnull + default Session getSession() { + return session(); + } + + /** + * Gets the type of the event. + * + * @return the type of the event, never {@code null} + * @deprecated Use {@link ExecutionEvent#type()} instead. */ + @Deprecated(since = "4.1.0", forRemoval = true) @Nonnull - Session getSession(); + default EventType getType() { + if (this instanceof ExecutionEvent ee) { + return EventType.fromExecutionEventType(ee.type()); + } + throw new UnsupportedOperationException("getType() is only supported on ExecutionEvent instances"); + } /** * Gets the current project (if any). * * @return the current project or {@code empty()} if not applicable + * @deprecated Use {@link ExecutionEvent#project()} instead. */ + @Deprecated(since = "4.1.0", forRemoval = true) @Nonnull - Optional getProject(); + default Optional getProject() { + if (this instanceof ExecutionEvent ee) { + return ee.project(); + } + return Optional.empty(); + } /** * Gets the current mojo execution (if any). * * @return the current mojo execution or {@code empty()} if not applicable + * @deprecated Use {@link ExecutionEvent#mojoExecution()} instead. */ + @Deprecated(since = "4.1.0", forRemoval = true) @Nonnull - Optional getMojoExecution(); + default Optional getMojoExecution() { + if (this instanceof ExecutionEvent ee) { + return ee.mojoExecution(); + } + return Optional.empty(); + } /** * Gets the exception that caused the event (if any). * * @return the exception or {@code empty()} if none + * @deprecated Use {@link ExecutionEvent#exception()} instead. */ - Optional getException(); + @Deprecated(since = "4.1.0", forRemoval = true) + default Optional getException() { + if (this instanceof ExecutionEvent ee) { + return ee.exception(); + } + return Optional.empty(); + } } diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/EventType.java b/api/maven-api-core/src/main/java/org/apache/maven/api/EventType.java index 63d3479fbf39..45c672bb4555 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/EventType.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/EventType.java @@ -24,7 +24,9 @@ * The possible types of execution events. * * @since 4.0.0 + * @deprecated Use {@link ExecutionEventType} instead. */ +@Deprecated(since = "4.1.0", forRemoval = true) @Experimental public enum EventType { PROJECT_DISCOVERY_STARTED, @@ -43,5 +45,24 @@ public enum EventType { FORK_FAILED, FORKED_PROJECT_STARTED, FORKED_PROJECT_SUCCEEDED, - FORKED_PROJECT_FAILED, + FORKED_PROJECT_FAILED; + + /** + * Converts this deprecated {@code EventType} to the new {@link ExecutionEventType}. + * + * @return the corresponding {@link ExecutionEventType}, never {@code null} + */ + public ExecutionEventType toExecutionEventType() { + return ExecutionEventType.values()[ordinal()]; + } + + /** + * Converts an {@link ExecutionEventType} to the deprecated {@code EventType}. + * + * @param type the {@link ExecutionEventType} to convert, must not be {@code null} + * @return the corresponding {@code EventType}, never {@code null} + */ + public static EventType fromExecutionEventType(ExecutionEventType type) { + return values()[type.ordinal()]; + } } diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/ExecutionEvent.java b/api/maven-api-core/src/main/java/org/apache/maven/api/ExecutionEvent.java new file mode 100644 index 000000000000..2d9a2c795c7e --- /dev/null +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/ExecutionEvent.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * http://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. + */ +package org.apache.maven.api; + +import java.util.Optional; + +import org.apache.maven.api.annotations.Experimental; +import org.apache.maven.api.annotations.Immutable; +import org.apache.maven.api.annotations.Nonnull; + +/** + * Event sent by Maven during various phases of the build lifecycle (session, project, mojo). + * Such events can be listened to using {@link ExecutionListener} objects registered in the {@link Session}. + * + * @see ExecutionListener + * @see ExecutionEventType + * @since 4.1.0 + */ +@Experimental +@Immutable +public interface ExecutionEvent extends Event { + + /** + * Gets the type of the execution event. + * + * @return the type of the event, never {@code null} + */ + @Nonnull + ExecutionEventType type(); + + /** + * Gets the current project (if any). + * + * @return the current project or {@code empty()} if not applicable + */ + @Nonnull + Optional project(); + + /** + * Gets the current mojo execution (if any). + * + * @return the current mojo execution or {@code empty()} if not applicable + */ + @Nonnull + Optional mojoExecution(); + + /** + * Gets the exception that caused the event (if any). + * + * @return the exception or {@code empty()} if none + */ + @Nonnull + Optional exception(); +} diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/ExecutionEventType.java b/api/maven-api-core/src/main/java/org/apache/maven/api/ExecutionEventType.java new file mode 100644 index 000000000000..b71f8fc9bdf5 --- /dev/null +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/ExecutionEventType.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * http://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. + */ +package org.apache.maven.api; + +import org.apache.maven.api.annotations.Experimental; + +/** + * The possible types of execution events during the Maven build lifecycle. + * + * @see ExecutionEvent + * @see ExecutionListener + * @since 4.0.0 + * @since 4.1.0 (renamed from {@link EventType}) + */ +@Experimental +public enum ExecutionEventType { + PROJECT_DISCOVERY_STARTED, + SESSION_STARTED, + SESSION_ENDED, + PROJECT_SKIPPED, + PROJECT_STARTED, + PROJECT_SUCCEEDED, + PROJECT_FAILED, + MOJO_SKIPPED, + MOJO_STARTED, + MOJO_SUCCEEDED, + MOJO_FAILED, + FORK_STARTED, + FORK_SUCCEEDED, + FORK_FAILED, + FORKED_PROJECT_STARTED, + FORKED_PROJECT_SUCCEEDED, + FORKED_PROJECT_FAILED, +} diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/ExecutionListener.java b/api/maven-api-core/src/main/java/org/apache/maven/api/ExecutionListener.java new file mode 100644 index 000000000000..ca97298e86f9 --- /dev/null +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/ExecutionListener.java @@ -0,0 +1,158 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * http://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. + */ +package org.apache.maven.api; + +import org.apache.maven.api.annotations.Consumer; +import org.apache.maven.api.annotations.Experimental; +import org.apache.maven.api.annotations.Nonnull; + +/** + * A typed listener for Maven build lifecycle execution events. + * Each method corresponds to a specific {@link ExecutionEventType} and has a no-op default implementation, + * so implementations only need to override the methods they care about. + *

+ * Register an {@code ExecutionListener} via {@link Session#registerListener(Listener)}. + * + * @see ExecutionEvent + * @see ExecutionEventType + * @since 4.1.0 + */ +@Experimental +@Consumer +public interface ExecutionListener extends Listener { + + /** + * Called when project discovery has started. + * + * @param event the execution event, never {@code null} + */ + default void projectDiscoveryStarted(@Nonnull ExecutionEvent event) {} + + /** + * Called when the Maven session has started. + * + * @param event the execution event, never {@code null} + */ + default void sessionStarted(@Nonnull ExecutionEvent event) {} + + /** + * Called when the Maven session has ended. + * + * @param event the execution event, never {@code null} + */ + default void sessionEnded(@Nonnull ExecutionEvent event) {} + + /** + * Called when a project has been skipped. + * + * @param event the execution event, never {@code null} + */ + default void projectSkipped(@Nonnull ExecutionEvent event) {} + + /** + * Called when a project build has started. + * + * @param event the execution event, never {@code null} + */ + default void projectStarted(@Nonnull ExecutionEvent event) {} + + /** + * Called when a project build has succeeded. + * + * @param event the execution event, never {@code null} + */ + default void projectSucceeded(@Nonnull ExecutionEvent event) {} + + /** + * Called when a project build has failed. + * + * @param event the execution event, never {@code null} + */ + default void projectFailed(@Nonnull ExecutionEvent event) {} + + /** + * Called when a mojo execution has been skipped. + * + * @param event the execution event, never {@code null} + */ + default void mojoSkipped(@Nonnull ExecutionEvent event) {} + + /** + * Called when a mojo execution has started. + * + * @param event the execution event, never {@code null} + */ + default void mojoStarted(@Nonnull ExecutionEvent event) {} + + /** + * Called when a mojo execution has succeeded. + * + * @param event the execution event, never {@code null} + */ + default void mojoSucceeded(@Nonnull ExecutionEvent event) {} + + /** + * Called when a mojo execution has failed. + * + * @param event the execution event, never {@code null} + */ + default void mojoFailed(@Nonnull ExecutionEvent event) {} + + /** + * Called when a forked execution has started. + * + * @param event the execution event, never {@code null} + */ + default void forkStarted(@Nonnull ExecutionEvent event) {} + + /** + * Called when a forked execution has succeeded. + * + * @param event the execution event, never {@code null} + */ + default void forkSucceeded(@Nonnull ExecutionEvent event) {} + + /** + * Called when a forked execution has failed. + * + * @param event the execution event, never {@code null} + */ + default void forkFailed(@Nonnull ExecutionEvent event) {} + + /** + * Called when a forked project build has started. + * + * @param event the execution event, never {@code null} + */ + default void forkedProjectStarted(@Nonnull ExecutionEvent event) {} + + /** + * Called when a forked project build has succeeded. + * + * @param event the execution event, never {@code null} + */ + default void forkedProjectSucceeded(@Nonnull ExecutionEvent event) {} + + /** + * Called when a forked project build has failed. + * + * @param event the execution event, never {@code null} + */ + default void forkedProjectFailed(@Nonnull ExecutionEvent event) {} +} diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/Listener.java b/api/maven-api-core/src/main/java/org/apache/maven/api/Listener.java index dda744f7375a..95dbe7ba671b 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/Listener.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/Listener.java @@ -23,14 +23,27 @@ import org.apache.maven.api.annotations.Nonnull; /** - * A listener for session events. - * TODO: open this to other events like similar to {@code org.apache.maven.eventspy.EventSpy} + * Base marker interface for all Maven event listeners. + * Specific listener sub-interfaces (such as {@link ExecutionListener}) provide typed callbacks + * for particular event families. + * Register listeners via {@link Session#registerListener(Listener)}. * + * @see ExecutionListener * @since 4.0.0 */ @Experimental -@FunctionalInterface @Consumer public interface Listener { - void onEvent(@Nonnull Event event); + + /** + * Called when an event occurs. + *

+ * This generic callback is provided for backward compatibility. Prefer implementing + * {@link ExecutionListener} or another typed sub-interface for type-safe event handling. + * + * @param event the event + * @deprecated Implement {@link ExecutionListener} or a specific listener sub-interface instead. + */ + @Deprecated(since = "4.1.0", forRemoval = true) + default void onEvent(@Nonnull Event event) {} } diff --git a/impl/maven-core/src/main/java/org/apache/maven/execution/ExecutionEvent.java b/impl/maven-core/src/main/java/org/apache/maven/execution/ExecutionEvent.java index 895faef03060..02bcebe946b5 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/execution/ExecutionEvent.java +++ b/impl/maven-core/src/main/java/org/apache/maven/execution/ExecutionEvent.java @@ -31,7 +31,7 @@ public interface ExecutionEvent { * The possible types of execution events. * * Note: do not modify this enum, or, make sure that this enum and - * {@link org.apache.maven.api.EventType} have same elements in same order. + * {@link org.apache.maven.api.ExecutionEventType} have same elements in same order. */ enum Type { ProjectDiscoveryStarted, diff --git a/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultEvent.java b/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultEvent.java index 7003824de62f..1505fc57f6d9 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultEvent.java +++ b/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultEvent.java @@ -20,46 +20,48 @@ import java.util.Optional; -import org.apache.maven.api.Event; -import org.apache.maven.api.EventType; +import org.apache.maven.api.ExecutionEvent; +import org.apache.maven.api.ExecutionEventType; import org.apache.maven.api.MojoExecution; import org.apache.maven.api.Project; import org.apache.maven.api.Session; -import org.apache.maven.execution.ExecutionEvent; -public class DefaultEvent implements Event { +public class DefaultEvent implements ExecutionEvent { private final InternalMavenSession session; - private final ExecutionEvent delegate; - private final EventType eventType; + private final org.apache.maven.execution.ExecutionEvent delegate; + private final ExecutionEventType eventType; - public DefaultEvent(InternalMavenSession session, ExecutionEvent delegate, EventType eventType) { + public DefaultEvent( + InternalMavenSession session, + org.apache.maven.execution.ExecutionEvent delegate, + ExecutionEventType eventType) { this.session = session; this.delegate = delegate; this.eventType = eventType; } @Override - public EventType getType() { + public ExecutionEventType type() { return eventType; } @Override - public Session getSession() { + public Session session() { return session; } @Override - public Optional getProject() { + public Optional project() { return Optional.ofNullable(session.getProject(delegate.getProject())); } @Override - public Optional getMojoExecution() { + public Optional mojoExecution() { return Optional.ofNullable(delegate.getMojoExecution()).map(me -> new DefaultMojoExecution(session, me)); } @Override - public Optional getException() { + public Optional exception() { return Optional.ofNullable(delegate.getException()); } } diff --git a/impl/maven-core/src/main/java/org/apache/maven/internal/impl/EventSpyImpl.java b/impl/maven-core/src/main/java/org/apache/maven/internal/impl/EventSpyImpl.java index 757b08a7eedb..635a288d0f2d 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/internal/impl/EventSpyImpl.java +++ b/impl/maven-core/src/main/java/org/apache/maven/internal/impl/EventSpyImpl.java @@ -23,14 +23,16 @@ import java.util.Collection; -import org.apache.maven.api.Event; -import org.apache.maven.api.EventType; +import org.apache.maven.api.ExecutionEvent; +import org.apache.maven.api.ExecutionEventType; +import org.apache.maven.api.ExecutionListener; import org.apache.maven.api.Listener; import org.apache.maven.eventspy.EventSpy; -import org.apache.maven.execution.ExecutionEvent; /** * Bridges between Maven3 events and Maven4 events. + * Dispatches to both the deprecated generic {@link Listener#onEvent(org.apache.maven.api.Event)} handler + * and the typed {@link ExecutionListener} callbacks. */ @Named @Singleton @@ -40,25 +42,53 @@ public void init(Context context) throws Exception {} @Override public void onEvent(Object arg) throws Exception { - if (arg instanceof ExecutionEvent ee) { + if (arg instanceof org.apache.maven.execution.ExecutionEvent ee) { InternalMavenSession session = InternalMavenSession.from(ee.getSession().getSession()); - EventType eventType = convert(ee.getType()); + ExecutionEventType eventType = convert(ee.getType()); Collection listeners = session.getListeners(); if (!listeners.isEmpty()) { - Event event = new DefaultEvent(session, ee, eventType); + ExecutionEvent event = new DefaultEvent(session, ee, eventType); for (Listener listener : listeners) { + // Call deprecated generic handler for backward compatibility listener.onEvent(event); + // Call typed handler for new-style listeners + if (listener instanceof ExecutionListener el) { + dispatchTyped(el, event, eventType); + } } } } } + private void dispatchTyped(ExecutionListener listener, ExecutionEvent event, ExecutionEventType type) { + switch (type) { + case PROJECT_DISCOVERY_STARTED -> listener.projectDiscoveryStarted(event); + case SESSION_STARTED -> listener.sessionStarted(event); + case SESSION_ENDED -> listener.sessionEnded(event); + case PROJECT_SKIPPED -> listener.projectSkipped(event); + case PROJECT_STARTED -> listener.projectStarted(event); + case PROJECT_SUCCEEDED -> listener.projectSucceeded(event); + case PROJECT_FAILED -> listener.projectFailed(event); + case MOJO_SKIPPED -> listener.mojoSkipped(event); + case MOJO_STARTED -> listener.mojoStarted(event); + case MOJO_SUCCEEDED -> listener.mojoSucceeded(event); + case MOJO_FAILED -> listener.mojoFailed(event); + case FORK_STARTED -> listener.forkStarted(event); + case FORK_SUCCEEDED -> listener.forkSucceeded(event); + case FORK_FAILED -> listener.forkFailed(event); + case FORKED_PROJECT_STARTED -> listener.forkedProjectStarted(event); + case FORKED_PROJECT_SUCCEEDED -> listener.forkedProjectSucceeded(event); + case FORKED_PROJECT_FAILED -> listener.forkedProjectFailed(event); + default -> {} + } + } + /** * Simple "conversion" from Maven3 event type enum to Maven4 enum. */ - protected EventType convert(ExecutionEvent.Type type) { - return EventType.values()[type.ordinal()]; + protected ExecutionEventType convert(org.apache.maven.execution.ExecutionEvent.Type type) { + return ExecutionEventType.values()[type.ordinal()]; } @Override