Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: jooby-hbv module #3494

Closed
wants to merge 22 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
720643c
build: add more tests to #3488
jknack Aug 6, 2024
00c8444
jooby-apt: print error on failure
jknack Aug 6, 2024
c034bf9
v3.2.8
jknack Aug 6, 2024
daca727
jooby-hbv module draft
kliushnichenko Aug 6, 2024
f414c82
415 unsupported media type when using mount to add routes
jknack Aug 13, 2024
3b66df8
Merge pull request #3502 from jooby-project/3500
jknack Aug 15, 2024
f7f6c4b
build(deps): bump the dependencies group across 1 directory with 19 u…
dependabot[bot] Aug 15, 2024
ffc72e6
Merge pull request #3503 from jooby-project/dependabot/maven/dependen…
jknack Aug 15, 2024
3c3215c
assets: custom response when asset isn't found fix #3501
jknack Aug 15, 2024
a54321b
router: change assets return type from Route to AssetHandler fix #3504
jknack Aug 15, 2024
9cb0d1d
v3.2.9
jknack Aug 15, 2024
655e4dc
prepare for next development cycle
jknack Aug 15, 2024
f53b779
build(deps): bump the dependencies group with 9 updates
dependabot[bot] Aug 19, 2024
d2f41dd
Merge pull request #3506 from jooby-project/dependabot/maven/dependen…
jknack Aug 19, 2024
c4744a0
jooby-run: Hot reloading not working when .class file is changed by e…
jknack Aug 19, 2024
9366f99
jooby-apt: kotlin: should generates a code with !! null operator for …
jknack Aug 22, 2024
1bf0e00
jooby-hbv module draft
kliushnichenko Aug 6, 2024
887dc9d
rollback core changes
kliushnichenko Aug 17, 2024
8d71495
cleanup hbv module
kliushnichenko Aug 17, 2024
06eb67e
basic implementation for MVC
kliushnichenko Aug 24, 2024
7adc2a2
rebase
kliushnichenko Aug 24, 2024
951a459
Merge remote-tracking branch 'origin/feat/jooby-hbv' into feat/jooby-hbv
kliushnichenko Aug 24, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 42 additions & 19 deletions docs/asciidoc/static-files.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -134,23 +134,19 @@ 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);
}
----

.Kotlin
[source, kotlin, role="secondary"]
----
{
val www = AssetSource.create(Paths.get("www"))
assets("/static/*", AssetHandler(www)
assets("/static/*", Paths.get("www"))
.setLastModified(false)
.setEtag(false)
);
}
----

Expand All @@ -160,21 +156,17 @@ 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))
);
}
----

.Kotlin
[source, kotlin, role="secondary"]
----
{
val www = AssetSource.create(Paths.get("www"))
assets("/static/*", AssetHandler(www)
assets("/static/*", Paths.get("www"))
.setMaxAge(Duration.ofDays(365))
);
}
----

Expand All @@ -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
Expand All @@ -200,16 +191,15 @@ specify a function via javadoc:AssetHandler[cacheControl, java.util.Function]:
} else {
return CacheControl.defaults(); // AssetHandler defaults
}
}));
});
}
----

.Kotlin
[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
Expand All @@ -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
}
}
----
8 changes: 7 additions & 1 deletion jooby/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>io.jooby</groupId>
<artifactId>jooby-project</artifactId>
<version>3.3.0-SNAPSHOT</version>
<version>3.2.10-SNAPSHOT</version>
</parent>

<modelVersion>4.0.0</modelVersion>
Expand Down Expand Up @@ -49,6 +49,12 @@
<artifactId>jakarta.inject-api</artifactId>
</dependency>

<!-- jakarta.validation -->
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
</dependency>

<!-- config -->
<dependency>
<groupId>com.typesafe</groupId>
Expand Down
24 changes: 18 additions & 6 deletions jooby/src/main/java/io/jooby/DefaultContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<ConstraintViolation<T>> violations = validator.validate(object);
if (!violations.isEmpty()) {
throw new ConstraintViolationException(violations);
}
}
}
return object;

} catch (Exception x) {
throw SneakyThrows.propagate(x);
}
Expand Down
15 changes: 15 additions & 0 deletions jooby/src/main/java/io/jooby/Jooby.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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<Type> 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);
Expand Down
9 changes: 9 additions & 0 deletions jooby/src/main/java/io/jooby/MessageValidator.java
Original file line number Diff line number Diff line change
@@ -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<Type> predicate) {
}
17 changes: 11 additions & 6 deletions jooby/src/main/java/io/jooby/Router.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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<Type> predicate);
@Nullable MessageValidator getMessageValidator();
/**
* Returns the worker thread pool. This thread pool is used to run application blocking code.
*
Expand Down Expand Up @@ -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));
}

Expand All @@ -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);
Expand All @@ -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) {
Expand All @@ -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;
}

/**
Expand Down
27 changes: 20 additions & 7 deletions jooby/src/main/java/io/jooby/handler/AssetHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -28,6 +26,8 @@
* @since 2.0.0
*/
public class AssetHandler implements Route.Handler {
private static final SneakyThrows.Consumer<Context> NOT_FOUND =
ctx -> ctx.send(StatusCode.NOT_FOUND);
private static final int ONE_SEC = 1000;

private final AssetSource[] sources;
Expand All @@ -41,6 +41,7 @@ public class AssetHandler implements Route.Handler {
private Function<String, CacheControl> cacheControl = path -> defaults;

private Function<Asset, MediaType> mediaTypeResolver = Asset::getContentType;
private SneakyThrows.Consumer<Context> notFound = NOT_FOUND;

/**
* Creates a new asset handler that fallback to the given fallback asset when the asset is not
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -230,7 +231,19 @@ public AssetHandler cacheControl(@NonNull Function<String, CacheControl> cacheCo
return this;
}

private Asset resolve(String filepath) {
/**
* Sets a custom handler for <code>404</code> asset/resource. By default, generates a <code>404
* </code> status code response.
*
* @param handler Handler.
* @return This handler.
*/
public AssetHandler notFound(@NonNull SneakyThrows.Consumer<Context> handler) {
this.notFound = handler;
return this;
}

private @Nullable Asset resolve(String filepath) {
for (AssetSource source : sources) {
Asset asset = source.resolve(filepath);
if (asset != null) {
Expand All @@ -243,7 +256,7 @@ private Asset resolve(String filepath) {
@Override
public void setRoute(Route route) {
List<String> 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);
}
Expand Down
Loading
Loading