Skip to content

Feature/filter lifecycle and builder refactor - #94

Closed
kristinaxm wants to merge 32 commits into
mainfrom
feature/filter-lifecycle-and-builder-refactor
Closed

Feature/filter lifecycle and builder refactor#94
kristinaxm wants to merge 32 commits into
mainfrom
feature/filter-lifecycle-and-builder-refactor

Conversation

@kristinaxm

@kristinaxm kristinaxm commented Feb 18, 2026

Copy link
Copy Markdown

Closing this PR since it became messy due to multiple auto-generated fixes.
I will open a new, clean PR with the same changes.

Refactors the server bootstrap and request processing architecture.

Introduces annotation-based filter registration (@global, @route), ordered filter execution, and enforced filter lifecycle management.

Moves HttpParser, DefaultConnectionHandlerFactory, and pipeline initialization into ServerBuilder, establishing a clear composition root and improving separation of concerns.

Adds FilterConfiguration and FilterScanner to centralize filter wiring and simplify application bootstrap.

Includes unit tests.

Summary by CodeRabbit

Release Notes

  • Refactor
    • Server initialization now uses a fluent builder pattern for streamlined configuration.
    • Filter configuration is now declarative via annotations instead of manual inline setup.
    • Filter lifecycle management improved with initialization and cleanup hooks.
    • Internal packages reorganized for better separation of concerns.

@kristinaxm kristinaxm self-assigned this Feb 18, 2026
@coderabbitai

coderabbitai Bot commented Feb 18, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This refactor restructures the request-processing architecture by introducing a fluent ServerBuilder pattern, moving Pipeline to the Server package with lifecycle management (init/destroy), adding filter annotations for declarative registration, and implementing FilterScanner for automatic filter discovery. Existing filters are annotated with @Global or @Route, and FilterConfiguration consolidates filter setup. Package restructuring organizes classes into nested packages.

Changes

Cohort / File(s) Summary
Server Builder & App Initialization
src/main/java/org/juv25d/App.java, src/main/java/org/juv25d/Server/ServerBuilder.java, src/main/java/org/juv25d/Server/Server.java
Replaced manual pipeline construction with fluent ServerBuilder for port, logger, router, and filter configuration. Server now registers a shutdown hook calling pipeline.destroyFilters() for lifecycle cleanup.
Pipeline & Filter Lifecycle
src/main/java/org/juv25d/Server/Pipeline.java, src/main/java/org/juv25d/filter/FilterScanner.java
Introduced new Pipeline class in Server package with dynamic global/route filter registration, pattern-based matching (exact and wildcard), deduplication, and lifecycle methods (initFilters(), destroyFilters()). Added FilterScanner to automatically register annotated filters into the pipeline.
Filter Annotations
src/main/java/org/juv25d/filter/IpFilter.java, src/main/java/org/juv25d/filter/LoggingFilter.java, src/main/java/org/juv25d/filter/RateLimitingFilter.java, src/main/java/org/juv25d/filter/RedirectFilter.java
Annotated with @Global decorator specifying execution order (0, 5, 10, 20) to enable declarative filter registration instead of manual wiring.
Filter Configuration
src/main/java/org/juv25d/filter/config/FilterConfiguration.java
New configuration helper providing static configure() method to declaratively wire filters and redirect rules into ServerBuilder based on config settings.
Package Restructuring
src/main/java/org/juv25d/filter/FilterRegistration.java, src/main/java/org/juv25d/Connections/..., src/main/java/org/juv25d/Connections/DefaultConnectionHandlerFactory.java
Relocated FilterRegistration to org.juv25d.filter package; moved connection-related classes to org.juv25d.Connections; updated imports for Pipeline reference.
Unit Tests
src/test/java/org/juv25d/Server/ServerBuilderTest.java, src/test/java/org/juv25d/filter/FilterScannerTest.java, src/test/java/org/juv25d/.../PipelineTest.java, src/test/java/org/juv25d/filter/GlobalFilterTests.java, src/test/java/org/juv25d/filter/RouteFilterTests.java
Added comprehensive tests for ServerBuilder (validation of logger/router requirements, filter initialization), FilterScanner (global/route registration, annotation validation, error cases). Updated existing tests to import from new package locations.

Sequence Diagram

sequenceDiagram
    participant App as App.main()
    participant Builder as ServerBuilder
    participant Config as FilterConfiguration
    participant Pipeline as Pipeline
    participant Scanner as FilterScanner
    participant Filters as Filters<br/>[`@Global/`@Route]
    participant Server as Server

    App->>Builder: new ServerBuilder()
    App->>Builder: port(8080)
    App->>Builder: logger(logger)
    App->>Builder: router(router)
    
    App->>Config: configure(builder, config)
    Config->>Builder: addFilter(RedirectFilter)
    Config->>Builder: addFilter(IpFilter)
    Config->>Builder: addFilter(LoggingFilter)
    Config->>Builder: addFilterIf(enabled, RateLimitingFilter)
    
    App->>Builder: build()
    
    Builder->>Pipeline: new Pipeline()
    Builder->>Pipeline: setRouter(router)
    
    Builder->>Scanner: register(filters, pipeline)
    Scanner->>Filters: inspect `@Global/`@Route
    Filters-->>Scanner: annotations found
    Scanner->>Pipeline: addGlobalFilter() / addRouteFilter()
    
    Builder->>Pipeline: initFilters()
    Pipeline->>Filters: init() on each
    Filters-->>Pipeline: initialized
    
    Builder->>Server: new Server(port, logger, handlerFactory, pipeline)
    Builder-->>App: Server instance
    
    App->>Server: start()
    
    Note over Server: On shutdown
    Server->>Pipeline: destroyFilters()
    Pipeline->>Filters: destroy() on each
    Filters-->>Pipeline: destroyed
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • PR #51: Modifies the pipeline-based filter system with global/route filter registration and request-aware chain creation, paralleling the core filter orchestration refactoring.
  • PR #64: Introduces RedirectRule/RedirectFilter and redirect wiring; this PR consolidates that registration into FilterConfiguration using the builder pattern.
  • PR #83: Adds RateLimitingFilter and config accessors; this PR integrates the filter into the new ServerBuilder/Pipeline lifecycle with annotation-based registration.

Suggested labels

enhancement

Suggested reviewers

  • bamsemats
  • fmazmz
  • TatjanaTrajkovic
  • kristina0x7

Poem

🐰 A builder hops along the way,
With filters marching in array,
Annotations guide the dance,
Pipeline takes its rightful stance,
Lifecycle blooms—init, destroy with grace!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Feature/filter lifecycle and builder refactor' clearly summarizes the main changes: introduction of filter lifecycle management and ServerBuilder.
Linked Issues check ✅ Passed All objectives from #93 are met: ordered filter execution via Pipeline; filter lifecycle init/destroy methods; annotation scanning via @Global/@route with FilterScanner; ServerBuilder with port/logger/router/filters configuration; unit tests for ServerBuilder and FilterScanner; mandatory filter annotations with conflict detection.
Out of Scope Changes check ✅ Passed All changes are within scope: package reorganization supports builder/pipeline refactoring, Pipeline lifecycle methods with filter discovery, FilterConfiguration enables declarative filter setup, annotation-driven filter registration, and comprehensive unit tests validate new functionality.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/filter-lifecycle-and-builder-refactor

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🧹 Nitpick comments (6)
src/main/java/org/juv25d/Server/ServerBuilder.java (2)

22-25: No port validation — invalid ports fail silently until ServerSocket creation.

port(int port) accepts any int. Values ≤ 0 (except 0, which selects an ephemeral port) or > 65535 are invalid and will only fail at runtime inside start(). Validating eagerly in port() or build() gives a cleaner developer experience.

🔧 Suggested guard in `port()`
 public ServerBuilder port(int port) {
+    if (port < 0 || port > 65535) {
+        throw new IllegalArgumentException("Port must be between 0 and 65535, got: " + port);
+    }
     this.port = port;
     return this;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/juv25d/Server/ServerBuilder.java` around lines 22 - 25, The
ServerBuilder.port(int port) setter currently accepts any int and defers
invalid-port failures to runtime; add eager validation in ServerBuilder.port (or
in build() if you prefer centralized checks) to ensure port is either 0 or in
range 1..65535 and throw an IllegalArgumentException with a clear message for
out-of-range values, otherwise assign this.port and return this; also update any
Javadoc/comments on ServerBuilder.port and adjust callers/tests if they relied
on silent acceptance.

37-40: addFilter(null) silently no-ops — consider explicit null guard.

FilterScanner.register() short-circuits when the instance is not a Filter (null satisfies that check), so passing null to addFilter() is swallowed without any error. An explicit precondition check would surface misconfigurations earlier.

🔧 Suggested null guard
 public ServerBuilder addFilter(Filter filter) {
+    Objects.requireNonNull(filter, "Filter must not be null");
     FilterScanner.register(filter, pipeline);
     return this;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/juv25d/Server/ServerBuilder.java` around lines 37 - 40, The
addFilter method currently forwards null to FilterScanner.register, which
silently no-ops; update ServerBuilder.addFilter(Filter filter) to explicitly
guard against null by validating the parameter (e.g., if filter == null) and
throw a clear runtime exception (IllegalArgumentException or
NullPointerException) with a descriptive message before calling
FilterScanner.register, so misconfigurations are surfaced immediately; reference
ServerBuilder.addFilter, FilterScanner.register, and the Filter
parameter/pipeline variable when making the change.
src/test/java/org/juv25d/Server/ServerBuilderTest.java (2)

63-67: TestGlobalFilter.doFilter() is a no-op and does not forward to chain.doFilter().

While this is harmless for the current test (which only verifies init() is called), a filter that silently swallows the chain call would block all downstream filters and the terminal route handler if accidentally reused in an integration context. Adding the chain forward makes the intent clearer.

🔧 Suggested fix
 `@Override`
 public void doFilter(HttpRequest request,
                      HttpResponse response,
                      FilterChain chain) {
+    chain.doFilter(request, response);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/test/java/org/juv25d/Server/ServerBuilderTest.java` around lines 63 - 67,
The TestGlobalFilter.doFilter(HttpRequest request, HttpResponse response,
FilterChain chain) currently swallows the call; modify this method to forward
the request and response to the next filter by invoking chain.doFilter(request,
response) so downstream filters and the terminal handler are executed—update the
doFilter implementation to call chain.doFilter(...) using the provided
FilterChain parameter.

19-47: Missing coverage for FilterScanner conflict detection and the addFilterIf path.

The three existing tests cover required-field validation and the happy-path initialization. The following scenarios described in the PR objectives and FilterScanner contract are not exercised:

  1. A filter annotated with both @Global and @RouteFilterScanner should throw IllegalStateException.
  2. A filter with no annotation — should be silently ignored (no-op registration).
  3. addFilterIf(false, supplier) — supplier must not be invoked and filter must not be registered.
  4. addFilterIf(true, supplier) — filter must be registered and initialized.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/test/java/org/juv25d/Server/ServerBuilderTest.java` around lines 19 - 47,
Add tests exercising FilterScanner conflict detection and the addFilterIf paths:
create a filter class annotated with both `@Global` and `@Route` and assert
ServerBuilder.build() throws IllegalStateException (verify FilterScanner detects
the conflict), create a filter class with no annotation and assert build() does
not register or initialize it (silently ignored), add tests calling
ServerBuilder.addFilterIf(false, () -> supplier) and assert the supplier is
never invoked and no filter is registered, and add tests calling
ServerBuilder.addFilterIf(true, () -> supplier) and assert the supplier is
invoked and the filter is registered and initialized (use the existing
TestGlobalFilter/initialized AtomicBoolean pattern and reference ServerBuilder,
FilterScanner, addFilterIf, and TestGlobalFilter to locate code).
src/main/java/org/juv25d/Server/Server.java (1)

26-29: destroyFilters() is called without draining in-flight requests.

Virtual threads spawned in the accept loop are daemon threads; when the JVM starts shutdown, the hook calls destroyFilters() while those threads may still be mid-request and actively using filters. Consider waiting for in-flight handlers to complete (e.g., tracking them with a CountDownLatch or a Semaphore) before calling pipeline.destroyFilters().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/juv25d/Server/Server.java` around lines 26 - 29, The
shutdown hook calls pipeline.destroyFilters() while virtual threads in the
accept loop may still be processing requests; change shutdown to first wait for
in-flight handlers to finish by adding a concurrency tracker (e.g., an
AtomicInteger or a Semaphore/CountDownLatch) that is incremented at the start of
each request handler in the accept loop and decremented when the handler
completes, then in the Runtime.getRuntime().addShutdownHook block call await
with a reasonable timeout on that tracker before invoking
pipeline.destroyFilters(); update the accept-loop handler logic (where virtual
threads are spawned) to use the tracker so destroyFilters() runs only after
in-flight requests drain (or timeout).
src/main/java/org/juv25d/App.java (1)

38-52: Filter execution order is driven by @Global(order=…) annotations, not registration order.

The builder registers filters via FilterScanner, which reads the order field from @Global/@Route annotations and passes it to Pipeline.addGlobalFilter(), which re-sorts on every insertion. The order of .addFilter(...) calls in App.java has no effect on execution order. Developers who expect execution to follow registration sequence (e.g., assuming RedirectFilter runs before IpFilter) will be surprised. A comment documenting the intended annotation-order values of each filter would prevent future mis-ordering.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/juv25d/App.java` around lines 38 - 52, The current
.addFilter(...) calls in App.java suggest registration order matters but
FilterScanner and Pipeline.addGlobalFilter() actually drive execution via
`@Global`(order=...), so update the codebase to make ordering explicit: annotate
each global filter class (RedirectFilter, IpFilter, LoggingFilter,
RateLimitingFilter) with a clear `@Global`(order = X) value reflecting the
intended execution sequence and/or add a concise in-file comment above the
builder block listing those intended order values (e.g., RedirectFilter -> order
N, IpFilter -> order N+1, LoggingFilter -> order N+2, RateLimitingFilter ->
order N+3) so future maintainers know which numeric orders to use; verify
FilterScanner and Pipeline.addGlobalFilter() will pick up those annotation
values.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/java/org/juv25d/filter/FilterScanner.java`:
- Around line 26-38: In FilterScanner, when scanning an object (instance) that
implements Filter but has neither the `@Global` (global) nor `@Route` (route)
annotation, do not silently ignore it: add a check after the existing if blocks
that detects global == null && route == null and then either throw a clear
IllegalStateException (include the class name of instance) or call the
pipeline/processLogger to emit a warning identifying the unannotated Filter;
update the logic around pipeline.addGlobalFilter and pipeline.addRouteFilter so
the new check runs for the same code path that currently falls through,
referencing the same symbols (FilterScanner, instance, global, route,
pipeline.addGlobalFilter, pipeline.addRouteFilter).

In `@src/main/java/org/juv25d/Pipeline.java`:
- Around line 58-74: createChain currently adds filters from
routeFilters.get(path) and then again from wildcard entries (routeFilters entry
keys ending with "*"), which causes duplicate FilterRegistration.filter()
invocations when both exact (e.g., "/users") and wildcard (e.g., "/users*")
registrations exist; fix by deduplicating before adding to the filters
list—e.g., maintain a LinkedHashSet of Filter instances or their identity keys
while building filters in createChain (use FilterRegistration::filter as the
unique symbol) so you only add each filter once and preserve order, or
alternatively skip wildcard entry processing when an exact match for the same
base path was already applied.
- Around line 94-96: destroyFilters currently calls
getAllFilters().forEach(Filter::destroy) which allows an exception in one
Filter.destroy() to abort cleanup; change destroyFilters to iterate over
getAllFilters() and call filter.destroy() inside a try-catch for each filter,
logging the exception (using your logger) and continuing so all filters get
their destroy lifecycle callback; leave initFilters
(initFilters()/Filter.init()) behavior as-is for now but consider whether its
exceptions should be handled differently if you want init to be resilient.
- Around line 23-30: Concurrent calls to addGlobalFilter can overwrite
sortedGlobalFilters because each thread snapshots globalFilters separately; to
fix, guard the add + recompute sequence with a single lock (e.g., synchronize
the addGlobalFilter method or use a dedicated mutex) so that updating
globalFilters and then recomputing sortedGlobalFilters happens atomically.
Ensure the synchronized block covers globalFilters.add(new
FilterRegistration(...)) and the subsequent stream/sort/map/collect into
sortedGlobalFilters (referencing addGlobalFilter, globalFilters,
sortedGlobalFilters, and FilterRegistration).

In `@src/main/java/org/juv25d/Server/Server.java`:
- Line 31: The code in Server.java creates a plaintext ServerSocket (see the
Server class and the ServerSocket instantiation using the port variable), which
sends traffic unencrypted; update the Server class to either (a) document the
assumption that TLS termination is performed upstream (reverse proxy/load
balancer) in the class Javadoc and any deployment/readme, or (b) switch to an
SSLServerSocket by creating an SSLServerSocketFactory and using it to create an
SSLServerSocket (replacing the ServerSocket instantiation) and loading the
keystore/SSLContext appropriately for production; pick one approach and apply
the change consistently where the ServerSocket is constructed.
- Around line 26-29: Move shutdown hook registration out of start() to a single
guarded location: register the Runtime.getRuntime().addShutdownHook(...) once
(e.g., in the Server constructor) or add an AtomicBoolean field (e.g.,
shutdownHookRegistered) and check-and-set it before calling addShutdownHook in
start(), so pipeline.destroyFilters() is not registered multiple times; modify
Server's constructor or start() to perform this single-registration guard and
reference the existing pipeline.destroyFilters() call inside the hook.

In `@src/main/java/org/juv25d/Server/ServerBuilder.java`:
- Around line 49-65: Add a one-time "built" guard to ServerBuilder: introduce a
private boolean built field, check at the start of build() and throw an
IllegalStateException (e.g., "ServerBuilder already built") if built is true,
then set built = true before or immediately after calling
pipeline.initFilters(); this ensures pipeline.initFilters() (and Filter.init())
run only once and prevents double-initialization when build() is called multiple
times.

In `@src/test/java/org/juv25d/filter/FilterScannerTest.java`:
- Around line 14-50: The tests are missing coverage for a Filter implementation
that has no `@Global` or `@Route` annotation, which lets FilterScanner.register
silently ignore valid Filter objects; add a unit test named
shouldRejectFilterWithNoAnnotation that creates a mock Pipeline and an instance
of an UnannotatedFilter (implements Filter), then assertThrows
IllegalStateException when calling FilterScanner.register(filter, pipeline) and
verifyNoInteractions(pipeline); this ensures FilterScanner.register correctly
rejects Filter instances without either `@Global` or `@Route` annotations.

---

Nitpick comments:
In `@src/main/java/org/juv25d/App.java`:
- Around line 38-52: The current .addFilter(...) calls in App.java suggest
registration order matters but FilterScanner and Pipeline.addGlobalFilter()
actually drive execution via `@Global`(order=...), so update the codebase to make
ordering explicit: annotate each global filter class (RedirectFilter, IpFilter,
LoggingFilter, RateLimitingFilter) with a clear `@Global`(order = X) value
reflecting the intended execution sequence and/or add a concise in-file comment
above the builder block listing those intended order values (e.g.,
RedirectFilter -> order N, IpFilter -> order N+1, LoggingFilter -> order N+2,
RateLimitingFilter -> order N+3) so future maintainers know which numeric orders
to use; verify FilterScanner and Pipeline.addGlobalFilter() will pick up those
annotation values.

In `@src/main/java/org/juv25d/Server/Server.java`:
- Around line 26-29: The shutdown hook calls pipeline.destroyFilters() while
virtual threads in the accept loop may still be processing requests; change
shutdown to first wait for in-flight handlers to finish by adding a concurrency
tracker (e.g., an AtomicInteger or a Semaphore/CountDownLatch) that is
incremented at the start of each request handler in the accept loop and
decremented when the handler completes, then in the
Runtime.getRuntime().addShutdownHook block call await with a reasonable timeout
on that tracker before invoking pipeline.destroyFilters(); update the
accept-loop handler logic (where virtual threads are spawned) to use the tracker
so destroyFilters() runs only after in-flight requests drain (or timeout).

In `@src/main/java/org/juv25d/Server/ServerBuilder.java`:
- Around line 22-25: The ServerBuilder.port(int port) setter currently accepts
any int and defers invalid-port failures to runtime; add eager validation in
ServerBuilder.port (or in build() if you prefer centralized checks) to ensure
port is either 0 or in range 1..65535 and throw an IllegalArgumentException with
a clear message for out-of-range values, otherwise assign this.port and return
this; also update any Javadoc/comments on ServerBuilder.port and adjust
callers/tests if they relied on silent acceptance.
- Around line 37-40: The addFilter method currently forwards null to
FilterScanner.register, which silently no-ops; update
ServerBuilder.addFilter(Filter filter) to explicitly guard against null by
validating the parameter (e.g., if filter == null) and throw a clear runtime
exception (IllegalArgumentException or NullPointerException) with a descriptive
message before calling FilterScanner.register, so misconfigurations are surfaced
immediately; reference ServerBuilder.addFilter, FilterScanner.register, and the
Filter parameter/pipeline variable when making the change.

In `@src/test/java/org/juv25d/Server/ServerBuilderTest.java`:
- Around line 63-67: The TestGlobalFilter.doFilter(HttpRequest request,
HttpResponse response, FilterChain chain) currently swallows the call; modify
this method to forward the request and response to the next filter by invoking
chain.doFilter(request, response) so downstream filters and the terminal handler
are executed—update the doFilter implementation to call chain.doFilter(...)
using the provided FilterChain parameter.
- Around line 19-47: Add tests exercising FilterScanner conflict detection and
the addFilterIf paths: create a filter class annotated with both `@Global` and
`@Route` and assert ServerBuilder.build() throws IllegalStateException (verify
FilterScanner detects the conflict), create a filter class with no annotation
and assert build() does not register or initialize it (silently ignored), add
tests calling ServerBuilder.addFilterIf(false, () -> supplier) and assert the
supplier is never invoked and no filter is registered, and add tests calling
ServerBuilder.addFilterIf(true, () -> supplier) and assert the supplier is
invoked and the filter is registered and initialized (use the existing
TestGlobalFilter/initialized AtomicBoolean pattern and reference ServerBuilder,
FilterScanner, addFilterIf, and TestGlobalFilter to locate code).

Comment thread src/main/java/org/juv25d/filter/FilterScanner.java
Comment thread src/main/java/org/juv25d/Pipeline.java Outdated
Comment thread src/main/java/org/juv25d/Pipeline.java Outdated
Comment thread src/main/java/org/juv25d/Pipeline.java Outdated
Comment thread src/main/java/org/juv25d/Server/Server.java Outdated
Comment thread src/main/java/org/juv25d/Server/Server.java Outdated
Comment thread src/main/java/org/juv25d/Server/ServerBuilder.java
Comment thread src/test/java/org/juv25d/filter/FilterScannerTest.java

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (3)
src/main/java/org/juv25d/Pipeline.java (2)

17-18: globalFilters and routeFilters should remain final.

Both fields are initialized at declaration and never reassigned; removing final is unnecessary and weakens the immutability guarantee provided by the JMM for final fields.

♻️ Proposed fix
-    private List<FilterRegistration> globalFilters = new CopyOnWriteArrayList<>();
-    private Map<String, List<FilterRegistration>> routeFilters = new ConcurrentHashMap<>();
+    private final List<FilterRegistration> globalFilters = new CopyOnWriteArrayList<>();
+    private final Map<String, List<FilterRegistration>> routeFilters = new ConcurrentHashMap<>();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/juv25d/Pipeline.java` around lines 17 - 18, In class
Pipeline, restore the immutability guarantee by marking the fields globalFilters
and routeFilters as final again; since both are initialized at declaration and
never reassigned, update the declarations of globalFilters
(CopyOnWriteArrayList<FilterRegistration>) and routeFilters
(ConcurrentHashMap<String, List<FilterRegistration>>) to include the final
modifier so the Java Memory Model treats them as properly immutable references.

86-88: initFilters has no per-filter error isolation, unlike destroyFilters.

A single failing init() aborts all subsequent filter initialization. This is the symmetric counterpart of the destroyFilters issue that was resolved in this PR. While it is arguable that init failures should propagate (signaling a fatal misconfiguration), at minimum the partially-initialized state should be logged clearly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/juv25d/Pipeline.java` around lines 86 - 88, initFilters
currently calls Filter::init for all filters without per-filter isolation so one
exception aborts the rest; update initFilters to iterate over getAllFilters()
and call each filter.init() inside a try-catch that logs the filter identity
(e.g., class or name) and the caught exception (using the same logger used in
destroyFilters) and continues initializing remaining filters; optionally collect
exceptions and after the loop either rethrow a combined exception or return
status, but at minimum ensure per-filter exceptions are logged and do not stop
subsequent init calls.
src/main/java/org/juv25d/Server/Server.java (1)

24-27: Lambda body inside addShutdownHook is not indented consistently.

♻️ Proposed fix
         Runtime.getRuntime().addShutdownHook(new Thread(() -> {
-        logger.info("Shutting down server...");
-        pipeline.destroyFilters();
+            logger.info("Shutting down server...");
+            pipeline.destroyFilters();
         }));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/juv25d/Server/Server.java` around lines 24 - 27, The
shutdown hook lambda passed to Runtime.getRuntime().addShutdownHook has
inconsistent indentation; reformat the lambda body so its statements are
consistently indented (align the logger.info("Shutting down server..."); and
pipeline.destroyFilters(); lines inside the new Thread(() -> { ... }) block),
preserving the existing calls to logger and pipeline.destroyFilters and keeping
the surrounding addShutdownHook/new Thread invocation unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/java/org/juv25d/Pipeline.java`:
- Around line 28-34: The addRouteFilter method performs a non-atomic compound
operation on a CopyOnWriteArrayList (routeFilters.computeIfAbsent(...) ->
registrations.add(...) -> registrations.sort(...)), which can race with other
threads and with createChain; fix by making the add+sort atomic: locate
addRouteFilter and ensure the mutation is done inside a single synchronized
block (e.g., synchronize on the registrations list or a dedicated lock for that
pattern) when creating the new FilterRegistration and sorting, so no concurrent
insertion can occur between add and sort; keep using FilterRegistration and
CopyOnWriteArrayList but guard the compound operation to preserve priority order
for subsequent createChain calls.
- Around line 47-74: createChain currently passes the field router (which may be
null if setRouter was never called) into FilterChainImpl; add a fail-fast null
check at the start of createChain and throw an IllegalStateException with a
clear message (e.g., "router not initialized - call setRouter(...) before
createChain") if router is null so FilterChainImpl and subsequent
doFilter/router.resolve(...) never receive a null router; reference the router
field and the createChain method (and mention setRouter for context) when
implementing the check.

In `@src/main/java/org/juv25d/Server/Server.java`:
- Line 17: Remove the unused AtomicBoolean guard by deleting the field
declaration shutdownHookRegistered in class Server and also remove its unused
import (java.util.concurrent.atomic.AtomicBoolean) from the top of the file;
ensure no other code references shutdownHookRegistered (search for
"shutdownHookRegistered") and if found, remove or refactor those references
accordingly since the constructor-only shutdown hook already prevents
duplicates.
- Line 1: Update the package declaration and directory names from uppercase
Server to lowercase server: change the package statement in Server.java and
ServerBuilder.java from "package org.juv25d.Server;" to "package
org.juv25d.server;" and move/rename the physical directories from
src/main/java/org/juv25d/Server to src/main/java/org/juv25d/server and likewise
for src/test/java/org/juv25d/Server; ensure any imports or references to
org.juv25d.Server throughout the codebase are updated to org.juv25d.server so
Server.java, ServerBuilder.java and tests compile with the new package path.

---

Duplicate comments:
In `@src/main/java/org/juv25d/Server/Server.java`:
- Line 32: The Server.java currently creates a plain ServerSocket via "new
ServerSocket(port, 64)" which exposes plaintext transport (CWE-319); replace
this with an SSL/TLS server socket by using an SSLServerSocket from
SSLServerSocketFactory (or configure an SSLContext and call
getServerSocketFactory()) and initialize it with the appropriate
keystore/credentials, enabled protocols and cipher suites before accepting
connections; update any references to the "serverSocket" variable and the Server
class accept/close logic to use the SSLServerSocket type so all inbound traffic
is encrypted.

---

Nitpick comments:
In `@src/main/java/org/juv25d/Pipeline.java`:
- Around line 17-18: In class Pipeline, restore the immutability guarantee by
marking the fields globalFilters and routeFilters as final again; since both are
initialized at declaration and never reassigned, update the declarations of
globalFilters (CopyOnWriteArrayList<FilterRegistration>) and routeFilters
(ConcurrentHashMap<String, List<FilterRegistration>>) to include the final
modifier so the Java Memory Model treats them as properly immutable references.
- Around line 86-88: initFilters currently calls Filter::init for all filters
without per-filter isolation so one exception aborts the rest; update
initFilters to iterate over getAllFilters() and call each filter.init() inside a
try-catch that logs the filter identity (e.g., class or name) and the caught
exception (using the same logger used in destroyFilters) and continues
initializing remaining filters; optionally collect exceptions and after the loop
either rethrow a combined exception or return status, but at minimum ensure
per-filter exceptions are logged and do not stop subsequent init calls.

In `@src/main/java/org/juv25d/Server/Server.java`:
- Around line 24-27: The shutdown hook lambda passed to
Runtime.getRuntime().addShutdownHook has inconsistent indentation; reformat the
lambda body so its statements are consistently indented (align the
logger.info("Shutting down server..."); and pipeline.destroyFilters(); lines
inside the new Thread(() -> { ... }) block), preserving the existing calls to
logger and pipeline.destroyFilters and keeping the surrounding
addShutdownHook/new Thread invocation unchanged.

Comment thread src/main/java/org/juv25d/Pipeline.java Outdated
Comment thread src/main/java/org/juv25d/Pipeline.java Outdated
@@ -1,25 +1,36 @@
package org.juv25d;
package org.juv25d.Server;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all Java files in or under directories named 'Server' and show package declarations
fd -t f '\.java$' | xargs head -3 | grep -E '(^==|^package)' | head -50

Repository: ithsjava25/project-webserver-juv25d

Length of output: 2190


🏁 Script executed:

#!/bin/bash
# Also check the actual directory structure of the Server directory
find . -type d -name 'Server' 2>/dev/null

Repository: ithsjava25/project-webserver-juv25d

Length of output: 146


🌐 Web query:

Java package naming conventions lowercase

💡 Result:

  • Use all-lowercase package names (no uppercase). This avoids confusion with type names (which typically start uppercase) and matches common Java style. [1], [2]
  • For widely distributed packages, use reversed Internet domain name + project parts, e.g. com.example.myapp.feature. The leading domain components are lowercase (e.g., com, org, uk). [1]
  • Avoid underscores and separators; if you have multiple words, concatenate in lowercase, e.g. com.example.deepspace (not deepSpace or deep_space). [2]
  • Don’t start your own packages with java (reserved for the Java platform). [1]

Examples

  • Good: com.acme.payments.api, org.example.tools.json
  • Bad: com.Acme.Payments, com.acme.payment_api, java.mycompany.app

Sources: Oracle Java Language Specification (package naming) [1]; OpenJDK style guide / common Java style (lowercase, no underscores) [2].

[1] (docs.oracle.com)
[2] (cr.openjdk.org)

Citations:


Package name org.juv25d.Server violates Java naming conventions — must be all lowercase.

Java Language Specification and OpenJDK style guide require package names to use only lowercase letters. The package should be org.juv25d.server, and the physical directory structure must match accordingly (rename Server directory to server in both src/main/java/org/juv25d/ and src/test/java/org/juv25d/).

This affects Server.java and ServerBuilder.java.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/juv25d/Server/Server.java` at line 1, Update the package
declaration and directory names from uppercase Server to lowercase server:
change the package statement in Server.java and ServerBuilder.java from "package
org.juv25d.Server;" to "package org.juv25d.server;" and move/rename the physical
directories from src/main/java/org/juv25d/Server to
src/main/java/org/juv25d/server and likewise for
src/test/java/org/juv25d/Server; ensure any imports or references to
org.juv25d.Server throughout the codebase are updated to org.juv25d.server so
Server.java, ServerBuilder.java and tests compile with the new package path.

Comment thread src/main/java/org/juv25d/Server/Server.java Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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)
src/main/java/org/juv25d/Pipeline.java (1)

60-69: Extract duplicate sort-and-deduplicate logic into a private helper.

The sort + LinkedHashSet-style deduplication block is copy-pasted verbatim between createChain (Lines 60–69) and getAllFilters (Lines 110–119). Extracting it keeps future changes consistent.

♻️ Proposed refactor
+    private List<Filter> toOrderedFilters(List<FilterRegistration> registrations) {
+        Collections.sort(registrations);
+        List<Filter> result = new ArrayList<>();
+        Set<Filter> seen = new HashSet<>();
+        for (FilterRegistration reg : registrations) {
+            if (seen.add(reg.filter())) {
+                result.add(reg.filter());
+            }
+        }
+        return result;
+    }

Then in createChain:

-        Collections.sort(collected);
-
-        List<Filter> finalFilters = new ArrayList<>();
-        Set<Filter> seen = new HashSet<>();
-
-        for (FilterRegistration reg : collected) {
-            if (seen.add(reg.filter())) {
-                finalFilters.add(reg.filter());
-            }
-        }
-
-        return new FilterChainImpl(finalFilters, router);
+        return new FilterChainImpl(toOrderedFilters(collected), currentRouter);

And in getAllFilters:

-        Collections.sort(all);
-
-        List<Filter> result = new ArrayList<>();
-        Set<Filter> seen = new HashSet<>();
-
-        for (FilterRegistration reg : all) {
-            if (seen.add(reg.filter())) {
-                result.add(reg.filter());
-            }
-        }
-
-        return result;
+        return toOrderedFilters(all);

Also applies to: 105-122

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/juv25d/Pipeline.java` around lines 60 - 69, Extract the
repeated "sort then dedupe" logic into a private helper method (e.g.
sortAndDeduplicateFilters) that accepts the List<FilterRegistration> collected
and returns List<Filter>; inside the helper sort the registrations, iterate
using a Set<Filter> seen to preserve insertion order and build the final
List<Filter>, and replace the duplicated blocks in createChain and getAllFilters
with calls to this new private method (refer to FilterRegistration, collected,
createChain, getAllFilters, finalFilters, and seen when locating and replacing
the code).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/java/org/juv25d/Pipeline.java`:
- Around line 41-71: createChain currently reads the router field twice (check
and later use), opening a TOCTOU race; fix by reading the router into a local
final variable at the top of createChain (e.g., final Router localRouter =
this.router), perform the null check against localRouter, and pass localRouter
into the FilterChainImpl constructor instead of re-reading the field; ensure all
uses of router inside createChain reference that local variable.

---

Duplicate comments:
In `@src/main/java/org/juv25d/Pipeline.java`:
- Around line 20-28: The concurrency issue noted earlier is resolved: both
addGlobalFilter(Filter,int) and addRouteFilter(Filter,int,String) now perform a
single thread-safe mutation using CopyOnWriteArrayList.add, so no further
changes are required; keep the deferred sorting logic in
createChain()/getAllFilters() to perform ordering at read-time rather than
during mutation to avoid races.

---

Nitpick comments:
In `@src/main/java/org/juv25d/Pipeline.java`:
- Around line 60-69: Extract the repeated "sort then dedupe" logic into a
private helper method (e.g. sortAndDeduplicateFilters) that accepts the
List<FilterRegistration> collected and returns List<Filter>; inside the helper
sort the registrations, iterate using a Set<Filter> seen to preserve insertion
order and build the final List<Filter>, and replace the duplicated blocks in
createChain and getAllFilters with calls to this new private method (refer to
FilterRegistration, collected, createChain, getAllFilters, finalFilters, and
seen when locating and replacing the code).

Comment on lines 41 to +71
public FilterChainImpl createChain(HttpRequest request) {
List<Filter> filters = new ArrayList<>();
filters.addAll(sortedGlobalFilters);
String path = request.path();
List<FilterRegistration> exactMatches = routeFilters.get(path);
if (exactMatches != null) {
exactMatches.stream()
.sorted()
.map(FilterRegistration::filter)
.forEach(filters::add);
if (router == null) {
throw new IllegalStateException("Router not set");
}

String path = request.path();

List<FilterRegistration> collected = new ArrayList<>();

collected.addAll(globalFilters);

for (Map.Entry<String, List<FilterRegistration>> entry : routeFilters.entrySet()) {
String pattern = entry.getKey();
if (pattern.endsWith("*") && path.startsWith(pattern.substring(0, pattern.length() - 1))) {
entry.getValue().stream()
.sorted()
.map(FilterRegistration::filter)
.forEach(filters::add);

if (matches(pattern, path)) {
collected.addAll(entry.getValue());
}
}
return new FilterChainImpl(filters, router); // Pass router instead of plugin

Collections.sort(collected);

List<Filter> finalFilters = new ArrayList<>();
Set<Filter> seen = new HashSet<>();

for (FilterRegistration reg : collected) {
if (seen.add(reg.filter())) {
finalFilters.add(reg.filter());
}
}

return new FilterChainImpl(finalFilters, router);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

TOCTOU: router field read twice — capture to a local variable.

this.router is checked for null at Line 42 then re-read from the volatile field implicitly at Line 71. A concurrent setRouter(newRouter) call between those two reads would silently attach a different router to the constructed chain. Capture the field once at the top of the method to eliminate the window.

🛡️ Proposed fix
     public FilterChainImpl createChain(HttpRequest request) {
-        if (router == null) {
+        Router currentRouter = this.router;
+        if (currentRouter == null) {
             throw new IllegalStateException("Router not set");
         }

         String path = request.path();

         List<FilterRegistration> collected = new ArrayList<>();

         collected.addAll(globalFilters);

         for (Map.Entry<String, List<FilterRegistration>> entry : routeFilters.entrySet()) {
             String pattern = entry.getKey();

             if (matches(pattern, path)) {
                 collected.addAll(entry.getValue());
             }
         }

         Collections.sort(collected);

         List<Filter> finalFilters = new ArrayList<>();
         Set<Filter> seen = new HashSet<>();

         for (FilterRegistration reg : collected) {
             if (seen.add(reg.filter())) {
                 finalFilters.add(reg.filter());
             }
         }

-        return new FilterChainImpl(finalFilters, router);
+        return new FilterChainImpl(finalFilters, currentRouter);
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public FilterChainImpl createChain(HttpRequest request) {
List<Filter> filters = new ArrayList<>();
filters.addAll(sortedGlobalFilters);
String path = request.path();
List<FilterRegistration> exactMatches = routeFilters.get(path);
if (exactMatches != null) {
exactMatches.stream()
.sorted()
.map(FilterRegistration::filter)
.forEach(filters::add);
if (router == null) {
throw new IllegalStateException("Router not set");
}
String path = request.path();
List<FilterRegistration> collected = new ArrayList<>();
collected.addAll(globalFilters);
for (Map.Entry<String, List<FilterRegistration>> entry : routeFilters.entrySet()) {
String pattern = entry.getKey();
if (pattern.endsWith("*") && path.startsWith(pattern.substring(0, pattern.length() - 1))) {
entry.getValue().stream()
.sorted()
.map(FilterRegistration::filter)
.forEach(filters::add);
if (matches(pattern, path)) {
collected.addAll(entry.getValue());
}
}
return new FilterChainImpl(filters, router); // Pass router instead of plugin
Collections.sort(collected);
List<Filter> finalFilters = new ArrayList<>();
Set<Filter> seen = new HashSet<>();
for (FilterRegistration reg : collected) {
if (seen.add(reg.filter())) {
finalFilters.add(reg.filter());
}
}
return new FilterChainImpl(finalFilters, router);
public FilterChainImpl createChain(HttpRequest request) {
Router currentRouter = this.router;
if (currentRouter == null) {
throw new IllegalStateException("Router not set");
}
String path = request.path();
List<FilterRegistration> collected = new ArrayList<>();
collected.addAll(globalFilters);
for (Map.Entry<String, List<FilterRegistration>> entry : routeFilters.entrySet()) {
String pattern = entry.getKey();
if (matches(pattern, path)) {
collected.addAll(entry.getValue());
}
}
Collections.sort(collected);
List<Filter> finalFilters = new ArrayList<>();
Set<Filter> seen = new HashSet<>();
for (FilterRegistration reg : collected) {
if (seen.add(reg.filter())) {
finalFilters.add(reg.filter());
}
}
return new FilterChainImpl(finalFilters, currentRouter);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/juv25d/Pipeline.java` around lines 41 - 71, createChain
currently reads the router field twice (check and later use), opening a TOCTOU
race; fix by reading the router into a local final variable at the top of
createChain (e.g., final Router localRouter = this.router), perform the null
check against localRouter, and pass localRouter into the FilterChainImpl
constructor instead of re-reading the field; ensure all uses of router inside
createChain reference that local variable.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/main/java/org/juv25d/Server/Server.java (1)

22-25: Lambda body should be indented one level inside the braces.

The shutdown hook logic itself is correct (constructor placement addresses the prior issue), but the lambda body is unindented.

✏️ Formatting fix
         Runtime.getRuntime().addShutdownHook(new Thread(() -> {
-        logger.info("Shutting down server...");
-        pipeline.destroyFilters();
+            logger.info("Shutting down server...");
+            pipeline.destroyFilters();
         }));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/juv25d/Server/Server.java` around lines 22 - 25, The lambda
passed to Runtime.getRuntime().addShutdownHook(new Thread(...)) has its body
misindented; reformat the lambda in the Server constructor so the statements
logger.info("Shutting down server..."); and pipeline.destroyFilters(); are
indented one level inside the lambda braces (the block given to new Thread),
preserving the same code and semantics in the
Runtime.getRuntime().addShutdownHook/new Thread(lambda) expression.
src/main/java/org/juv25d/App.java (1)

22-23: Share a single StaticFilesPlugin instance for / and /*.

Both registrations construct separate instances of an apparently stateless plugin. A single instance is sufficient.

♻️ Proposed simplification
+        StaticFilesPlugin staticFilesPlugin = new StaticFilesPlugin();
-        router.registerPlugin("/", new StaticFilesPlugin());
-        router.registerPlugin("/*", new StaticFilesPlugin());
+        router.registerPlugin("/", staticFilesPlugin);
+        router.registerPlugin("/*", staticFilesPlugin);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/juv25d/App.java` around lines 22 - 23, The code registers
two separate StaticFilesPlugin instances via router.registerPlugin("/", new
StaticFilesPlugin()) and router.registerPlugin("/*", new StaticFilesPlugin());
instead create a single StaticFilesPlugin instance (e.g., final
StaticFilesPlugin staticFilesPlugin = new StaticFilesPlugin()) and pass that
same instance to both router.registerPlugin("/", staticFilesPlugin) and
router.registerPlugin("/*", staticFilesPlugin") to avoid redundant construction
of an apparently stateless plugin.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/java/org/juv25d/config/FilterConfiguration.java`:
- Around line 16-20: Replace the hardcoded List<RedirectRule> in
FilterConfiguration with rules loaded from the application's configuration: add
a redirect-rules block in application-properties.yml (entries with
from/to/status), expose those as a List<RedirectRule> via ConfigLoader (or a
getter like getRedirectRules()), and change FilterConfiguration to obtain
redirect rules from ConfigLoader instead of using the inline new
RedirectRule(...) list (remove the placeholder rule to example.com). Ensure
FilterConfiguration uses the provided list (and preserves handling for wildcards
and HTTP status codes) and update any constructor or bean method signatures to
accept ConfigLoader or the injected List<RedirectRule>.
- Line 24: The IpFilter instance registered via addFilter in FilterConfiguration
is created with empty allowList and denyList, making it a no-op and adding
overhead; either remove the addFilter(new IpFilter(Set.of(), Set.of()))
registration entirely from FilterConfiguration or replace it with a properly
configured IpFilter by populating the allowList and/or denyList with the
intended IP rules (or pass a configuration source into the IpFilter constructor)
so that IpFilter's filtering logic is effective.

---

Duplicate comments:
In `@src/main/java/org/juv25d/Server/Server.java`:
- Line 1: The package declaration in Server.java uses an uppercase segment
"org.juv25d.Server" which violates Java package naming conventions; rename the
package declaration to "org.juv25d.server" and move/rename the corresponding
directory from Server to server, update any import statements or references to
Server (e.g., package declaration in Server.java and any other classes that
import org.juv25d.Server) to the new lowercase package, and ensure build/IDE
configuration is refreshed so the new package path is recognized.
- Line 30: The code is creating a plaintext ServerSocket (ServerSocket
serverSocket = new ServerSocket(port, 64)), which must be replaced with an
SSL/TLS server socket; update the Server class to obtain an SSLServerSocket from
SSLServerSocketFactory (configure with a loaded KeyStore/KeyManagerFactory or
existing server SSLContext), replace the new ServerSocket(...) with
SSLServerSocket creation, enable only secure TLS protocols/cipher suites and
require client auth if needed, and ensure proper keystore loading and exception
handling so the server communicates over encrypted channels instead of
plaintext.

---

Nitpick comments:
In `@src/main/java/org/juv25d/App.java`:
- Around line 22-23: The code registers two separate StaticFilesPlugin instances
via router.registerPlugin("/", new StaticFilesPlugin()) and
router.registerPlugin("/*", new StaticFilesPlugin()); instead create a single
StaticFilesPlugin instance (e.g., final StaticFilesPlugin staticFilesPlugin =
new StaticFilesPlugin()) and pass that same instance to both
router.registerPlugin("/", staticFilesPlugin) and router.registerPlugin("/*",
staticFilesPlugin") to avoid redundant construction of an apparently stateless
plugin.

In `@src/main/java/org/juv25d/Server/Server.java`:
- Around line 22-25: The lambda passed to
Runtime.getRuntime().addShutdownHook(new Thread(...)) has its body misindented;
reformat the lambda in the Server constructor so the statements
logger.info("Shutting down server..."); and pipeline.destroyFilters(); are
indented one level inside the lambda braces (the block given to new Thread),
preserving the same code and semantics in the
Runtime.getRuntime().addShutdownHook/new Thread(lambda) expression.

Comment on lines +16 to +20
List<RedirectRule> redirectRules = List.of(
new RedirectRule("/old-page", "/new-page", 301),
new RedirectRule("/temp", "https://example.com/temporary", 302),
new RedirectRule("/docs/*", "/documentation/", 301)
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Hardcoded redirect rules include a placeholder URL that will redirect real users to example.com.

/temp → https://example.com/temporary (line 18) is clearly a placeholder, but it is wired into the live filter chain. Any request to /temp will issue a 302 to the IANA-reserved example.com domain. The other two rules (/old-page, /docs/*) are also baked in — they cannot be changed without recompiling.

Since ConfigLoader already reads from application-properties.yml, the redirect rules should follow the same pattern rather than being hardcoded here.

♻️ Suggested approach — load rules from ConfigLoader

Add a redirect-rules block to application-properties.yml:

redirect-rules:
  - from: "/old-page"
    to: "/new-page"
    status: 301
  - from: "/docs/*"
    to: "/documentation/"
    status: 301

Then expose them via ConfigLoader and consume them in FilterConfiguration:

-        List<RedirectRule> redirectRules = List.of(
-            new RedirectRule("/old-page", "/new-page", 301),
-            new RedirectRule("/temp", "https://example.com/temporary", 302),
-            new RedirectRule("/docs/*", "/documentation/", 301)
-        );
-
-        builder
-            .addFilter(new RedirectFilter(redirectRules))
+        List<RedirectRule> redirectRules = config.getRedirectRules();
+        if (!redirectRules.isEmpty()) {
+            builder.addFilter(new RedirectFilter(redirectRules));
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/juv25d/config/FilterConfiguration.java` around lines 16 -
20, Replace the hardcoded List<RedirectRule> in FilterConfiguration with rules
loaded from the application's configuration: add a redirect-rules block in
application-properties.yml (entries with from/to/status), expose those as a
List<RedirectRule> via ConfigLoader (or a getter like getRedirectRules()), and
change FilterConfiguration to obtain redirect rules from ConfigLoader instead of
using the inline new RedirectRule(...) list (remove the placeholder rule to
example.com). Ensure FilterConfiguration uses the provided list (and preserves
handling for wildcards and HTTP status codes) and update any constructor or bean
method signatures to accept ConfigLoader or the injected List<RedirectRule>.


builder
.addFilter(new RedirectFilter(redirectRules))
.addFilter(new IpFilter(Set.of(), Set.of()))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -name "*.java" -type f | xargs grep -l "class IpFilter"

Repository: ithsjava25/project-webserver-juv25d

Length of output: 126


🏁 Script executed:

cat -n ./src/main/java/org/juv25d/filter/IpFilter.java

Repository: ithsjava25/project-webserver-juv25d

Length of output: 2106


Remove the unconfigured IpFilter instance or configure it with actual IP rules.

With both allowList and denyList empty, the filter falls through to the default case (line 32) and allows all requests. This makes it a no-op filter that adds processing overhead without providing any filtering benefit. Either configure it with actual whitelist/blacklist rules or remove the registration entirely.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/juv25d/config/FilterConfiguration.java` at line 24, The
IpFilter instance registered via addFilter in FilterConfiguration is created
with empty allowList and denyList, making it a no-op and adding overhead; either
remove the addFilter(new IpFilter(Set.of(), Set.of())) registration entirely
from FilterConfiguration or replace it with a properly configured IpFilter by
populating the allowList and/or denyList with the intended IP rules (or pass a
configuration source into the IpFilter constructor) so that IpFilter's filtering
logic is effective.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (2)
src/test/java/org/juv25d/filter/GlobalFilterTests.java (1)

25-29: ⚠️ Potential issue | 🟡 Minor

Path registration doesn't match the request path — register "/*" instead of "/" to catch all routes.

SimpleRouter.resolve has a fallback to notFoundPlugin and never returns null, so there's no NPE risk. However, the registered path "/" (exact match) won't match the request path "/anything". Since "/" doesn't end with "/*", it won't be treated as a wildcard either. The test will execute but with notFoundPlugin instead of the intended NoOpPlugin. Change the registration to router.registerPlugin("/*", new NoOpPlugin()) to ensure the catch-all pattern matches all paths.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/test/java/org/juv25d/filter/GlobalFilterTests.java` around lines 25 - 29,
The test registers NoOpPlugin for path "/" which is an exact match and won't
match "/anything", causing SimpleRouter.resolve to return the notFoundPlugin;
update the registration in GlobalFilterTests to use the catch-all pattern by
calling SimpleRouter.registerPlugin with "/*" and NoOpPlugin so the request path
"/anything" matches; ensure the change is applied where router is created and
used in the execute(pipeline, "/anything") test invocation.
src/main/java/org/juv25d/Connections/DefaultConnectionHandlerFactory.java (1)

9-23: ⚠️ Potential issue | 🟡 Minor

DefaultConnectionHandlerFactory stores a Pipeline field that is never read.

The constructor accepts and stores pipeline (line 14-17), but create() (line 21) uses the pipeline parameter from the interface method instead. The field is dead state. Either remove the field (and the constructor parameter) or change the interface so create doesn't accept a Pipeline and uses the stored instance.

♻️ Option A — remove the unused field
 public class DefaultConnectionHandlerFactory implements ConnectionHandlerFactory {
     private final HttpParser httpParser;
     private final Logger logger;
-    private final Pipeline pipeline;

-    public DefaultConnectionHandlerFactory(HttpParser httpParser, Logger logger, Pipeline pipeline) {
+    public DefaultConnectionHandlerFactory(HttpParser httpParser, Logger logger) {
         this.httpParser = httpParser;
         this.logger = logger;
-        this.pipeline = pipeline;
     }

     `@Override`
     public Runnable create(Socket socket, Pipeline pipeline) {
         return new ConnectionHandler(socket, httpParser, logger, pipeline);
     }
 }
♻️ Option B — use the stored field, simplify the interface
 // ConnectionHandlerFactory.java
 public interface ConnectionHandlerFactory {
-    Runnable create(Socket socket, Pipeline pipeline);
+    Runnable create(Socket socket);
 }

 // DefaultConnectionHandlerFactory.java
     `@Override`
-    public Runnable create(Socket socket, Pipeline pipeline) {
-        return new ConnectionHandler(socket, httpParser, logger, pipeline);
+    public Runnable create(Socket socket) {
+        return new ConnectionHandler(socket, httpParser, logger, this.pipeline);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/juv25d/Connections/DefaultConnectionHandlerFactory.java`
around lines 9 - 23, The stored Pipeline field in
DefaultConnectionHandlerFactory is unused; remove the private final Pipeline
pipeline field and the Pipeline parameter from the constructor (adjust the
constructor signature and assignments accordingly) so the factory only keeps
HttpParser and Logger, and keep the create(Socket socket, Pipeline pipeline)
method as-is to pass the pipeline parameter into new ConnectionHandler(socket,
httpParser, logger, pipeline).
🧹 Nitpick comments (7)
src/main/java/org/juv25d/Server/Pipeline.java (2)

20-28: No null-guard on filter / pattern parameters in addGlobalFilter and addRouteFilter.

Passing a null filter silently registers it and will throw an NPE later during chain execution or lifecycle calls. Fail-fast with a null check, similar to setRouter.

🛡️ Proposed fix (addGlobalFilter example)
     public void addGlobalFilter(Filter filter, int order) {
+        Objects.requireNonNull(filter, "Filter must not be null");
         globalFilters.add(new FilterRegistration(filter, order, null));
     }
 
     public void addRouteFilter(Filter filter, int order, String pattern) {
+        Objects.requireNonNull(filter, "Filter must not be null");
+        Objects.requireNonNull(pattern, "Pattern must not be null");
         routeFilters
             .computeIfAbsent(pattern, k -> new CopyOnWriteArrayList<>())
             .add(new FilterRegistration(filter, order, pattern));
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/juv25d/Server/Pipeline.java` around lines 20 - 28, add
explicit null-guards for the parameters in addGlobalFilter and addRouteFilter to
fail fast: check that the Filter argument is non-null in both methods (and that
pattern is non-null in addRouteFilter) and throw the same kind of exception used
by setRouter (e.g., IllegalArgumentException or NullPointerException with a
clear message) instead of registering a null; this will prevent later NPEs
during filter execution and matches existing validation style.

48-69: Duplicated sort-and-deduplicate logic between createChain and getAllFilters.

The "collect registrations → sort → deduplicate into List<Filter>" block is copy-pasted in both methods. Extract a shared helper to reduce duplication and keep the two paths consistent.

♻️ Sketch
+    private List<Filter> dedup(List<FilterRegistration> registrations) {
+        Collections.sort(registrations);
+        List<Filter> result = new ArrayList<>();
+        Set<Filter> seen = new HashSet<>();
+        for (FilterRegistration reg : registrations) {
+            if (seen.add(reg.filter())) {
+                result.add(reg.filter());
+            }
+        }
+        return result;
+    }

Also applies to: 105-122

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/juv25d/Server/Pipeline.java` around lines 48 - 69, The
duplicated "collect → sort → deduplicate" logic in createChain and getAllFilters
should be extracted into a single helper method (e.g.,
buildFilterListFromRegistrations(Collection<FilterRegistration> regs, String
path) or similar) that encapsulates the existing steps: gather applicable
FilterRegistration objects (using matches where needed), sort the registrations
(preserving the current sort via Collections.sort(collected)), then deduplicate
into a List<Filter> by iterating registrations and adding reg.filter() only if
not already seen (using a Set<Filter> seen). Replace the copy-pasted blocks in
createChain and getAllFilters with calls to this new helper (keep using
FilterRegistration, Filter, matches, and the existing sorting behavior so
semantics remain identical).
src/main/java/org/juv25d/Server/ServerBuilder.java (2)

36-39: addFilter requires the filter to be annotated with @Global or @Route — document this contract.

FilterScanner.register throws IllegalStateException if the filter lacks the required annotation. This implicit requirement may surprise callers. A Javadoc note on addFilter would clarify the expectation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/juv25d/Server/ServerBuilder.java` around lines 36 - 39,
Document that ServerBuilder.addFilter(Filter filter) requires the provided
Filter to be annotated with either `@Global` or `@Route` because
FilterScanner.register(filter, pipeline) will throw IllegalStateException if
those annotations are missing; update the Javadoc on addFilter to state this
contract, mention the specific annotations (`@Global`, `@Route`), and note that
callers should ensure their Filter implementations carry one of these
annotations to avoid the IllegalStateException thrown by FilterScanner.register.

21-23: No port-range validation — invalid values will fail late at ServerSocket construction.

A negative or out-of-range port will only fail when new ServerSocket(port, 64) is called during start(). Fail-fast in the builder for better developer experience.

🛡️ Proposed fix
     public ServerBuilder port(int port) {
+        if (port < 0 || port > 65535) {
+            throw new IllegalArgumentException("Port must be between 0 and 65535");
+        }
         this.port = port;
         return this;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/juv25d/Server/ServerBuilder.java` around lines 21 - 23, Add
fail-fast validation in ServerBuilder.port(int): check that the provided port is
within valid TCP/UDP range 0–65535 and throw an IllegalArgumentException with a
clear message if it's out of range; keep returning this on success. Update the
ServerBuilder.port method (and any callers if needed) so invalid values are
rejected immediately rather than letting new ServerSocket(port, 64) in start()
fail later.
src/main/java/org/juv25d/Server/Server.java (1)

21-24: Shutdown hook indentation is inconsistent with the enclosing lambda.

The lambda body should be indented one level deeper than the Runtime.getRuntime() call for readability.

♻️ Proposed fix
         Runtime.getRuntime().addShutdownHook(new Thread(() -> {
-        logger.info("Shutting down server...");
-        pipeline.destroyFilters();
+            logger.info("Shutting down server...");
+            pipeline.destroyFilters();
         }));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/juv25d/Server/Server.java` around lines 21 - 24, The
shutdown hook lambda passed to Runtime.getRuntime().addShutdownHook has
inconsistent indentation; reformat the lambda body so its statements
(logger.info("Shutting down server..."); and pipeline.destroyFilters();) are
indented one level deeper than the Runtime.getRuntime() call for readability.
Locate the Runtime.getRuntime().addShutdownHook(...) expression and adjust the
indentation of the inner lambda block containing logger.info and
pipeline.destroyFilters() to match the enclosing style.
src/main/java/org/juv25d/filter/config/FilterConfiguration.java (1)

22-25: IpFilter is placed after RedirectFilter — consider security-first ordering.

With the current order, a request from a blocked IP first hits redirect logic before the IP check can reject it. Putting IpFilter first short-circuits all further processing for blocked clients.

♻️ Proposed ordering
         builder
-            .addFilter(new RedirectFilter(redirectRules))
             .addFilter(new IpFilter(Set.of(), Set.of()))
+            .addFilter(new RedirectFilter(redirectRules))
             .addFilter(new LoggingFilter())
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/juv25d/filter/config/FilterConfiguration.java` around lines
22 - 25, The filter ordering is insecure because RedirectFilter is added before
IpFilter; move the IP-based access control earlier so blocked clients are
rejected before any redirect logic runs. Update the builder sequence where
addFilter is called (the calls constructing RedirectFilter, IpFilter,
LoggingFilter) to add new IpFilter(Set.of(), Set.of()) before new
RedirectFilter(redirectRules) while keeping LoggingFilter last so IpFilter
short-circuits processing for blocked IPs.
src/test/java/org/juv25d/filter/FilterScannerTest.java (1)

44-50: Add a null-input guard test to document expected behavior.

FilterScanner.register() will throw an NPE if called with null as the first argument (line 10: instance.getClass()). The current test only covers a non-Filter Object, not null input. Adding a test would lock down the expected behavior—either that null is silently ignored or that an exception is explicitly expected.

✅ Suggested test
`@Test`
void shouldHandleNullInput() {
    Pipeline pipeline = mock(Pipeline.class);
    assertThrows(NullPointerException.class, () -> FilterScanner.register(null, pipeline));
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/test/java/org/juv25d/filter/FilterScannerTest.java` around lines 44 - 50,
The test suite is missing coverage for null input to FilterScanner.register
which currently throws a NullPointerException at instance.getClass(); add a unit
test in FilterScannerTest (e.g., shouldHandleNullInput) that creates a mocked
Pipeline and asserts that calling FilterScanner.register(null, pipeline) throws
NullPointerException so the expected behavior is documented by tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/java/org/juv25d/Server/Pipeline.java`:
- Around line 87-89: initFilters() currently calls
getAllFilters().forEach(Filter::init) without per-filter error handling, so if
any Filter.init throws the whole initialization stops; change it to iterate over
getAllFilters() and call init() for each filter inside a try/catch, mirroring
destroyFilters() behavior: catch exceptions from filter.init(), log the error
(using the same logger/mechanism used in destroyFilters()) with context
including the filter identity, and continue to initialize the remaining filters
so one faulty filter cannot prevent others from initializing.

---

Outside diff comments:
In `@src/main/java/org/juv25d/Connections/DefaultConnectionHandlerFactory.java`:
- Around line 9-23: The stored Pipeline field in DefaultConnectionHandlerFactory
is unused; remove the private final Pipeline pipeline field and the Pipeline
parameter from the constructor (adjust the constructor signature and assignments
accordingly) so the factory only keeps HttpParser and Logger, and keep the
create(Socket socket, Pipeline pipeline) method as-is to pass the pipeline
parameter into new ConnectionHandler(socket, httpParser, logger, pipeline).

In `@src/test/java/org/juv25d/filter/GlobalFilterTests.java`:
- Around line 25-29: The test registers NoOpPlugin for path "/" which is an
exact match and won't match "/anything", causing SimpleRouter.resolve to return
the notFoundPlugin; update the registration in GlobalFilterTests to use the
catch-all pattern by calling SimpleRouter.registerPlugin with "/*" and
NoOpPlugin so the request path "/anything" matches; ensure the change is applied
where router is created and used in the execute(pipeline, "/anything") test
invocation.

---

Duplicate comments:
In `@src/main/java/org/juv25d/Connections/ConnectionHandlerFactory.java`:
- Around line 1-3: The package declaration uses mixed case; change the package
line in ConnectionHandlerFactory from "package org.juv25d.Connections;" to the
lowercase "package org.juv25d.connections;" and update any imports referencing
the server package (e.g., change "import org.juv25d.Server.Pipeline;" to "import
org.juv25d.server.Pipeline;"); also rename the source directory to match the new
lowercase package (move files into org/juv25d/connections) and update any other
references to ConnectionHandlerFactory or Pipeline across the codebase to use
the lowercase package names.

In `@src/main/java/org/juv25d/filter/config/FilterConfiguration.java`:
- Line 24: The IpFilter is being instantiated with empty allowList/denyList
(Set.of()) in FilterConfiguration which makes it a no-op and wastes processing
time; either remove the .addFilter(new IpFilter(Set.of(), Set.of()))
registration from FilterConfiguration or replace the empty sets with the
intended configured IP rules (e.g., inject or read allow/deny collections and
pass them into new IpFilter(allowList, denyList)), ensuring the IpFilter
constructor receives meaningful data instead of Set.of().
- Around line 16-20: The FilterConfiguration currently contains hardcoded
RedirectRule entries (redirectRules and new RedirectRule("/temp", ...)); replace
this static list by loading redirect rules from the central configuration source
(e.g., ConfigLoader or application-properties.yml). Modify FilterConfiguration
to call the existing ConfigLoader (or bind a properties class) to obtain the
list of RedirectRule objects and populate redirectRules from that source instead
of using new RedirectRule(...) literals, and remove the placeholder entries
("/temp", "/old-page", "/docs/*") so rules come solely from configuration.

In `@src/main/java/org/juv25d/Server/Pipeline.java`:
- Around line 41-71: The createChain method reads this.router twice causing a
TOCTOU race; capture the router into a local variable (e.g., Router routerLocal
= this.router) before the null check, validate routerLocal for null, and then
use routerLocal when constructing and returning the new FilterChainImpl; update
all references to router in createChain (including the final FilterChainImpl
constructor call) to use the captured local variable and leave setRouter
unchanged.

In `@src/main/java/org/juv25d/Server/ServerBuilder.java`:
- Around line 48-64: build() is not idempotent because pipeline.initFilters() is
called every time; add a one-time guard in ServerBuilder (e.g., a private
boolean filtersInitialized or an AtomicBoolean) and update build() to check the
guard before calling pipeline.initFilters(), setting the guard true immediately
after the first successful init; reference the existing build(), pipeline, and
pipeline.initFilters() to locate where to add the guard so subsequent calls to
build() skip re-initializing filters.

---

Nitpick comments:
In `@src/main/java/org/juv25d/filter/config/FilterConfiguration.java`:
- Around line 22-25: The filter ordering is insecure because RedirectFilter is
added before IpFilter; move the IP-based access control earlier so blocked
clients are rejected before any redirect logic runs. Update the builder sequence
where addFilter is called (the calls constructing RedirectFilter, IpFilter,
LoggingFilter) to add new IpFilter(Set.of(), Set.of()) before new
RedirectFilter(redirectRules) while keeping LoggingFilter last so IpFilter
short-circuits processing for blocked IPs.

In `@src/main/java/org/juv25d/Server/Pipeline.java`:
- Around line 20-28: add explicit null-guards for the parameters in
addGlobalFilter and addRouteFilter to fail fast: check that the Filter argument
is non-null in both methods (and that pattern is non-null in addRouteFilter) and
throw the same kind of exception used by setRouter (e.g.,
IllegalArgumentException or NullPointerException with a clear message) instead
of registering a null; this will prevent later NPEs during filter execution and
matches existing validation style.
- Around line 48-69: The duplicated "collect → sort → deduplicate" logic in
createChain and getAllFilters should be extracted into a single helper method
(e.g., buildFilterListFromRegistrations(Collection<FilterRegistration> regs,
String path) or similar) that encapsulates the existing steps: gather applicable
FilterRegistration objects (using matches where needed), sort the registrations
(preserving the current sort via Collections.sort(collected)), then deduplicate
into a List<Filter> by iterating registrations and adding reg.filter() only if
not already seen (using a Set<Filter> seen). Replace the copy-pasted blocks in
createChain and getAllFilters with calls to this new helper (keep using
FilterRegistration, Filter, matches, and the existing sorting behavior so
semantics remain identical).

In `@src/main/java/org/juv25d/Server/Server.java`:
- Around line 21-24: The shutdown hook lambda passed to
Runtime.getRuntime().addShutdownHook has inconsistent indentation; reformat the
lambda body so its statements (logger.info("Shutting down server..."); and
pipeline.destroyFilters();) are indented one level deeper than the
Runtime.getRuntime() call for readability. Locate the
Runtime.getRuntime().addShutdownHook(...) expression and adjust the indentation
of the inner lambda block containing logger.info and pipeline.destroyFilters()
to match the enclosing style.

In `@src/main/java/org/juv25d/Server/ServerBuilder.java`:
- Around line 36-39: Document that ServerBuilder.addFilter(Filter filter)
requires the provided Filter to be annotated with either `@Global` or `@Route`
because FilterScanner.register(filter, pipeline) will throw
IllegalStateException if those annotations are missing; update the Javadoc on
addFilter to state this contract, mention the specific annotations (`@Global`,
`@Route`), and note that callers should ensure their Filter implementations carry
one of these annotations to avoid the IllegalStateException thrown by
FilterScanner.register.
- Around line 21-23: Add fail-fast validation in ServerBuilder.port(int): check
that the provided port is within valid TCP/UDP range 0–65535 and throw an
IllegalArgumentException with a clear message if it's out of range; keep
returning this on success. Update the ServerBuilder.port method (and any callers
if needed) so invalid values are rejected immediately rather than letting new
ServerSocket(port, 64) in start() fail later.

In `@src/test/java/org/juv25d/filter/FilterScannerTest.java`:
- Around line 44-50: The test suite is missing coverage for null input to
FilterScanner.register which currently throws a NullPointerException at
instance.getClass(); add a unit test in FilterScannerTest (e.g.,
shouldHandleNullInput) that creates a mocked Pipeline and asserts that calling
FilterScanner.register(null, pipeline) throws NullPointerException so the
expected behavior is documented by tests.

Comment on lines +87 to +89
public void initFilters() {
getAllFilters().forEach(Filter::init);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

initFilters() lacks per-filter error handling, unlike destroyFilters().

If any filter's init() throws, subsequent filters are never initialized, leaving the pipeline in a partially initialized state. destroyFilters() already wraps each call in a try/catch — apply the same pattern here so that one faulty filter doesn't prevent the rest from initializing.

🛡️ Proposed fix
     public void initFilters() {
-        getAllFilters().forEach(Filter::init);
+        for (Filter filter : getAllFilters()) {
+            try {
+                filter.init();
+            } catch (Exception e) {
+                System.err.println(
+                    "Error initializing filter " +
+                        filter.getClass().getName() +
+                        ": " + e.getMessage()
+                );
+            }
+        }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public void initFilters() {
getAllFilters().forEach(Filter::init);
}
public void initFilters() {
for (Filter filter : getAllFilters()) {
try {
filter.init();
} catch (Exception e) {
System.err.println(
"Error initializing filter " +
filter.getClass().getName() +
": " + e.getMessage()
);
}
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/juv25d/Server/Pipeline.java` around lines 87 - 89,
initFilters() currently calls getAllFilters().forEach(Filter::init) without
per-filter error handling, so if any Filter.init throws the whole initialization
stops; change it to iterate over getAllFilters() and call init() for each filter
inside a try/catch, mirroring destroyFilters() behavior: catch exceptions from
filter.init(), log the error (using the same logger/mechanism used in
destroyFilters()) with context including the filter identity, and continue to
initialize the remaining filters so one faulty filter cannot prevent others from
initializing.

@kristinaxm kristinaxm closed this Feb 19, 2026
@kristinaxm
kristinaxm deleted the feature/filter-lifecycle-and-builder-refactor branch February 22, 2026 16:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant