Skip to content

onany listener - event listener logic#60

Merged
sanjomo merged 16 commits intomainfrom
onanylistener-onevent
Nov 28, 2025
Merged

onany listener - event listener logic#60
sanjomo merged 16 commits intomainfrom
onanylistener-onevent

Conversation

@sanjomo
Copy link
Member

@sanjomo sanjomo commented Nov 27, 2025

Description

Brief description of the changes in this PR.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Code refactoring
  • Test improvements
  • Build/tooling changes

Related Issue

Closes #58

Changes Made

Testing

  • All existing tests pass
  • New tests added for new functionality
  • Tests pass locally with mvn test
  • Integration tests pass (if applicable)

Checklist

  • Code follows project coding standards
  • Self-review completed
  • Code is commented where necessary
  • Documentation updated (if needed)
  • Commit messages follow conventional format
  • No merge conflicts
  • All CI checks pass

Additional Notes

Any additional information, screenshots, or context that reviewers should know.

Summary by CodeRabbit

  • New Features

    • Add catch-all event listener API (onAny/offAny) across server, namespace, and client listener APIs.
    • New functional listener type receiving client, event name, args, and ack.
    • Added convenience aliases and adapters for simpler catch-all callbacks.
  • Bug Fixes

    • Unknown-event parsing now preserves and returns event arguments when present.
    • Catch-all listeners are invoked after regular listeners and interceptors for consistent ordering.
  • Style

    • Minor formatting cleanup.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 27, 2025

Walkthrough

Adds a catch‑all listener API and wiring: new CatchAllEventListener, default methods in ClientListeners, onAny/offAny API on Namespace and SocketIOServer, invocation of catch‑all listeners from Namespace.onEvent, and JSON deserialization fallback to generic args for unknown events.

Changes

Cohort / File(s) Change Summary
Catch‑all listener interface
netty-socketio-core/src/main/java/com/socketio4j/socketio/listener/CatchAllEventListener.java
New public functional interface CatchAllEventListener with void onEvent(SocketIOClient client, String event, List<Object> args, AckRequest ackRequest).
Client listeners base
netty-socketio-core/src/main/java/com/socketio4j/socketio/listener/ClientListeners.java
Added default methods: addOnAnyEventListener, removeOnAnyEventListener, onAny, offAny (default implementations throw UnsupportedOperationException or delegate).
Namespace: event handling & API
netty-socketio-core/src/main/java/com/socketio4j/socketio/namespace/Namespace.java
Added catchAllEventListeners storage and public API: addOnAnyEventListener, removeOnAnyEventListener, onAny/offAny overloads (including BiConsumer/Consumer adapters). Modified onEvent to process DataListeners only if an EventEntry exists and to invoke catch‑all listeners after regular listeners and interceptors.
Server API
netty-socketio-core/src/main/java/com/socketio4j/socketio/SocketIOServer.java
Imported CatchAllEventListener and added addOnAnyEventListener, removeOnAnyEventListener, onAny, offAny methods delegating to the main namespace.
Event deserialization fallback
netty-socketio-core/src/main/java/com/socketio4j/socketio/protocol/JacksonJsonSupport.java
EventDeserializer now falls back to parsing remaining tokens into a List<Object> of generic JSON values when no mapping exists and returns an Event with those args.
Minor formatting
netty-socketio-core/src/main/java/com/socketio4j/socketio/listener/EventInterceptor.java
Removed extra whitespace in the EventInterceptor interface declaration line (formatting only).

Sequence Diagram

sequenceDiagram
    participant Client
    participant Deserializer as EventDeserializer
    participant Namespace
    participant DataListeners as DataListeners
    participant Interceptors as EventInterceptors
    participant CatchAll as CatchAllListeners

    Client->>Deserializer: Send event payload (namespace, event, data)
    alt Event mapped in namespace
        Deserializer->>Namespace: Event with mapped args
    else No mapping for event
        Deserializer->>Deserializer: Parse remaining tokens -> List<Object>
        Deserializer->>Namespace: Event with generic args
    end

    Namespace->>DataListeners: Lookup EventEntry and invoke DataListeners (only if entry != null)
    DataListeners-->>Namespace: Completed
    Namespace->>Interceptors: Run EventInterceptors
    Interceptors-->>Namespace: Completed
    Namespace->>CatchAll: Invoke each CatchAllEventListener(client, event, args, ack)
    CatchAll-->>Namespace: Completed
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Inspect Namespace.onEvent changes for preserved semantics and listener invocation ordering.
  • Verify concurrent safety of catchAllEventListeners (add/remove while iterating).
  • Validate JacksonJsonSupport fallback consumes tokens correctly and maps types as expected.
  • Confirm SocketIOServer delegates to the correct namespace and method names/signatures.

Poem

🐰
I twitch my nose and listen wide,
For every event I never hide.
Unknown tokens turn to things,
I catch them all on nimble wings.
Hopping in — onAny, let's glide!

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description follows the template structure but lacks substantive details; 'Changes Made' section contains only placeholders with no actual implementation specifics. Populate the 'Changes Made' section with concrete details about the new CatchAllEventListener interface, onAny/offAny methods, and event dispatch logic modifications.
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'onany listener - event listener logic' is partially related to the changeset but lacks clarity and specificity about the actual implementation. Consider revising to be more descriptive, e.g., 'Add onAny catch-all event listener support' or 'Implement server-side onAny listener API'.
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR implementation addresses all coding requirements from issue #58: introduces CatchAllEventListener interface, adds onAny/offAny methods across SocketIOServer/Namespace, and enables catch-all listener invocation in event dispatch flow.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing catch-all listener support. Minor whitespace fix in EventInterceptor.java is negligible and the JacksonJsonSupport fallback parsing aligns with supporting unregistered event types.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch onanylistener-onevent

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c0272fa and 56a13c7.

📒 Files selected for processing (1)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/listener/CatchAllEventListener.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/listener/CatchAllEventListener.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: build (17) / build
  • GitHub Check: build (21) / build
  • GitHub Check: build (25) / build
  • GitHub Check: Analyze (java-kotlin)
  • GitHub Check: Analyze (java-kotlin)

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
netty-socketio-core/src/main/java/com/socketio4j/socketio/SocketIOServer.java (1)

26-26: Consider explicit imports for better code clarity.

While wildcard imports are convenient, explicit imports make dependencies clearer and help avoid naming conflicts. However, since this is an internal package and the change is consistent across the codebase, this is acceptable.

netty-socketio-core/src/main/java/com/socketio4j/socketio/listener/CatchAllEventListener.java (1)

8-15: Add comprehensive JavaDoc for the new public API.

The CatchAllEventListener interface would benefit from detailed documentation covering:

  • The purpose and use case for catch-all event listeners
  • When onEvent is invoked (for all events, including unregistered ones)
  • Execution order relative to EventInterceptor and DataListener
  • Thread safety guarantees (called from event processing thread)
  • Parameter descriptions
  • Usage example showing registration via SocketIOServer.onAny() or Namespace.onAny()

Example documentation structure:

/**
 * A catch-all event listener that receives all Socket.IO events for a namespace,
 * regardless of whether specific event listeners are registered.
 * <p>
 * Catch-all listeners are invoked after {@link DataListener}s and {@link EventInterceptor}s
 * in the following order:
 * <ol>
 *   <li>Data listeners (if the event is registered)</li>
 *   <li>Event interceptors</li>
 *   <li>Catch-all listeners</li>
 * </ol>
 * <p>
 * This is useful for forwarding unknown events to downstream services or logging all events.
 * <p>
 * Thread Safety: Invoked from the event processing thread. Implementations should be thread-safe
 * if registered on multiple namespaces.
 * <p>
 * Example usage:
 * <pre>{@code
 * server.onAny((client, eventName, args, ackRequest) -> {
 *     logger.info("Event received: {} from {}", eventName, client.getSessionId());
 * });
 * }</pre>
 *
 * @author https://github.com/sanjomo
 * @date 27/11/25 10:31 am
 */
@FunctionalInterface
public interface CatchAllEventListener {
    /**
     * Called when any Socket.IO event is received.
     *
     * @param client the client that sent the event
     * @param eventName the name of the event
     * @param args the event arguments
     * @param ackRequest the acknowledgment request, can be used to send a response
     */
    void onEvent(SocketIOClient client, String eventName, List<Object> args, AckRequest ackRequest);
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 482d525 and 527f848.

📒 Files selected for processing (4)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/SocketIOServer.java (2 hunks)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/listener/CatchAllEventListener.java (1 hunks)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/listener/ClientListeners.java (1 hunks)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/namespace/Namespace.java (4 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: build (25) / build
  • GitHub Check: build (21) / build
  • GitHub Check: build (17) / build
  • GitHub Check: Analyze (java-kotlin)
  • GitHub Check: Analyze (java-kotlin)
🔇 Additional comments (6)
netty-socketio-core/src/main/java/com/socketio4j/socketio/SocketIOServer.java (1)

367-370: LGTM!

The implementation correctly delegates to mainNamespace.onAny(listener), consistent with the delegation pattern used for other listener methods in this class.

netty-socketio-core/src/main/java/com/socketio4j/socketio/namespace/Namespace.java (4)

32-32: Wildcard import is acceptable for internal package.

Consistent with the change in SocketIOServer.java, using wildcard import for the listener package is acceptable since it's internal.


71-71: LGTM!

Using ConcurrentLinkedQueue for catch-all listeners is correct and consistent with the thread-safe pattern used for other listener queues in this class.


131-134: LGTM!

The onAny implementation correctly adds the listener to the catchAllEventListeners queue, consistent with the registration pattern used for other listener types.


158-184: LGTM! Catch-all listener invocation is correctly implemented.

The modification correctly enables catch-all listeners to handle events regardless of whether specific listeners are registered, which fulfills the PR objective. Key aspects:

  • Regular data listeners are processed only when an EventEntry exists
  • Event interceptors and catch-all listeners are invoked for all events, including unregistered ones
  • Execution order: data listeners → event interceptors → catch-all listeners → acknowledgment

The null-guarding on line 162 is essential for supporting the catch-all feature.

netty-socketio-core/src/main/java/com/socketio4j/socketio/listener/ClientListeners.java (1)

50-51: I'll help you verify this review comment. Let me search the codebase to understand the scope and impact of adding the onAny() method to the ClientListeners interface.
<function_calls>


#!/bin/bash

First, let's locate and understand the ClientListeners interface

echo "=== ClientListeners interface definition ==="
find . -name "ClientListeners.java" -type f 2>/dev/null

Search for the interface and its documentation

echo -e "\n=== ClientListeners interface content ==="
rg "interface ClientListeners" -A 20 --type java

Look for implementations of ClientListeners

echo -e "\n=== Implementations of ClientListeners ==="
rg "implements.*ClientListeners" --type java -B 2 -A 5

Check if ClientListeners is mentioned in public API or documentation

echo -e "\n=== References to ClientListeners in codebase ==="
rg "ClientListeners" --type java -l | head -20


</function_calls>

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
netty-socketio-core/src/main/java/com/socketio4j/socketio/namespace/Namespace.java (1)

19-40: Missing ExceptionListener import causes compilation failure

The ExceptionListener type is used at line 96 (private final ExceptionListener exceptionListener;) and line 101 (constructor), but the import statement is missing from the file. This will cause a "cannot find symbol: class ExceptionListener" compile error.

Add the import:

 import com.socketio4j.socketio.listener.EventInterceptor;
+import com.socketio4j.socketio.listener.ExceptionListener;
 import com.socketio4j.socketio.listener.MultiTypeEventListener;
🧹 Nitpick comments (3)
netty-socketio-core/src/main/java/com/socketio4j/socketio/zTest.java (1)

9-30: Demo main class should live in tests/examples, not main library, and minor naming cleanups

Functionally this is a good minimal example of onAny + specific listener usage. However:

  • Having a zTest main class under src/main/java means it will be packaged with the library; this is usually undesirable for a core server artifact. Consider moving this to a test (src/test/java) or a dedicated examples/demo module.
  • The variable name fff in the onAny lambda is unclear; renaming it to something like args would make the example easier to follow.
  • Optional: the hard-coded port 9092 is fine for a demo, but you might want to at least mention in a comment that it’s just an example/default.
netty-socketio-core/src/main/java/com/socketio4j/socketio/namespace/Namespace.java (2)

47-79: Catch‑all listener storage and onAny API look consistent with existing listener patterns

The introduction of CatchAllEventListener, the catchAllEventListeners concurrent queue, and the onAny(CatchAllEventListener listener) method aligns well with the existing listener infrastructure (connectListeners, eventInterceptors, etc.). Using ConcurrentLinkedQueue keeps listener registration thread‑safe and iteration weakly consistent, which matches how the other listener queues behave here.

From an API perspective, this is a reasonable way to surface namespace‑level catch‑all listeners.

If you expect a high number of catch‑all registrations over time, you might later consider adding a removal API (similar to removeAllListeners for named events), but it’s not strictly necessary to support the feature as requested.

Also applies to: 138-141


165-191: onEvent now runs interceptors and catch‑all listeners even when no specific listener is registered—verify this behavior and add tests

The refactor to:

  • Guard the DataListener loop with if (entry != null), and
  • Always run eventInterceptors and then catchAllEventListener instances,

means that for events without a registered EventEntry you will now:

  • Still invoke EventInterceptor.onEvent(...).
  • Invoke CatchAllEventListener.onEvent(...).
  • Still follow the ACK flow via sendAck(ackRequest) (subject to ackMode and exceptions).

This change appears intentional and correctly addresses the “onAny runs even when no specific listener exists” requirement, but it is also a subtle behavioral change compared to only processing events with a concrete EventEntry.

I recommend adding targeted tests that cover:

  • Events with a specific listener (ensure existing behavior remains unchanged).
  • Events without a specific listener but with an interceptor and/or catch‑all listener (ensure they now fire, and ACK behavior matches your intended contract).
  • Failure paths (exceptions in a DataListener, EventInterceptor, or CatchAllEventListener) to confirm AckMode.AUTO_SUCCESS_ONLY still behaves as expected.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 527f848 and 5ce3edd.

📒 Files selected for processing (4)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/SocketIOServer.java (2 hunks)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/listener/CatchAllEventListener.java (1 hunks)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/namespace/Namespace.java (4 hunks)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/zTest.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/listener/CatchAllEventListener.java
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/SocketIOServer.java
🧰 Additional context used
🧬 Code graph analysis (1)
netty-socketio-core/src/main/java/com/socketio4j/socketio/zTest.java (1)
netty-socketio-core/src/main/java/com/socketio4j/socketio/namespace/Namespace.java (1)
  • Namespace (70-506)
🪛 GitHub Actions: Build PR
netty-socketio-core/src/main/java/com/socketio4j/socketio/namespace/Namespace.java

[error] 95-95: cannot find symbol: class ExceptionListener

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyze (java-kotlin)
  • GitHub Check: Analyze (java-kotlin)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
netty-socketio-core/src/main/java/com/socketio4j/socketio/namespace/Namespace.java (1)

139-142: Consider adding a null check and JavaDoc.

The method implementation is correct. For improved robustness and API clarity, consider:

  • Adding a null check for the listener parameter
  • Adding JavaDoc to document the behavior, especially that catch-all listeners execute after both regular event listeners and interceptors
+    /**
+     * Registers a catch-all event listener that will be invoked for every event,
+     * regardless of whether a specific listener is registered for that event.
+     * Catch-all listeners execute after regular event listeners and interceptors.
+     *
+     * @param listener the catch-all listener to register
+     */
     @Override
     public void onAny(CatchAllEventListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener cannot be null");
+        }
         catchAllEventListeners.add(listener);
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5ce3edd and d266fd3.

📒 Files selected for processing (1)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/namespace/Namespace.java (4 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: build (25) / build
  • GitHub Check: build (21) / build
  • GitHub Check: build (17) / build
  • GitHub Check: Analyze (java-kotlin)
  • GitHub Check: Analyze (java-kotlin)
🔇 Additional comments (4)
netty-socketio-core/src/main/java/com/socketio4j/socketio/namespace/Namespace.java (4)

79-79: LGTM!

The use of ConcurrentLinkedQueue is consistent with other listener collections in this class and provides appropriate thread-safety for concurrent listener registration and iteration.


170-183: LGTM! Critical logic correctly enables catch-all functionality.

The modification to wrap data listener processing in if (entry != null) is the key enabler for the catch-all feature. This ensures that interceptors and catch-all listeners execute even when no specific event listener is registered, fulfilling the PR objective.

Execution order is well-designed:

  1. Specific event listeners (if registered)
  2. Event interceptors (always)
  3. Catch-all listeners (always)

Exception handling correctly covers all listener invocations, and the thread-safe iteration of ConcurrentLinkedQueue is appropriate for concurrent scenarios.


47-47: I'll help you verify the review comment about the CatchAllEventListener import. Let me start by finding and inspecting the interface definition.
<function_calls>


#!/bin/bash

Find the CatchAllEventListener interface file

fd -type f -name "CatchAllEventListener.java"


</function_calls>

Let me also search for the interface definition within the listener package:
<function_calls>


#!/bin/bash

Search for CatchAllEventListener interface definition

rg -n "interface CatchAllEventListener" --type java


</function_calls>


139-142: Integration verified as correctly implemented.

The catch-all listener registration flows properly through the API layers:

  • SocketIOServer.onAny(listener) correctly delegates to mainNamespace.onAny(listener) (line 377 in SocketIOServer.java)
  • mainNamespace is typed as SocketIONamespace interface and instantiated as a Namespace object
  • ClientListeners interface defines the onAny method contract
  • Namespace.onAny() implementation correctly registers listeners in the catchAllEventListeners collection
  • Consistent delegation pattern is used across all listener registration methods

No integration issues found.

@sanjomo sanjomo closed this Nov 27, 2025
@sanjomo sanjomo reopened this Nov 27, 2025
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
netty-socketio-core/src/main/java/com/socketio4j/socketio/protocol/JacksonJsonSupport.java (1)

183-200: Generic-args fallback for unmapped events looks correct; add tests to lock behavior in

The new branch that, when no mapping exists for either the current namespace or DEFAULT_NAME, parses remaining array elements into List<Object> and returns new Event(eventName, genericArgs) is consistent with the catch‑all use case and appears token-safe.

Recommend adding focused tests for:

  • unmapped events with 0, 1, and multiple args, and
  • events with nested objects/arrays,

to prevent regressions in this fallback path.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d266fd3 and dc21393.

📒 Files selected for processing (5)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/SocketIOServer.java (2 hunks)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/listener/CatchAllEventListener.java (1 hunks)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/listener/ClientListeners.java (1 hunks)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/namespace/Namespace.java (4 hunks)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/protocol/JacksonJsonSupport.java (1 hunks)
🔇 Additional comments (1)
netty-socketio-core/src/main/java/com/socketio4j/socketio/SocketIOServer.java (1)

29-29: Server‑level catch‑all registration is wired correctly

onAnyEvent and its onAny alias delegate straight to mainNamespace, which keeps the server API thin and consistent with existing listener wiring. No issues spotted.

Also applies to: 375-383

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
netty-socketio-core/src/main/java/com/socketio4j/socketio/namespace/Namespace.java (1)

139-147: Consider adding a removal API for catch-all listeners.

The registration methods are straightforward and consistent with Socket.IO conventions (providing both onAnyEvent and the onAny alias). However, without a corresponding removal method (e.g., offAny or removeOnAnyEventListener), dynamically registered catch-all listeners cannot be unregistered, potentially leading to memory accumulation in long-running applications.

If dynamic registration/removal is expected, consider adding:

@Override
public void offAny(CatchAllEventListener listener) {
    catchAllEventListeners.remove(listener);
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dc21393 and 8d66be4.

📒 Files selected for processing (2)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/listener/CatchAllEventListener.java (1 hunks)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/namespace/Namespace.java (4 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: build (25) / build
  • GitHub Check: build (21) / build
  • GitHub Check: build (17) / build
🔇 Additional comments (3)
netty-socketio-core/src/main/java/com/socketio4j/socketio/listener/CatchAllEventListener.java (1)

1-16: LGTM! Functional interface signature is correct.

The interface correctly defines the catch-all event listener contract with all four parameters (client, eventName, args, ackRequest). This signature matches the invocation in Namespace.onEvent (line 186) and aligns with the existing EventInterceptor pattern. The parameters are part of the listener contract even if some implementations choose to ignore certain parameters.

netty-socketio-core/src/main/java/com/socketio4j/socketio/namespace/Namespace.java (2)

79-79: LGTM! Appropriate data structure for concurrent listener storage.

Using ConcurrentLinkedQueue is consistent with other listener queues in this class and provides thread-safe, non-blocking operations suitable for the event-driven architecture.


174-187: LGTM! Event handling flow correctly implements catch-all listener support.

The changes successfully address issue #58 by:

  1. Making specific listener invocation conditional (if (entry != null)), preventing NPEs for unregistered events
  2. Ensuring event interceptors run regardless of specific listener registration
  3. Invoking catch-all listeners after interceptors, providing the desired "onAny" capability

The execution order (specific listeners → interceptors → catch-all listeners) is logical, and the signature at line 186 correctly matches the CatchAllEventListener interface. Exception handling appropriately wraps all listener types.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
netty-socketio-core/src/main/java/com/socketio4j/socketio/listener/ClientListeners.java (1)

49-53: Abstract method on public interface remains a breaking change.

The past review correctly identified that adding onAnyEvent as an abstract method breaks existing implementations of ClientListeners. The PR claims to be non-breaking, but this change would cause compile errors for any external code implementing this interface.

The previous suggestion to use a default method has not been applied.

🧹 Nitpick comments (1)
netty-socketio-core/src/main/java/com/socketio4j/socketio/namespace/Namespace.java (1)

141-162: Add null validation and consider a removal API.

  1. Null check: Passing a null listener would silently add it to the queue, causing an NPE during event dispatch.

  2. Removal API: Without an offAny/removeOnAnyEventListener method, callers cannot unregister catch-all listeners. This can cause memory leaks or unintended behavior in scenarios with dynamic listener lifecycle management.

     @Override
     public void onAnyEvent(CatchAllEventListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener cannot be null");
+        }
         catchAllEventListeners.add(listener);
     }
+
+    public void offAny(CatchAllEventListener listener) {
+        catchAllEventListeners.remove(listener);
+    }

Apply similar null checks to the other onAny overloads.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8d66be4 and ae778a5.

📒 Files selected for processing (4)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/SocketIOServer.java (2 hunks)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/listener/CatchAllEventListener.java (1 hunks)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/listener/ClientListeners.java (1 hunks)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/namespace/Namespace.java (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/SocketIOServer.java
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/listener/CatchAllEventListener.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: build (21) / build
  • GitHub Check: build (17) / build
  • GitHub Check: build (25) / build
  • GitHub Check: Analyze (java-kotlin)
  • GitHub Check: Analyze (java-kotlin)
🔇 Additional comments (3)
netty-socketio-core/src/main/java/com/socketio4j/socketio/namespace/Namespace.java (3)

31-32: LGTM!

The imports are appropriately added for the new functional interfaces (BiConsumer, Consumer) and the CatchAllEventListener.

Also applies to: 49-49


81-81: LGTM!

Using ConcurrentLinkedQueue is consistent with how other listener collections are managed in this class and provides appropriate thread-safety.


186-212: LGTM!

The event processing flow correctly implements the catch-all listener requirement:

  • DataListeners are only invoked when a specific listener exists (entry != null), preventing NPE for unknown events.
  • EventInterceptors and CatchAllEventListeners are always invoked regardless of whether a specific listener is registered, which fulfills the objective from issue #58.

The ordering (specific listeners → interceptors → catch-all) is reasonable, and all listeners are wrapped in the existing exception handling.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
netty-socketio-core/src/main/java/com/socketio4j/socketio/listener/ClientListeners.java (1)

51-53: Add javadoc to document the new public API.

The onAnyEvent method is a new public API but lacks documentation. Adding javadoc will help users understand its purpose, the role of CatchAllEventListener, and why the default throws UnsupportedOperationException.

Consider adding documentation similar to this:

+/**
+ * Registers a catch-all listener that will be invoked for every event received by this client,
+ * regardless of whether a specific event listener is registered.
+ * <p>
+ * This method provides a default implementation that throws {@link UnsupportedOperationException}.
+ * Concrete implementations must override this method to provide actual catch-all listener support.
+ *
+ * @param listener the catch-all event listener to register
+ * @throws UnsupportedOperationException if the implementation does not support catch-all listeners
+ * @see CatchAllEventListener
+ */
 default void onAnyEvent(CatchAllEventListener listener) {
     throw new UnsupportedOperationException("onAnyEvent is not implemented");
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ae778a5 and 0b9ed18.

📒 Files selected for processing (1)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/listener/ClientListeners.java (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: build (25) / build
  • GitHub Check: build (21) / build
  • GitHub Check: build (17) / build
  • GitHub Check: Analyze (java-kotlin)
  • GitHub Check: Analyze (java-kotlin)
🔇 Additional comments (1)
netty-socketio-core/src/main/java/com/socketio4j/socketio/listener/ClientListeners.java (1)

51-53: Verified: Both dependent implementations properly override onAnyEvent with actual functionality.

The verification confirms the review comment's concern is valid and the implementations are correct:

  • SocketIOServer (line 376-378): Overrides onAnyEvent and delegates to mainNamespace.onAnyEvent(listener)
  • Namespace (line 142-144): Overrides onAnyEvent and adds the listener to catchAllEventListeners collection

Both implementations provide actual catch-all listener functionality instead of throwing the UnsupportedOperationException from the default method. The inheritance chain is: NamespaceSocketIONamespace (which extends ClientListeners) → ClientListeners.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
netty-socketio-core/src/main/java/com/socketio4j/socketio/namespace/Namespace.java (1)

31-33: Event flow + catch‑all integration meet goals; consider making BiConsumer/Consumer listeners removable

Positives:

  • catchAllEventListeners backed by ConcurrentLinkedQueue (Lines 79-82) is appropriate for concurrent registration and iteration.
  • onEvent now checks entry != null before iterating listeners (Lines 199-205), avoiding NPEs on unknown events.
  • eventInterceptors are always invoked, even when there is no specific EventEntry (Lines 208-210), matching the requirement that interceptors run for all events.
  • Catch‑all listeners are invoked for every event after interceptors (Lines 211-213), with full context (client, eventName, args, ackRequest), which is exactly what a server‑side onAny needs.

One design improvement to consider:

  • The convenience overloads onAny(BiConsumer<String, List<Object>>) and onAny(Consumer<String>) (Lines 162-172) wrap user callbacks into anonymous CatchAllEventListener instances stored internally. Because those instances are not exposed, it’s hard for callers to later remove them via removeOnAnyEventListener/offAny, which can lead to listener buildup over time.

A simple pattern to keep the convenience API while enabling removal would be to return the created listener so callers can hold onto it:

-    public void onAny(BiConsumer<String, List<Object>> listener) {
-        catchAllEventListeners.add((client, event, args, ack) ->
-                listener.accept(event, args)
-        );
-    }
+    public CatchAllEventListener onAny(BiConsumer<String, List<Object>> listener) {
+        CatchAllEventListener l =
+                (client, event, args, ack) -> listener.accept(event, args);
+        catchAllEventListeners.add(l);
+        return l;
+    }

(and similarly for the Consumer<String> overload).

That keeps the sugar methods while allowing callers to later pass the same CatchAllEventListener instance to removeOnAnyEventListener/offAny to avoid leaks.

Also applies to: 49-49, 79-82, 141-172, 195-213

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0b9ed18 and 177f5ff.

📒 Files selected for processing (4)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/SocketIOServer.java (2 hunks)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/listener/ClientListeners.java (1 hunks)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/listener/EventInterceptor.java (1 hunks)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/namespace/Namespace.java (5 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: build (25) / build
  • GitHub Check: build (21) / build
  • GitHub Check: build (17) / build
  • GitHub Check: Analyze (java-kotlin)
  • GitHub Check: Analyze (java-kotlin)
🔇 Additional comments (2)
netty-socketio-core/src/main/java/com/socketio4j/socketio/listener/EventInterceptor.java (1)

24-25: Whitespace-only touch; interface contract unchanged

Line 24 just normalizes spacing in the interface declaration; signature and behavior remain identical, so no risk here.

netty-socketio-core/src/main/java/com/socketio4j/socketio/listener/ClientListeners.java (1)

51-57: Non-breaking SPI extension for catch‑all listeners looks good

Making addOnAnyEventListener/removeOnAnyEventListener default methods that throw UnsupportedOperationException preserves compatibility for existing ClientListeners implementors while clearly signaling optional support for the new feature. This aligns well with the stated “non‑breaking” goal.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
netty-socketio-core/src/main/java/com/socketio4j/socketio/namespace/Namespace.java (1)

164-174: Consider exposing a removable handle for functional onAny() overloads

The onAny(BiConsumer<String, List<Object>>) and onAny(Consumer<String>) helpers are convenient, but since they internally wrap the passed functional value into a new CatchAllEventListener, there’s no straightforward way for callers to later unregister these via offAny without holding that wrapper themselves.

You might want to either:

  • Return the created CatchAllEventListener from these overloads so callers can pass it to offAny, or
  • Add corresponding offAny(BiConsumer<String, List<Object>>) / offAny(Consumer<String>) overloads that track and remove the underlying wrapper.

This would bring the ergonomic parity of the functional helpers closer to the core CatchAllEventListener API.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6415478 and 89c3c71.

📒 Files selected for processing (3)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/SocketIOServer.java (2 hunks)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/listener/ClientListeners.java (1 hunks)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/namespace/Namespace.java (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/listener/ClientListeners.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: build (17) / build
  • GitHub Check: build (25) / build
  • GitHub Check: build (21) / build
  • GitHub Check: Analyze (java-kotlin)
  • GitHub Check: Analyze (java-kotlin)
🔇 Additional comments (4)
netty-socketio-core/src/main/java/com/socketio4j/socketio/SocketIOServer.java (2)

29-29: CatchAllEventListener import matches new listener API usage

The new import is correct and scoped only to support the onAny/offAny delegation methods added below; no issues here.


375-395: Server-level onAny/offAny delegation is consistent and correct

The add/remove/onAny/offAny methods cleanly delegate to mainNamespace, mirroring how other listener APIs are exposed at the server level. offAny correctly calls removeOnAnyEventListener, so catch‑all listeners can be unregistered as expected.

netty-socketio-core/src/main/java/com/socketio4j/socketio/namespace/Namespace.java (2)

31-32: Catch-all listener storage and core onAny/offAny API look solid

Using a ConcurrentLinkedQueue<CatchAllEventListener> plus addOnAnyEventListener / removeOnAnyEventListener and the onAny/offAny aliases is consistent with the rest of the listener infrastructure and thread-safe for concurrent registration and dispatch. The API surface aligns well with the new server-level delegation.

Also applies to: 49-49, 81-81, 141-162


201-215: onEvent flow correctly preserves interceptors and catch-all behavior for unknown events

Guarding the DataListener loop with if (entry != null) avoids NPEs for events without registered listeners, while eventInterceptors and catchAllEventListeners are still invoked for every event. This matches the catch‑all/interceptor objectives and maintains existing ack behavior.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
netty-socketio-core/src/main/java/com/socketio4j/socketio/protocol/JacksonJsonSupport.java (1)

183-199: Generic fallback for unmapped events looks sound; consider observability/docs

The new branch that checks !eventMapping.containsKey(ek) for both the current namespace and Namespace.DEFAULT_NAME and then parses remaining tokens as Object into genericArgs is logically correct and matches the catch‑all/onAny goal: unknown events no longer lose their payloads, and the parser state stays consistent by walking to END_ARRAY.

Two optional improvements you might consider:

  • Add a low‑level debug (or maybe info gated by config) log when this generic fallback is taken, including namespaceClass.get() and eventName, so operators can see when events are arriving without explicit mappings.
  • Document this behavior (e.g., Javadoc on JsonSupport/JacksonJsonSupport or server docs) so users know that unmapped events produce List<Object> args using Jackson’s default typing, which is what onAny/catch‑all handlers will see.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 89c3c71 and c0272fa.

📒 Files selected for processing (1)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/protocol/JacksonJsonSupport.java (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: build (17) / build
  • GitHub Check: build (21) / build
  • GitHub Check: build (25) / build
  • GitHub Check: Analyze (java-kotlin)
  • GitHub Check: Analyze (java-kotlin)

@sanjomo
Copy link
Member Author

sanjomo commented Nov 27, 2025

@NeatGuyCoding Please review

Copy link
Collaborator

@NeatGuyCoding NeatGuyCoding left a comment

Choose a reason for hiding this comment

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

LGTM

@NeatGuyCoding
Copy link
Collaborator

29e2216a-1a29-41eb-abed-b542ba69a085

@sanjomo sanjomo merged commit ca5a904 into main Nov 28, 2025
8 checks passed
@sanjomo sanjomo deleted the onanylistener-onevent branch November 28, 2025 04:47
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.

[FEATURE] Support for "onAny" catch-all listener.

2 participants