Skip to content

Commit

Permalink
router: Add HTTP predicate filter to install
Browse files Browse the repository at this point in the history
- fix #3418
  • Loading branch information
jknack committed May 12, 2024
1 parent f521422 commit 8018014
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 27 deletions.
81 changes: 80 additions & 1 deletion jooby/src/main/java/io/jooby/Jooby.java
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public class Jooby implements Router, Registry {

private final transient AtomicBoolean stopped = new AtomicBoolean(false);

private static transient Jooby owner;
private static Jooby owner;

private RouterImpl router;

Expand Down Expand Up @@ -380,6 +380,85 @@ public String getContextPath() {
}
}

/**
* Installs/imports a full application into this one. Applications share services, registry,
* callbacks, etc.
*
* <p>Application must be instantiated/created lazily via a supplier/factory. This is required due
* to the way an application is usually initialized (constructor initializer).
*
* <p>Working example:
*
* <pre>{@code
* install("/subapp", ctx -> ctx.header("v").value("").equals("1.0"), SubApp::new);
*
* }</pre>
*
* Lazy creation allows to configure and setup <code>SubApp</code> correctly, the next example
* won't work:
*
* <pre>{@code
* SubApp app = new SubApp();
* install("/subapp", ctx -> ctx.header("v").value("").equals("1.0"), app); // WONT WORK
*
* }</pre>
*
* Note: you must take care of application services across the applications. For example make sure
* you don't configure the same service twice or more in the main and imported applications too.
*
* @param path Sub path.
* @param predicate HTTP predicate.
* @param factory Application factory.
* @return This application.
*/
@NonNull public Jooby install(
@NonNull String path,
@NonNull Predicate<Context> predicate,
@NonNull SneakyThrows.Supplier<Jooby> factory) {
try {
owner = this;
router.install(path, predicate, factory);
return this;
} finally {
owner = null;
}
}

/**
* Installs/imports a full application into this one. Applications share services, registry,
* callbacks, etc.
*
* <p>Application must be instantiated/created lazily via a supplier/factory. This is required due
* to the way an application is usually initialized (constructor initializer).
*
* <p>Working example:
*
* <pre>{@code
* install(ctx -> ctx.header("v").value("").equals("1.0"), SubApp::new);
*
* }</pre>
*
* Lazy creation allows to configure and setup <code>SubApp</code> correctly, the next example
* won't work:
*
* <pre>{@code
* SubApp app = new SubApp();
* install(ctx -> ctx.header("v").value("").equals("1.0"), app); // WONT WORK
*
* }</pre>
*
* Note: you must take care of application services across the applications. For example make sure
* you don't configure the same service twice or more in the main and imported applications too.
*
* @param predicate HTTP predicate.
* @param factory Application factory.
* @return This application.
*/
@NonNull public Jooby install(
@NonNull Predicate<Context> predicate, @NonNull SneakyThrows.Supplier<Jooby> factory) {
return install("/", predicate, factory);
}

/**
* The underlying router.
*
Expand Down
45 changes: 19 additions & 26 deletions jooby/src/main/java/io/jooby/internal/RouterImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,30 +41,7 @@

import com.typesafe.config.Config;
import edu.umd.cs.findbugs.annotations.NonNull;
import io.jooby.BeanConverter;
import io.jooby.Context;
import io.jooby.Cookie;
import io.jooby.Environment;
import io.jooby.ErrorHandler;
import io.jooby.ExecutionMode;
import io.jooby.Jooby;
import io.jooby.MediaType;
import io.jooby.MessageDecoder;
import io.jooby.MessageEncoder;
import io.jooby.ResultHandler;
import io.jooby.Route;
import io.jooby.RouteSet;
import io.jooby.Router;
import io.jooby.RouterOption;
import io.jooby.ServerOptions;
import io.jooby.ServerSentEmitter;
import io.jooby.ServiceKey;
import io.jooby.ServiceRegistry;
import io.jooby.SessionStore;
import io.jooby.StatusCode;
import io.jooby.ValueConverter;
import io.jooby.WebSocket;
import io.jooby.XSS;
import io.jooby.*;
import io.jooby.buffer.DataBufferFactory;
import io.jooby.buffer.DefaultDataBufferFactory;
import io.jooby.exception.RegistryException;
Expand Down Expand Up @@ -299,15 +276,31 @@ public Router domain(@NonNull String domain, @NonNull Router subrouter) {

@NonNull @Override
public RouteSet mount(@NonNull Predicate<Context> predicate, @NonNull Runnable body) {
RouteSet routeSet = new RouteSet();
Chi tree = new Chi();
var routeSet = new RouteSet();
var tree = new Chi();
putPredicate(predicate, tree);
int start = this.routes.size();
newStack(tree, "/", body);
routeSet.setRoutes(this.routes.subList(start, this.routes.size()));
return routeSet;
}

public Router install(
@NonNull String path,
@NonNull Predicate<Context> predicate,
@NonNull SneakyThrows.Supplier<Jooby> factory) {
var existingRouter = this.chi;
try {
var tree = new Chi();
this.chi = tree;
putPredicate(predicate, tree);
path(path, factory::get);
return this;
} finally {
this.chi = existingRouter;
}
}

@NonNull @Override
public Router mount(@NonNull Predicate<Context> predicate, @NonNull Router subrouter) {
/** Override services: */
Expand Down
14 changes: 14 additions & 0 deletions tests/src/test/java/io/jooby/i3418/Bar3418.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Jooby https://jooby.io
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
* Copyright 2014 Edgar Espina
*/
package io.jooby.i3418;

import io.jooby.Jooby;

public class Bar3418 extends Jooby {
{
get("/app", ctx -> getClass().getSimpleName());
}
}
14 changes: 14 additions & 0 deletions tests/src/test/java/io/jooby/i3418/Foo3418.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Jooby https://jooby.io
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
* Copyright 2014 Edgar Espina
*/
package io.jooby.i3418;

import io.jooby.Jooby;

public class Foo3418 extends Jooby {
{
get("/app", ctx -> getClass().getSimpleName());
}
}
54 changes: 54 additions & 0 deletions tests/src/test/java/io/jooby/i3418/Issue3418.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Jooby https://jooby.io
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
* Copyright 2014 Edgar Espina
*/
package io.jooby.i3418;

import static org.junit.jupiter.api.Assertions.assertEquals;

import io.jooby.junit.ServerTest;
import io.jooby.junit.ServerTestRunner;

public class Issue3418 {

@ServerTest
public void shouldInstallWithPredicate(ServerTestRunner runner) {
runner
.define(
app -> {
app.install(ctx -> ctx.header("v").value("").equals("1.0"), Foo3418::new);
app.install(ctx -> ctx.header("v").value("").equals("2.0"), Bar3418::new);
app.get("/app", ctx -> "App");
})
.ready(
http -> {
http.header("v", "1.0")
.get(
"/app",
rsp -> {
assertEquals("Foo3418", rsp.body().string());
});

http.header("v", "2.0")
.get(
"/app",
rsp -> {
assertEquals("Bar3418", rsp.body().string());
});

http.header("v", "somethingElse")
.get(
"/app",
rsp -> {
assertEquals("App", rsp.body().string());
});

http.get(
"/app",
rsp -> {
assertEquals("App", rsp.body().string());
});
});
}
}

0 comments on commit 8018014

Please sign in to comment.