diff --git a/docs/asciidoc/static-files.adoc b/docs/asciidoc/static-files.adoc index 35ee43d374..4d4c60a469 100644 --- a/docs/asciidoc/static-files.adoc +++ b/docs/asciidoc/static-files.adoc @@ -134,11 +134,9 @@ control these headers programmatically: [source, java, role="primary"] ---- { - AssetSource www = AssetSource.create(Paths.get("www")); - assets("/static/*", new AssetHandler(www) + assets("/static/*", Paths.get("www")) .setLastModified(false) - .setEtag(false) - ); + .setEtag(false); } ---- @@ -146,11 +144,9 @@ control these headers programmatically: [source, kotlin, role="secondary"] ---- { - val www = AssetSource.create(Paths.get("www")) - assets("/static/*", AssetHandler(www) + assets("/static/*", Paths.get("www")) .setLastModified(false) .setEtag(false) - ); } ---- @@ -160,10 +156,8 @@ The `maxAge` option set a `Cache-Control` header: [source, java, role="primary"] ---- { - AssetSource www = AssetSource.create(Paths.get("www")); - assets("/static/*", new AssetHandler(www) + assets("/static/*", Paths.get("www")) .setMaxAge(Duration.ofDays(365)) - ); } ---- @@ -171,10 +165,8 @@ The `maxAge` option set a `Cache-Control` header: [source, kotlin, role="secondary"] ---- { - val www = AssetSource.create(Paths.get("www")) - assets("/static/*", AssetHandler(www) + assets("/static/*", Paths.get("www")) .setMaxAge(Duration.ofDays(365)) - ); } ---- @@ -188,8 +180,7 @@ specify a function via javadoc:AssetHandler[cacheControl, java.util.Function]: [source, java, role="primary"] ---- { - AssetSource www = AssetSource.create(Paths.get("www")); - assets("/static/*", new AssetHandler(www) + assets("/static/*", Paths.get("www")) .cacheControl(path -> { if (path.endsWith("dont-cache-me.html")) { return CacheControl.noCache(); // disable caching @@ -200,7 +191,7 @@ specify a function via javadoc:AssetHandler[cacheControl, java.util.Function]: } else { return CacheControl.defaults(); // AssetHandler defaults } - })); + }); } ---- @@ -208,8 +199,7 @@ specify a function via javadoc:AssetHandler[cacheControl, java.util.Function]: [source, kotlin, role="secondary"] ---- { - val www = AssetSource.create(Paths.get("www")) - assets("/static/*", AssetHandler(www) + assets("/static/*", Paths.get("www")) .cacheControl { when { it.endsWith("dont-cache-me.html") -> CacheControl.noCache() // disable caching @@ -218,6 +208,39 @@ specify a function via javadoc:AssetHandler[cacheControl, java.util.Function]: .setMaxAge(Duration.ofDays(365)) else -> CacheControl.defaults() // AssetHandler defaults } - }) + } +} +---- + +The asset handler generates a `404` response code when requested path is not found. You can change this by throwing +an exception or generating any other content you want: + + +.Custom not found: +[source, java, role="primary"] +---- +{ + assets("/static/*", Paths.get("www")) + .notFound(ctx -> { + throw new MyAssetException(); + }); + + error(MyAssetException.class, (ctx, cause, code) -> { + // render MyAssetException as you want + }); } ---- + +.Kotlin +[source, kotlin, role="secondary"] +---- +{ + assets("/static/*", Paths.get("www")) + .notFound { _ -> + throw MyAssetException() + } + error(MyAssetException::class) { + // render MyAssetException as you want + } +} +---- \ No newline at end of file diff --git a/jooby/pom.xml b/jooby/pom.xml index c8c464b4d7..682fe2816a 100644 --- a/jooby/pom.xml +++ b/jooby/pom.xml @@ -6,7 +6,7 @@ io.jooby jooby-project - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 @@ -49,6 +49,12 @@ jakarta.inject-api + + + jakarta.validation + jakarta.validation-api + + com.typesafe diff --git a/jooby/src/main/java/io/jooby/DefaultContext.java b/jooby/src/main/java/io/jooby/DefaultContext.java index 21302366ff..20c35289ad 100644 --- a/jooby/src/main/java/io/jooby/DefaultContext.java +++ b/jooby/src/main/java/io/jooby/DefaultContext.java @@ -20,13 +20,12 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.time.Instant; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; +import java.util.*; import java.util.stream.Collectors; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import jakarta.validation.Validator; import org.slf4j.Logger; import edu.umd.cs.findbugs.annotations.NonNull; @@ -417,7 +416,20 @@ default boolean isSecure() { T result = ValueConverters.convert(body(), type, getRouter()); return result; } - return (T) decoder(contentType).decode(this, type); + T object = (T) decoder(contentType).decode(this, type); + + MessageValidator messageValidator = getRouter().getMessageValidator(); + if (messageValidator != null) { + if (messageValidator.predicate().test(type)) { + Validator validator = messageValidator.validator(); + Set> violations = validator.validate(object); + if (!violations.isEmpty()) { + throw new ConstraintViolationException(violations); + } + } + } + return object; + } catch (Exception x) { throw SneakyThrows.propagate(x); } diff --git a/jooby/src/main/java/io/jooby/Jooby.java b/jooby/src/main/java/io/jooby/Jooby.java index 12d3d0d985..43aa2f0285 100644 --- a/jooby/src/main/java/io/jooby/Jooby.java +++ b/jooby/src/main/java/io/jooby/Jooby.java @@ -12,6 +12,7 @@ import java.io.IOException; import java.lang.reflect.Constructor; +import java.lang.reflect.Type; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -41,6 +42,7 @@ import java.util.function.Supplier; import java.util.stream.Collectors; +import jakarta.validation.Validator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -627,6 +629,19 @@ public Jooby decoder(@NonNull MediaType contentType, @NonNull MessageDecoder dec return this; } + @NonNull + @Override + public Router messageValidator(@NonNull Validator validator, @NonNull Predicate predicate) { + router.messageValidator(validator, predicate); + return this; + } + + @Nullable + @Override + public MessageValidator getMessageValidator() { + return router.getMessageValidator(); + } + @NonNull @Override public Jooby encoder(@NonNull MediaType contentType, @NonNull MessageEncoder encoder) { router.encoder(contentType, encoder); diff --git a/jooby/src/main/java/io/jooby/MessageValidator.java b/jooby/src/main/java/io/jooby/MessageValidator.java new file mode 100644 index 0000000000..f4dcb0f420 --- /dev/null +++ b/jooby/src/main/java/io/jooby/MessageValidator.java @@ -0,0 +1,9 @@ +package io.jooby; + +import jakarta.validation.Validator; + +import java.lang.reflect.Type; +import java.util.function.Predicate; + +public record MessageValidator(Validator validator, Predicate predicate) { +} diff --git a/jooby/src/main/java/io/jooby/Router.java b/jooby/src/main/java/io/jooby/Router.java index 1ed9754edf..7073d82de8 100644 --- a/jooby/src/main/java/io/jooby/Router.java +++ b/jooby/src/main/java/io/jooby/Router.java @@ -8,6 +8,7 @@ import static java.util.Collections.unmodifiableList; import static java.util.Objects.requireNonNull; +import java.lang.reflect.Type; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -28,6 +29,7 @@ import java.util.stream.IntStream; import java.util.stream.Stream; +import jakarta.validation.Validator; import org.slf4j.Logger; import com.typesafe.config.Config; @@ -508,6 +510,8 @@ default Object execute(@NonNull Context context) { */ @NonNull Router decoder(@NonNull MediaType contentType, @NonNull MessageDecoder decoder); + @NonNull Router messageValidator(@NonNull Validator validator, @NonNull Predicate predicate); + @Nullable MessageValidator getMessageValidator(); /** * Returns the worker thread pool. This thread pool is used to run application blocking code. * @@ -708,7 +712,7 @@ default Object execute(@NonNull Context context) { * @param source File system directory. * @return A route. */ - default @NonNull Route assets(@NonNull String pattern, @NonNull Path source) { + default @NonNull AssetHandler assets(@NonNull String pattern, @NonNull Path source) { return assets(pattern, AssetSource.create(source)); } @@ -722,9 +726,9 @@ default Object execute(@NonNull Context context) { * * @param pattern Path pattern. * @param source File-System folder when exists, or fallback to a classpath folder. - * @return A route. + * @return AssetHandler. */ - default @NonNull Route assets(@NonNull String pattern, @NonNull String source) { + default @NonNull AssetHandler assets(@NonNull String pattern, @NonNull String source) { Path path = Stream.of(source.split("/")) .reduce(Paths.get(System.getProperty("user.dir")), Path::resolve, Path::resolve); @@ -742,7 +746,7 @@ default Object execute(@NonNull Context context) { * @param sources additional Asset sources. * @return A route. */ - default @NonNull Route assets( + default @NonNull AssetHandler assets( @NonNull String pattern, @NonNull AssetSource source, @NonNull AssetSource... sources) { AssetSource[] allSources; if (sources.length == 0) { @@ -762,8 +766,9 @@ default Object execute(@NonNull Context context) { * @param handler Asset handler. * @return A route. */ - default @NonNull Route assets(@NonNull String pattern, @NonNull AssetHandler handler) { - return route(GET, pattern, handler); + default @NonNull AssetHandler assets(@NonNull String pattern, @NonNull AssetHandler handler) { + route(GET, pattern, handler); + return handler; } /** diff --git a/jooby/src/main/java/io/jooby/handler/AssetHandler.java b/jooby/src/main/java/io/jooby/handler/AssetHandler.java index 704cad16df..f7f13a960f 100644 --- a/jooby/src/main/java/io/jooby/handler/AssetHandler.java +++ b/jooby/src/main/java/io/jooby/handler/AssetHandler.java @@ -14,10 +14,8 @@ import java.util.function.Function; import edu.umd.cs.findbugs.annotations.NonNull; -import io.jooby.Context; -import io.jooby.MediaType; -import io.jooby.Route; -import io.jooby.StatusCode; +import edu.umd.cs.findbugs.annotations.Nullable; +import io.jooby.*; /** * Handler for static resources represented by the {@link Asset} contract. @@ -28,6 +26,8 @@ * @since 2.0.0 */ public class AssetHandler implements Route.Handler { + private static final SneakyThrows.Consumer NOT_FOUND = + ctx -> ctx.send(StatusCode.NOT_FOUND); private static final int ONE_SEC = 1000; private final AssetSource[] sources; @@ -41,6 +41,7 @@ public class AssetHandler implements Route.Handler { private Function cacheControl = path -> defaults; private Function mediaTypeResolver = Asset::getContentType; + private SneakyThrows.Consumer notFound = NOT_FOUND; /** * Creates a new asset handler that fallback to the given fallback asset when the asset is not @@ -82,7 +83,7 @@ public Object apply(@NonNull Context ctx) throws Exception { } // Still null? if (asset == null) { - ctx.send(StatusCode.NOT_FOUND); + notFound.accept(ctx); return ctx; } else { resolvedPath = fallback; @@ -230,7 +231,19 @@ public AssetHandler cacheControl(@NonNull Function cacheCo return this; } - private Asset resolve(String filepath) { + /** + * Sets a custom handler for 404 asset/resource. By default, generates a 404 + * status code response. + * + * @param handler Handler. + * @return This handler. + */ + public AssetHandler notFound(@NonNull SneakyThrows.Consumer handler) { + this.notFound = handler; + return this; + } + + private @Nullable Asset resolve(String filepath) { for (AssetSource source : sources) { Asset asset = source.resolve(filepath); if (asset != null) { @@ -243,7 +256,7 @@ private Asset resolve(String filepath) { @Override public void setRoute(Route route) { List keys = route.getPathKeys(); - this.filekey = keys.size() == 0 ? route.getPattern().substring(1) : keys.get(0); + this.filekey = keys.isEmpty() ? route.getPattern().substring(1) : keys.get(0); // NOTE: It send an inputstream we don't need a renderer route.setReturnType(Context.class); } diff --git a/jooby/src/main/java/io/jooby/internal/RouterImpl.java b/jooby/src/main/java/io/jooby/internal/RouterImpl.java index 4c7819e191..33f7f6689b 100644 --- a/jooby/src/main/java/io/jooby/internal/RouterImpl.java +++ b/jooby/src/main/java/io/jooby/internal/RouterImpl.java @@ -8,6 +8,7 @@ import static java.util.Objects.requireNonNull; import java.io.FileNotFoundException; +import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; import java.nio.file.NoSuchFileException; import java.nio.file.Path; @@ -35,6 +36,7 @@ import java.util.stream.IntStream; import java.util.stream.Stream; +import jakarta.validation.Validator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -147,6 +149,8 @@ public Stack executor(Executor executor) { private Map decoders = new HashMap<>(); + private MessageValidator messageValidator = null; + private Map attributes = new ConcurrentHashMap<>(); private ServiceRegistry services = new ServiceRegistryImpl(); @@ -368,6 +372,18 @@ public Router decoder(@NonNull MediaType contentType, @NonNull MessageDecoder de return this; } + @NonNull + @Override + public Router messageValidator(@NonNull Validator validator, @NonNull Predicate predicate) { + this.messageValidator = new MessageValidator(validator, predicate); + return this; + } + + @Override + public MessageValidator getMessageValidator() { + return messageValidator; + } + @NonNull @Override public Executor getWorker() { return worker; @@ -911,7 +927,8 @@ private void copy(Route src, Route it) { it.setExecutorKey(src.getExecutorKey()); it.setTags(src.getTags()); it.setDescription(src.getDescription()); - it.setDecoders(src.getDecoders()); + // DO NOT COPY: See https://github.com/jooby-project/jooby/issues/3500 + // it.setDecoders(src.getDecoders()); it.setMvcMethod(src.getMvcMethod()); it.setNonBlocking(src.isNonBlocking()); it.setSummary(src.getSummary()); @@ -983,7 +1000,7 @@ private static void override( Jooby app = (Jooby) router; override(src, app.getRouter(), consumer); } else if (router instanceof RouterImpl that) { - consumer.accept((RouterImpl) src, that); + consumer.accept(src, that); } } diff --git a/jooby/src/main/java/module-info.java b/jooby/src/main/java/module-info.java index cedb71a4ac..3d3521cb4f 100644 --- a/jooby/src/main/java/module-info.java +++ b/jooby/src/main/java/module-info.java @@ -22,6 +22,7 @@ * True core deps */ requires jakarta.inject; + requires jakarta.validation; requires org.slf4j; requires static com.github.spotbugs.annotations; requires typesafe.config; diff --git a/modules/jooby-apt/pom.xml b/modules/jooby-apt/pom.xml index aedb169917..20f4d4c121 100644 --- a/modules/jooby-apt/pom.xml +++ b/modules/jooby-apt/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 @@ -19,6 +19,12 @@ + + io.jooby + jooby-validation + ${jooby.version} + + io.jooby jooby 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 d37fe69c6d..b3396b3ca6 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 @@ -15,7 +15,7 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.util.*; -import java.util.function.Consumer; +import java.util.function.BiConsumer; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -71,10 +71,10 @@ static String string(ProcessingEnvironment environment, String option, String de } protected MvcContext context; - private Consumer output; + private BiConsumer output; private final Set processed = new HashSet<>(); - public JoobyProcessor(Consumer output) { + public JoobyProcessor(BiConsumer output) { this.output = output; } @@ -88,38 +88,42 @@ public synchronized void init(ProcessingEnvironment processingEnvironment) { ofNullable(output) .orElseGet( () -> - message -> - processingEnvironment - .getMessager() - .printMessage(Diagnostic.Kind.OTHER, message))); + (kind, message) -> + processingEnvironment.getMessager().printMessage(kind, message))); super.init(processingEnvironment); } @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { - if (roundEnv.processingOver()) { - context.debug("Output:"); - context.getRouters().forEach(it -> context.debug(" %s.java", it.getGeneratedType())); - if (context.generateServices()) { - doServices(context.getProcessingEnvironment().getFiler(), context.getRouters()); - } - return false; - } else { - var routeMap = buildRouteRegistry(annotations, roundEnv); - for (var router : routeMap.values()) { - try { - var sourceCode = router.toSourceCode(null); - var sourceLocation = router.getGeneratedFilename(); - onGeneratedSource(toJavaFileObject(sourceLocation, sourceCode)); - context.debug("router %s: %s", router.getTargetType(), router.getGeneratedType()); - router.getRoutes().forEach(it -> context.debug(" %s", it)); - writeSource(router, sourceLocation, sourceCode); - context.add(router); - } catch (IOException cause) { - throw new RuntimeException("Unable to generate: " + router.getTargetType(), cause); + try { + if (roundEnv.processingOver()) { + context.debug("Output:"); + context.getRouters().forEach(it -> context.debug(" %s.java", it.getGeneratedType())); + if (context.generateServices()) { + doServices(context.getProcessingEnvironment().getFiler(), context.getRouters()); + } + return false; + } else { + var routeMap = buildRouteRegistry(annotations, roundEnv); + for (var router : routeMap.values()) { + try { + var sourceCode = router.toSourceCode(null); + var sourceLocation = router.getGeneratedFilename(); + onGeneratedSource(toJavaFileObject(sourceLocation, sourceCode)); + context.debug("router %s: %s", router.getTargetType(), router.getGeneratedType()); + router.getRoutes().forEach(it -> context.debug(" %s", it)); + writeSource(router, sourceLocation, sourceCode); + context.add(router); + } catch (IOException cause) { + throw new RuntimeException("Unable to generate: " + router.getTargetType(), cause); + } } + return true; } - return true; + } catch (Exception cause) { + context.error( + Optional.ofNullable(cause.getMessage()).orElse("Unable to generate routes"), cause); + throw sneakyThrow0(cause); } } diff --git a/modules/jooby-apt/src/main/java/io/jooby/internal/apt/AnnotationSupport.java b/modules/jooby-apt/src/main/java/io/jooby/internal/apt/AnnotationSupport.java index 0b46a9ff83..b8f1678e84 100644 --- a/modules/jooby-apt/src/main/java/io/jooby/internal/apt/AnnotationSupport.java +++ b/modules/jooby-apt/src/main/java/io/jooby/internal/apt/AnnotationSupport.java @@ -17,6 +17,9 @@ import javax.lang.model.element.VariableElement; public interface AnnotationSupport { + Predicate NULLABLE = name -> name.toLowerCase().endsWith(".nullable"); + Predicate NON_NULL = + name -> name.toLowerCase().endsWith(".nonnull") || name.toLowerCase().endsWith(".notnull"); Predicate VALUE = "value"::equals; Predicate NAME = "name"::equals; diff --git a/modules/jooby-apt/src/main/java/io/jooby/internal/apt/MvcContext.java b/modules/jooby-apt/src/main/java/io/jooby/internal/apt/MvcContext.java index a5485e6f56..0ca1d61a0a 100644 --- a/modules/jooby-apt/src/main/java/io/jooby/internal/apt/MvcContext.java +++ b/modules/jooby-apt/src/main/java/io/jooby/internal/apt/MvcContext.java @@ -5,15 +5,17 @@ */ package io.jooby.internal.apt; +import java.io.PrintWriter; +import java.io.StringWriter; import java.util.*; import java.util.function.BiConsumer; -import java.util.function.Consumer; import java.util.stream.Collectors; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.*; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; +import javax.tools.Diagnostic; import io.jooby.apt.JoobyProcessor.Options; @@ -30,13 +32,14 @@ private record ResultType(String type, String handler, boolean nonBlocking) {} private final boolean services; private final String routerPrefix; private final String routerSuffix; - private final Consumer output; + private final BiConsumer output; private final List routers = new ArrayList<>(); private final boolean returnType; private final boolean mvcMethod; private final Map handler = new HashMap<>(); - public MvcContext(ProcessingEnvironment processingEnvironment, Consumer output) { + public MvcContext( + ProcessingEnvironment processingEnvironment, BiConsumer output) { this.processingEnvironment = processingEnvironment; this.output = output; this.debug = Options.boolOpt(processingEnvironment, Options.DEBUG, false); @@ -258,13 +261,29 @@ public boolean isIncremental() { public void debug(String message, Object... args) { if (debug) { - info(message, args); + report(Diagnostic.Kind.OTHER, message, args); } } - public void info(String message, Object... args) { + public void error(String message, Object... args) { + Throwable cause = + args.length > 0 && args[args.length - 1] instanceof Throwable + ? (Throwable) args[args.length - 1] + : null; + if (cause != null) { + var str = new StringWriter(); + cause.printStackTrace(new PrintWriter(str, true)); + var errorMessage = cause.getMessage(); + args[args.length - 1] = + (errorMessage == null || errorMessage.equals(message) ? "" : errorMessage) + ":\n" + str; + message += ": %s"; + } + report(Diagnostic.Kind.ERROR, message, args); + } + + private void report(Diagnostic.Kind kind, String message, Object... args) { var msg = args.length == 0 ? message : message.formatted(args); - output.accept(msg); + output.accept(kind, msg); } public void generateStaticImports(MvcRouter mvcRouter, BiConsumer consumer) { diff --git a/modules/jooby-apt/src/main/java/io/jooby/internal/apt/MvcParameter.java b/modules/jooby-apt/src/main/java/io/jooby/internal/apt/MvcParameter.java index af8110f761..738cb3ea71 100644 --- a/modules/jooby-apt/src/main/java/io/jooby/internal/apt/MvcParameter.java +++ b/modules/jooby-apt/src/main/java/io/jooby/internal/apt/MvcParameter.java @@ -5,6 +5,9 @@ */ package io.jooby.internal.apt; +import static io.jooby.internal.apt.AnnotationSupport.NON_NULL; +import static io.jooby.internal.apt.AnnotationSupport.NULLABLE; + import java.util.*; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -15,14 +18,11 @@ import javax.lang.model.element.VariableElement; public class MvcParameter { - private static final Predicate NULLABLE = - name -> name.toLowerCase().endsWith(".nullable"); - private static final Predicate NON_NULL = - name -> name.toLowerCase().endsWith(".nonnull") || name.toLowerCase().endsWith(".notnull"); private final MvcRoute route; private final VariableElement parameter; private final Map annotations; private final TypeDefinition type; + private final boolean requireBeanValidation; public MvcParameter(MvcContext context, MvcRoute route, VariableElement parameter) { this.route = route; @@ -30,6 +30,7 @@ public MvcParameter(MvcContext context, MvcRoute route, VariableElement paramete this.annotations = annotationMap(parameter); this.type = new TypeDefinition(context.getProcessingEnvironment().getTypeUtils(), parameter.asType()); + this.requireBeanValidation = annotations.get("jakarta.validation.Valid") != null; } public TypeDefinition getType() { @@ -145,4 +146,8 @@ private Map annotationMap(VariableElement parameter) { private List annotationFromAnnotationType(Element element) { return Optional.ofNullable(element.getAnnotationMirrors()).orElse(Collections.emptyList()); } + + public boolean isRequireBeanValidation() { + return requireBeanValidation; + } } diff --git a/modules/jooby-apt/src/main/java/io/jooby/internal/apt/MvcRoute.java b/modules/jooby-apt/src/main/java/io/jooby/internal/apt/MvcRoute.java index df45a5fbc6..94bdfc1c40 100644 --- a/modules/jooby-apt/src/main/java/io/jooby/internal/apt/MvcRoute.java +++ b/modules/jooby-apt/src/main/java/io/jooby/internal/apt/MvcRoute.java @@ -203,13 +203,28 @@ public List generateHandlerCall(boolean kt) { /* Parameters */ var paramList = new StringJoiner(", ", "(", ")"); for (var parameter : getParameters(true)) { - paramList.add(parameter.generateMapping(kt)); + String generatedParameter = parameter.generateMapping(kt); + if (parameter.isRequireBeanValidation()) { + generatedParameter = CodeBlock.of( + "io.jooby.validation.ValidationHelper.validate(", + "ctx, ", + generatedParameter, + ")"); + } + + paramList.add(generatedParameter); } var throwsException = !method.getThrownTypes().isEmpty(); var returnTypeGenerics = getReturnType().getArgumentsString(kt, false, Set.of(TypeKind.TYPEVAR)); var returnTypeString = type(kt, getReturnType().toString()); + boolean nullable = false; if (kt) { + nullable = + method.getAnnotationMirrors().stream() + .map(AnnotationMirror::getAnnotationType) + .map(Objects::toString) + .anyMatch(NULLABLE); if (throwsException) { buffer.add(statement("@Throws(Exception::class)")); } @@ -312,7 +327,7 @@ public List generateHandlerCall(boolean kt) { setUncheckedCast(true); call = kt ? call + " as " + returnTypeString : "(" + returnTypeString + ") " + call; } - buffer.add(statement(indent(2), "return ", call, semicolon(kt))); + buffer.add(statement(indent(2), "return ", call, kt && nullable ? "!!" : "", semicolon(kt))); } buffer.add(statement("}", System.lineSeparator())); if (uncheckedCast) { 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 485e4384b2..256a8c158c 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 @@ -60,7 +60,7 @@ private static class HookJoobyProcessor extends JoobyProcessor { private JavaFileObject source; public HookJoobyProcessor(Consumer console) { - super(console); + super((kind, message) -> console.accept(message)); } public GeneratedSourceClassLoader createClassLoader() { diff --git a/modules/jooby-apt/src/test/java/tests/i3507/C3507.java b/modules/jooby-apt/src/test/java/tests/i3507/C3507.java new file mode 100644 index 0000000000..b8082c4cc7 --- /dev/null +++ b/modules/jooby-apt/src/test/java/tests/i3507/C3507.java @@ -0,0 +1,17 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ +package tests.i3507; + +import edu.umd.cs.findbugs.annotations.Nullable; +import io.jooby.annotation.GET; +import io.jooby.annotation.QueryParam; + +public class C3507 { + @GET("/3507") + @Nullable public String get(@QueryParam String query) { + return null; + } +} diff --git a/modules/jooby-apt/src/test/java/tests/i3507/Issue3507.java b/modules/jooby-apt/src/test/java/tests/i3507/Issue3507.java new file mode 100644 index 0000000000..73da53fa21 --- /dev/null +++ b/modules/jooby-apt/src/test/java/tests/i3507/Issue3507.java @@ -0,0 +1,27 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ +package tests.i3507; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; + +import io.jooby.apt.ProcessorRunner; + +public class Issue3507 { + + @Test + public void shouldGenerateNullSafeKtReturnType() throws IOException { + new ProcessorRunner(new C3507()) + .withSource( + true, + source -> { + assertTrue(source.contains("return c.get(ctx.query(\"query\").value())!!")); + }); + } +} diff --git a/modules/jooby-apt/src/test/java/tests/validation/Bean.java b/modules/jooby-apt/src/test/java/tests/validation/Bean.java new file mode 100644 index 0000000000..2309ed5dc2 --- /dev/null +++ b/modules/jooby-apt/src/test/java/tests/validation/Bean.java @@ -0,0 +1,13 @@ +package tests.validation; + +class Bean { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/modules/jooby-apt/src/test/java/tests/validation/BeanValidationGeneratorTest.java b/modules/jooby-apt/src/test/java/tests/validation/BeanValidationGeneratorTest.java new file mode 100644 index 0000000000..308813ff6c --- /dev/null +++ b/modules/jooby-apt/src/test/java/tests/validation/BeanValidationGeneratorTest.java @@ -0,0 +1,29 @@ +package tests.validation; + +import io.jooby.apt.ProcessorRunner; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class BeanValidationGeneratorTest { + + @Test + public void generate_validation_forBean() throws Exception { + new ProcessorRunner(new BeanValidationsController()).withSource( + false, + source -> { + assertTrue(source.contains( + "c.validateQueryBean(io.jooby.validation.ValidationHelper.validate(ctx, ctx.query(\"bean\").isMissing() ? ctx.query().toNullable(tests.validation.Bean.class) : ctx.query(\"bean\").toNullable(tests.validation.Bean.class)))") + ); + + assertTrue(source.contains( + "c.validateFormBean(io.jooby.validation.ValidationHelper.validate(ctx, ctx.form(\"bean\").isMissing() ? ctx.form().toNullable(tests.validation.Bean.class) : ctx.form(\"bean\").toNullable(tests.validation.Bean.class)))") + ); + + assertTrue(source.contains( + "c.validateBodyBean(io.jooby.validation.ValidationHelper.validate(ctx, ctx.body(tests.validation.Bean.class)))") + ); + + }); + } +} diff --git a/modules/jooby-apt/src/test/java/tests/validation/BeanValidationsController.java b/modules/jooby-apt/src/test/java/tests/validation/BeanValidationsController.java new file mode 100644 index 0000000000..1c8b2f3c75 --- /dev/null +++ b/modules/jooby-apt/src/test/java/tests/validation/BeanValidationsController.java @@ -0,0 +1,25 @@ +package tests.validation; + +import io.jooby.annotation.FormParam; +import io.jooby.annotation.POST; +import io.jooby.annotation.QueryParam; +import jakarta.validation.Valid; + +public class BeanValidationsController { + + @POST("/validate/query-bean") + public Bean validateQueryBean(@Valid @QueryParam Bean bean) { + return bean; + } + + @POST("/validate/form-bean") + public Bean validateFormBean(@Valid @FormParam Bean bean) { + return bean; + } + + @POST("/validate/body-bean") + public Bean validateBodyBean(@Valid Bean bean) { + return bean; + } + +} diff --git a/modules/jooby-avaje-inject/pom.xml b/modules/jooby-avaje-inject/pom.xml index 1bda07c5a8..0665ab5a38 100644 --- a/modules/jooby-avaje-inject/pom.xml +++ b/modules/jooby-avaje-inject/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-avaje-jsonb/pom.xml b/modules/jooby-avaje-jsonb/pom.xml index 770a2a3450..830989d4b3 100644 --- a/modules/jooby-avaje-jsonb/pom.xml +++ b/modules/jooby-avaje-jsonb/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-awssdk-v1/pom.xml b/modules/jooby-awssdk-v1/pom.xml index bdbd0e4d3e..8df5f137b2 100644 --- a/modules/jooby-awssdk-v1/pom.xml +++ b/modules/jooby-awssdk-v1/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-bom/pom.xml b/modules/jooby-bom/pom.xml index 5cab5fd4b1..484646dcfe 100644 --- a/modules/jooby-bom/pom.xml +++ b/modules/jooby-bom/pom.xml @@ -7,13 +7,13 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT io.jooby jooby-bom pom - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT Jooby (Bill of Materials) https://jooby.io diff --git a/modules/jooby-caffeine/pom.xml b/modules/jooby-caffeine/pom.xml index fbff7b36bb..3e88fe90f7 100644 --- a/modules/jooby-caffeine/pom.xml +++ b/modules/jooby-caffeine/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-camel/pom.xml b/modules/jooby-camel/pom.xml index ef1a0b3898..701259384a 100644 --- a/modules/jooby-camel/pom.xml +++ b/modules/jooby-camel/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-cli/pom.xml b/modules/jooby-cli/pom.xml index 88fbbb9ff2..728d6589b5 100644 --- a/modules/jooby-cli/pom.xml +++ b/modules/jooby-cli/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-commons-email/pom.xml b/modules/jooby-commons-email/pom.xml index 9e8110f2d5..fb55258ee4 100644 --- a/modules/jooby-commons-email/pom.xml +++ b/modules/jooby-commons-email/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-conscrypt/pom.xml b/modules/jooby-conscrypt/pom.xml index 159f62fb4c..409a0323a8 100644 --- a/modules/jooby-conscrypt/pom.xml +++ b/modules/jooby-conscrypt/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-distribution/pom.xml b/modules/jooby-distribution/pom.xml index 4e1f2fc1c8..73f2a78f21 100644 --- a/modules/jooby-distribution/pom.xml +++ b/modules/jooby-distribution/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-ebean/pom.xml b/modules/jooby-ebean/pom.xml index a9f70a035f..860c776fc8 100644 --- a/modules/jooby-ebean/pom.xml +++ b/modules/jooby-ebean/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-flyway/pom.xml b/modules/jooby-flyway/pom.xml index 6f978c7e42..5604655a39 100644 --- a/modules/jooby-flyway/pom.xml +++ b/modules/jooby-flyway/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-freemarker/pom.xml b/modules/jooby-freemarker/pom.xml index d0c6adf0a9..4d2839f8ff 100644 --- a/modules/jooby-freemarker/pom.xml +++ b/modules/jooby-freemarker/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-gradle-setup/pom.xml b/modules/jooby-gradle-setup/pom.xml index d82300bb17..97750f7796 100644 --- a/modules/jooby-gradle-setup/pom.xml +++ b/modules/jooby-gradle-setup/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-graphiql/pom.xml b/modules/jooby-graphiql/pom.xml index 3db45cf62d..f4c7e2cb65 100644 --- a/modules/jooby-graphiql/pom.xml +++ b/modules/jooby-graphiql/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-graphql-playground/pom.xml b/modules/jooby-graphql-playground/pom.xml index 530c8d6e17..03f3cfa1a6 100644 --- a/modules/jooby-graphql-playground/pom.xml +++ b/modules/jooby-graphql-playground/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-graphql/pom.xml b/modules/jooby-graphql/pom.xml index ff87f4beec..29f2b06bcc 100644 --- a/modules/jooby-graphql/pom.xml +++ b/modules/jooby-graphql/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-gson/pom.xml b/modules/jooby-gson/pom.xml index c5a30acd64..f3fb1b2865 100644 --- a/modules/jooby-gson/pom.xml +++ b/modules/jooby-gson/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-guice/pom.xml b/modules/jooby-guice/pom.xml index b8d8cc02d1..aebb2a4661 100644 --- a/modules/jooby-guice/pom.xml +++ b/modules/jooby-guice/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-handlebars/pom.xml b/modules/jooby-handlebars/pom.xml index 05cce97f03..aa79ce8320 100644 --- a/modules/jooby-handlebars/pom.xml +++ b/modules/jooby-handlebars/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-hbv/pom.xml b/modules/jooby-hbv/pom.xml new file mode 100644 index 0000000000..f293345950 --- /dev/null +++ b/modules/jooby-hbv/pom.xml @@ -0,0 +1,68 @@ + + + + + io.jooby + modules + 3.2.10-SNAPSHOT + + + 4.0.0 + jooby-hbv + + + + io.jooby + jooby + ${jooby.version} + + + + io.jooby + jooby-validation + ${jooby.version} + + + + + org.hibernate.validator + hibernate-validator + 8.0.1.Final + + + + jakarta.el + jakarta.el-api + 5.0.1 + + + + org.glassfish.expressly + expressly + 5.0.0 + + + + + org.junit.jupiter + junit-jupiter-engine + test + + + + org.jacoco + org.jacoco.agent + runtime + test + + + + org.mockito + mockito-core + test + + + + diff --git a/modules/jooby-hbv/src/main/java/io/jooby/hbv/HbvModule.java b/modules/jooby-hbv/src/main/java/io/jooby/hbv/HbvModule.java new file mode 100644 index 0000000000..af49f0019f --- /dev/null +++ b/modules/jooby-hbv/src/main/java/io/jooby/hbv/HbvModule.java @@ -0,0 +1,44 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ +package io.jooby.hbv; + + +import edu.umd.cs.findbugs.annotations.NonNull; +import io.jooby.Extension; +import io.jooby.Jooby; +import jakarta.validation.Validator; +import jakarta.validation.ValidatorFactory; +import org.hibernate.validator.HibernateValidator; +import org.hibernate.validator.HibernateValidatorConfiguration; + +import java.util.function.Consumer; + +import static jakarta.validation.Validation.byProvider; +import static java.util.Objects.requireNonNull; + +public class HbvModule implements Extension { + + private Consumer configurer; + + public HbvModule doWith(final Consumer configurer) { + this.configurer = requireNonNull(configurer, "Configurer callback is required."); + return this; + } + + @Override + public void install(@NonNull Jooby application) { + HibernateValidatorConfiguration cfg = byProvider(HibernateValidator.class).configure(); + + if (configurer != null) { + configurer.accept(cfg); + } + + try (ValidatorFactory factory = cfg.buildValidatorFactory()) { + Validator validator = factory.getValidator(); + application.getServices().put(Validator.class, validator); + } + } +} diff --git a/modules/jooby-hbv/src/main/java/io/jooby/hbv/package-info.java b/modules/jooby-hbv/src/main/java/io/jooby/hbv/package-info.java new file mode 100644 index 0000000000..da7909aad1 --- /dev/null +++ b/modules/jooby-hbv/src/main/java/io/jooby/hbv/package-info.java @@ -0,0 +1 @@ +package io.jooby.hbv; diff --git a/modules/jooby-hbv/src/main/java/module-info.java b/modules/jooby-hbv/src/main/java/module-info.java new file mode 100644 index 0000000000..e9a1799ee9 --- /dev/null +++ b/modules/jooby-hbv/src/main/java/module-info.java @@ -0,0 +1,17 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ +/** + * Hbv module. + */ +module io.jooby.hbv { + exports io.jooby.hbv; + + requires transitive io.jooby; + requires static com.github.spotbugs.annotations; + requires typesafe.config; + requires org.hibernate.validator; + requires jakarta.validation; +} diff --git a/modules/jooby-hibernate/pom.xml b/modules/jooby-hibernate/pom.xml index b3fdd5b229..54fb7cf7de 100644 --- a/modules/jooby-hibernate/pom.xml +++ b/modules/jooby-hibernate/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-hikari/pom.xml b/modules/jooby-hikari/pom.xml index 1bcb7578f8..4a2c63da60 100644 --- a/modules/jooby-hikari/pom.xml +++ b/modules/jooby-hikari/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-jackson/pom.xml b/modules/jooby-jackson/pom.xml index f76b4e275d..02d8e64a0d 100644 --- a/modules/jooby-jackson/pom.xml +++ b/modules/jooby-jackson/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-jasypt/pom.xml b/modules/jooby-jasypt/pom.xml index f180fd172e..cf659ed92d 100644 --- a/modules/jooby-jasypt/pom.xml +++ b/modules/jooby-jasypt/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-jdbi/pom.xml b/modules/jooby-jdbi/pom.xml index 98c5159966..4b7cfd1516 100644 --- a/modules/jooby-jdbi/pom.xml +++ b/modules/jooby-jdbi/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-jetty/pom.xml b/modules/jooby-jetty/pom.xml index 5d022191f4..655d755eb6 100644 --- a/modules/jooby-jetty/pom.xml +++ b/modules/jooby-jetty/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-jstachio/pom.xml b/modules/jooby-jstachio/pom.xml index 0b2c6d6da4..9d78802301 100644 --- a/modules/jooby-jstachio/pom.xml +++ b/modules/jooby-jstachio/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-jte/pom.xml b/modules/jooby-jte/pom.xml index ad56359e7f..25970de61b 100644 --- a/modules/jooby-jte/pom.xml +++ b/modules/jooby-jte/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-jwt/pom.xml b/modules/jooby-jwt/pom.xml index 877759d108..32dc7dc553 100644 --- a/modules/jooby-jwt/pom.xml +++ b/modules/jooby-jwt/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-kafka/pom.xml b/modules/jooby-kafka/pom.xml index 774d0fbad4..ab9e525a71 100644 --- a/modules/jooby-kafka/pom.xml +++ b/modules/jooby-kafka/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-kotlin/pom.xml b/modules/jooby-kotlin/pom.xml index 416a15ff78..67850367de 100644 --- a/modules/jooby-kotlin/pom.xml +++ b/modules/jooby-kotlin/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-log4j/pom.xml b/modules/jooby-log4j/pom.xml index eb14c3e84a..6cc6cb5e69 100644 --- a/modules/jooby-log4j/pom.xml +++ b/modules/jooby-log4j/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-logback/pom.xml b/modules/jooby-logback/pom.xml index 95ae109b98..38898324d1 100644 --- a/modules/jooby-logback/pom.xml +++ b/modules/jooby-logback/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-maven-plugin/pom.xml b/modules/jooby-maven-plugin/pom.xml index a9b0d99bfc..4ed656eb77 100644 --- a/modules/jooby-maven-plugin/pom.xml +++ b/modules/jooby-maven-plugin/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-metrics/pom.xml b/modules/jooby-metrics/pom.xml index be88af8ad7..5b2f587706 100644 --- a/modules/jooby-metrics/pom.xml +++ b/modules/jooby-metrics/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-mutiny/pom.xml b/modules/jooby-mutiny/pom.xml index d772e333a2..8320f7f184 100644 --- a/modules/jooby-mutiny/pom.xml +++ b/modules/jooby-mutiny/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-netty/pom.xml b/modules/jooby-netty/pom.xml index 7bdcd5674d..c4628f11bc 100644 --- a/modules/jooby-netty/pom.xml +++ b/modules/jooby-netty/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-node/pom.xml b/modules/jooby-node/pom.xml index 078aa0df58..2bad25e29d 100644 --- a/modules/jooby-node/pom.xml +++ b/modules/jooby-node/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-openapi/pom.xml b/modules/jooby-openapi/pom.xml index 66992aeec7..65c9363c8d 100644 --- a/modules/jooby-openapi/pom.xml +++ b/modules/jooby-openapi/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 @@ -86,7 +86,7 @@ org.apache.commons commons-lang3 - 3.15.0 + 3.16.0 test diff --git a/modules/jooby-pac4j/pom.xml b/modules/jooby-pac4j/pom.xml index ecd2a6f1ec..d081767eef 100644 --- a/modules/jooby-pac4j/pom.xml +++ b/modules/jooby-pac4j/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-pebble/pom.xml b/modules/jooby-pebble/pom.xml index ca1c93f549..6e20895940 100644 --- a/modules/jooby-pebble/pom.xml +++ b/modules/jooby-pebble/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-quartz/pom.xml b/modules/jooby-quartz/pom.xml index 57d3c7adc4..c040c952b2 100644 --- a/modules/jooby-quartz/pom.xml +++ b/modules/jooby-quartz/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-reactor/pom.xml b/modules/jooby-reactor/pom.xml index 30e97d3823..e7b6d9c640 100644 --- a/modules/jooby-reactor/pom.xml +++ b/modules/jooby-reactor/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 @@ -27,7 +27,7 @@ io.projectreactor reactor-core - 3.6.8 + 3.6.9 diff --git a/modules/jooby-redis/pom.xml b/modules/jooby-redis/pom.xml index e72d626629..df61b4d17d 100644 --- a/modules/jooby-redis/pom.xml +++ b/modules/jooby-redis/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-redoc/pom.xml b/modules/jooby-redoc/pom.xml index 9173dff574..d744f122b8 100644 --- a/modules/jooby-redoc/pom.xml +++ b/modules/jooby-redoc/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-rocker/pom.xml b/modules/jooby-rocker/pom.xml index 716e95550a..b36b55c9ee 100644 --- a/modules/jooby-rocker/pom.xml +++ b/modules/jooby-rocker/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-run/pom.xml b/modules/jooby-run/pom.xml index 94a6a56aff..e97283c009 100644 --- a/modules/jooby-run/pom.xml +++ b/modules/jooby-run/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-run/src/main/java/io/jooby/run/JoobyRun.java b/modules/jooby-run/src/main/java/io/jooby/run/JoobyRun.java index eaf53853c6..12aa2fe4b6 100644 --- a/modules/jooby-run/src/main/java/io/jooby/run/JoobyRun.java +++ b/modules/jooby-run/src/main/java/io/jooby/run/JoobyRun.java @@ -435,7 +435,8 @@ private synchronized void actualRestart() { var unload = false; Supplier compileTask = null; for (; e != null && (t - e.time) > waitTimeBeforeRestartMillis; e = queue.peek()) { - unload = unload || options.isCompileExtension(e.path); + // unload on source code changes (.java, .kt) or binary changes (.class) + unload = unload || options.isCompileExtension(e.path) || options.isClass(e.path); compileTask = Optional.ofNullable(compileTask).orElse(e.compileTask); queue.poll(); } diff --git a/modules/jooby-run/src/main/java/io/jooby/run/JoobyRunOptions.java b/modules/jooby-run/src/main/java/io/jooby/run/JoobyRunOptions.java index e29ae29dd5..a48ff0273e 100644 --- a/modules/jooby-run/src/main/java/io/jooby/run/JoobyRunOptions.java +++ b/modules/jooby-run/src/main/java/io/jooby/run/JoobyRunOptions.java @@ -171,6 +171,10 @@ public boolean isCompileExtension(Path path) { return containsExtension(compileExtensions, path); } + public boolean isClass(Path path) { + return containsExtension(List.of("class"), path); + } + /** * Test if the given path matches a restart extension. * diff --git a/modules/jooby-rxjava3/pom.xml b/modules/jooby-rxjava3/pom.xml index d17fc656f2..6ae8c70c56 100644 --- a/modules/jooby-rxjava3/pom.xml +++ b/modules/jooby-rxjava3/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 @@ -27,7 +27,7 @@ io.reactivex.rxjava3 rxjava - 3.1.8 + 3.1.9 diff --git a/modules/jooby-stork/pom.xml b/modules/jooby-stork/pom.xml index 9772ae05d1..dcab1b529e 100644 --- a/modules/jooby-stork/pom.xml +++ b/modules/jooby-stork/pom.xml @@ -5,7 +5,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT jooby-stork diff --git a/modules/jooby-swagger-ui/pom.xml b/modules/jooby-swagger-ui/pom.xml index f378637ac1..b897b831a4 100644 --- a/modules/jooby-swagger-ui/pom.xml +++ b/modules/jooby-swagger-ui/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-test/pom.xml b/modules/jooby-test/pom.xml index bc244a3bee..2b9233e3b4 100644 --- a/modules/jooby-test/pom.xml +++ b/modules/jooby-test/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 @@ -90,7 +90,7 @@ io.reactivex.rxjava3 rxjava - 3.1.8 + 3.1.9 test diff --git a/modules/jooby-thymeleaf/pom.xml b/modules/jooby-thymeleaf/pom.xml index ca5ab592d6..7d0e2ed71c 100644 --- a/modules/jooby-thymeleaf/pom.xml +++ b/modules/jooby-thymeleaf/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-undertow/pom.xml b/modules/jooby-undertow/pom.xml index 3b6832ffd6..0a06ae25aa 100644 --- a/modules/jooby-undertow/pom.xml +++ b/modules/jooby-undertow/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT diff --git a/modules/jooby-validation/pom.xml b/modules/jooby-validation/pom.xml new file mode 100644 index 0000000000..17626a6a41 --- /dev/null +++ b/modules/jooby-validation/pom.xml @@ -0,0 +1,28 @@ + + + + + io.jooby + modules + 3.2.10-SNAPSHOT + + + 4.0.0 + jooby-validation + + + + io.jooby + jooby + ${jooby.version} + + + + jakarta.validation + jakarta.validation-api + + + + diff --git a/modules/jooby-validation/src/main/java/io/jooby/validation/ValidationHelper.java b/modules/jooby-validation/src/main/java/io/jooby/validation/ValidationHelper.java new file mode 100644 index 0000000000..652eb17588 --- /dev/null +++ b/modules/jooby-validation/src/main/java/io/jooby/validation/ValidationHelper.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.validation; + +import io.jooby.Context; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import jakarta.validation.Validator; + +import java.util.*; + +public final class ValidationHelper { + + public static T validate(Context ctx, T bean) { + Validator validator = ctx.require(Validator.class); + + if (bean instanceof Collection) { + validateCollection(validator, (Collection) bean); + } else if (bean.getClass().isArray()) { + validateCollection(validator, Arrays.asList((Object[])bean)); + } else if (bean instanceof Map) { + validateCollection(validator, ((Map)bean).values()); + } else { + validateObject(validator, bean); + } + + return bean; + } + + private static void validateCollection(Validator validator, Collection beans) { + for (Object item : beans) { + validateObject(validator, item); + } + } + + private static void validateObject(Validator validator, Object bean) { + Set> violations = validator.validate(bean); + if (!violations.isEmpty()) { + throw new ConstraintViolationException(violations); + } + } +} diff --git a/modules/jooby-validation/src/main/java/io/jooby/validation/package-info.java b/modules/jooby-validation/src/main/java/io/jooby/validation/package-info.java new file mode 100644 index 0000000000..1cfba80ce3 --- /dev/null +++ b/modules/jooby-validation/src/main/java/io/jooby/validation/package-info.java @@ -0,0 +1 @@ +package io.jooby.validation; diff --git a/modules/jooby-validation/src/main/java/module-info.java b/modules/jooby-validation/src/main/java/module-info.java new file mode 100644 index 0000000000..a221cbf4c3 --- /dev/null +++ b/modules/jooby-validation/src/main/java/module-info.java @@ -0,0 +1,14 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ +/** + * Validation Module. + */ +module io.jooby.validation { + exports io.jooby.validation; + + requires jakarta.validation; + requires io.jooby; +} diff --git a/modules/jooby-whoops/pom.xml b/modules/jooby-whoops/pom.xml index 461494e15a..f12987ad80 100644 --- a/modules/jooby-whoops/pom.xml +++ b/modules/jooby-whoops/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/jooby-yasson/pom.xml b/modules/jooby-yasson/pom.xml index d87495ab55..91859617a0 100644 --- a/modules/jooby-yasson/pom.xml +++ b/modules/jooby-yasson/pom.xml @@ -6,7 +6,7 @@ io.jooby modules - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 diff --git a/modules/pom.xml b/modules/pom.xml index c9ed80a218..fbad803452 100644 --- a/modules/pom.xml +++ b/modules/pom.xml @@ -6,7 +6,7 @@ io.jooby jooby-project - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT modules @@ -58,6 +58,11 @@ jooby-thymeleaf jooby-node jooby-camel + jooby-hbv + + + jooby-validation + jooby-hbv jooby-pac4j diff --git a/pom.xml b/pom.xml index b4f510b489..ad0646c2d4 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ 4.0.0 io.jooby jooby-project - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT pom jooby-project @@ -21,7 +21,7 @@ 2.3.33 4.4.0 - 1.3.5 + 1.3.6 3.2.2 2.17.2 2.11.0 @@ -34,11 +34,11 @@ 5.1.0 1.2 - 6.5.2.Final + 6.6.0.Final 15.5.0 3.45.3 9.22.3 - 22.1 + 22.2 6.4.0.RELEASE 2.12.0 3.8.0 @@ -51,15 +51,15 @@ 7.0.0 - 1.5.6 + 1.5.7 2.23.1 - 2.0.13 + 2.0.16 1.6.0 - 4.2.26 + 4.2.27 2.1.5.Final @@ -80,10 +80,11 @@ 2.5.2 - + 2.0.1 - 4.8.6 + 3.1.0 4.0.0 + 4.8.6 4.12.0 @@ -92,7 +93,7 @@ 2.3.2 9.2.1 8.0.1 - 1.12.767 + 1.12.769 3.22.2 1.15.0 1.9.3 @@ -106,10 +107,10 @@ 0.8.12 - 5.10.3 + 5.11.0 5.5.0 5.12.0 - 33.2.1-jre + 33.3.0-jre 1.1.1 0.21.0 @@ -127,28 +128,28 @@ 3.7.1 2.40 3.13.0 - 3.9.8 + 3.9.9 3.1.2 3.5.0 - 3.2.4 + 3.2.5 3.4.2 3.8.0 3.2.1 - 3.13.1 - 3.9.8 - 3.13.1 + 3.14.0 + 3.9.9 + 3.14.0 2.2.1 3.3.1 3.6.0 3.8.2 3.3.1 - 3.3.1 + 3.4.0 2.3.1 1.7.0 4.0.1 3.1.3 2.17.1 - 3.3.0 + 3.4.1 3.1.2 1.15.0 v20.14.0 @@ -165,7 +166,7 @@ false yyyy-MM-dd HH:mm:ssa - 2024-07-30T18:37:16Z + 2024-08-15T22:50:45Z UTF-8 ${maven.build.timestamp} @@ -893,6 +894,13 @@ ${jakarta.inject.version} + + + jakarta.validation + jakarta.validation-api + ${jakarta.validation.version} + + org.flywaydb diff --git a/tests/pom.xml b/tests/pom.xml index 29cd9bd048..088a9ed487 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -6,7 +6,7 @@ io.jooby jooby-project - 3.3.0-SNAPSHOT + 3.2.10-SNAPSHOT 4.0.0 @@ -33,6 +33,11 @@ jooby-jackson ${jooby.version} + + io.jooby + jooby-gson + ${jooby.version} + io.jooby jooby-avaje-jsonb @@ -250,7 +255,6 @@ 1.37 test - diff --git a/tests/src/test/java/io/jooby/i3400/Issue3400.java b/tests/src/test/java/io/jooby/i3400/Issue3400.java new file mode 100644 index 0000000000..23f367de6c --- /dev/null +++ b/tests/src/test/java/io/jooby/i3400/Issue3400.java @@ -0,0 +1,66 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ +package io.jooby.i3400; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import io.jooby.Jooby; +import io.jooby.jackson.JacksonModule; +import io.jooby.junit.ServerTest; +import io.jooby.junit.ServerTestRunner; +import okhttp3.MediaType; +import okhttp3.RequestBody; + +public class Issue3400 { + + static class AppA extends Jooby { + { + post("/pets", ctx -> ctx.body(Pet3400.class)); + } + } + + @ServerTest + public void shouldShareDecodersOnMountedResources(ServerTestRunner runner) { + runner + .define( + app -> { + app.install(new JacksonModule()); + app.mount(new AppA()); + }) + .ready( + http -> { + http.post( + "/pets", + RequestBody.create( + "{\"id\": 1, \"name\": \"Cheddar\"}", MediaType.parse("application/json")), + rsp -> { + assertEquals("{\"id\":1,\"name\":\"Cheddar\"}", rsp.body().string()); + assertEquals("application/json;charset=UTF-8", rsp.header("Content-Type")); + }); + }); + } + + @ServerTest + public void shouldShareDecodersOnInstalledResources(ServerTestRunner runner) { + runner + .define( + app -> { + app.install(new JacksonModule()); + app.install(AppA::new); + }) + .ready( + http -> { + http.post( + "/pets", + RequestBody.create( + "{\"id\": 1, \"name\": \"Cheddar\"}", MediaType.parse("application/json")), + rsp -> { + assertEquals("{\"id\":1,\"name\":\"Cheddar\"}", rsp.body().string()); + assertEquals("application/json;charset=UTF-8", rsp.header("Content-Type")); + }); + }); + } +} diff --git a/tests/src/test/java/io/jooby/i3400/Pet3400.java b/tests/src/test/java/io/jooby/i3400/Pet3400.java new file mode 100644 index 0000000000..a7fbb20a4f --- /dev/null +++ b/tests/src/test/java/io/jooby/i3400/Pet3400.java @@ -0,0 +1,32 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ +package io.jooby.i3400; + +public class Pet3400 { + private int id; + private String name; + + public Pet3400(int id, String name) { + this.id = id; + this.name = name; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/tests/src/test/java/io/jooby/i3500/Issue3500.java b/tests/src/test/java/io/jooby/i3500/Issue3500.java new file mode 100644 index 0000000000..85fecbc6b6 --- /dev/null +++ b/tests/src/test/java/io/jooby/i3500/Issue3500.java @@ -0,0 +1,37 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ +package io.jooby.i3500; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import io.jooby.junit.ServerTest; +import io.jooby.junit.ServerTestRunner; +import okhttp3.MediaType; +import okhttp3.RequestBody; + +public class Issue3500 { + + @ServerTest + public void shouldShareDecodersOnMountedResources(ServerTestRunner runner) { + runner + .use(WidgetService::new) + .ready( + http -> { + http.post( + "/api/widgets1", + RequestBody.create("{\"id\": 1}", MediaType.get("application/json")), + rsp -> { + assertEquals(201, rsp.code()); + }); + http.post( + "/api/widgets2", + RequestBody.create("{\"id\": 1}", MediaType.get("application/json")), + rsp -> { + assertEquals(201, rsp.code()); + }); + }); + } +} diff --git a/tests/src/test/java/io/jooby/i3500/Widget.java b/tests/src/test/java/io/jooby/i3500/Widget.java new file mode 100644 index 0000000000..e2ad4a7a5b --- /dev/null +++ b/tests/src/test/java/io/jooby/i3500/Widget.java @@ -0,0 +1,18 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ +package io.jooby.i3500; + +public class Widget { + private int id; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } +} diff --git a/tests/src/test/java/io/jooby/i3500/WidgetService.java b/tests/src/test/java/io/jooby/i3500/WidgetService.java new file mode 100644 index 0000000000..59330c3ce9 --- /dev/null +++ b/tests/src/test/java/io/jooby/i3500/WidgetService.java @@ -0,0 +1,44 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ +package io.jooby.i3500; + +import io.jooby.Jooby; +import io.jooby.StatusCode; +import io.jooby.gson.GsonModule; + +public class WidgetService extends Jooby { + + public WidgetService() { + install(new GsonModule()); + + post( + "/api/widgets1", + ctx -> { + Widget widget = ctx.body().to(Widget.class); + System.out.println("Created " + widget); + return ctx.send(StatusCode.CREATED); + }); + + mount(new WidgetRouter()); + } + + public static void main(String[] args) { + new WidgetService().start(); + } +} + +class WidgetRouter extends Jooby { + + public WidgetRouter() { + + post( + "/api/widgets2", + ctx -> { + Widget widget = ctx.body().to(Widget.class); + return ctx.send(StatusCode.CREATED); + }); + } +} diff --git a/tests/src/test/java/io/jooby/i3501/Issue3501.java b/tests/src/test/java/io/jooby/i3501/Issue3501.java new file mode 100644 index 0000000000..cb9bc430d5 --- /dev/null +++ b/tests/src/test/java/io/jooby/i3501/Issue3501.java @@ -0,0 +1,43 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ +package io.jooby.i3501; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import io.jooby.junit.ServerTest; +import io.jooby.junit.ServerTestRunner; + +public class Issue3501 { + + @ServerTest + public void assetHandlerShouldGenerateCustom404Response(ServerTestRunner runner) { + runner + .define( + app -> { + app.assets("/issue3501/*", "/static") + .notFound( + ctx -> { + throw new UnsupportedOperationException(); + }); + + app.error( + UnsupportedOperationException.class, + ((ctx, cause, code) -> { + ctx.send(cause.getClass().getName()); + })); + }) + .ready( + http -> { + http.get( + "/issue3501/index.js", + rsp -> { + assertEquals( + UnsupportedOperationException.class.getName(), rsp.body().string()); + assertEquals(500, rsp.code()); + }); + }); + } +} diff --git a/tests/src/test/java/io/jooby/test/FeaturedTest.java b/tests/src/test/java/io/jooby/test/FeaturedTest.java index 332c7ea4fc..ae0ff6701b 100644 --- a/tests/src/test/java/io/jooby/test/FeaturedTest.java +++ b/tests/src/test/java/io/jooby/test/FeaturedTest.java @@ -2625,7 +2625,7 @@ public void staticAssetsCaching(ServerTestRunner runner) { runner .define( app -> { - app.assets("/www/?*", new AssetHandler(source).setNoCache()); + app.assets("/www/?*", source).setNoCache(); }) .ready( client -> { @@ -2642,19 +2642,17 @@ public void staticAssetsCaching(ServerTestRunner runner) { runner .define( app -> { - app.assets( - "/www/?*", - new AssetHandler(source) - .cacheControl( - path -> { - if (path.endsWith("about.html")) { - return CacheControl.noCache(); - } else if (path.equals("foo.js")) { - return CacheControl.defaults().setETag(false); - } else { - return CacheControl.defaults(); - } - })); + app.assets("/www/?*", source) + .cacheControl( + path -> { + if (path.endsWith("about.html")) { + return CacheControl.noCache(); + } else if (path.equals("foo.js")) { + return CacheControl.defaults().setETag(false); + } else { + return CacheControl.defaults(); + } + }); }) .ready( client -> { diff --git a/tests/src/test/kotlin/i3490/C3490.kt b/tests/src/test/kotlin/i3490/C3490.kt new file mode 100644 index 0000000000..b9f2eb5be3 --- /dev/null +++ b/tests/src/test/kotlin/i3490/C3490.kt @@ -0,0 +1,67 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ +package i3490 + +import io.jooby.Context +import io.jooby.annotation.* +import java.io.IOException +import java.util.concurrent.CompletableFuture +import kotlinx.coroutines.delay + +@Path("/") +class C3490 { + @GET + suspend fun sayHi(@QueryParam n: String?, @QueryParam bit: String?): String { + return Thread.currentThread().name + ": " + n + ": " + bit + } + + @GET("/bye") + suspend fun sayGoodBye(@QueryParam n: String): String { + val from = Thread.currentThread().name + delay(100) + return from + ":" + Thread.currentThread().name + ": " + n + } + + @GET("/completable") + fun completable(): CompletableFuture { + val from = Thread.currentThread().name + return CompletableFuture.supplyAsync { from + ": " + Thread.currentThread().name + ": Async" } + } + + @Throws(IOException::class) + @GET("/fo\"o") + fun foo(ctx: Context) { + ctx.send("fff") + } + + @GET("/bean") fun bean(@BindParam bean: Bean3490) = bean + + @GET("/gen") fun gen(@BindParam bean: Bean3490): List = listOf() + + @GET("/gene") fun genE(@BindParam bean: Bean3490): List = listOf() + + @GET("/genlist") fun genE(@QueryParam bean: List): List = listOf() + + @GET("/context") fun contextAttr(@ContextParam attributes: Map) = attributes + + @GET("/box") fun box(@QueryParam box: Box3490) = box + + @GET("/list") + fun box(@QueryParam id: Int?): Box3490> = + Box3490(listOf(Bean3490(id?.toString() ?: "none"))) +} + +data class Bean3490(val value: String) { + override fun toString(): String { + return value + } + + companion object { + @JvmStatic fun of(ctx: Context) = Bean3490(ctx.query("value").toString()) + } +} + +data class Box3490(val value: T) diff --git a/tests/src/test/kotlin/i3490/Issue3490.kt b/tests/src/test/kotlin/i3490/Issue3490.kt new file mode 100644 index 0000000000..b5752191f5 --- /dev/null +++ b/tests/src/test/kotlin/i3490/Issue3490.kt @@ -0,0 +1,20 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ +package i3490 + +import io.jooby.junit.ServerTest +import io.jooby.junit.ServerTestRunner +import io.jooby.kt.Kooby + +class Issue3490 { + @ServerTest + fun shouldBootComplexGenericTypes(runner: ServerTestRunner) = + runner + .use { Kooby { mvc(C3490_()) } } + .ready { _ -> + // NOOP + } +}