From eb16902b813714d9bee31d04e1fe1135700a7635 Mon Sep 17 00:00:00 2001 From: Edgar Espina Date: Mon, 13 Oct 2025 20:34:22 -0300 Subject: [PATCH 1/7] server: Allow to configure max_form_parameters across servers - fix #3783 --- jooby/src/main/java/io/jooby/Route.java | 19 +++++--- .../src/main/java/io/jooby/ServerOptions.java | 30 ++++++++++++- .../java/io/jooby/internal/RouterMatch.java | 1 - .../io/jooby/internal/jetty/JettyContext.java | 16 ++++++- .../io/jooby/internal/jetty/JettyHandler.java | 12 +++-- .../internal/jetty/JettyStopPipeline.java | 8 ++++ .../main/java/io/jooby/jetty/JettyServer.java | 9 ++-- .../io/jooby/internal/netty/NettyHandler.java | 36 +++++++++------ .../jooby/internal/netty/NettyPipeline.java | 5 ++- .../main/java/io/jooby/netty/NettyServer.java | 1 + .../internal/undertow/UndertowHandler.java | 8 +++- .../io/jooby/undertow/UndertowServer.java | 8 ++-- .../test/java/io/jooby/i3783/Issue3783.java | 45 +++++++++++++++++++ 13 files changed, 159 insertions(+), 39 deletions(-) create mode 100644 modules/jooby-jetty/src/main/java/io/jooby/internal/jetty/JettyStopPipeline.java create mode 100644 tests/src/test/java/io/jooby/i3783/Issue3783.java diff --git a/jooby/src/main/java/io/jooby/Route.java b/jooby/src/main/java/io/jooby/Route.java index 68d89988bc..b04d7a9dd8 100644 --- a/jooby/src/main/java/io/jooby/Route.java +++ b/jooby/src/main/java/io/jooby/Route.java @@ -26,11 +26,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import io.jooby.annotation.Transactional; -import io.jooby.exception.MethodNotAllowedException; -import io.jooby.exception.NotAcceptableException; -import io.jooby.exception.NotFoundException; -import io.jooby.exception.StatusCodeException; -import io.jooby.exception.UnsupportedMediaType; +import io.jooby.exception.*; /** * Route contains information about the HTTP method, path pattern, which content types consumes and @@ -350,6 +346,19 @@ public interface Handler extends Serializable, Aware { } }; + /** Handler for body error decoder responses. */ + public static final Handler FORM_DECODER_HANDLER = + ctx -> { + var tooManyFields = (Throwable) ctx.getAttributes().remove("__too_many_fields"); + BadRequestException cause; + if (tooManyFields != null) { + cause = new BadRequestException("Too many form fields", tooManyFields); + } else { + cause = new BadRequestException("Failed to decode HTTP body"); + } + return ctx.sendError(cause); + }; + /** Handler for {@link StatusCode#REQUEST_ENTITY_TOO_LARGE} responses. */ public static final Handler REQUEST_ENTITY_TOO_LARGE = ctx -> diff --git a/jooby/src/main/java/io/jooby/ServerOptions.java b/jooby/src/main/java/io/jooby/ServerOptions.java index a565a742ad..bb07654157 100644 --- a/jooby/src/main/java/io/jooby/ServerOptions.java +++ b/jooby/src/main/java/io/jooby/ServerOptions.java @@ -106,6 +106,9 @@ public class ServerOptions { */ private int maxRequestSize = _10MB; + /** Max number of form fields. Default: 1000. */ + private int maxFormFields = 1000; + /** The maximum size in bytes of a http request header. Default is 8kb */ private int maxHeaderSize = _8KB; @@ -131,7 +134,7 @@ public class ServerOptions { * @param conf Configuration object. * @return Server options. */ - public static @NonNull Optional from(@NonNull Config conf) { + public static Optional from(@NonNull Config conf) { if (conf.hasPath("server")) { ServerOptions options = new ServerOptions(); if (conf.hasPath("server.port")) { @@ -162,6 +165,9 @@ public class ServerOptions { if (conf.hasPath("server.maxRequestSize")) { options.setMaxRequestSize((int) conf.getMemorySize("server.maxRequestSize").toBytes()); } + if (conf.hasPath("server.maxFormFields")) { + options.setMaxFormFields(conf.getInt("server.maxFormFields")); + } if (conf.hasPath("server.workerThreads")) { options.setWorkerThreads(conf.getInt("server.workerThreads")); } @@ -445,11 +451,31 @@ public int getMaxRequestSize() { * @param maxRequestSize Max request size in bytes. * @return This options. */ - public @NonNull ServerOptions setMaxRequestSize(int maxRequestSize) { + public ServerOptions setMaxRequestSize(int maxRequestSize) { this.maxRequestSize = maxRequestSize; return this; } + /** + * Max number of form fields. Default: 1000. + * + * @return Max number of form fields. Default: 1000. + */ + public int getMaxFormFields() { + return maxFormFields; + } + + /** + * Set max number of form fields. Default: 1000. + * + * @param maxFormFields Max number of form fields. + * @return Max number of form fields. Default: 1000. + */ + public ServerOptions setMaxFormFields(int maxFormFields) { + this.maxFormFields = maxFormFields; + return this; + } + /** * The maximum size in bytes of an http request header. Exceeding the size generates a different * response across server implementations. diff --git a/jooby/src/main/java/io/jooby/internal/RouterMatch.java b/jooby/src/main/java/io/jooby/internal/RouterMatch.java index cb99732c6c..b4aecb711f 100644 --- a/jooby/src/main/java/io/jooby/internal/RouterMatch.java +++ b/jooby/src/main/java/io/jooby/internal/RouterMatch.java @@ -109,7 +109,6 @@ public RouterMatch missing(String method, String path, MessageEncoder encoder) { } this.route = new Route(method, path, h); this.route.setEncoder(encoder); - // this.route.setReturnType(Context.class); return this; } } diff --git a/modules/jooby-jetty/src/main/java/io/jooby/internal/jetty/JettyContext.java b/modules/jooby-jetty/src/main/java/io/jooby/internal/jetty/JettyContext.java index 6350054d9a..39cb28e15c 100644 --- a/modules/jooby-jetty/src/main/java/io/jooby/internal/jetty/JettyContext.java +++ b/modules/jooby-jetty/src/main/java/io/jooby/internal/jetty/JettyContext.java @@ -697,7 +697,7 @@ private FileUpload register(FileUpload upload) { return upload; } - private static void formParam(Request request, Formdata form) { + private void formParam(Request request, Formdata form) { try { var params = Request.getParameters(request); for (Fields.Field param : params) { @@ -710,7 +710,19 @@ private static void formParam(Request request, Formdata form) { } } } catch (Exception ex) { - throw SneakyThrows.propagate(ex); + if (ex instanceof IllegalStateException + && ex.getMessage() != null + && ex.getMessage().startsWith("form with too many")) { + this.setAttribute("__too_many_fields", ex); + try { + Route.FORM_DECODER_HANDLER.apply(this); + } catch (Exception cause) { + throw SneakyThrows.propagate(cause); + } + throw new JettyStopPipeline(); + } else { + throw SneakyThrows.propagate(ex); + } } } } diff --git a/modules/jooby-jetty/src/main/java/io/jooby/internal/jetty/JettyHandler.java b/modules/jooby-jetty/src/main/java/io/jooby/internal/jetty/JettyHandler.java index f3b2744f68..ac90b7d20f 100644 --- a/modules/jooby-jetty/src/main/java/io/jooby/internal/jetty/JettyHandler.java +++ b/modules/jooby-jetty/src/main/java/io/jooby/internal/jetty/JettyHandler.java @@ -39,10 +39,14 @@ public boolean handle(Request request, Response response, Callback callback) { if (defaultHeaders) { responseHeaders.put(HttpHeader.SERVER.asString(), "J"); } - var context = - new JettyContext( - getInvocationType(), request, response, callback, router, bufferSize, maxRequestSize); - router.match(context).execute(context); + try { + var context = + new JettyContext( + getInvocationType(), request, response, callback, router, bufferSize, maxRequestSize); + router.match(context).execute(context); + } catch (JettyStopPipeline ignored) { + // handled already, + } return true; } } diff --git a/modules/jooby-jetty/src/main/java/io/jooby/internal/jetty/JettyStopPipeline.java b/modules/jooby-jetty/src/main/java/io/jooby/internal/jetty/JettyStopPipeline.java new file mode 100644 index 0000000000..4358c174bc --- /dev/null +++ b/modules/jooby-jetty/src/main/java/io/jooby/internal/jetty/JettyStopPipeline.java @@ -0,0 +1,8 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ +package io.jooby.internal.jetty; + +public class JettyStopPipeline extends RuntimeException {} diff --git a/modules/jooby-jetty/src/main/java/io/jooby/jetty/JettyServer.java b/modules/jooby-jetty/src/main/java/io/jooby/jetty/JettyServer.java index 26903c4463..fd5567f6e1 100644 --- a/modules/jooby-jetty/src/main/java/io/jooby/jetty/JettyServer.java +++ b/modules/jooby-jetty/src/main/java/io/jooby/jetty/JettyServer.java @@ -98,10 +98,6 @@ public io.jooby.Server start(@NonNull Jooby... application) { var portInUse = options.getPort(); try { this.applications = List.of(application); - /* Set max request size attribute: */ - System.setProperty( - "org.eclipse.jetty.server.Request.maxFormContentSize", - Long.toString(options.getMaxRequestSize())); addShutdownHook(); @@ -114,7 +110,7 @@ public io.jooby.Server start(@NonNull Jooby... application) { var acceptors = 1; var selectors = options.getIoThreads(); - this.server = new Server(threadPool); + server = new Server(threadPool); server.setStopAtShutdown(false); JettyHttp2Configurer http2 = @@ -200,7 +196,8 @@ public io.jooby.Server start(@NonNull Jooby... application) { } var context = new ContextHandler(); - + context.setAttribute(FormFields.MAX_FIELDS_ATTRIBUTE, options.getMaxFormFields()); + context.setAttribute(FormFields.MAX_LENGTH_ATTRIBUTE, options.getMaxRequestSize()); boolean webSockets = application[0].getRoutes().stream().anyMatch(it -> it.getMethod().equals(Router.WS)); diff --git a/modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyHandler.java b/modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyHandler.java index 261592f720..77b4440615 100644 --- a/modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyHandler.java +++ b/modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyHandler.java @@ -34,6 +34,7 @@ public class NettyHandler extends ChannelInboundHandlerAdapter { private final int bufferSize; private final boolean defaultHeaders; private final long maxRequestSize; + private final int maxFormFields; private long contentLength; private long chunkSize; private final boolean http2; @@ -43,6 +44,7 @@ public NettyHandler( NettyDateService dateService, List applications, long maxRequestSize, + int maxFormFields, int bufferSize, boolean defaultHeaders, boolean http2) { @@ -50,6 +52,7 @@ public NettyHandler( this.applications = applications; this.ctxSelector = Context.Selector.create(applications); this.maxRequestSize = maxRequestSize; + this.maxFormFields = maxFormFields; this.bufferSize = bufferSize; this.defaultHeaders = defaultHeaders; this.http2 = http2; @@ -79,7 +82,7 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) { if (contentLength > 0 || isTransferEncodingChunked(req)) { context.httpDataFactory = new DefaultHttpDataFactory(bufferSize); context.httpDataFactory.setBaseDir(app.getTmpdir().toString()); - context.decoder = newDecoder(req, context.httpDataFactory); + context.decoder = newDecoder(req, context.httpDataFactory, maxFormFields); } else { // no body, move on router.match(context).execute(context); @@ -90,10 +93,11 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) { try { // when decoder == null, chunk is always a LastHttpContent.EMPTY, ignore it if (context.decoder != null) { - offer(context, chunk); - Router.Match route = router.match(context); - resetDecoderState(context, !route.matches()); - route.execute(context); + if (offer(context, chunk)) { + Router.Match route = router.match(context); + resetDecoderState(context, !route.matches()); + route.execute(context); + } } } finally { release(chunk); @@ -172,14 +176,16 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { } } - private void offer(NettyContext context, HttpContent chunk) { + private boolean offer(NettyContext context, HttpContent chunk) { try { context.decoder.offer(chunk); - } catch (HttpPostRequestDecoder.ErrorDataDecoderException - | HttpPostRequestDecoder.TooLongFormFieldException - | HttpPostRequestDecoder.TooManyFormFieldsException x) { - resetDecoderState(context, true); - context.sendError(x, StatusCode.BAD_REQUEST); + return true; + } catch (Exception x) { + if (x instanceof HttpPostRequestDecoder.TooManyFormFieldsException) { + context.setAttribute("__too_many_fields", x); + } + router.match(context).execute(context, Route.FORM_DECODER_HANDLER); + return false; } } @@ -197,14 +203,16 @@ private void resetDecoderState(NettyContext context, boolean destroy) { } private static InterfaceHttpPostRequestDecoder newDecoder( - HttpRequest request, HttpDataFactory factory) { + HttpRequest request, HttpDataFactory factory, int maxFormFields) { String contentType = request.headers().get(HttpHeaderNames.CONTENT_TYPE); if (contentType != null) { String lowerContentType = contentType.toLowerCase(); if (lowerContentType.startsWith(MediaType.MULTIPART_FORMDATA)) { - return new HttpPostMultipartRequestDecoder(factory, request, StandardCharsets.UTF_8); + return new HttpPostMultipartRequestDecoder( + factory, request, StandardCharsets.UTF_8, maxFormFields, -1); } else if (lowerContentType.startsWith(MediaType.FORM_URLENCODED)) { - return new HttpPostStandardRequestDecoder(factory, request, StandardCharsets.UTF_8); + return new HttpPostStandardRequestDecoder( + factory, request, StandardCharsets.UTF_8, maxFormFields, -1); } } return new HttpRawPostRequestDecoder(factory, request); diff --git a/modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyPipeline.java b/modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyPipeline.java index 44b12e46cc..32123a4d77 100644 --- a/modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyPipeline.java +++ b/modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyPipeline.java @@ -24,6 +24,7 @@ public class NettyPipeline extends ChannelInitializer { private final HttpDecoderConfig decoderConfig; private final List applications; private final long maxRequestSize; + private final int maxFormFields; private final int bufferSize; private final boolean defaultHeaders; private final boolean http2; @@ -36,6 +37,7 @@ public NettyPipeline( HttpDecoderConfig decoderConfig, List applications, long maxRequestSize, + int maxFormFields, int bufferSize, boolean defaultHeaders, boolean http2, @@ -46,6 +48,7 @@ public NettyPipeline( this.decoderConfig = decoderConfig; this.applications = applications; this.maxRequestSize = maxRequestSize; + this.maxFormFields = maxFormFields; this.bufferSize = bufferSize; this.defaultHeaders = defaultHeaders; this.http2 = http2; @@ -55,7 +58,7 @@ public NettyPipeline( private NettyHandler createHandler() { return new NettyHandler( - serverDate, applications, maxRequestSize, bufferSize, defaultHeaders, http2); + serverDate, applications, maxRequestSize, maxFormFields, bufferSize, defaultHeaders, http2); } @Override diff --git a/modules/jooby-netty/src/main/java/io/jooby/netty/NettyServer.java b/modules/jooby-netty/src/main/java/io/jooby/netty/NettyServer.java index eaea22c579..ce5d5a99ce 100644 --- a/modules/jooby-netty/src/main/java/io/jooby/netty/NettyServer.java +++ b/modules/jooby-netty/src/main/java/io/jooby/netty/NettyServer.java @@ -213,6 +213,7 @@ private NettyPipeline newPipeline( decoderConfig, applications, options.getMaxRequestSize(), + options.getMaxFormFields(), bufferSize, options.getDefaultHeaders(), http2, diff --git a/modules/jooby-undertow/src/main/java/io/jooby/internal/undertow/UndertowHandler.java b/modules/jooby-undertow/src/main/java/io/jooby/internal/undertow/UndertowHandler.java index 677e91a063..88463181d4 100644 --- a/modules/jooby-undertow/src/main/java/io/jooby/internal/undertow/UndertowHandler.java +++ b/modules/jooby-undertow/src/main/java/io/jooby/internal/undertow/UndertowHandler.java @@ -7,6 +7,7 @@ import java.nio.charset.StandardCharsets; import java.util.List; +import java.util.Optional; import io.jooby.*; import io.undertow.io.Receiver; @@ -18,6 +19,7 @@ import io.undertow.server.handlers.form.MultiPartParserDefinition; import io.undertow.util.HeaderMap; import io.undertow.util.Headers; +import io.undertow.util.ParameterLimitException; public class UndertowHandler implements HttpHandler { protected final List applications; @@ -92,7 +94,11 @@ public void handleRequest(HttpServerExchange exchange) throws Exception { try { parser.parse(execute(router, context)); } catch (Exception x) { - context.sendError(x, StatusCode.BAD_REQUEST); + var cause = Optional.ofNullable(x.getCause()).orElse(x); + if (cause instanceof ParameterLimitException) { + context.setAttribute("__too_many_fields", cause); + } + router.match(context).execute(context, Route.FORM_DECODER_HANDLER); } } } else { diff --git a/modules/jooby-undertow/src/main/java/io/jooby/undertow/UndertowServer.java b/modules/jooby-undertow/src/main/java/io/jooby/undertow/UndertowServer.java index db3b2bfe5e..7a214dea81 100644 --- a/modules/jooby-undertow/src/main/java/io/jooby/undertow/UndertowServer.java +++ b/modules/jooby-undertow/src/main/java/io/jooby/undertow/UndertowServer.java @@ -64,13 +64,13 @@ protected ServerOptions defaultOptions() { return new ServerOptions().setIoThreads(ServerOptions.IO_THREADS).setServer("utow"); } - @NonNull @Override + @Override public String getName() { return "undertow"; } @Override - public @NonNull Server start(@NonNull Jooby... application) { + public Server start(@NonNull Jooby... application) { // force options to be non-null var options = getOptions(); var portInUse = options.getPort(); @@ -114,7 +114,7 @@ public String getName() { .addAll(OptionMap.create(Options.WORKER_NAME, "worker")) .getMap()); - Undertow.Builder builder = + var builder = Undertow.builder() .setBufferSize(options.getBufferSize()) /** Socket : */ @@ -123,6 +123,8 @@ public String getName() { // HTTP/1.1 is keep-alive by default, turn this option off .setServerOption(UndertowOptions.ALWAYS_SET_KEEP_ALIVE, false) .setServerOption(UndertowOptions.MAX_HEADER_SIZE, options.getMaxHeaderSize()) + .setServerOption(UndertowOptions.MAX_PARAMETERS, options.getMaxFormFields()) + .setServerOption(UndertowOptions.MAX_ENTITY_SIZE, (long) options.getMaxRequestSize()) .setServerOption(UndertowOptions.ALLOW_EQUALS_IN_COOKIE_VALUE, true) .setServerOption(UndertowOptions.ALWAYS_SET_DATE, options.getDefaultHeaders()) .setServerOption(UndertowOptions.RECORD_REQUEST_START_TIME, false) diff --git a/tests/src/test/java/io/jooby/i3783/Issue3783.java b/tests/src/test/java/io/jooby/i3783/Issue3783.java new file mode 100644 index 0000000000..282df458bf --- /dev/null +++ b/tests/src/test/java/io/jooby/i3783/Issue3783.java @@ -0,0 +1,45 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ +package io.jooby.i3783; + +import static org.junit.jupiter.api.Assertions.*; + +import io.jooby.ServerOptions; +import io.jooby.junit.ServerTest; +import io.jooby.junit.ServerTestRunner; +import okhttp3.FormBody; + +public class Issue3783 { + + @ServerTest + public void shouldAllowToSetMaxFormFields(ServerTestRunner runner) { + runner + .define( + app -> { + app.setServerOptions(new ServerOptions().setMaxFormFields(2)); + app.error((ctx, cause, code) -> ctx.send(cause.getMessage())); + app.post( + "/3723", + ctx -> { + return ctx.form("f").toList(); + }); + }) + .ready( + http -> { + http.post( + "/3723", + new FormBody.Builder() + .add("f1", "value 1") + .add("f2", "value 2") + .add("f3", "value 3") + .build(), + rsp -> { + assertEquals(400, rsp.code()); + assertEquals("Too many form fields", rsp.body().string()); + }); + }); + } +} From 9fd6e31c8289155e04f24dbdae6c332ecd2a9228 Mon Sep 17 00:00:00 2001 From: Edgar Espina Date: Mon, 13 Oct 2025 20:49:20 -0300 Subject: [PATCH 2/7] build: dependencies upgrade --- modules/jooby-openapi/pom.xml | 2 +- pom.xml | 38 +++++++++++++++++------------------ 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/modules/jooby-openapi/pom.xml b/modules/jooby-openapi/pom.xml index d967cb7340..2ab9202af3 100644 --- a/modules/jooby-openapi/pom.xml +++ b/modules/jooby-openapi/pom.xml @@ -56,7 +56,7 @@ com.fasterxml.jackson.core jackson-annotations - ${jackson.version} + 2.20 diff --git a/pom.xml b/pom.xml index 0d496e44f7..df343c6392 100644 --- a/pom.xml +++ b/pom.xml @@ -58,11 +58,11 @@ 2.3.34 - 4.4.0 + 4.5.0 1.3.7 3.2.4 - 2.19.2 - 2.13.1 + 2.20.0 + 2.13.2 3.0.1 3.0.4 2.2.1 @@ -70,12 +70,12 @@ 3.2.1 - 6.3.2 + 6.3.3 1.2 6.6.22.Final 15.11.0 3.49.5 - 11.8.2 + 11.14.0 24.1 6.7.1.RELEASE 2.12.1 @@ -88,7 +88,7 @@ 7.0.0 - 1.5.18 + 1.5.19 2.25.1 2.0.17 @@ -96,7 +96,7 @@ 1.6.0 - 4.2.33 + 4.2.37 2.1.6.Final @@ -106,8 +106,8 @@ 2.3.18.Final - 12.0.23 - 4.2.3.Final + 12.1.2 + 4.2.6.Final 2.2.34 @@ -120,23 +120,23 @@ 11.6 3.6 - 2.13 + 2.14 2.0.1.MR 3.1.1 4.0.0 - 4.9.3 + 4.9.6 5.1.0 0.12.6 - 6.2.0 + 6.2.2 2.5.0 9.2.1 8.0.1 - 1.12.788 - 4.13.0 + 1.12.792 + 4.15.0 1.9.3 2.20.0 @@ -147,15 +147,15 @@ true - 0.8.13 - 5.13.4 + 0.8.14 + 6.0.0 5.5.5 3.27.3 - 5.18.0 - 33.4.8-jre + 5.20.0 + 33.5.0-jre 0.21.0 - 1.4.4 + 1.4.5 2.6 From 46c72cad37870234cc6342d07bc3dd95a42b19fc Mon Sep 17 00:00:00 2001 From: Edgar Espina Date: Mon, 13 Oct 2025 21:02:42 -0300 Subject: [PATCH 3/7] Route inheritance from base controller no longer works when multiple subclasses extend it (Jooby 4.x vs 1.x) - ref #3786 --- .../src/test/java/tests/i3786/Base3786.java | 30 ++++++++++ .../src/test/java/tests/i3786/C3786.java | 24 ++++++++ .../src/test/java/tests/i3786/D3786.java | 24 ++++++++ .../src/test/java/tests/i3786/Issue3786.java | 55 +++++++++++++++++++ 4 files changed, 133 insertions(+) create mode 100644 modules/jooby-apt/src/test/java/tests/i3786/Base3786.java create mode 100644 modules/jooby-apt/src/test/java/tests/i3786/C3786.java create mode 100644 modules/jooby-apt/src/test/java/tests/i3786/D3786.java create mode 100644 modules/jooby-apt/src/test/java/tests/i3786/Issue3786.java diff --git a/modules/jooby-apt/src/test/java/tests/i3786/Base3786.java b/modules/jooby-apt/src/test/java/tests/i3786/Base3786.java new file mode 100644 index 0000000000..1fdfca7662 --- /dev/null +++ b/modules/jooby-apt/src/test/java/tests/i3786/Base3786.java @@ -0,0 +1,30 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ +package tests.i3786; + +import io.jooby.Context; +import io.jooby.annotation.GET; +import io.jooby.annotation.Path; +import io.jooby.annotation.QueryParam; + +@Path("/base") +public abstract class Base3786 { + + @GET + public String base() { + return "base"; + } + + @GET("/withPath") + public String withPath(@QueryParam String q) { + return "withPath"; + } + + @GET("/{id}") + public String getOne(Context ctx) { + return "base: " + ctx.path("id").value(); + } +} diff --git a/modules/jooby-apt/src/test/java/tests/i3786/C3786.java b/modules/jooby-apt/src/test/java/tests/i3786/C3786.java new file mode 100644 index 0000000000..1a044762f1 --- /dev/null +++ b/modules/jooby-apt/src/test/java/tests/i3786/C3786.java @@ -0,0 +1,24 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ +package tests.i3786; + +import io.jooby.Context; +import io.jooby.annotation.GET; +import io.jooby.annotation.POST; +import io.jooby.annotation.Path; + +@Path("/inherited") +public class C3786 extends Base3786 { + @GET("/childOnly") + public String childOnly(Context ctx) { + return ctx.getMethod() + ctx.getRequestPath(); + } + + @POST("/childOnly") + public String childOnlyPost(Context ctx) { + return ctx.getMethod() + ctx.getRequestPath(); + } +} diff --git a/modules/jooby-apt/src/test/java/tests/i3786/D3786.java b/modules/jooby-apt/src/test/java/tests/i3786/D3786.java new file mode 100644 index 0000000000..f83b5cb98f --- /dev/null +++ b/modules/jooby-apt/src/test/java/tests/i3786/D3786.java @@ -0,0 +1,24 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ +package tests.i3786; + +import io.jooby.Context; +import io.jooby.annotation.GET; +import io.jooby.annotation.POST; +import io.jooby.annotation.Path; + +@Path("/overrideMethod") +public class D3786 extends Base3786 { + @GET("/childOnly") + public String childOnly(Context ctx) { + return ctx.getRequestPath(); + } + + @POST("/user") + public String newPath(Context q) { + return "/overrideMethod/user"; + } +} diff --git a/modules/jooby-apt/src/test/java/tests/i3786/Issue3786.java b/modules/jooby-apt/src/test/java/tests/i3786/Issue3786.java new file mode 100644 index 0000000000..8c0b6b3e7a --- /dev/null +++ b/modules/jooby-apt/src/test/java/tests/i3786/Issue3786.java @@ -0,0 +1,55 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ +package tests.i3786; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +import io.jooby.apt.ProcessorRunner; +import io.jooby.test.MockContext; +import io.jooby.test.MockRouter; + +public class Issue3786 { + @Test + public void shouldCheckBase() throws Exception { + new ProcessorRunner(new C3786()) + .withRouter( + app -> { + var router = new MockRouter(app); + assertEquals("base", router.get("/inherited", new MockContext()).value()); + assertEquals( + "withPath", router.get("/inherited/withPath", new MockContext()).value()); + assertEquals("base: 123", router.get("/inherited/123", new MockContext()).value()); + assertEquals( + "GET/inherited/childOnly", + router.get("/inherited/childOnly", new MockContext()).value()); + assertEquals( + "POST/inherited/childOnly", + router.post("/inherited/childOnly", new MockContext()).value()); + }); + } + + @Test + public void shouldCheckOverride() throws Exception { + new ProcessorRunner(new D3786()) + .withRouter( + app -> { + var router = new MockRouter(app); + assertEquals("base", router.get("/overrideMethod", new MockContext()).value()); + assertEquals( + "withPath", router.get("/overrideMethod/withPath", new MockContext()).value()); + assertEquals( + "base: 123", router.get("/overrideMethod/123", new MockContext()).value()); + assertEquals( + "/overrideMethod/childOnly", + router.get("/overrideMethod/childOnly", new MockContext()).value()); + assertEquals( + "/overrideMethod/user", + router.post("/overrideMethod/user", new MockContext()).value()); + }); + } +} From bbcaa13df36c90829891df61b5c006a701c670a1 Mon Sep 17 00:00:00 2001 From: Edgar Espina Date: Mon, 13 Oct 2025 21:05:26 -0300 Subject: [PATCH 4/7] v3.11.3 --- jooby/pom.xml | 2 +- modules/jooby-apt/pom.xml | 2 +- modules/jooby-avaje-inject/pom.xml | 2 +- modules/jooby-avaje-jsonb/pom.xml | 2 +- modules/jooby-avaje-validator/pom.xml | 2 +- modules/jooby-awssdk-v1/pom.xml | 2 +- modules/jooby-awssdk-v2/pom.xml | 2 +- modules/jooby-bom/pom.xml | 4 ++-- modules/jooby-caffeine/pom.xml | 2 +- modules/jooby-camel/pom.xml | 2 +- modules/jooby-cli/pom.xml | 2 +- modules/jooby-commons-email/pom.xml | 2 +- modules/jooby-conscrypt/pom.xml | 2 +- modules/jooby-db-scheduler/pom.xml | 2 +- modules/jooby-distribution/pom.xml | 2 +- modules/jooby-ebean/pom.xml | 2 +- modules/jooby-flyway/pom.xml | 2 +- modules/jooby-freemarker/pom.xml | 2 +- modules/jooby-gradle-setup/pom.xml | 2 +- modules/jooby-graphiql/pom.xml | 2 +- modules/jooby-graphql/pom.xml | 2 +- modules/jooby-gson/pom.xml | 2 +- modules/jooby-guice/pom.xml | 2 +- modules/jooby-handlebars/pom.xml | 2 +- modules/jooby-hibernate-validator/pom.xml | 2 +- modules/jooby-hibernate/pom.xml | 2 +- modules/jooby-hikari/pom.xml | 2 +- modules/jooby-jackson/pom.xml | 2 +- modules/jooby-jasypt/pom.xml | 2 +- modules/jooby-jdbi/pom.xml | 2 +- modules/jooby-jetty/pom.xml | 2 +- modules/jooby-jstachio/pom.xml | 2 +- modules/jooby-jte/pom.xml | 2 +- modules/jooby-jwt/pom.xml | 2 +- modules/jooby-kafka/pom.xml | 2 +- modules/jooby-kotlin/pom.xml | 2 +- modules/jooby-log4j/pom.xml | 2 +- modules/jooby-logback/pom.xml | 2 +- modules/jooby-maven-plugin/pom.xml | 2 +- modules/jooby-metrics/pom.xml | 2 +- modules/jooby-mutiny/pom.xml | 2 +- modules/jooby-netty/pom.xml | 2 +- modules/jooby-openapi/pom.xml | 2 +- modules/jooby-pac4j/pom.xml | 2 +- modules/jooby-pebble/pom.xml | 2 +- modules/jooby-quartz/pom.xml | 2 +- modules/jooby-reactor/pom.xml | 2 +- modules/jooby-redis/pom.xml | 2 +- modules/jooby-redoc/pom.xml | 2 +- modules/jooby-rocker/pom.xml | 2 +- modules/jooby-run/pom.xml | 2 +- modules/jooby-rxjava3/pom.xml | 2 +- modules/jooby-stork/pom.xml | 2 +- modules/jooby-swagger-ui/pom.xml | 2 +- modules/jooby-test/pom.xml | 2 +- modules/jooby-thymeleaf/pom.xml | 2 +- modules/jooby-undertow/pom.xml | 2 +- modules/jooby-whoops/pom.xml | 2 +- modules/jooby-yasson/pom.xml | 2 +- modules/pom.xml | 2 +- pom.xml | 4 ++-- tests/pom.xml | 2 +- 62 files changed, 64 insertions(+), 64 deletions(-) diff --git a/jooby/pom.xml b/jooby/pom.xml index 98bbfee975..6d7c9e856c 100644 --- a/jooby/pom.xml +++ b/jooby/pom.xml @@ -6,7 +6,7 @@ io.jooby jooby-project - 3.11.2 + 3.11.3 jooby jooby diff --git a/modules/jooby-apt/pom.xml b/modules/jooby-apt/pom.xml index 366e2a548e..10f49d9f6d 100644 --- a/modules/jooby-apt/pom.xml +++ b/modules/jooby-apt/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-apt jooby-apt diff --git a/modules/jooby-avaje-inject/pom.xml b/modules/jooby-avaje-inject/pom.xml index 536402517d..2e3afa07de 100644 --- a/modules/jooby-avaje-inject/pom.xml +++ b/modules/jooby-avaje-inject/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-avaje-inject jooby-avaje-inject diff --git a/modules/jooby-avaje-jsonb/pom.xml b/modules/jooby-avaje-jsonb/pom.xml index 219de1748f..d728de8dd9 100644 --- a/modules/jooby-avaje-jsonb/pom.xml +++ b/modules/jooby-avaje-jsonb/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-avaje-jsonb jooby-avaje-jsonb diff --git a/modules/jooby-avaje-validator/pom.xml b/modules/jooby-avaje-validator/pom.xml index 49b7d34bcf..082abd2626 100644 --- a/modules/jooby-avaje-validator/pom.xml +++ b/modules/jooby-avaje-validator/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-avaje-validator jooby-avaje-validator diff --git a/modules/jooby-awssdk-v1/pom.xml b/modules/jooby-awssdk-v1/pom.xml index b60b879edd..d3f38be396 100644 --- a/modules/jooby-awssdk-v1/pom.xml +++ b/modules/jooby-awssdk-v1/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-awssdk-v1 jooby-awssdk-v1 diff --git a/modules/jooby-awssdk-v2/pom.xml b/modules/jooby-awssdk-v2/pom.xml index e4f39312a0..a63752e558 100644 --- a/modules/jooby-awssdk-v2/pom.xml +++ b/modules/jooby-awssdk-v2/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-awssdk-v2 jooby-awssdk-v2 diff --git a/modules/jooby-bom/pom.xml b/modules/jooby-bom/pom.xml index b1333fbd7a..75b1b66505 100644 --- a/modules/jooby-bom/pom.xml +++ b/modules/jooby-bom/pom.xml @@ -7,14 +7,14 @@ io.jooby modules - 3.11.2 + 3.11.3 io.jooby jooby-bom jooby-bom pom - 3.11.2 + 3.11.3 Jooby (Bill of Materials) https://jooby.io diff --git a/modules/jooby-caffeine/pom.xml b/modules/jooby-caffeine/pom.xml index b18eebd077..93aa2b6a90 100644 --- a/modules/jooby-caffeine/pom.xml +++ b/modules/jooby-caffeine/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-caffeine jooby-caffeine diff --git a/modules/jooby-camel/pom.xml b/modules/jooby-camel/pom.xml index bd90d89055..0453b01138 100644 --- a/modules/jooby-camel/pom.xml +++ b/modules/jooby-camel/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-camel jooby-camel diff --git a/modules/jooby-cli/pom.xml b/modules/jooby-cli/pom.xml index ea7e602daf..48c6b6d221 100644 --- a/modules/jooby-cli/pom.xml +++ b/modules/jooby-cli/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-cli jooby-cli diff --git a/modules/jooby-commons-email/pom.xml b/modules/jooby-commons-email/pom.xml index 4316529343..5a8cca995d 100644 --- a/modules/jooby-commons-email/pom.xml +++ b/modules/jooby-commons-email/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-commons-email jooby-commons-email diff --git a/modules/jooby-conscrypt/pom.xml b/modules/jooby-conscrypt/pom.xml index bb8754bb48..3a79e857a3 100644 --- a/modules/jooby-conscrypt/pom.xml +++ b/modules/jooby-conscrypt/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-conscrypt jooby-conscrypt diff --git a/modules/jooby-db-scheduler/pom.xml b/modules/jooby-db-scheduler/pom.xml index ded8c698b3..8b4e6a9082 100644 --- a/modules/jooby-db-scheduler/pom.xml +++ b/modules/jooby-db-scheduler/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-db-scheduler jooby-db-scheduler diff --git a/modules/jooby-distribution/pom.xml b/modules/jooby-distribution/pom.xml index 46ae5da901..1e29b39db3 100644 --- a/modules/jooby-distribution/pom.xml +++ b/modules/jooby-distribution/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-distribution jooby-distribution diff --git a/modules/jooby-ebean/pom.xml b/modules/jooby-ebean/pom.xml index 0a91b5749d..f00335fada 100644 --- a/modules/jooby-ebean/pom.xml +++ b/modules/jooby-ebean/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-ebean jooby-ebean diff --git a/modules/jooby-flyway/pom.xml b/modules/jooby-flyway/pom.xml index 4eea25d85a..90b63507ef 100644 --- a/modules/jooby-flyway/pom.xml +++ b/modules/jooby-flyway/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-flyway jooby-flyway diff --git a/modules/jooby-freemarker/pom.xml b/modules/jooby-freemarker/pom.xml index c717122964..427f27b1e6 100644 --- a/modules/jooby-freemarker/pom.xml +++ b/modules/jooby-freemarker/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-freemarker jooby-freemarker diff --git a/modules/jooby-gradle-setup/pom.xml b/modules/jooby-gradle-setup/pom.xml index 342876d3b6..0b23f0d420 100644 --- a/modules/jooby-gradle-setup/pom.xml +++ b/modules/jooby-gradle-setup/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-gradle-setup jooby-gradle-setup diff --git a/modules/jooby-graphiql/pom.xml b/modules/jooby-graphiql/pom.xml index 7f0224b504..7313f8c177 100644 --- a/modules/jooby-graphiql/pom.xml +++ b/modules/jooby-graphiql/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-graphiql jooby-graphiql diff --git a/modules/jooby-graphql/pom.xml b/modules/jooby-graphql/pom.xml index dfb5e21c86..cb2706730e 100644 --- a/modules/jooby-graphql/pom.xml +++ b/modules/jooby-graphql/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-graphql jooby-graphql diff --git a/modules/jooby-gson/pom.xml b/modules/jooby-gson/pom.xml index 465c8c925b..302fd60860 100644 --- a/modules/jooby-gson/pom.xml +++ b/modules/jooby-gson/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-gson jooby-gson diff --git a/modules/jooby-guice/pom.xml b/modules/jooby-guice/pom.xml index 4531bf58bf..2092fcef09 100644 --- a/modules/jooby-guice/pom.xml +++ b/modules/jooby-guice/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-guice jooby-guice diff --git a/modules/jooby-handlebars/pom.xml b/modules/jooby-handlebars/pom.xml index 4ac0d23fed..53ce293c34 100644 --- a/modules/jooby-handlebars/pom.xml +++ b/modules/jooby-handlebars/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-handlebars jooby-handlebars diff --git a/modules/jooby-hibernate-validator/pom.xml b/modules/jooby-hibernate-validator/pom.xml index c80a0b96c6..ff4a912c62 100644 --- a/modules/jooby-hibernate-validator/pom.xml +++ b/modules/jooby-hibernate-validator/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-hibernate-validator jooby-hibernate-validator diff --git a/modules/jooby-hibernate/pom.xml b/modules/jooby-hibernate/pom.xml index e5321f3af3..f8afd16c80 100644 --- a/modules/jooby-hibernate/pom.xml +++ b/modules/jooby-hibernate/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-hibernate jooby-hibernate diff --git a/modules/jooby-hikari/pom.xml b/modules/jooby-hikari/pom.xml index 5db3cb1052..3b05fbdcdb 100644 --- a/modules/jooby-hikari/pom.xml +++ b/modules/jooby-hikari/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-hikari jooby-hikari diff --git a/modules/jooby-jackson/pom.xml b/modules/jooby-jackson/pom.xml index c0f4568858..e8448b6265 100644 --- a/modules/jooby-jackson/pom.xml +++ b/modules/jooby-jackson/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-jackson jooby-jackson diff --git a/modules/jooby-jasypt/pom.xml b/modules/jooby-jasypt/pom.xml index 1ccd2bc6f2..c02202fb74 100644 --- a/modules/jooby-jasypt/pom.xml +++ b/modules/jooby-jasypt/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-jasypt jooby-jasypt diff --git a/modules/jooby-jdbi/pom.xml b/modules/jooby-jdbi/pom.xml index e281659f4b..b9624a8457 100644 --- a/modules/jooby-jdbi/pom.xml +++ b/modules/jooby-jdbi/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-jdbi jooby-jdbi diff --git a/modules/jooby-jetty/pom.xml b/modules/jooby-jetty/pom.xml index e61c46dcfa..d0673d054f 100644 --- a/modules/jooby-jetty/pom.xml +++ b/modules/jooby-jetty/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-jetty jooby-jetty diff --git a/modules/jooby-jstachio/pom.xml b/modules/jooby-jstachio/pom.xml index 4f5484f744..67192f20cf 100644 --- a/modules/jooby-jstachio/pom.xml +++ b/modules/jooby-jstachio/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-jstachio jooby-jstachio diff --git a/modules/jooby-jte/pom.xml b/modules/jooby-jte/pom.xml index 412af4dac9..b9a7ce081f 100644 --- a/modules/jooby-jte/pom.xml +++ b/modules/jooby-jte/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-jte jooby-jte diff --git a/modules/jooby-jwt/pom.xml b/modules/jooby-jwt/pom.xml index 65002ec3c2..923a78e77e 100644 --- a/modules/jooby-jwt/pom.xml +++ b/modules/jooby-jwt/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-jwt jooby-jwt diff --git a/modules/jooby-kafka/pom.xml b/modules/jooby-kafka/pom.xml index 51e764d5dd..d98ceb0923 100644 --- a/modules/jooby-kafka/pom.xml +++ b/modules/jooby-kafka/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-kafka jooby-kafka diff --git a/modules/jooby-kotlin/pom.xml b/modules/jooby-kotlin/pom.xml index 5fe1d8427e..e7280af29d 100644 --- a/modules/jooby-kotlin/pom.xml +++ b/modules/jooby-kotlin/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-kotlin jooby-kotlin diff --git a/modules/jooby-log4j/pom.xml b/modules/jooby-log4j/pom.xml index 5059bf87c8..9d8cc0a747 100644 --- a/modules/jooby-log4j/pom.xml +++ b/modules/jooby-log4j/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-log4j jooby-log4j diff --git a/modules/jooby-logback/pom.xml b/modules/jooby-logback/pom.xml index 9aad7bfd9b..1ebea3ec96 100644 --- a/modules/jooby-logback/pom.xml +++ b/modules/jooby-logback/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-logback jooby-logback diff --git a/modules/jooby-maven-plugin/pom.xml b/modules/jooby-maven-plugin/pom.xml index 94c3cc8585..3931b58fd8 100644 --- a/modules/jooby-maven-plugin/pom.xml +++ b/modules/jooby-maven-plugin/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-maven-plugin jooby-maven-plugin diff --git a/modules/jooby-metrics/pom.xml b/modules/jooby-metrics/pom.xml index f02551ff1f..69b0b0cc40 100644 --- a/modules/jooby-metrics/pom.xml +++ b/modules/jooby-metrics/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-metrics jooby-metrics diff --git a/modules/jooby-mutiny/pom.xml b/modules/jooby-mutiny/pom.xml index 6f7d66622b..713840ed3b 100644 --- a/modules/jooby-mutiny/pom.xml +++ b/modules/jooby-mutiny/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-mutiny jooby-mutiny diff --git a/modules/jooby-netty/pom.xml b/modules/jooby-netty/pom.xml index ab91cb7d30..453747cca3 100644 --- a/modules/jooby-netty/pom.xml +++ b/modules/jooby-netty/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-netty jooby-netty diff --git a/modules/jooby-openapi/pom.xml b/modules/jooby-openapi/pom.xml index 2ab9202af3..6cd177515c 100644 --- a/modules/jooby-openapi/pom.xml +++ b/modules/jooby-openapi/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-openapi jooby-openapi diff --git a/modules/jooby-pac4j/pom.xml b/modules/jooby-pac4j/pom.xml index ca413cb0eb..4e9aa6396b 100644 --- a/modules/jooby-pac4j/pom.xml +++ b/modules/jooby-pac4j/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-pac4j jooby-pac4j diff --git a/modules/jooby-pebble/pom.xml b/modules/jooby-pebble/pom.xml index 000558afcc..6af4e2a13b 100644 --- a/modules/jooby-pebble/pom.xml +++ b/modules/jooby-pebble/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-pebble jooby-pebble diff --git a/modules/jooby-quartz/pom.xml b/modules/jooby-quartz/pom.xml index b08c2b06c5..dcb7ddf2ec 100644 --- a/modules/jooby-quartz/pom.xml +++ b/modules/jooby-quartz/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-quartz jooby-quartz diff --git a/modules/jooby-reactor/pom.xml b/modules/jooby-reactor/pom.xml index ab178864b9..b2d1ddf8e5 100644 --- a/modules/jooby-reactor/pom.xml +++ b/modules/jooby-reactor/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-reactor jooby-reactor diff --git a/modules/jooby-redis/pom.xml b/modules/jooby-redis/pom.xml index 486c758fd4..028168af77 100644 --- a/modules/jooby-redis/pom.xml +++ b/modules/jooby-redis/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-redis jooby-redis diff --git a/modules/jooby-redoc/pom.xml b/modules/jooby-redoc/pom.xml index 4cfe7e5176..d703607444 100644 --- a/modules/jooby-redoc/pom.xml +++ b/modules/jooby-redoc/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-redoc jooby-redoc diff --git a/modules/jooby-rocker/pom.xml b/modules/jooby-rocker/pom.xml index 65ea881b7a..91a99bbf54 100644 --- a/modules/jooby-rocker/pom.xml +++ b/modules/jooby-rocker/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-rocker jooby-rocker diff --git a/modules/jooby-run/pom.xml b/modules/jooby-run/pom.xml index 801f7996a8..f822be1a65 100644 --- a/modules/jooby-run/pom.xml +++ b/modules/jooby-run/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-run jooby-run diff --git a/modules/jooby-rxjava3/pom.xml b/modules/jooby-rxjava3/pom.xml index 494fe23b4b..4efb13bfed 100644 --- a/modules/jooby-rxjava3/pom.xml +++ b/modules/jooby-rxjava3/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-rxjava3 jooby-rxjava3 diff --git a/modules/jooby-stork/pom.xml b/modules/jooby-stork/pom.xml index 77385fd370..aecb5d83fa 100644 --- a/modules/jooby-stork/pom.xml +++ b/modules/jooby-stork/pom.xml @@ -4,7 +4,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-stork diff --git a/modules/jooby-swagger-ui/pom.xml b/modules/jooby-swagger-ui/pom.xml index 35bf77f63f..e7851afd2e 100644 --- a/modules/jooby-swagger-ui/pom.xml +++ b/modules/jooby-swagger-ui/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-swagger-ui jooby-swagger-ui diff --git a/modules/jooby-test/pom.xml b/modules/jooby-test/pom.xml index ba1dab2b56..fa070f19a7 100644 --- a/modules/jooby-test/pom.xml +++ b/modules/jooby-test/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-test jooby-test diff --git a/modules/jooby-thymeleaf/pom.xml b/modules/jooby-thymeleaf/pom.xml index 43770dd2b7..4abdea010e 100644 --- a/modules/jooby-thymeleaf/pom.xml +++ b/modules/jooby-thymeleaf/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-thymeleaf jooby-thymeleaf diff --git a/modules/jooby-undertow/pom.xml b/modules/jooby-undertow/pom.xml index 902037bdc0..9971abe61b 100644 --- a/modules/jooby-undertow/pom.xml +++ b/modules/jooby-undertow/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-undertow jooby-undertow diff --git a/modules/jooby-whoops/pom.xml b/modules/jooby-whoops/pom.xml index faf1c21c37..a4f8ff7e9c 100644 --- a/modules/jooby-whoops/pom.xml +++ b/modules/jooby-whoops/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-whoops jooby-whoops diff --git a/modules/jooby-yasson/pom.xml b/modules/jooby-yasson/pom.xml index d081993d08..c583aa5a1c 100644 --- a/modules/jooby-yasson/pom.xml +++ b/modules/jooby-yasson/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.11.2 + 3.11.3 jooby-yasson jooby-yasson diff --git a/modules/pom.xml b/modules/pom.xml index 054e68b2fd..274f9c3c1f 100644 --- a/modules/pom.xml +++ b/modules/pom.xml @@ -4,7 +4,7 @@ io.jooby jooby-project - 3.11.2 + 3.11.3 modules diff --git a/pom.xml b/pom.xml index df343c6392..caf4f779a2 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 io.jooby jooby-project - 3.11.2 + 3.11.3 pom jooby-project @@ -207,7 +207,7 @@ false yyyy-MM-dd HH:mm:ssa - 2025-08-07T18:05:55Z + 2025-10-14T00:05:09Z UTF-8 etc${file.separator}source${file.separator}formatter.sh diff --git a/tests/pom.xml b/tests/pom.xml index e399f90d8d..189835603c 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -6,7 +6,7 @@ io.jooby jooby-project - 3.11.2 + 3.11.3 tests tests From b6aed0b2d6b8fbdb51fd3242b931f151d054d11a Mon Sep 17 00:00:00 2001 From: Edgar Espina Date: Mon, 13 Oct 2025 21:09:08 -0300 Subject: [PATCH 5/7] build: fix jetty upgrade --- tests/src/test/java/io/jooby/test/Http2Test.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/test/java/io/jooby/test/Http2Test.java b/tests/src/test/java/io/jooby/test/Http2Test.java index 3e7293aac1..851f8ea960 100644 --- a/tests/src/test/java/io/jooby/test/Http2Test.java +++ b/tests/src/test/java/io/jooby/test/Http2Test.java @@ -131,7 +131,7 @@ public void onAccept(Session session) {} Response.Builder builder = new Response.Builder(); session.newStream( frame, - new Promise.Adapter<>(), + Promise.noop(), new Stream.Listener() { @Override public void onHeaders(final Stream stream, final HeadersFrame frame) { From e7cf67dff40f35127ec4be56a6e0dafc2ed6ed11 Mon Sep 17 00:00:00 2001 From: Edgar Espina Date: Tue, 14 Oct 2025 12:05:37 -0300 Subject: [PATCH 6/7] undertow: remove maxsize, handled manually --- .../src/main/java/io/jooby/undertow/UndertowServer.java | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/jooby-undertow/src/main/java/io/jooby/undertow/UndertowServer.java b/modules/jooby-undertow/src/main/java/io/jooby/undertow/UndertowServer.java index 7a214dea81..649cc9d413 100644 --- a/modules/jooby-undertow/src/main/java/io/jooby/undertow/UndertowServer.java +++ b/modules/jooby-undertow/src/main/java/io/jooby/undertow/UndertowServer.java @@ -124,7 +124,6 @@ public Server start(@NonNull Jooby... application) { .setServerOption(UndertowOptions.ALWAYS_SET_KEEP_ALIVE, false) .setServerOption(UndertowOptions.MAX_HEADER_SIZE, options.getMaxHeaderSize()) .setServerOption(UndertowOptions.MAX_PARAMETERS, options.getMaxFormFields()) - .setServerOption(UndertowOptions.MAX_ENTITY_SIZE, (long) options.getMaxRequestSize()) .setServerOption(UndertowOptions.ALLOW_EQUALS_IN_COOKIE_VALUE, true) .setServerOption(UndertowOptions.ALWAYS_SET_DATE, options.getDefaultHeaders()) .setServerOption(UndertowOptions.RECORD_REQUEST_START_TIME, false) From 002eb06086145333b35c14643ca17d0ec1943900 Mon Sep 17 00:00:00 2001 From: Edgar Espina Date: Tue, 14 Oct 2025 12:15:03 -0300 Subject: [PATCH 7/7] Route inheritance from base controller no longer works when multiple subclasses extend it (Jooby 4.x vs 1.x) for 3.x --- .../java/io/jooby/apt/JoobyProcessor.java | 77 +++++++-------- .../java/io/jooby/apt/ProcessorRunner.java | 97 +++++++++++++------ .../src/test/java/tests/i3786/Issue3786.java | 17 ++-- 3 files changed, 111 insertions(+), 80 deletions(-) diff --git a/modules/jooby-apt/src/main/java/io/jooby/apt/JoobyProcessor.java b/modules/jooby-apt/src/main/java/io/jooby/apt/JoobyProcessor.java index a59f968558..a209fff3d8 100644 --- a/modules/jooby-apt/src/main/java/io/jooby/apt/JoobyProcessor.java +++ b/modules/jooby-apt/src/main/java/io/jooby/apt/JoobyProcessor.java @@ -113,7 +113,8 @@ public boolean process(Set annotations, RoundEnvironment context.add(router); var sourceCode = router.toSourceCode(null); var sourceLocation = router.getGeneratedFilename(); - onGeneratedSource(toJavaFileObject(sourceLocation, sourceCode)); + onGeneratedSource( + router.getGeneratedType(), toJavaFileObject(sourceLocation, sourceCode)); context.debug("router %s: %s", router.getTargetType(), router.getGeneratedType()); router.getRoutes().forEach(it -> context.debug(" %s", it)); writeSource(router, sourceLocation, sourceCode); @@ -182,7 +183,7 @@ public String toString() { }; } - protected void onGeneratedSource(JavaFileObject source) {} + protected void onGeneratedSource(String className, JavaFileObject source) {} private void doServices(Filer filer, List routers) { try { @@ -261,44 +262,40 @@ private Map buildRouteRegistry( */ private void buildRouteRegistry(Map registry, TypeElement currentType) { for (TypeElement superType : context.superTypes(currentType)) { - if (processed.add(superType)) { - // collect all declared methods - superType.getEnclosedElements().stream() - .filter(ExecutableElement.class::isInstance) - .map(ExecutableElement.class::cast) - .forEach( - method -> { - if (method.getModifiers().contains(Modifier.ABSTRACT)) { - context.debug("ignoring abstract method: %s %s", superType, method); - } else { - method.getAnnotationMirrors().stream() - .map(AnnotationMirror::getAnnotationType) - .map(DeclaredType::asElement) - .filter(TypeElement.class::isInstance) - .map(TypeElement.class::cast) - .filter(HttpMethod::hasAnnotation) - .forEach( - annotation -> { - Stream.of(currentType, superType) - .distinct() - .forEach( - routerClass -> - registry - .computeIfAbsent( - routerClass, type -> new MvcRouter(context, type)) - .put(annotation, method)); - }); - } - }); - } else { - if (!currentType.equals(superType)) { - // edge-case #1: when controller has no method and extends another class which has. - // edge-case #2: some odd usage a controller could be empty. - // See https://github.com/jooby-project/jooby/issues/3656 - if (registry.containsKey(superType)) { - registry.computeIfAbsent( - currentType, key -> new MvcRouter(key, registry.get(superType))); - } + // collect all declared methods + superType.getEnclosedElements().stream() + .filter(ExecutableElement.class::isInstance) + .map(ExecutableElement.class::cast) + .forEach( + method -> { + if (method.getModifiers().contains(Modifier.ABSTRACT)) { + context.debug("ignoring abstract method: %s %s", superType, method); + } else { + method.getAnnotationMirrors().stream() + .map(AnnotationMirror::getAnnotationType) + .map(DeclaredType::asElement) + .filter(TypeElement.class::isInstance) + .map(TypeElement.class::cast) + .filter(HttpMethod::hasAnnotation) + .forEach( + annotation -> { + Stream.of(currentType, superType) + .distinct() + .forEach( + routerClass -> + registry + .computeIfAbsent( + routerClass, type -> new MvcRouter(context, type)) + .put(annotation, method)); + }); + } + }); + if (!currentType.equals(superType)) { + // edge-case #1: when controller has no method and extends another class which has. + // edge-case #2: some odd usage a controller could be empty. + // See https://github.com/jooby-project/jooby/issues/3656 + if (registry.containsKey(superType)) { + registry.computeIfAbsent(currentType, key -> new MvcRouter(key, registry.get(superType))); } } } diff --git a/modules/jooby-apt/src/test/java/io/jooby/apt/ProcessorRunner.java b/modules/jooby-apt/src/test/java/io/jooby/apt/ProcessorRunner.java index 1cf3617846..ab3c3005ef 100644 --- a/modules/jooby-apt/src/test/java/io/jooby/apt/ProcessorRunner.java +++ b/modules/jooby-apt/src/test/java/io/jooby/apt/ProcessorRunner.java @@ -27,21 +27,18 @@ public class ProcessorRunner { private static class GeneratedSourceClassLoader extends ClassLoader { - private final JavaFileObject classFile; - private final String className; + private final Map classes = new LinkedHashMap<>(); - public GeneratedSourceClassLoader(ClassLoader parent, JavaFileObject source) { + public GeneratedSourceClassLoader(ClassLoader parent, Map sources) { super(parent); - this.classFile = javac().compile(List.of(source)).generatedFiles().get(0); - this.className = source.getName().replace('/', '.').replace(".java", ""); - } - - public String getClassName() { - return className; + for (var e : sources.entrySet()) { + classes.put(e.getKey(), javac().compile(List.of(e.getValue())).generatedFiles().get(0)); + } } protected Class findClass(String name) throws ClassNotFoundException { - if (name.equals(className)) { + if (classes.containsKey(name)) { + var classFile = classes.get(name); try (var in = classFile.openInputStream()) { var bytes = in.readAllBytes(); return defineClass(name, bytes, 0, bytes.length); @@ -54,24 +51,23 @@ protected Class findClass(String name) throws ClassNotFoundException { } private static class HookJoobyProcessor extends JoobyProcessor { - private JavaFileObject source; - private String kotlinSource; + private Map javaFiles = new LinkedHashMap<>(); + private Map kotlinFiles = new LinkedHashMap<>(); public HookJoobyProcessor(Consumer console) { super((kind, message) -> console.accept(message)); } public GeneratedSourceClassLoader createClassLoader() { - Objects.requireNonNull(source); - return new GeneratedSourceClassLoader(getClass().getClassLoader(), source); + return new GeneratedSourceClassLoader(getClass().getClassLoader(), javaFiles); } public JavaFileObject getSource() { - return source; + return javaFiles.isEmpty() ? null : javaFiles.entrySet().iterator().next().getValue(); } public String getKotlinSource() { - return kotlinSource; + return kotlinFiles.entrySet().iterator().next().getValue(); } public MvcContext getContext() { @@ -79,23 +75,28 @@ public MvcContext getContext() { } @Override - protected void onGeneratedSource(JavaFileObject source) { - this.source = source; + protected void onGeneratedSource(String classname, JavaFileObject source) { + javaFiles.put(classname, source); try { // Generate kotlin source code inside the compiler scope... avoid false positive errors - this.kotlinSource = context.getRouters().get(0).toSourceCode(true); + kotlinFiles.put(classname, context.getRouters().get(0).toSourceCode(true)); } catch (IOException e) { SneakyThrows.propagate(e); } } } + private final List instances; private final HookJoobyProcessor processor; public ProcessorRunner(Object instance) throws IOException { this(instance, Map.of()); } + public ProcessorRunner(List instances) throws IOException { + this(instances, System.out::println, Map.of()); + } + public ProcessorRunner(Object instance, Consumer stdout) throws IOException { this(instance, stdout, Map.of()); } @@ -106,12 +107,19 @@ public ProcessorRunner(Object instance, Map options) throws IOEx public ProcessorRunner(Object instance, Consumer stdout, Map options) throws IOException { - this.processor = new HookJoobyProcessor(stdout::accept); + this(List.of(instance), stdout, options); + } + + public ProcessorRunner( + List instances, Consumer stdout, Map options) + throws IOException { + this.instances = instances; + this.processor = new HookJoobyProcessor(stdout); var optionsArray = options.entrySet().stream().map(e -> "-A" + e.getKey() + "=" + e.getValue()).toList(); Truth.assert_() .about(JavaSourcesSubjectFactory.javaSources()) - .that(sources(sourceNames(instance.getClass()))) + .that(sources(sourceNames(instances.stream().map(Object::getClass).toList()))) .withCompilerOptions(optionsArray.toArray(new String[0])) .processedWith(processor) .compilesWithoutError(); @@ -123,11 +131,33 @@ public ProcessorRunner withRouter(SneakyThrows.Consumer consumer) throws public ProcessorRunner withRouter(SneakyThrows.Consumer2 consumer) throws Exception { + return withRouter(instances.get(0).getClass(), consumer); + } + + public ProcessorRunner withRouter(Class routerType, SneakyThrows.Consumer consumer) + throws Exception { + return withRouter(routerType, (app, source) -> consumer.accept(app)); + } + + public ProcessorRunner withRouter( + Class routerType, SneakyThrows.Consumer2 consumer) + throws Exception { var classLoader = processor.createClassLoader(); - var factoryName = classLoader.getClassName(); - var factoryClass = (Class) classLoader.loadClass(factoryName); - var constructor = factoryClass.getDeclaredConstructor(); - var extension = constructor.newInstance(); + var factoryName = routerType.getName() + "_"; + var factoryClass = (Class) classLoader.loadClass(factoryName); + Extension extension; + try { + var constructor = factoryClass.getDeclaredConstructor(); + extension = constructor.newInstance(); + } catch (NoSuchMethodException x) { + var instance = + instances.stream() + .filter(it -> it.getClass().equals(routerType)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("Not found: " + routerType)); + extension = factoryClass.getDeclaredConstructor(routerType).newInstance(instance); + } + var application = new Jooby(); application.install(extension); consumer.accept(application, processor.getSource()); @@ -146,17 +176,22 @@ public ProcessorRunner withSourceCode(SneakyThrows.Consumer consumer) { public ProcessorRunner withSourceCode(boolean kt, SneakyThrows.Consumer consumer) { consumer.accept( kt - ? processor.kotlinSource + ? processor.kotlinFiles.values().iterator().next() : Optional.ofNullable(processor.getSource()).map(Objects::toString).orElse(null)); return this; } - private String[] sourceNames(Class input) { + private String[] sourceNames(List> inputs) { List result = new ArrayList<>(); - while (input != Object.class) { - result.add(input.getName()); - input = input.getSuperclass(); - } + Set visited = new HashSet<>(); + inputs.stream() + .forEach( + input -> { + while (input != Object.class && visited.add(input)) { + result.add(input.getName()); + input = input.getSuperclass(); + } + }); return result.toArray(new String[0]); } diff --git a/modules/jooby-apt/src/test/java/tests/i3786/Issue3786.java b/modules/jooby-apt/src/test/java/tests/i3786/Issue3786.java index 8c0b6b3e7a..beaa56eaf0 100644 --- a/modules/jooby-apt/src/test/java/tests/i3786/Issue3786.java +++ b/modules/jooby-apt/src/test/java/tests/i3786/Issue3786.java @@ -7,6 +7,8 @@ import static org.junit.jupiter.api.Assertions.*; +import java.util.List; + import org.junit.jupiter.api.Test; import io.jooby.apt.ProcessorRunner; @@ -16,9 +18,10 @@ public class Issue3786 { @Test public void shouldCheckBase() throws Exception { - new ProcessorRunner(new C3786()) + new ProcessorRunner(List.of(new C3786(), new D3786())) .withRouter( - app -> { + C3786.class, + (app, source) -> { var router = new MockRouter(app); assertEquals("base", router.get("/inherited", new MockContext()).value()); assertEquals( @@ -30,14 +33,10 @@ public void shouldCheckBase() throws Exception { assertEquals( "POST/inherited/childOnly", router.post("/inherited/childOnly", new MockContext()).value()); - }); - } - - @Test - public void shouldCheckOverride() throws Exception { - new ProcessorRunner(new D3786()) + }) .withRouter( - app -> { + D3786.class, + (app, source) -> { var router = new MockRouter(app); assertEquals("base", router.get("/overrideMethod", new MockContext()).value()); assertEquals(