package examples;

import io.vertx.codegen.annotations.DataObject;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.VertxOptions;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.eventbus.EventBus;
import io.vertx.core.http.*;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.auth.JWTOptions;
import io.vertx.ext.auth.KeyStoreOptions;
import io.vertx.ext.auth.User;
import io.vertx.ext.auth.authentication.AuthenticationProvider;
import io.vertx.ext.auth.authorization.AuthorizationProvider;
import io.vertx.ext.auth.authorization.PermissionBasedAuthorization;
import io.vertx.ext.auth.authorization.RoleBasedAuthorization;
import io.vertx.ext.auth.jwt.JWTAuth;
import io.vertx.ext.auth.jwt.JWTAuthOptions;
import io.vertx.ext.auth.oauth2.OAuth2Auth;
import io.vertx.ext.auth.oauth2.OAuth2FlowType;
import io.vertx.ext.auth.oauth2.OAuth2Options;
import io.vertx.ext.auth.oauth2.providers.GithubAuth;
import io.vertx.ext.auth.webauthn.*;
import io.vertx.ext.bridge.BridgeEventType;
import io.vertx.ext.bridge.PermittedOptions;
import io.vertx.ext.web.*;
import io.vertx.ext.web.common.template.TemplateEngine;
import io.vertx.ext.web.handler.*;
import io.vertx.ext.web.handler.sockjs.SockJSBridgeOptions;
import io.vertx.ext.web.handler.sockjs.SockJSHandler;
import io.vertx.ext.web.handler.sockjs.SockJSHandlerOptions;
import io.vertx.ext.web.sstore.ClusteredSessionStore;
import io.vertx.ext.web.sstore.LocalSessionStore;
import io.vertx.ext.web.sstore.SessionStore;

import java.util.List;
import java.util.Set;
import java.util.function.Function;

/**
 * These are the examples used in the documentation.
 *
 * @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
 * @author <a href="http://tfox.org">Tim Fox</a>
 */
public class WebExamples {

  public void example1(Vertx vertx) {
    HttpServer server = vertx.createHttpServer();

    server.requestHandler(request -> {

      // This handler gets called for each request that arrives on the server
      HttpServerResponse response = request.response();
      response.putHeader("content-type", "text/plain");

      // Write to the response and end it
      response.end("Hello World!");
    });

    server.listen(8080);
  }

  public void example2(Vertx vertx) {
    HttpServer server = vertx.createHttpServer();

    Router router = Router.router(vertx);

    router.route().handler(ctx -> {

      // This handler will be called for every request
      HttpServerResponse response = ctx.response();
      response.putHeader("content-type", "text/plain");

      // Write to the response and end it
      response.end("Hello World from Vert.x-Web!");
    });

    server.requestHandler(router).listen(8080);

  }

  public void example3(Router router) {

    Route route = router.route().path("/some/path/");

    route.handler(ctx -> {
      // This handler will be called for the following request paths:

      // `/some/path/`
      // `/some/path//`
      //
      // but not:
      // `/some/path` the end slash in the path makes it strict
      // `/some/path/subdir`
    });

    // paths that do not end with slash are not strict
    // this means that the trailing slash is optional
    // and they match regardless
    Route route2 = router.route().path("/some/path");

    route2.handler(ctx -> {
      // This handler will be called for the following request paths:

      // `/some/path`
      // `/some/path/`
      // `/some/path//`
      //
      // but not:
      // `/some/path/subdir`
    });


  }

  public void example3_1(Router router) {

    Route route = router.route().path("/some/path/*");

    route.handler(ctx -> {
      // This handler will be called for any path that starts with
      // `/some/path/`, e.g.

      // `/some/path/`
      // `/some/path/subdir`
      // `/some/path/subdir/blah.html`
      //
      // but not:
      // `/some/path` the path is strict because it ends with slash
      // `/some/bath`
    });

  }

  public void example4(Router router) {

    Route route = router.route("/some/path/*");

    route.handler(ctx -> {
      // This handler will be called same as previous example
    });

  }

  public void example4_1(Router router) {

    router
      .route(HttpMethod.POST, "/catalogue/products/:productType/:productID/")
      .handler(ctx -> {

        String productType = ctx.pathParam("productType");
        String productID = ctx.pathParam("productID");

        // Do something with them...
      });

  }


  public void example5(Router router) {

    // Matches any path ending with 'foo'
    Route route = router.route().pathRegex(".*foo");

    route.handler(ctx -> {

      // This handler will be called for:

      // /some/path/foo
      // /foo
      // /foo/bar/wibble/foo
      // /bar/foo

      // But not:
      // /bar/wibble
    });

  }

  public void example6(Router router) {

    Route route = router.routeWithRegex(".*foo");

    route.handler(ctx -> {

      // This handler will be called same as previous example

    });

  }

  public void example6_1(Router router) {

    Route route = router.routeWithRegex(".*foo");

    // This regular expression matches paths that start with something like:
    // "/foo/bar" - where the "foo" is captured into param0 and the "bar" is
    // captured into param1
    route.pathRegex("\\/([^\\/]+)\\/([^\\/]+)").handler(ctx -> {

      String productType = ctx.pathParam("param0");
      String productID = ctx.pathParam("param1");

      // Do something with them...
    });

  }

  public void example6_2(Router router) {

    // This regular expression matches paths that start with
    // something like: "/foo/bar". It uses named regex groups
    // to capture path params
    router
      .routeWithRegex("\\/(?<productType>[^\\/]+)\\/(?<productID>[^\\/]+)")
      .handler(ctx -> {

        String productType = ctx.pathParam("productType");
        String productID = ctx.pathParam("productID");

        // Do something with them...
      });

  }

  public void example7(Router router) {

    Route route = router.route().method(HttpMethod.POST);

    route.handler(ctx -> {

      // This handler will be called for any POST request

    });

  }

  public void example8(Router router) {

    Route route = router.route(HttpMethod.POST, "/some/path/");

    route.handler(ctx -> {
      // This handler will be called for any POST request
      // to a URI path starting with /some/path/
    });

  }

  public void example8_1(Router router) {

    router.get().handler(ctx -> {

      // Will be called for any GET request

    });

    router.get("/some/path/").handler(ctx -> {

      // Will be called for any GET request to a path
      // starting with /some/path

    });

    router.getWithRegex(".*foo").handler(ctx -> {

      // Will be called for any GET request to a path
      // ending with `foo`

    });

    // There are also equivalents to the above for:
    // PUT, POST, DELETE, HEAD and OPTIONS

  }

  public void example9(Router router) {

    Route route = router.route().method(HttpMethod.POST).method(HttpMethod.PUT);

    route.handler(ctx -> {

      // This handler will be called for any POST or PUT request

    });

  }

  public void example9_1(Router router) {

    Route route = router.route()
      .method(HttpMethod.valueOf("MKCOL"))
      .handler(ctx -> {
        // This handler will be called for any MKCOL request
      });

  }

  public void example10(Router router) {

    router
      .route("/some/path/")
      .handler(ctx -> {

        HttpServerResponse response = ctx.response();
        // enable chunked responses because we will be adding data as
        // we execute over other handlers. This is only required once and
        // only if several handlers do output.
        response.setChunked(true);

        response.write("route1\n");

        // Now call the next matching route
        ctx.next();
      });

    router
      .route("/some/path/")
      .handler(ctx -> {

        HttpServerResponse response = ctx.response();
        response.write("route2\n");

        // Now call the next matching route
        ctx.next();
      });

    router
      .route("/some/path/")
      .handler(ctx -> {

        HttpServerResponse response = ctx.response();
        response.write("route3");

        // Now end the response
        ctx.response().end();
      });

  }

  public void example11(Router router) {

    router
      .route("/some/path/")
      .order(1)
      .handler(ctx -> {

        HttpServerResponse response = ctx.response();
        response.write("route1\n");

        // Now call the next matching route
        ctx.next();
      });

    router
      .route("/some/path/")
      .order(0)
      .handler(ctx -> {

        HttpServerResponse response = ctx.response();
        // enable chunked responses because we will be adding data as
        // we execute over other handlers. This is only required once and
        // only if several handlers do output.
        response.setChunked(true);

        response.write("route2\n");

        // Now call the next matching route
        ctx.next();
      });

    router
      .route("/some/path/")
      .order(2)
      .handler(ctx -> {

        HttpServerResponse response = ctx.response();
        response.write("route3");

        // Now end the response
        ctx.response().end();
      });
  }

  public void example12(Router router) {

    // Exact match
    router.route()
      .consumes("text/html")
      .handler(ctx -> {

        // This handler will be called for any request with
        // content-type header set to `text/html`

      });
  }

  public void example13(Router router) {

    // Multiple exact matches
    router.route()
      .consumes("text/html")
      .consumes("text/plain")
      .handler(ctx -> {

        // This handler will be called for any request with
        // content-type header set to `text/html` or `text/plain`.

      });
  }

  public void example14(Router router) {

    // Sub-type wildcard match
    router.route()
      .consumes("text/*")
      .handler(ctx -> {

        // This handler will be called for any request
        // with top level type `text` e.g. content-type
        // header set to `text/html` or `text/plain`
        // will both match

      });
  }

  public void example15(Router router) {

    // Top level type wildcard match
    router.route()
      .consumes("*/json")
      .handler(ctx -> {

        // This handler will be called for any request with sub-type json
        // e.g. content-type header set to `text/json` or
        // `application/json` will both match

      });
  }

  public void example16(Router router, String someJSON) {

    router.route()
      .produces("application/json")
      .handler(ctx -> {

        HttpServerResponse response = ctx.response();
        response.putHeader("content-type", "application/json");
        response.end(someJSON);

      });
  }

  public void example17(Router router, String whatever) {

    // This route can produce two different MIME types
    router.route()
      .produces("application/json")
      .produces("text/html")
      .handler(ctx -> {

        HttpServerResponse response = ctx.response();

        // Get the actual MIME type acceptable
        String acceptableContentType = ctx.getAcceptableContentType();

        response.putHeader("content-type", acceptableContentType);
        response.end(whatever);
      });
  }

  public void example18(Router router) {

    router.route(HttpMethod.PUT, "myapi/orders")
      .consumes("application/json")
      .produces("application/json")
      .handler(ctx -> {

        // This would be match for any PUT method to paths starting
        // with "myapi/orders" with a content-type of "application/json"
        // and an accept header matching "application/json"

      });

  }

  public void example20(Router router) {

    Route route = router.route("/some/path/");
    route.handler(ctx -> {

      HttpServerResponse response = ctx.response();
      // enable chunked responses because we will be adding data as
      // we execute over other handlers. This is only required once and
      // only if several handlers do output.
      response.setChunked(true);

      response.write("route1\n");

      // Call the next matching route after a 5 second delay
      ctx.vertx().setTimer(5000, tid -> ctx.next());
    });

    route.handler(ctx -> {

      HttpServerResponse response = ctx.response();
      response.write("route2\n");

      // Call the next matching route after a 5 second delay
      ctx.vertx().setTimer(5000, tid -> ctx.next());
    });

    route.handler(ctx -> {

      HttpServerResponse response = ctx.response();
      response.write("route3");

      // Now end the response
      ctx.response().end();
    });

  }

  public void example20_1(Router router, SomeLegacyService service) {

    router.route().blockingHandler(ctx -> {

      // Do something that might take some time synchronously
      service.doSomethingThatBlocks();

      // Now call the next handler
      ctx.next();

    });
  }

  public void example20_2(Router router) {
    router.post("/some/endpoint").handler(ctx -> {
      ctx.request().setExpectMultipart(true);
      ctx.next();
    }).blockingHandler(ctx -> {
      // ... Do some blocking operation
    });
  }

  interface SomeLegacyService {

    void doSomethingThatBlocks();
  }

  public void example21(Router router) {

    router.get("/some/path").handler(ctx -> {

      ctx.put("foo", "bar");
      ctx.next();

    });

    router.get("/some/path/other").handler(ctx -> {

      String bar = ctx.get("foo");
      // Do something with bar
      ctx.response().end();

    });

  }

  public void example22(Vertx vertx, String productJSON) {

    Router restAPI = Router.router(vertx);

    restAPI.get("/products/:productID").handler(ctx -> {

      // TODO Handle the lookup of the product....
      ctx.response().write(productJSON);

    });

    restAPI.put("/products/:productID").handler(ctx -> {

      // TODO Add a new product...
      ctx.response().end();

    });

    restAPI.delete("/products/:productID").handler(ctx -> {

      // TODO delete the product...
      ctx.response().end();

    });
  }

  public void example23(Vertx vertx, Handler<RoutingContext> myStaticHandler, Handler<RoutingContext> myTemplateHandler) {
    Router mainRouter = Router.router(vertx);

    // Handle static resources
    mainRouter.route("/static/*").handler(myStaticHandler);

    mainRouter.route(".*\\.templ").handler(myTemplateHandler);
  }

  public void example24(Router mainRouter, Router restAPI) {

    mainRouter.mountSubRouter("/productsAPI", restAPI);

  }

  public void example25(Router router) {

    Route route = router.get("/somepath/*");

    route.failureHandler(ctx -> {

      // This will be called for failures that occur
      // when routing requests to paths starting with
      // '/somepath/'

    });
  }

  public void example26(Router router) {

    Route route1 = router.get("/somepath/path1/");

    route1.handler(ctx -> {

      // Let's say this throws a RuntimeException
      throw new RuntimeException("something happened!");

    });

    Route route2 = router.get("/somepath/path2");

    route2.handler(ctx -> {

      // This one deliberately fails the request passing in the status code
      // E.g. 403 - Forbidden
      ctx.fail(403);

    });

    // Define a failure handler
    // This will get called for any failures in the above handlers
    Route route3 = router.get("/somepath/*");

    route3.failureHandler(failureRoutingContext -> {

      int statusCode = failureRoutingContext.statusCode();

      // Status code will be 500 for the RuntimeException
      // or 403 for the other failure
      HttpServerResponse response = failureRoutingContext.response();
      response.setStatusCode(statusCode).end("Sorry! Not today");

    });

  }

  public void example27(Router router) {

    // This body handler will be called for all routes
    router.route().handler(BodyHandler.create());

  }

  public void example27_1(Router router) {

    router.route().handler(ctx -> {

      HttpServerRequest request = ctx.request();

      // Pause the request
      request.pause();

      someAsyncCall(result -> {

        // Resume the request
        request.resume();

        // And continue processing
        ctx.next();
      });
    });

    // This body handler will be called for all routes
    router.route().handler(BodyHandler.create());
  }

  private void someAsyncCall(Handler<Void> handler) {
  }

  public void example28(Router router) {

    router.route().handler(BodyHandler.create());

    router.post("/some/path/uploads").handler(ctx -> {

      Set<FileUpload> uploads = ctx.fileUploads();
      // Do something with uploads....

    });
  }

  public void example30(RoutingContext ctx) {

    Cookie someCookie = ctx.getCookie("mycookie");
    String cookieValue = someCookie.getValue();

    // Do something with cookie...

    // Add a cookie - this will get written back in the response automatically
    ctx.addCookie(Cookie.cookie("othercookie", "somevalue"));
  }

  public void example31(Vertx vertx) {

    // Create a local session store using defaults
    SessionStore store1 = LocalSessionStore.create(vertx);

    // Create a local session store specifying the local shared map name to use
    // This might be useful if you have more than one application in the same
    // Vert.x instance and want to use different maps for different applications
    SessionStore store2 = LocalSessionStore.create(
      vertx,
      "myapp3.sessionmap");

    // Create a local session store specifying the local shared map name to use and
    // setting the reaper interval for expired sessions to 10 seconds
    SessionStore store3 = LocalSessionStore.create(
      vertx,
      "myapp3.sessionmap",
      10000);

  }

  public void example32() {

    // a clustered Vert.x
    Vertx.clusteredVertx(new VertxOptions(), res -> {

      Vertx vertx = res.result();

      // Create a clustered session store using defaults
      SessionStore store1 = ClusteredSessionStore.create(vertx);

      // Create a clustered session store specifying the distributed map name to use
      // This might be useful if you have more than one application in the cluster
      // and want to use different maps for different applications
      SessionStore store2 = ClusteredSessionStore.create(
        vertx,
        "myclusteredapp3.sessionmap");
    });

  }

  public void example33(Vertx vertx) {

    Router router = Router.router(vertx);

    // Create a clustered session store using defaults
    SessionStore store = ClusteredSessionStore.create(vertx);

    SessionHandler sessionHandler = SessionHandler.create(store);

    // the session handler controls the cookie used for the session
    // this includes configuring, for example, the same site policy
    // like this, for strict same site policy.
    sessionHandler.setCookieSameSite(CookieSameSite.STRICT);

    // Make sure all requests are routed through the session handler too
    router.route().handler(sessionHandler);

    // Now your application handlers
    router.route("/somepath/blah/").handler(ctx -> {

      Session session = ctx.session();
      session.put("foo", "bar");
      // etc

    });

  }

  public void example34(SessionHandler sessionHandler, Router router) {

    router.route().handler(sessionHandler);

    // Now your application handlers
    router.route("/somepath/blah").handler(ctx -> {

      Session session = ctx.session();

      // Put some data from the session
      session.put("foo", "bar");

      // Retrieve some data from a session
      int age = session.get("age");

      // Remove some data from a session
      JsonObject obj = session.remove("myobj");

    });

  }


  public void example37(Vertx vertx, AuthenticationProvider authProvider, Router router) {

    router.route().handler(SessionHandler.create(LocalSessionStore.create(vertx)));

    AuthenticationHandler basicAuthHandler = BasicAuthHandler.create(authProvider);
  }

  public void example38(Vertx vertx, AuthenticationProvider authProvider, Router router) {

    router.route().handler(SessionHandler.create(LocalSessionStore.create(vertx)));

    AuthenticationHandler basicAuthHandler = BasicAuthHandler.create(authProvider);

    // All requests to paths starting with '/private/' will be protected
    router.route("/private/*").handler(basicAuthHandler);

    router.route("/someotherpath").handler(ctx -> {

      // This will be public access - no login required

    });

    router.route("/private/somepath").handler(ctx -> {

      // This will require a login

      // This will have the value true
      boolean isAuthenticated = ctx.user() != null;

    });
  }

  public void example39(Vertx vertx, AuthenticationProvider authProvider, Router router) {

    router.route().handler(SessionHandler.create(LocalSessionStore.create(vertx)));

    // All requests to paths starting with '/private/' will be protected
    router
      .route("/private/*")
      .handler(RedirectAuthHandler.create(authProvider));

    // Handle the actual login
    // One of your pages must POST form login data
    router.post("/login").handler(FormLoginHandler.create(authProvider));

    // Set a static server to serve static resources, e.g. the login page
    router.route().handler(StaticHandler.create());

    router
      .route("/someotherpath")
      .handler(ctx -> {
        // This will be public access - no login required
      });

    router
      .route("/private/somepath")
      .handler(ctx -> {

        // This will require a login

        // This will have the value true
        boolean isAuthenticated = ctx.user() != null;

      });

  }

  public void example40_a(AuthorizationProvider authProvider, Router router) {
    // attest that all requests on the route match the required authorization
    router.route().handler(
      // create the handler that will perform the attestation
      AuthorizationHandler.create(
        // what to attest
        PermissionBasedAuthorization.create("can-do-work"))
        // where to lookup the authorizations for the user
        .addAuthorizationProvider(authProvider));
  }


  public void example40(AuthorizationProvider authProvider, Router router) {
    // Need "list_products" authorization to list products
    router.route("/listproducts/*").handler(
      // create the handler that will perform the attestation
      AuthorizationHandler.create(
        // what to attest
        PermissionBasedAuthorization.create("list_products"))
        // where to lookup the authorizations for the user
        .addAuthorizationProvider(authProvider));

    // Only "admin" has access to /private/settings
    router.route("/private/settings/*").handler(
      // create the handler that will perform the attestation
      AuthorizationHandler.create(
        // what to attest
        RoleBasedAuthorization.create("admin"))
        .addAuthorizationProvider(authProvider));
  }

  public void example41(Router router) {

    router.route("/static/*").handler(StaticHandler.create());

  }

  public void example41_0_1(Router router) {

    // Will only accept GET requests from origin "vertx.io"
    router.route()
      .handler(
        CorsHandler.create("vertx\\.io")
          .allowedMethod(HttpMethod.GET));

    router.route().handler(ctx -> {

      // Your app handlers

    });
  }

  public void example41_2(Router router, TemplateEngine engine) {

    TemplateHandler handler = TemplateHandler.create(engine);

    router.get("/dynamic").handler(ctx -> {

      ctx.put("request_path", ctx.request().path());
      ctx.put("session_data", ctx.session().data());

      ctx.next();
    });

    router.get("/dynamic/").handler(handler);

  }

  public void example41_3(Vertx vertx, Router router) {

    // Any errors on paths beginning with '/somepath/' will
    // be handled by this error handler
    router.route("/somepath/").failureHandler(ErrorHandler.create(vertx));

  }


  public void example42(Router router) {

    router.route("/foo/").handler(TimeoutHandler.create(5000));

  }

  public void example43(Vertx vertx) {

    Router router = Router.router(vertx);

    SockJSHandlerOptions options = new SockJSHandlerOptions()
      .setHeartbeatInterval(2000);

    SockJSHandler sockJSHandler = SockJSHandler.create(vertx, options);

  }

  public void example44(Vertx vertx) {

    Router router = Router.router(vertx);

    SockJSHandlerOptions options = new SockJSHandlerOptions()
      .setHeartbeatInterval(2000);

    SockJSHandler sockJSHandler = SockJSHandler.create(vertx, options);

    router.mountSubRouter("/myapp", sockJSHandler.socketHandler(sockJSSocket -> {

      // Just echo the data back
      sockJSSocket.handler(sockJSSocket::write);

    }));

  }

  public void sockJsWriteHandler(Vertx vertx) {
    Router router = Router.router(vertx);

    SockJSHandlerOptions options = new SockJSHandlerOptions().setRegisterWriteHandler(true);

    SockJSHandler sockJSHandler = SockJSHandler.create(vertx, options);

    router.mountSubRouter("/myapp", sockJSHandler.socketHandler(sockJSSocket -> {

      // Retrieve the writeHandlerID and store it (e.g. in a local map)
      String writeHandlerID = sockJSSocket.writeHandlerID();

    }));

  }

  public void sockJsSendBufferEventBus(EventBus eventBus, String writeHandlerID) {

    // Send buffers directly to the SockJS socket

    eventBus.send(writeHandlerID, Buffer.buffer("foo"));

  }


  public void example45(Vertx vertx) {

    Router router = Router.router(vertx);

    SockJSHandler sockJSHandler = SockJSHandler.create(vertx);
    SockJSBridgeOptions options = new SockJSBridgeOptions();
    // mount the bridge on the router
    router.mountSubRouter("/eventbus", sockJSHandler.bridge(options));
  }

  public void example46(Vertx vertx) {

    Router router = Router.router(vertx);

    SockJSHandler sockJSHandler = SockJSHandler.create(vertx);


    // Let through any messages sent to 'demo.orderMgr' from the client
    PermittedOptions inboundPermitted1 = new PermittedOptions()
      .setAddress("demo.orderMgr");

    // Allow calls to the address 'demo.persistor' from the client as
    // long as the messages have an action field with value 'find'
    // and a collection field with value 'albums'
    PermittedOptions inboundPermitted2 = new PermittedOptions()
      .setAddress("demo.persistor")
      .setMatch(new JsonObject().put("action", "find")
        .put("collection", "albums"));

    // Allow through any message with a field `wibble` with value `foo`.
    PermittedOptions inboundPermitted3 = new PermittedOptions()
      .setMatch(new JsonObject().put("wibble", "foo"));

    // First let's define what we're going to allow from server -> client

    // Let through any messages coming from address 'ticker.mystock'
    PermittedOptions outboundPermitted1 = new PermittedOptions()
      .setAddress("ticker.mystock");

    // Let through any messages from addresses starting with "news."
    // (e.g. news.europe, news.usa, etc)
    PermittedOptions outboundPermitted2 = new PermittedOptions()
      .setAddressRegex("news\\..+");

    // Let's define what we're going to allow from client -> server
    SockJSBridgeOptions options = new SockJSBridgeOptions().
      addInboundPermitted(inboundPermitted1).
      addInboundPermitted(inboundPermitted1).
      addInboundPermitted(inboundPermitted3).
      addOutboundPermitted(outboundPermitted1).
      addOutboundPermitted(outboundPermitted2);

    // mount the bridge on the router
    router.mountSubRouter("/eventbus", sockJSHandler.bridge(options));
  }

  public void example47() {

    // Let through any messages sent to 'demo.orderService' from the client
    PermittedOptions inboundPermitted = new PermittedOptions()
      .setAddress("demo.orderService");

    // But only if the user is logged in and has the authority "place_orders"
    inboundPermitted.setRequiredAuthority("place_orders");

    SockJSBridgeOptions options = new SockJSBridgeOptions()
      .addInboundPermitted(inboundPermitted);
  }

  public void example48(Vertx vertx, AuthenticationProvider authProvider) {

    Router router = Router.router(vertx);

    // Let through any messages sent to 'demo.orderService' from the client
    PermittedOptions inboundPermitted = new PermittedOptions()
      .setAddress("demo.orderService");

    // But only if the user is logged in and has the authority "place_orders"
    inboundPermitted.setRequiredAuthority("place_orders");

    SockJSHandler sockJSHandler = SockJSHandler.create(vertx);

    // Now set up some basic auth handling:

    router.route().handler(SessionHandler.create(LocalSessionStore.create(vertx)));

    AuthenticationHandler basicAuthHandler = BasicAuthHandler.create(authProvider);

    router.route("/eventbus/*").handler(basicAuthHandler);

    // mount the bridge on the router
    router.mountSubRouter(
      "/eventbus",
      sockJSHandler.bridge(new SockJSBridgeOptions()
        .addInboundPermitted(inboundPermitted)));
  }

  public void example48_1(Vertx vertx) {

    Router router = Router.router(vertx);

    // Let through any messages sent to 'demo.orderService' from the client
    PermittedOptions inboundPermitted = new PermittedOptions()
      .setAddress("demo.orderService");

    SockJSHandler sockJSHandler = SockJSHandler.create(vertx);
    SockJSBridgeOptions options = new SockJSBridgeOptions()
      .addInboundPermitted(inboundPermitted);

    // mount the bridge on the router
    router.mountSubRouter(
      "/eventbus",
      sockJSHandler.bridge(options, be -> {
        if (
          be.type() == BridgeEventType.PUBLISH ||
            be.type() == BridgeEventType.SEND) {

          // Add some headers
          JsonObject headers = new JsonObject()
            .put("header1", "val")
            .put("header2", "val2");

          JsonObject rawMessage = be.getRawMessage();
          rawMessage.put("headers", headers);
          be.setRawMessage(rawMessage);
        }
        be.complete(true);
      }));
  }

  public void example49(Vertx vertx) {

    Router router = Router.router(vertx);

    // Let through any messages sent to 'demo.orderMgr' from the client
    PermittedOptions inboundPermitted = new PermittedOptions()
      .setAddress("demo.someService");

    SockJSHandler sockJSHandler = SockJSHandler.create(vertx);
    SockJSBridgeOptions options = new SockJSBridgeOptions()
      .addInboundPermitted(inboundPermitted);

    // mount the bridge on the router
    router
      .mountSubRouter("/eventbus", sockJSHandler
        .bridge(options, be -> {
          if (be.type() == BridgeEventType.PUBLISH ||
            be.type() == BridgeEventType.RECEIVE) {

            if (be.getRawMessage().getString("body").equals("armadillos")) {
              // Reject it
              be.complete(false);
              return;
            }
          }
          be.complete(true);
        }));
  }

  public void handleSocketIdle(Vertx vertx, PermittedOptions inboundPermitted) {
    Router router = Router.router(vertx);

    // Initialize SockJS handler
    SockJSHandler sockJSHandler = SockJSHandler.create(vertx);
    SockJSBridgeOptions options = new SockJSBridgeOptions()
      .addInboundPermitted(inboundPermitted)
      .setPingTimeout(5000);

    // mount the bridge on the router
    router
      .mountSubRouter("/eventbus", sockJSHandler.bridge(options, be -> {
        if (be.type() == BridgeEventType.SOCKET_IDLE) {
          // Do some custom handling...
        }

        be.complete(true);
      }));
  }

  public void example50(Vertx vertx) {

    Router router = Router.router(vertx);

    JWTAuthOptions authConfig = new JWTAuthOptions()
      .setKeyStore(new KeyStoreOptions()
        .setType("jceks")
        .setPath("keystore.jceks")
        .setPassword("secret"));

    JWTAuth jwt = JWTAuth.create(vertx, authConfig);

    router.route("/login").handler(ctx -> {
      // this is an example, authentication should be done with another provider...
      if (
        "paulo".equals(ctx.request().getParam("username")) &&
          "secret".equals(ctx.request().getParam("password"))) {
        ctx.response()
          .end(jwt.generateToken(new JsonObject().put("sub", "paulo")));
      } else {
        ctx.fail(401);
      }
    });
  }

  public void example51(Vertx vertx) {

    Router router = Router.router(vertx);

    JWTAuthOptions authConfig = new JWTAuthOptions()
      .setKeyStore(new KeyStoreOptions()
        .setType("jceks")
        .setPath("keystore.jceks")
        .setPassword("secret"));

    JWTAuth authProvider = JWTAuth.create(vertx, authConfig);

    router.route("/protected/*").handler(JWTAuthHandler.create(authProvider));

    router.route("/protected/somepage").handler(ctx -> {
      // some handle code...
    });
  }

  public void example52(Vertx vertx) {

    JWTAuthOptions authConfig = new JWTAuthOptions()
      .setKeyStore(new KeyStoreOptions()
        .setType("jceks")
        .setPath("keystore.jceks")
        .setPassword("secret"));

    JWTAuth authProvider = JWTAuth.create(vertx, authConfig);

    authProvider
      .generateToken(
        new JsonObject()
          .put("sub", "paulo")
          .put("someKey", "some value"),
        new JWTOptions());
  }

  public void example53(Vertx vertx) {

    Handler<RoutingContext> handler = ctx -> {
      String theSubject = ctx.user().principal().getString("sub");
      String someKey = ctx.user().principal().getString("someKey");
    };
  }

  public void example54(Vertx vertx, Router router) {

    router.route().handler(CSRFHandler.create(vertx, "abracadabra"));
    router.route().handler(ctx -> {

    });
  }

  public void example55(Router router) {

    router.get("/some/path").handler(ctx -> {

      ctx.put("foo", "bar");
      ctx.next();

    });

    router
      .get("/some/path/B")
      .handler(ctx -> ctx.response().end());

    router
      .get("/some/path")
      .handler(ctx -> ctx.reroute("/some/path/B"));

  }

  public void example55b(Router router) {

    router.get("/my-pretty-notfound-handler").handler(ctx -> ctx.response()
      .setStatusCode(404)
      .end("NOT FOUND fancy html here!!!"));

    router.get().failureHandler(ctx -> {
      if (ctx.statusCode() == 404) {
        ctx.reroute("/my-pretty-notfound-handler");
      } else {
        ctx.next();
      }
    });
  }

  public void example55c(Router router) {

    router.get("/final-target").handler(ctx -> {
      // continue from here...
    });

    // (Will reroute to /final-target including the query string)
    router.get().handler(ctx -> ctx.reroute("/final-target?variable=value"));

    // A safer way would be to add the variable to the context
    router.get().handler(ctx -> ctx
      .put("variable", "value")
      .reroute("/final-target"));
  }

  public void example56(Router router) {
    router.route().virtualHost("*.vertx.io").handler(ctx -> {
      // do something if the request is for *.vertx.io
    });
  }

  public void example57(Router router) {

    Route route = router.get("/localized").handler(ctx -> {
      // although it might seem strange by running a loop with a switch we
      // make sure that the locale order of preference is preserved when
      // replying in the users language.
      for (LanguageHeader language : ctx.acceptableLanguages()) {
        switch (language.tag()) {
          case "en":
            ctx.response().end("Hello!");
            return;
          case "fr":
            ctx.response().end("Bonjour!");
            return;
          case "pt":
            ctx.response().end("Olá!");
            return;
          case "es":
            ctx.response().end("Hola!");
            return;
        }
      }
      // we do not know the user language so lets just inform that back:
      ctx.response().end("Sorry we don't speak: " + ctx.preferredLanguage());
    });
  }

  public void example58(Vertx vertx, Router router) {

    // create an OAuth2 provider, clientID and clientSecret
    // should be requested to github
    OAuth2Auth authProvider = GithubAuth
      .create(vertx, "CLIENT_ID", "CLIENT_SECRET");

    // create a oauth2 handler on our running server
    // the second argument is the full url to the
    // callback as you entered in your provider management console.
    OAuth2AuthHandler oauth2 = OAuth2AuthHandler
      .create(vertx, authProvider, "https://myserver.com/callback");

    // setup the callback handler for receiving the GitHub callback
    oauth2.setupCallback(router.route());

    // protect everything under /protected
    router.route("/protected/*").handler(oauth2);
    // mount some handler under the protected zone
    router
      .route("/protected/somepage")
      .handler(ctx -> ctx.response().end("Welcome to the protected resource!"));

    // welcome page
    router
      .get("/")
      .handler(ctx -> ctx.response()
        .putHeader("content-type", "text/html")
        .end("Hello<br><a href=\"/protected/somepage\">Protected by Github</a>"));
  }

  public void example59(Vertx vertx, Router router) {

    // create an OAuth2 provider, clientID and clientSecret
    // should be requested to Google
    OAuth2Auth authProvider = OAuth2Auth.create(vertx, new OAuth2Options()
      .setClientID("CLIENT_ID")
      .setClientSecret("CLIENT_SECRET")
      .setFlow(OAuth2FlowType.AUTH_CODE)
      .setSite("https://accounts.google.com")
      .setTokenPath("https://www.googleapis.com/oauth2/v3/token")
      .setAuthorizationPath("/o/oauth2/auth"));

    // create a oauth2 handler on our domain: "http://localhost:8080"
    OAuth2AuthHandler oauth2 = OAuth2AuthHandler
      .create(vertx, authProvider, "http://localhost:8080");

    // these are the scopes
    oauth2.withScope("profile");

    // setup the callback handler for receiving the Google callback
    oauth2.setupCallback(router.get("/callback"));

    // protect everything under /protected
    router.route("/protected/*").handler(oauth2);
    // mount some handler under the protected zone
    router
      .route("/protected/somepage")
      .handler(ctx -> ctx.response().end("Welcome to the protected resource!"));

    // welcome page
    router
      .get("/")
      .handler(ctx -> ctx.response()
        .putHeader("content-type", "text/html")
        .end("Hello<br><a href=\"/protected/somepage\">Protected by Google</a>"));
  }

  public void example61(Vertx vertx, Router router, OAuth2Auth provider) {
    // create a oauth2 handler pinned to
    // myserver.com: "https://myserver.com:8447/callback"
    OAuth2AuthHandler oauth2 = OAuth2AuthHandler
      .create(vertx, provider, "https://myserver.com:8447/callback");

    // now allow the handler to setup the callback url for you
    oauth2.setupCallback(router.route());
  }

  public void example62(Vertx vertx, Router router) {
    // To simplify the development of the web components
    // we use a Router to route all HTTP requests
    // to organize our code in a reusable way.

    // Simple auth service which uses a GitHub to
    // authenticate the user
    OAuth2Auth authProvider =
      GithubAuth
        .create(vertx, "CLIENTID", "CLIENT SECRET");
    // We need a user session handler too to make sure
    // the user is stored in the session between requests
    router.route()
      .handler(SessionHandler.create(LocalSessionStore.create(vertx)));
    // we now protect the resource under the path "/protected"
    router.route("/protected").handler(
      OAuth2AuthHandler.create(vertx, authProvider)
        // we now configure the oauth2 handler, it will
        // setup the callback handler
        // as expected by your oauth2 provider.
        .setupCallback(router.route("/callback"))
        // for this resource we require that users have
        // the authority to retrieve the user emails
        .withScope("user:email")
    );
    // Entry point to the application, this will render
    // a custom template.
    router.get("/").handler(ctx -> ctx.response()
      .putHeader("Content-Type", "text/html")
      .end(
        "<html>\n" +
          "  <body>\n" +
          "    <p>\n" +
          "      Well, hello there!\n" +
          "    </p>\n" +
          "    <p>\n" +
          "      We're going to the protected resource, if there is no\n" +
          "      user in the session we will talk to the GitHub API. Ready?\n" +
          "      <a href=\"/protected\">Click here</a> to begin!</a>\n" +
          "    </p>\n" +
          "    <p>\n" +
          "      <b>If that link doesn't work</b>, remember to provide your\n" +
          "      own <a href=\"https://github.com/settings/applications/new\">\n" +
          "      Client ID</a>!\n" +
          "    </p>\n" +
          "  </body>\n" +
          "</html>"));
    // The protected resource
    router.get("/protected").handler(ctx -> {
      // at this moment your user object should contain the info
      // from the Oauth2 response, since this is a protected resource
      // as specified above in the handler config the user object is never null
      User user = ctx.user();
      // just dump it to the client for demo purposes
      ctx.response().end(user.toString());
    });
  }

  public void manualContentType(Router router) {
    router
      .get("/api/books")
      .produces("application/json")
      .handler(ctx -> findBooks()
        .onSuccess(books -> ctx.response()
          .putHeader("Content-Type", "application/json")
          .end(toJson(books))).onFailure(ctx::fail));
  }

  public void contentTypeHandler(Router router) {
    router.route("/api/*").handler(ResponseContentTypeHandler.create());
    router
      .get("/api/books")
      .produces("application/json")
      .handler(ctx -> findBooks()
        .onSuccess(books -> ctx.response()
          .end(toJson(books))).onFailure(ctx::fail));
  }

  private Future<List<Book>> findBooks() {
    throw new UnsupportedOperationException();
  }

  static class Book {
  }

  Buffer toJson(List<Book> books) {
    throw new UnsupportedOperationException();
  }

  Buffer toXML(List<Book> books) {
    throw new UnsupportedOperationException();
  }

  public void mostAcceptableContentTypeHandler(Router router) {
    router.route("/api/*").handler(ResponseContentTypeHandler.create());

    router
      .get("/api/books")
      .produces("text/xml")
      .produces("application/json")
      .handler(ctx -> findBooks()
        .onSuccess(books -> {
          if (ctx.getAcceptableContentType().equals("text/xml")) {
            ctx.response().end(toXML(books));
          } else {
            ctx.response().end(toJson(books));
          }
        })
        .onFailure(ctx::fail));
  }

  public void example63(Router router, AuthenticationProvider provider) {

    ChainAuthHandler chain = ChainAuthHandler.any();

    // add http basic auth handler to the chain
    chain.add(BasicAuthHandler.create(provider));
    // add form redirect auth handler to the chain
    chain.add(RedirectAuthHandler.create(provider));

    // secure your route
    router.route("/secure/resource").handler(chain);
    // your app
    router.route("/secure/resource").handler(ctx -> {
      // do something...
    });
  }

  public void example64(Router router) {
    router.route().handler(MethodOverrideHandler.create());

    router.route(HttpMethod.GET, "/").handler(ctx -> {
      // do GET stuff...
    });

    router.route(HttpMethod.POST, "/").handler(ctx -> {
      // do POST stuff...
    });
  }

  public void example65(Router router) {
    router.route().handler(MethodOverrideHandler.create(false));

    router.route(HttpMethod.GET, "/").handler(ctx -> {
      // do GET stuff...
    });

    router.route(HttpMethod.POST, "/").handler(ctx -> {
      // do POST stuff...
    });
  }

  public void example66(RoutingContext ctx, Buffer pdfBuffer) {
    ctx
      .attachment("weekly-report.pdf")
      .end(pdfBuffer);
  }

  public void example67(RoutingContext ctx) {

    ctx.redirect("https://securesite.com/");

    // there is a special handling for the target "back".
    // In this case the redirect would send the user to the
    // referrer url or "/" if there's no referrer.

    ctx.redirect("back");
  }

  public void example68(RoutingContext ctx, Object someObject) {
    // no need to specify the content type headers
    ctx.json(new JsonObject().put("hello", "vert.x"));
    // also applies to arrays
    ctx.json(new JsonArray().add("vertx").add("web"));
    // or any object that will be converted according
    // to the json encoder available at runtime.
    ctx.json(someObject);
  }

  public void example69(RoutingContext ctx) {
    // Check if the incoming request contains the "Content-Type"
    // get field, and it contains the give mime `type`.
    // If there is no request body, `false` is returned.
    // If there is no content type, `false` is returned.
    // Otherwise, it returns true if the `type` that matches.

    // With Content-Type: text/html; getCharset=utf-8
    ctx.is("html"); // => true
    ctx.is("text/html"); // => true

    // When Content-Type is application/json
    ctx.is("application/json"); // => true
    ctx.is("html"); // => false
  }

  public void example70(RoutingContext ctx) {
    // set the response resource meta data
    ctx.lastModified("Wed, 13 Jul 2011 18:30:00 GMT");
    // this will now be used to verify the freshness of the request
    if (ctx.isFresh()) {
      // client cache value is fresh perhaps we
      // can stop and return 304?
    }
  }

  public void example71(RoutingContext ctx, Buffer buffer) {
    // this response etag with a given value
    ctx.etag("W/123456789");

    // set the last modified value
    ctx.lastModified("Wed, 13 Jul 2011 18:30:00 GMT");

    // quickly end
    ctx.end();
    ctx.end("body");
    ctx.end(buffer);
  }

  public void example72(Router router) {
    router.route().handler(MultiTenantHandler.create("X-Tenant"));
  }

  public void example73() {
    MultiTenantHandler.create("X-Tenant")
      .addTenantHandler("tenant-A", ctx -> {
        // do something for tenant A...
      })
      .addTenantHandler("tenant-B", ctx -> {
        // do something for tenant B...
      })
      // optionally
      .addDefaultHandler(ctx -> {
        // do something when no tenant matches...
      });

  }

  public void example74(Vertx vertx, Router router) {
    // create an OAuth2 provider, clientID and clientSecret
    // should be requested to github
    OAuth2Auth gitHubAuthProvider = GithubAuth
      .create(vertx, "CLIENT_ID", "CLIENT_SECRET");

    // create a oauth2 handler on our running server
    // the second argument is the full url to the callback
    // as you entered in your provider management console.
    OAuth2AuthHandler githubOAuth2 = OAuth2AuthHandler.create(
      vertx,
      gitHubAuthProvider,
      "https://myserver.com/github-callback");

    // setup the callback handler for receiving the GitHub callback
    githubOAuth2.setupCallback(router.route());

    // create an OAuth2 provider, clientID and clientSecret
    // should be requested to Google
    OAuth2Auth googleAuthProvider = OAuth2Auth.create(vertx, new OAuth2Options()
      .setClientID("CLIENT_ID")
      .setClientSecret("CLIENT_SECRET")
      .setFlow(OAuth2FlowType.AUTH_CODE)
      .setSite("https://accounts.google.com")
      .setTokenPath("https://www.googleapis.com/oauth2/v3/token")
      .setAuthorizationPath("/o/oauth2/auth"));

    // create a oauth2 handler on our domain: "http://localhost:8080"
    OAuth2AuthHandler googleOAuth2 = OAuth2AuthHandler.create(
      vertx,
      googleAuthProvider,
      "https://myserver.com/google-callback");

    // setup the callback handler for receiving the Google callback
    googleOAuth2.setupCallback(router.route());

    // At this point the 2 callbacks endpoints are registered:

    // /github-callback -> handle github Oauth2 callbacks
    // /google-callback -> handle google Oauth2 callbacks

    // As the callbacks are made by the IdPs there's no header
    // to identify the source, hence the need of custom URLs

    // However for out Application we can control it so later
    // we can add the right handler for the right tenant

    router.route().handler(
      MultiTenantHandler.create("X-Tenant")
        // tenants using github should go this way:
        .addTenantHandler("github", githubOAuth2)
        // tenants using google should go this way:
        .addTenantHandler("google", googleOAuth2)
        // all other should be forbidden
        .addDefaultHandler(ctx -> ctx.fail(401)));

    // Proceed using the router as usual.
  }

  public void example81(Router router) {

    router.route().handler(ctx -> {
      // the default key is "tenant" as defined in
      // MultiTenantHandler.TENANT but this value can be
      // modified at creation time in the factory method
      String tenant = ctx.get(MultiTenantHandler.TENANT);

      switch(tenant) {
        case "google":
          // do something for google users
          break;
        case "github":
          // so something for github users
          break;
      }
    });
  }

  public void example75(Vertx vertx, Router router, Function<Authenticator, Future<List<Authenticator>>> fetcher, Function<Authenticator, Future<Void>> updater) {
    // create the webauthn security object
    WebAuthn webAuthn = WebAuthn.create(
      vertx,
      new WebAuthnOptions()
        .setRelyingParty(new RelyingParty().setName("Vert.x WebAuthN Demo"))
        // What kind of authentication do you want? do you care?
        // # security keys
        .setAuthenticatorAttachment(AuthenticatorAttachment.CROSS_PLATFORM)
        // # fingerprint
        .setAuthenticatorAttachment(AuthenticatorAttachment.PLATFORM)
        .setUserVerification(UserVerification.REQUIRED))
      // where to load the credentials from?
      .authenticatorFetcher(fetcher)
      // update the state of an authenticator
      .authenticatorUpdater(updater);

    // parse the BODY
    router.post()
      .handler(BodyHandler.create());
    // add a session handler
    router.route()
      .handler(SessionHandler
        .create(LocalSessionStore.create(vertx)));

    // security handler
    WebAuthnHandler webAuthNHandler = WebAuthnHandler.create(webAuthn)
      .setOrigin("https://192.168.178.74.xip.io:8443")
      // required callback
      .setupCallback(router.post("/webauthn/response"))
      // optional register callback
      .setupCredentialsCreateCallback(router.post("/webauthn/register"))
      // optional login callback
      .setupCredentialsGetCallback(router.post("/webauthn/login"));

    // secure the remaining routes
    router.route().handler(webAuthNHandler);
  }

  public void example76(Vertx vertx, Router router) {
    // we can now allow forward header parsing
    // and in this case only the "Forward" header will be considered
    router.allowForward(AllowForwardHeaders.FORWARD);

    // we can now allow forward header parsing
    // and in this case only the "X-Forward" headers will be considered
    router.allowForward(AllowForwardHeaders.X_FORWARD);

    // we can now allow forward header parsing
    // and in this case both the "Forward" header and "X-Forward" headers
    // will be considered, yet the values from "Forward" take precedence
    // this means if case of a conflict (2 headers for the same value)
    // the "Forward" value will be taken and the "X-Forward" ignored.
    router.allowForward(AllowForwardHeaders.ALL);
  }

  public void example77(Vertx vertx, Router router) {
    // we explicitly not allow forward header parsing
    // of any kind
    router.allowForward(AllowForwardHeaders.NONE);
  }

  public void example78(Router router, AuthenticationHandler authNHandlerA, AuthenticationHandler authNHandlerB, AuthenticationHandler authNHandlerC) {

    // Chain will verify (A Or (B And C))
    ChainAuthHandler chain =
      ChainAuthHandler.any()
        .add(authNHandlerA)
        .add(ChainAuthHandler.all()
          .add(authNHandlerB)
          .add(authNHandlerC));

    // secure your route
    router.route("/secure/resource").handler(chain);
    // your app
    router.route("/secure/resource").handler(ctx -> {
      // do something...
    });
  }

  public void example78(Router router, SessionHandler sessionHandler) {

    router.route().handler(ctx -> sessionHandler.flush(ctx)
      .onSuccess(v -> ctx.end("Success!"))
      .onFailure(err -> {
        // session wasn't saved...
        // go for plan B
      }));
  }

  public void example79(Router router, SessionStore store) {

    router.route()
      .handler(SessionHandler.create(store).setCookieless(true));
  }

  public void example80(Router router) {

    // all responses will then include the right
    // Strict-Transport-Security header if the
    // connection is secure (using TLS/SSL, or
    // the forwarding parsing is enabled
    router.route().handler(HSTSHandler.create());
  }

  @DataObject
  static class Pojo {}

  public void example82(Router router) {

    router
      .get("/some/path")
      // this handler will ensure that the response is serialized to json
      // the content type is set to "application/json"
      .respond(
        ctx -> Future.succeededFuture(new JsonObject().put("hello", "world")));

    router
      .get("/some/path")
      // this handler will ensure that the Pojo is serialized to json
      // the content type is set to "application/json"
      .respond(
        ctx -> Future.succeededFuture(new Pojo()));
  }

  public void example83(Router router) {

    router
      .get("/some/path")
      .respond(
        ctx -> ctx
          .response()
            .putHeader("Content-Type", "text/plain")
            .end("hello world!"));

    router
      .get("/some/path")
      // in this case, the handler ensures that the connection is ended
      .respond(
        ctx -> ctx
          .response()
            .setChunked(true)
            .write("Write some text..."));
  }

  public void example84(Router router) {

    // all responses will then include the right
    // Content-Security-Policy header allowing sub-domain
    // sources to be fetched from the parent "trusted.com" domain
    router.route().handler(
      CSPHandler.create()
        .addDirective("default-src", "*.trusted.com"));
  }
}
