All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jooby.Route Maven / Gradle / Ivy

The newest version!
/**
 *                                  Apache License
 *                            Version 2.0, January 2004
 *                         http://www.apache.org/licenses/
 *
 *    TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
 *
 *    1. Definitions.
 *
 *       "License" shall mean the terms and conditions for use, reproduction,
 *       and distribution as defined by Sections 1 through 9 of this document.
 *
 *       "Licensor" shall mean the copyright owner or entity authorized by
 *       the copyright owner that is granting the License.
 *
 *       "Legal Entity" shall mean the union of the acting entity and all
 *       other entities that control, are controlled by, or are under common
 *       control with that entity. For the purposes of this definition,
 *       "control" means (i) the power, direct or indirect, to cause the
 *       direction or management of such entity, whether by contract or
 *       otherwise, or (ii) ownership of fifty percent (50%) or more of the
 *       outstanding shares, or (iii) beneficial ownership of such entity.
 *
 *       "You" (or "Your") shall mean an individual or Legal Entity
 *       exercising permissions granted by this License.
 *
 *       "Source" form shall mean the preferred form for making modifications,
 *       including but not limited to software source code, documentation
 *       source, and configuration files.
 *
 *       "Object" form shall mean any form resulting from mechanical
 *       transformation or translation of a Source form, including but
 *       not limited to compiled object code, generated documentation,
 *       and conversions to other media types.
 *
 *       "Work" shall mean the work of authorship, whether in Source or
 *       Object form, made available under the License, as indicated by a
 *       copyright notice that is included in or attached to the work
 *       (an example is provided in the Appendix below).
 *
 *       "Derivative Works" shall mean any work, whether in Source or Object
 *       form, that is based on (or derived from) the Work and for which the
 *       editorial revisions, annotations, elaborations, or other modifications
 *       represent, as a whole, an original work of authorship. For the purposes
 *       of this License, Derivative Works shall not include works that remain
 *       separable from, or merely link (or bind by name) to the interfaces of,
 *       the Work and Derivative Works thereof.
 *
 *       "Contribution" shall mean any work of authorship, including
 *       the original version of the Work and any modifications or additions
 *       to that Work or Derivative Works thereof, that is intentionally
 *       submitted to Licensor for inclusion in the Work by the copyright owner
 *       or by an individual or Legal Entity authorized to submit on behalf of
 *       the copyright owner. For the purposes of this definition, "submitted"
 *       means any form of electronic, verbal, or written communication sent
 *       to the Licensor or its representatives, including but not limited to
 *       communication on electronic mailing lists, source code control systems,
 *       and issue tracking systems that are managed by, or on behalf of, the
 *       Licensor for the purpose of discussing and improving the Work, but
 *       excluding communication that is conspicuously marked or otherwise
 *       designated in writing by the copyright owner as "Not a Contribution."
 *
 *       "Contributor" shall mean Licensor and any individual or Legal Entity
 *       on behalf of whom a Contribution has been received by Licensor and
 *       subsequently incorporated within the Work.
 *
 *    2. Grant of Copyright License. Subject to the terms and conditions of
 *       this License, each Contributor hereby grants to You a perpetual,
 *       worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 *       copyright license to reproduce, prepare Derivative Works of,
 *       publicly display, publicly perform, sublicense, and distribute the
 *       Work and such Derivative Works in Source or Object form.
 *
 *    3. Grant of Patent License. Subject to the terms and conditions of
 *       this License, each Contributor hereby grants to You a perpetual,
 *       worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 *       (except as stated in this section) patent license to make, have made,
 *       use, offer to sell, sell, import, and otherwise transfer the Work,
 *       where such license applies only to those patent claims licensable
 *       by such Contributor that are necessarily infringed by their
 *       Contribution(s) alone or by combination of their Contribution(s)
 *       with the Work to which such Contribution(s) was submitted. If You
 *       institute patent litigation against any entity (including a
 *       cross-claim or counterclaim in a lawsuit) alleging that the Work
 *       or a Contribution incorporated within the Work constitutes direct
 *       or contributory patent infringement, then any patent licenses
 *       granted to You under this License for that Work shall terminate
 *       as of the date such litigation is filed.
 *
 *    4. Redistribution. You may reproduce and distribute copies of the
 *       Work or Derivative Works thereof in any medium, with or without
 *       modifications, and in Source or Object form, provided that You
 *       meet the following conditions:
 *
 *       (a) You must give any other recipients of the Work or
 *           Derivative Works a copy of this License; and
 *
 *       (b) You must cause any modified files to carry prominent notices
 *           stating that You changed the files; and
 *
 *       (c) You must retain, in the Source form of any Derivative Works
 *           that You distribute, all copyright, patent, trademark, and
 *           attribution notices from the Source form of the Work,
 *           excluding those notices that do not pertain to any part of
 *           the Derivative Works; and
 *
 *       (d) If the Work includes a "NOTICE" text file as part of its
 *           distribution, then any Derivative Works that You distribute must
 *           include a readable copy of the attribution notices contained
 *           within such NOTICE file, excluding those notices that do not
 *           pertain to any part of the Derivative Works, in at least one
 *           of the following places: within a NOTICE text file distributed
 *           as part of the Derivative Works; within the Source form or
 *           documentation, if provided along with the Derivative Works; or,
 *           within a display generated by the Derivative Works, if and
 *           wherever such third-party notices normally appear. The contents
 *           of the NOTICE file are for informational purposes only and
 *           do not modify the License. You may add Your own attribution
 *           notices within Derivative Works that You distribute, alongside
 *           or as an addendum to the NOTICE text from the Work, provided
 *           that such additional attribution notices cannot be construed
 *           as modifying the License.
 *
 *       You may add Your own copyright statement to Your modifications and
 *       may provide additional or different license terms and conditions
 *       for use, reproduction, or distribution of Your modifications, or
 *       for any such Derivative Works as a whole, provided Your use,
 *       reproduction, and distribution of the Work otherwise complies with
 *       the conditions stated in this License.
 *
 *    5. Submission of Contributions. Unless You explicitly state otherwise,
 *       any Contribution intentionally submitted for inclusion in the Work
 *       by You to the Licensor shall be under the terms and conditions of
 *       this License, without any additional terms or conditions.
 *       Notwithstanding the above, nothing herein shall supersede or modify
 *       the terms of any separate license agreement you may have executed
 *       with Licensor regarding such Contributions.
 *
 *    6. Trademarks. This License does not grant permission to use the trade
 *       names, trademarks, service marks, or product names of the Licensor,
 *       except as required for reasonable and customary use in describing the
 *       origin of the Work and reproducing the content of the NOTICE file.
 *
 *    7. Disclaimer of Warranty. Unless required by applicable law or
 *       agreed to in writing, Licensor provides the Work (and each
 *       Contributor provides its Contributions) on an "AS IS" BASIS,
 *       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 *       implied, including, without limitation, any warranties or conditions
 *       of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
 *       PARTICULAR PURPOSE. You are solely responsible for determining the
 *       appropriateness of using or redistributing the Work and assume any
 *       risks associated with Your exercise of permissions under this License.
 *
 *    8. Limitation of Liability. In no event and under no legal theory,
 *       whether in tort (including negligence), contract, or otherwise,
 *       unless required by applicable law (such as deliberate and grossly
 *       negligent acts) or agreed to in writing, shall any Contributor be
 *       liable to You for damages, including any direct, indirect, special,
 *       incidental, or consequential damages of any character arising as a
 *       result of this License or out of the use or inability to use the
 *       Work (including but not limited to damages for loss of goodwill,
 *       work stoppage, computer failure or malfunction, or any and all
 *       other commercial damages or losses), even if such Contributor
 *       has been advised of the possibility of such damages.
 *
 *    9. Accepting Warranty or Additional Liability. While redistributing
 *       the Work or Derivative Works thereof, You may choose to offer,
 *       and charge a fee for, acceptance of support, warranty, indemnity,
 *       or other liability obligations and/or rights consistent with this
 *       License. However, in accepting such obligations, You may act only
 *       on Your own behalf and on Your sole responsibility, not on behalf
 *       of any other Contributor, and only if You agree to indemnify,
 *       defend, and hold each Contributor harmless for any liability
 *       incurred by, or claims asserted against, such Contributor by reason
 *       of your accepting any such warranty or additional liability.
 *
 *    END OF TERMS AND CONDITIONS
 *
 *    APPENDIX: How to apply the Apache License to your work.
 *
 *       To apply the Apache License to your work, attach the following
 *       boilerplate notice, with the fields enclosed by brackets "{}"
 *       replaced with your own identifying information. (Don't include
 *       the brackets!)  The text should be enclosed in the appropriate
 *       comment syntax for the file format. We also recommend that a
 *       file or class name and description of purpose be included on the
 *       same "printed page" as the copyright notice for easier
 *       identification within third-party archives.
 *
 *    Copyright 2014 Edgar Espina
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.jooby;

import com.google.common.base.CaseFormat;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.primitives.Primitives;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import static java.util.Objects.requireNonNull;
import org.jooby.funzy.Throwing;
import org.jooby.handlers.AssetHandler;
import org.jooby.internal.RouteImpl;
import org.jooby.internal.RouteMatcher;
import org.jooby.internal.RoutePattern;
import org.jooby.internal.RouteSourceImpl;
import org.jooby.internal.SourceProvider;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * Routes are a key concept in Jooby. Routes are executed in the same order they are defined.
 *
 * 

handlers

*

* There are few type of handlers: {@link Route.Handler}, {@link Route.OneArgHandler} * {@link Route.ZeroArgHandler} and {@link Route.Filter}. They behave very similar, except that a * {@link Route.Filter} can decide if the next route handler can be executed or not. For example: *

* *
 *   get("/filter", (req, rsp, chain) {@literal ->} {
 *     if (someCondition) {
 *       chain.next(req, rsp);
 *     } else {
 *       // respond, throw err, etc...
 *     }
 *   });
 * 
* * While a {@link Route.Handler} always execute the next handler: * *
 *   get("/path", (req, rsp) {@literal ->} {
 *     rsp.send("handler");
 *   });
 *
 *   // filter version
 *   get("/path", (req, rsp, chain) {@literal ->} {
 *     rsp.send("handler");
 *     chain.next(req, rsp);
 *   });
 * 
* * The {@link Route.OneArgHandler} and {@link Route.ZeroArgHandler} offers a functional version of * generating a response: * *
{@code
 * {
 *   get("/path", req -> "handler");
 *
 *   get("/path", () -> "handler");
 * }
 * }
* * There is no need to call {@link Response#send(Object)}. * *

path patterns

*

* Jooby supports Ant-style path patterns: *

*

* Some examples: *

*
    *
  • {@code com/t?st.html} - matches {@code com/test.html} but also {@code com/tast.jsp} or * {@code com/txst.html}
  • *
  • {@code com/*.html} - matches all {@code .html} files in the {@code com} directory
  • *
  • com/{@literal **}/test.html - matches all {@code test.html} files underneath the * {@code com} path
  • *
  • {@code **}/{@code *} - matches any path at any level.
  • *
  • {@code *} - matches any path at any level, shorthand for {@code **}/{@code *}.
  • *
* *

variables

*

* Jooby supports path parameters too: *

*

* Some examples: *

*
    *
  • /user/{id} - /user/* and give you access to the id var.
  • *
  • /user/:id - /user/* and give you access to the id var.
  • *
  • /user/{id:\\d+} - /user/[digits] and give you access to the numeric * id var.
  • *
* *

routes semantic

*

* Routes are executed in the order they are defined, for example: *

* *
 *   get("/", (req, rsp) {@literal ->} {
 *     log.info("first"); // start here and go to second
 *   });
 *
 *   get("/", (req, rsp) {@literal ->} {
 *     log.info("second"); // execute after first and go to final
 *   });
 *
 *   get("/", (req, rsp) {@literal ->} {
 *     rsp.send("final"); // done!
 *   });
 * 
* * Please note first and second routes are converted to a filter, so previous example is the same * as: * *
 *   get("/", (req, rsp, chain) {@literal ->} {
 *     log.info("first"); // start here and go to second
 *     chain.next(req, rsp);
 *   });
 *
 *   get("/", (req, rsp, chain) {@literal ->} {
 *     log.info("second"); // execute after first and go to final
 *     chain.next(req, rsp);
 *   });
 *
 *   get("/", (req, rsp) {@literal ->} {
 *     rsp.send("final"); // done!
 *   });
 * 
* *

script route

*

* A script route can be defined using Lambda expressions, like: *

* *
 *   get("/", (request, response) {@literal ->} {
 *     response.send("Hello Jooby");
 *   });
 * 
* * Due to the use of lambdas a route is a singleton and you should NOT use global variables. * For example this is a bad practice: * *
 *  List{@literal <}String{@literal >} names = new ArrayList{@literal <>}(); // names produces side effects
 *  get("/", (req, rsp) {@literal ->} {
 *     names.add(req.param("name").value();
 *     // response will be different between calls.
 *     rsp.send(names);
 *   });
 * 
* *

mvc Route

*

* A Mvc Route use annotations to define routes: *

* *
 * {
 *   use(MyRoute.class);
 * }
 * 
* * MyRoute.java: *
 *   {@literal @}Path("/")
 *   public class MyRoute {
 *
 *    {@literal @}GET
 *    public String hello() {
 *      return "Hello Jooby";
 *    }
 *   }
 * 
*

* Programming model is quite similar to JAX-RS/Jersey with some minor differences and/or * simplifications. *

* *

* To learn more about Mvc Routes, please check {@link org.jooby.mvc.Path}, * {@link org.jooby.mvc.Produces} {@link org.jooby.mvc.Consumes}. *

* * @author edgar * @since 0.1.0 */ public interface Route { /** * Provides useful information about where the route was defined. * * See {@link Definition#source()} and {@link Route#source()}. * * @author edgar * @since 1.0.0.CR4 */ interface Source { /** * There is no source information. */ Source BUILTIN = new Source() { @Override public int line() { return -1; } @Override public Optional declaringClass() { return Optional.empty(); } @Override public String toString() { return "~builtin"; } }; /** * @return Line number where the route was defined or -1 when not available. */ int line(); /** * @return Class where the route */ @Nonnull Optional declaringClass(); } /** * Converts a route output to something else, see {@link Router#map(Mapper)}. * *
{@code
   * {
   *   // we got bar.. not foo
   *   get("/foo", () -> "foo")
   *       .map(value -> "bar");
   *
   *   // we got foo.. not bar
   *   get("/bar", () -> "bar")
   *       .map(value -> "foo");
   * }
   * }
* * If you want to apply a single map to several routes: * *
{@code
   * {
   *    with(() -> {
   *      get("/foo", () -> "foo");
   *
   *      get("/bar", () -> "bar");
   *
   *    }).map(v -> "foo or bar");
   * }
   * }
* * You can apply a {@link Mapper} to specific return type: * *
{@code
   * {
   *    with(() -> {
   *      get("/str", () -> "str");
   *
   *      get("/int", () -> 1);
   *
   *    }).map(String v -> "{" + v + "}");
   * }
   * }
* * A call to /str produces {str}, while /int just * 1. * * NOTE: You can apply the map operator to routes that produces an output. * * For example, the map operator will be silently ignored here: * *
{@code
   * {
   *    get("/", (req, rsp) -> {
   *      rsp.send(...);
   *    }).map(v -> ..);
   * }
   * }
* * @author edgar * @param Type to map. */ interface Mapper { /** * Produces a new mapper by combining the two mapper into one. * * @param it The first mapper to apply. * @param next The second mapper to apply. * @return A new mapper. */ @SuppressWarnings({"rawtypes", "unchecked"}) @Nonnull static Mapper chain(final Mapper it, final Mapper next) { return create(it.name() + ">" + next.name(), v -> next.map(it.map(v))); } /** * Creates a new named mapper (just syntax suggar for creating a new mapper). * * @param name Mapper's name. * @param fn Map function. * @param Value type. * @return A new mapper. */ @Nonnull static Mapper create(final String name, final Throwing.Function fn) { return new Route.Mapper() { @Override public String name() { return name; } @Override public Object map(final T value) throws Throwable { return fn.apply(value); } @Override public String toString() { return name(); } }; } /** * @return Mapper's name. */ @Nonnull default String name() { String name = Optional.ofNullable(Strings.emptyToNull(getClass().getSimpleName())) .orElseGet(() -> { String classname = getClass().getName(); return classname.substring(classname.lastIndexOf('.') + 1); }); return CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_HYPHEN, name); } /** * Map the type to something else. * * @param value Value to map. * @return Mapped value. * @throws Throwable If mapping fails. */ @Nonnull Object map(T value) throws Throwable; } /** * Common route properties, like static and global metadata via attributes, path exclusion, * produces and consumes types. * * @author edgar * @since 1.0.0.CR * @param Attribute subtype. */ interface Props> { /** * Set route attribute. Only primitives, string, class, enum or array of previous types are * allowed as attributes values. * * @param name Attribute's name. * @param value Attribute's value. * @return This instance. */ @Nonnull T attr(String name, Object value); /** * Tell jooby what renderer should use to render the output. * * @param name A renderer's name. * @return This instance. */ @Nonnull T renderer(final String name); /** * Explicit renderer to use or null. * * @return Explicit renderer to use or null. */ @Nullable String renderer(); /** * Set the route name. Route's name, helpful for debugging but also to implement dynamic and * advanced routing. See {@link Route.Chain#next(String, Request, Response)} * * * @param name A route's name. * @return This instance. */ @Nonnull T name(final String name); /** * Set the media types the route can consume. * * @param consumes The media types to test for. * @return This instance. */ @Nonnull default T consumes(final MediaType... consumes) { return consumes(Arrays.asList(consumes)); } /** * Set the media types the route can consume. * * @param consumes The media types to test for. * @return This instance. */ @Nonnull default T consumes(final String... consumes) { return consumes(MediaType.valueOf(consumes)); } /** * Set the media types the route can consume. * * @param consumes The media types to test for. * @return This instance. */ @Nonnull T consumes(final List consumes); /** * Set the media types the route can produces. * * @param produces The media types to test for. * @return This instance. */ @Nonnull default T produces(final MediaType... produces) { return produces(Arrays.asList(produces)); } /** * Set the media types the route can produces. * * @param produces The media types to test for. * @return This instance. */ @Nonnull default T produces(final String... produces) { return produces(MediaType.valueOf(produces)); } /** * Set the media types the route can produces. * * @param produces The media types to test for. * @return This instance. */ @Nonnull T produces(final List produces); /** * Excludes one or more path pattern from this route, useful for filter: * *
     * {
     *   use("*", req {@literal ->} {
     *    ...
     *   }).excludes("/logout");
     * }
     * 
* * @param excludes A path pattern. * @return This instance. */ @Nonnull default T excludes(final String... excludes) { return excludes(Arrays.asList(excludes)); } /** * Excludes one or more path pattern from this route, useful for filter: * *
     * {
     *   use("*", req {@literal ->} {
     *    ...
     *   }).excludes("/logout");
     * }
     * 
* * @param excludes A path pattern. * @return This instance. */ @Nonnull T excludes(final List excludes); @Nonnull T map(Mapper mapper); } /** * Group one ore more routes under a base path, see {@link Router#use(String)}. * * @author edgar * @deprecated Replaced by {@link Router#path(String, Runnable)}. */ @Deprecated class Group implements Props { /** List of definitions. */ private List routes = new ArrayList<>(); private String rootPattern; private String prefix; private String renderer; public Group(final String pattern, final String prefix) { requireNonNull(pattern, "Pattern is required."); this.rootPattern = pattern; this.prefix = prefix; } public Group(final String pattern) { this(pattern, null); } @Override public String renderer() { return renderer; } @Override public Group renderer(final String name) { this.renderer = name; return this; } public List routes() { return routes; } // ******************************************************************************************** // ALL // ******************************************************************************************** @Nonnull public Group all(final String pattern, final Route.Filter filter) { newRoute("*", pattern, filter); return this; } @Nonnull public Group all(final String pattern, final Route.Handler handler) { newRoute("*", pattern, handler); return this; } @Nonnull public Group all(final String pattern, final Route.OneArgHandler handler) { newRoute("*", pattern, handler); return this; } @Nonnull public Group all(final String pattern, final Route.ZeroArgHandler handler) { newRoute("*", pattern, handler); return this; } @Nonnull public Group all(final Route.Filter filter) { newRoute("*", "", filter); return this; } @Nonnull public Group all(final Route.Handler handler) { newRoute("*", "", handler); return this; } @Nonnull public Group all(final Route.OneArgHandler handler) { newRoute("*", "", handler); return this; } @Nonnull public Group all(final Route.ZeroArgHandler handler) { newRoute("*", "", handler); return this; } // ******************************************************************************************** // GET // ******************************************************************************************** @Nonnull public Group get(final String pattern, final Route.Filter filter) { newRoute(GET, pattern, filter); return this; } @Nonnull public Group get(final String pattern, final Route.Handler handler) { newRoute(GET, pattern, handler); return this; } @Nonnull public Group get(final String pattern, final Route.OneArgHandler handler) { newRoute(GET, pattern, handler); return this; } @Nonnull public Group get(final String pattern, final Route.ZeroArgHandler handler) { newRoute(GET, pattern, handler); return this; } @Nonnull public Group get(final Route.Filter filter) { newRoute(GET, "", filter); return this; } @Nonnull public Group get(final Route.Handler handler) { newRoute(GET, "", handler); return this; } @Nonnull public Group get(final Route.OneArgHandler handler) { newRoute(GET, "", handler); return this; } @Nonnull public Group get(final Route.ZeroArgHandler handler) { newRoute(GET, "", handler); return this; } // ******************************************************************************************** // POST // ******************************************************************************************** @Nonnull public Group post(final String pattern, final Route.Filter filter) { newRoute(POST, pattern, filter); return this; } @Nonnull public Group post(final String pattern, final Route.Handler handler) { newRoute(POST, pattern, handler); return this; } @Nonnull public Group post(final String pattern, final Route.OneArgHandler handler) { newRoute(POST, pattern, handler); return this; } @Nonnull public Group post(final String pattern, final Route.ZeroArgHandler handler) { newRoute(POST, pattern, handler); return this; } @Nonnull public Group post(final Route.Filter filter) { newRoute(POST, "", filter); return this; } @Nonnull public Group post(final Route.Handler handler) { newRoute(POST, "", handler); return this; } @Nonnull public Group post(final Route.OneArgHandler handler) { newRoute(POST, "", handler); return this; } @Nonnull public Group post(final Route.ZeroArgHandler handler) { newRoute(POST, "", handler); return this; } // ******************************************************************************************** // PUT // ******************************************************************************************** @Nonnull public Group put(final String pattern, final Route.Filter filter) { newRoute(PUT, pattern, filter); return this; } @Nonnull public Group put(final String pattern, final Route.Handler handler) { newRoute(PUT, pattern, handler); return this; } @Nonnull public Group put(final String pattern, final Route.OneArgHandler handler) { newRoute(PUT, pattern, handler); return this; } @Nonnull public Group put(final String pattern, final Route.ZeroArgHandler handler) { newRoute(PUT, pattern, handler); return this; } @Nonnull public Group put(final Route.Filter filter) { newRoute(PUT, "", filter); return this; } @Nonnull public Group put(final Route.Handler handler) { newRoute(PUT, "", handler); return this; } @Nonnull public Group put(final Route.OneArgHandler handler) { newRoute(PUT, "", handler); return this; } @Nonnull public Group put(final Route.ZeroArgHandler handler) { newRoute(PUT, "", handler); return this; } // ******************************************************************************************** // DELETE // ******************************************************************************************** @Nonnull public Group delete(final String pattern, final Route.Filter filter) { newRoute(DELETE, pattern, filter); return this; } @Nonnull public Group delete(final String pattern, final Route.Handler handler) { newRoute(DELETE, pattern, handler); return this; } @Nonnull public Group delete(final String pattern, final Route.OneArgHandler handler) { newRoute(DELETE, pattern, handler); return this; } @Nonnull public Group delete(final String pattern, final Route.ZeroArgHandler handler) { newRoute(DELETE, pattern, handler); return this; } @Nonnull public Group delete(final Route.Filter filter) { newRoute(DELETE, "", filter); return this; } @Nonnull public Group delete(final Route.Handler handler) { newRoute(DELETE, "", handler); return this; } @Nonnull public Group delete(final Route.OneArgHandler handler) { newRoute(DELETE, "", handler); return this; } @Nonnull public Group delete(final Route.ZeroArgHandler handler) { newRoute(DELETE, "", handler); return this; } // ******************************************************************************************** // PATCH // ******************************************************************************************** @Nonnull public Group patch(final String pattern, final Route.Filter filter) { newRoute(PATCH, pattern, filter); return this; } @Nonnull public Group patch(final String pattern, final Route.Handler handler) { newRoute(PATCH, pattern, handler); return this; } @Nonnull public Group patch(final String pattern, final Route.OneArgHandler handler) { newRoute(PATCH, pattern, handler); return this; } @Nonnull public Group patch(final String pattern, final Route.ZeroArgHandler handler) { newRoute(PATCH, pattern, handler); return this; } @Nonnull public Group patch(final Route.Filter filter) { newRoute(PATCH, "", filter); return this; } @Nonnull public Group patch(final Route.Handler handler) { newRoute(PATCH, "", handler); return this; } @Nonnull public Group patch(final Route.OneArgHandler handler) { newRoute(PATCH, "", handler); return this; } @Nonnull public Group patch(final Route.ZeroArgHandler handler) { newRoute(PATCH, "", handler); return this; } /** * Set the route name to the whole collection. * * @param name Name to use/set. * @return This instance. */ @Override @Nonnull public Group name(final String name) { for (Definition definition : routes) { if (prefix != null) { definition.name(prefix + "/" + name); } else { definition.name(name); } } return this; } @Override @Nonnull public Group consumes(final List types) { for (Definition definition : routes) { definition.consumes(types); } return this; } @Override @Nonnull public Group produces(final List types) { for (Definition definition : routes) { definition.produces(types); } return this; } @Override @Nonnull public Group attr(final String name, final Object value) { for (Definition definition : routes) { definition.attr(name, value); } return this; } @Override @Nonnull public Group excludes(final List excludes) { for (Definition definition : routes) { definition.excludes(excludes); } return this; } @Override @Nonnull public Group map(final Mapper mapper) { for (Definition definition : routes) { definition.map(mapper); } return this; } private void newRoute(final String method, final String pattern, final Route.Filter filter) { newRoute(new Route.Definition(method, this.rootPattern + pattern, filter)); } private void newRoute(final String method, final String pattern, final Route.Handler filter) { newRoute(new Route.Definition(method, this.rootPattern + pattern, filter)); } private void newRoute(final String method, final String pattern, final Route.OneArgHandler filter) { newRoute(new Route.Definition(method, this.rootPattern + pattern, filter)); } private void newRoute(final String method, final String pattern, final Route.ZeroArgHandler filter) { newRoute(new Route.Definition(method, this.rootPattern + pattern, filter)); } private void newRoute(final Route.Definition route) { if (prefix != null) { route.name(prefix); } routes.add(route); } } ; /** * Collection of {@link Route.Props} useful for registering/setting route options at once. * * See {@link Router#get(String, String, String, OneArgHandler)} and variants. * * @author edgar * @since 0.5.0 */ @SuppressWarnings({"unchecked", "rawtypes"}) class Collection implements Props { /** List of definitions. */ private final Route.Props[] routes; /** * Creates a new collection of route definitions. * * @param definitions Collection of route definitions. */ public Collection(final Route.Props... definitions) { this.routes = requireNonNull(definitions, "Route definitions are required."); } @Override public Collection name(final String name) { for (Props definition : routes) { definition.name(name); } return this; } @Override public String renderer() { return routes[0].renderer(); } @Override public Collection renderer(final String name) { for (Props definition : routes) { definition.renderer(name); } return this; } @Override public Collection consumes(final List types) { for (Props definition : routes) { definition.consumes(types); } return this; } @Override public Collection produces(final List types) { for (Props definition : routes) { definition.produces(types); } return this; } @Override public Collection attr(final String name, final Object value) { for (Props definition : routes) { definition.attr(name, value); } return this; } @Override public Collection excludes(final List excludes) { for (Props definition : routes) { definition.excludes(excludes); } return this; } @Override public Collection map(final Mapper mapper) { for (Props route : routes) { route.map(mapper); } return this; } } /** * DSL for customize routes. * *

* Some examples: *

* *
   *   public class MyApp extends Jooby {
   *     {
   *        get("/", () {@literal ->} "GET");
   *
   *        post("/", req {@literal ->} "POST");
   *
   *        put("/", (req, rsp) {@literal ->} rsp.send("PUT"));
   *     }
   *   }
   * 
* *

Setting what a route can consumes

* *
   *   public class MyApp extends Jooby {
   *     {
   *        post("/", (req, resp) {@literal ->} resp.send("POST"))
   *          .consumes(MediaType.json);
   *     }
   *   }
   * 
* *

Setting what a route can produces

* *
   *   public class MyApp extends Jooby {
   *     {
   *        post("/", (req, resp) {@literal ->} resp.send("POST"))
   *          .produces(MediaType.json);
   *     }
   *   }
   * 
* *

Adding a name

* *
   *   public class MyApp extends Jooby {
   *     {
   *        post("/", (req, resp) {@literal ->} resp.send("POST"))
   *          .name("My Root");
   *     }
   *   }
   * 
* * @author edgar * @since 0.1.0 */ class Definition implements Props { /** * Route's name. */ private String name = "/anonymous"; /** * A route pattern. */ private RoutePattern cpattern; /** * The target route. */ private Filter filter; /** * Defines the media types that the methods of a resource class or can accept. Default is: * {@code *}/{@code *}. */ private List consumes = MediaType.ALL; /** * Defines the media types that the methods of a resource class or can produces. Default is: * {@code *}/{@code *}. */ private List produces = MediaType.ALL; /** * A HTTP verb or *. */ private String method; /** * A path pattern. */ private String pattern; private List excludes = Collections.emptyList(); private Map attributes = ImmutableMap.of(); private Mapper mapper; private int line; private String declaringClass; String prefix; private String renderer; /** * Creates a new route definition. * * @param verb A HTTP verb or *. * @param pattern A path pattern. * @param handler A route handler. */ public Definition(final String verb, final String pattern, final Route.Handler handler) { this(verb, pattern, (Route.Filter) handler); } /** * Creates a new route definition. * * @param verb A HTTP verb or *. * @param pattern A path pattern. * @param handler A route handler. * @param caseSensitiveRouting Configure case for routing algorithm. */ public Definition(final String verb, final String pattern, final Route.Handler handler, boolean caseSensitiveRouting) { this(verb, pattern, (Route.Filter) handler, caseSensitiveRouting); } /** * Creates a new route definition. * * @param verb A HTTP verb or *. * @param pattern A path pattern. * @param handler A route handler. */ public Definition(final String verb, final String pattern, final Route.OneArgHandler handler) { this(verb, pattern, (Route.Filter) handler); } /** * Creates a new route definition. * * @param verb A HTTP verb or *. * @param pattern A path pattern. * @param handler A route handler. */ public Definition(final String verb, final String pattern, final Route.ZeroArgHandler handler) { this(verb, pattern, (Route.Filter) handler); } /** * Creates a new route definition. * * @param method A HTTP verb or *. * @param pattern A path pattern. * @param filter A callback to execute. */ public Definition(final String method, final String pattern, final Filter filter) { this(method, pattern, filter, true); } /** * Creates a new route definition. * * @param method A HTTP verb or *. * @param pattern A path pattern. * @param filter A callback to execute. * @param caseSensitiveRouting Configure case for routing algorithm. */ public Definition(final String method, final String pattern, final Filter filter, boolean caseSensitiveRouting) { requireNonNull(pattern, "A route path is required."); requireNonNull(filter, "A filter is required."); this.method = method.toUpperCase(); this.cpattern = new RoutePattern(method, pattern, !caseSensitiveRouting); // normalized pattern this.pattern = cpattern.pattern(); this.filter = filter; SourceProvider.INSTANCE.get().ifPresent(source -> { this.line = source.getLineNumber(); this.declaringClass = source.getClassName(); }); } /** *

Path Patterns

*

* Jooby supports Ant-style path patterns: *

*

* Some examples: *

*
    *
  • {@code com/t?st.html} - matches {@code com/test.html} but also {@code com/tast.jsp} or * {@code com/txst.html}
  • *
  • {@code com/*.html} - matches all {@code .html} files in the {@code com} directory
  • *
  • com/{@literal **}/test.html - matches all {@code test.html} files underneath * the {@code com} path
  • *
  • {@code **}/{@code *} - matches any path at any level.
  • *
  • {@code *} - matches any path at any level, shorthand for {@code **}/{@code *}.
  • *
* *

Variables

*

* Jooby supports path parameters too: *

*

* Some examples: *

*
    *
  • /user/{id} - /user/* and give you access to the id var.
  • *
  • /user/:id - /user/* and give you access to the id var.
  • *
  • /user/{id:\\d+} - /user/[digits] and give you access to the numeric * id var.
  • *
* * @return A path pattern. */ @Nonnull public String pattern() { return pattern; } @Nullable public String renderer() { return renderer; } @Override public Definition renderer(final String name) { this.renderer = name; return this; } /** * @return List of path variables (if any). */ @Nonnull public List vars() { return cpattern.vars(); } /** * Indicates if the {@link #pattern()} contains a glob charecter, like ?, * * or **. * * @return Indicates if the {@link #pattern()} contains a glob charecter, like ?, * * or **. */ @Nonnull public boolean glob() { return cpattern.glob(); } /** * Source information (where the route was defined). * * @return Source information (where the route was defined). */ @Nonnull public Route.Source source() { return new RouteSourceImpl(declaringClass, line); } /** * Recreate a route path and apply the given variables. * * @param vars Path variables. * @return A route pattern. */ @Nonnull public String reverse(final Map vars) { return cpattern.reverse(vars); } /** * Recreate a route path and apply the given variables. * * @param values Path variable values. * @return A route pattern. */ @Nonnull public String reverse(final Object... values) { return cpattern.reverse(values); } @Override @Nonnull public Definition attr(final String name, final Object value) { requireNonNull(name, "Attribute name is required."); requireNonNull(value, "Attribute value is required."); if (valid(value)) { attributes = ImmutableMap.builder() .putAll(attributes) .put(name, value) .build(); } return this; } private boolean valid(final Object value) { if (Primitives.isWrapperType(Primitives.wrap(value.getClass()))) { return true; } if (value instanceof String || value instanceof Enum || value instanceof Class) { return true; } if (value.getClass().isArray()) { return valid(Array.get(value, 0)); } if (value instanceof Map && ((Map) value).size() > 0) { Map.Entry e = (Map.Entry) ((Map) value).entrySet().iterator().next(); return valid(e.getKey()) && valid(e.getValue()); } return false; } /** * Get an attribute by name. * * @param name Attribute's name. * @param Attribute's type. * @return Attribute's value or null. */ @SuppressWarnings("unchecked") @Nonnull public T attr(final String name) { return (T) attributes.get(name); } /** * @return A read only view of attributes. */ @Nonnull public Map attributes() { return attributes; } /** * Test if the route matches the given verb, path, content type and accept header. * * @param method A HTTP verb. * @param path Current HTTP path. * @param contentType The Content-Type header. * @param accept The Accept header. * @return A route or an empty optional. */ @Nonnull public Optional matches(final String method, final String path, final MediaType contentType, final List accept) { String fpath = method + path; if (excludes.size() > 0 && excludes(fpath)) { return Optional.empty(); } RouteMatcher matcher = cpattern.matcher(fpath); if (matcher.matches()) { List result = MediaType.matcher(accept).filter(this.produces); if (result.size() > 0 && canConsume(contentType)) { // keep accept when */* List produces = result.size() == 1 && result.get(0).name().equals("*/*") ? accept : this.produces; return Optional .of(asRoute(method, matcher, produces, new RouteSourceImpl(declaringClass, line))); } } return Optional.empty(); } /** * @return HTTP method or *. */ @Nonnull public String method() { return method; } /** * @return Handler behind this route. */ @Nonnull public Route.Filter filter() { return filter; } /** * Route's name, helpful for debugging but also to implement dynamic and advanced routing. See * {@link Route.Chain#next(String, Request, Response)} * * @return Route name. Default is: anonymous. */ @Nonnull public String name() { return name; } /** * Set the route name. Route's name, helpful for debugging but also to implement dynamic and * advanced routing. See {@link Route.Chain#next(String, Request, Response)} * * * @param name A route's name. * @return This definition. */ @Override @Nonnull public Definition name(final String name) { checkArgument(!Strings.isNullOrEmpty(name), "A route's name is required."); this.name = normalize(prefix != null ? prefix + "/" + name : name); return this; } /** * Test if the route definition can consume a media type. * * @param type A media type to test. * @return True, if the route can consume the given media type. */ public boolean canConsume(final MediaType type) { return MediaType.matcher(Arrays.asList(type)).matches(consumes); } /** * Test if the route definition can consume a media type. * * @param type A media type to test. * @return True, if the route can consume the given media type. */ public boolean canConsume(final String type) { return MediaType.matcher(MediaType.valueOf(type)).matches(consumes); } /** * Test if the route definition can consume a media type. * * @param types A media types to test. * @return True, if the route can produces the given media type. */ public boolean canProduce(final List types) { return MediaType.matcher(types).matches(produces); } /** * Test if the route definition can consume a media type. * * @param types A media types to test. * @return True, if the route can produces the given media type. */ public boolean canProduce(final MediaType... types) { return canProduce(Arrays.asList(types)); } /** * Test if the route definition can consume a media type. * * @param types A media types to test. * @return True, if the route can produces the given media type. */ public boolean canProduce(final String... types) { return canProduce(MediaType.valueOf(types)); } @Override public Definition consumes(final List types) { checkArgument(types != null && types.size() > 0, "Consumes types are required"); if (types.size() > 1) { this.consumes = Lists.newLinkedList(types); Collections.sort(this.consumes); } else { this.consumes = ImmutableList.of(types.get(0)); } return this; } @Override public Definition produces(final List types) { checkArgument(types != null && types.size() > 0, "Produces types are required"); if (types.size() > 1) { this.produces = Lists.newLinkedList(types); Collections.sort(this.produces); } else { this.produces = ImmutableList.of(types.get(0)); } return this; } @Override public Definition excludes(final List excludes) { this.excludes = excludes.stream() .map(it -> new RoutePattern(method, it)) .collect(Collectors.toList()); return this; } /** * @return List of exclusion filters (if any). */ @Nonnull public List excludes() { return excludes.stream().map(r -> r.pattern()).collect(Collectors.toList()); } private boolean excludes(final String path) { for (RoutePattern pattern : excludes) { if (pattern.matcher(path).matches()) { return true; } } return false; } /** * @return All the types this route can consumes. */ @Nonnull public List consumes() { return Collections.unmodifiableList(this.consumes); } /** * @return All the types this route can produces. */ @Nonnull public List produces() { return Collections.unmodifiableList(this.produces); } @Override @Nonnull public Definition map(final Mapper mapper) { this.mapper = requireNonNull(mapper, "Mapper is required."); return this; } /** * Set the line where this route is defined. * * @param line Line number. * @return This instance. */ @Nonnull public Definition line(final int line) { this.line = line; return this; } /** * Set the class where this route is defined. * * @param declaringClass A source class. * @return This instance. */ @Nonnull public Definition declaringClass(final String declaringClass) { this.declaringClass = declaringClass; return this; } @Override public String toString() { StringBuilder buffer = new StringBuilder(); buffer.append(method()).append(" ").append(pattern()).append("\n"); buffer.append(" name: ").append(name()).append("\n"); buffer.append(" excludes: ").append(excludes).append("\n"); buffer.append(" consumes: ").append(consumes()).append("\n"); buffer.append(" produces: ").append(produces()).append("\n"); return buffer.toString(); } /** * Creates a new route. * * @param method A HTTP verb. * @param matcher A route matcher. * @param produces List of produces types. * @param source Route source. * @return A new route. */ private Route asRoute(final String method, final RouteMatcher matcher, final List produces, final Route.Source source) { return new RouteImpl(filter, this, method, matcher.path(), produces, matcher.vars(), mapper, source); } } /** * A forwarding route. * * @author edgar * @since 0.1.0 */ class Forwarding implements Route { /** * Target route. */ private final Route route; /** * Creates a new {@link Forwarding} route. * * @param route A target route. */ public Forwarding(final Route route) { if (route == null) { throw new NullPointerException("Route required"); } this.route = route; } @Override public String renderer() { return route.renderer(); } @Override public String path() { return route.path(); } @Override public String method() { return route.method(); } @Override public String pattern() { return route.pattern(); } @Override public String name() { return route.name(); } @Override public Map vars() { return route.vars(); } @Override public List consumes() { return route.consumes(); } @Override public List produces() { return route.produces(); } @Override public Map attributes() { return route.attributes(); } @Override public T attr(final String name) { return route.attr(name); } @Override public boolean glob() { return route.glob(); } @Override public String reverse(final Map vars) { return route.reverse(vars); } @Override public String reverse(final Object... values) { return route.reverse(values); } @Override public Source source() { return route.source(); } @Override public String print() { return route.print(); } @Override public String print(final int indent) { return route.print(indent); } @Override public String toString() { return route.toString(); } /** * Find a target route. * * @param route A route to check. * @return A target route. */ public static Route unwrap(final Route route) { Route root = route; while (root instanceof Forwarding) { root = ((Forwarding) root).route; } return root; } } /** * The most advanced route handler which let you decided if the next route handler in the chain * can be executed or not. Example of filters are: * *

* Auth handler example: *

* *
   *   String token = req.header("token").value();
   *   if (token != null) {
   *     // validate token...
   *     if (valid(token)) {
   *       chain.next(req, rsp);
   *     }
   *   } else {
   *     rsp.status(403);
   *   }
   * 
* *

* Logging/Around handler example: *

* *
   *   long start = System.currentTimeMillis();
   *   chain.next(req, rsp);
   *   long end = System.currentTimeMillis();
   *   log.info("Request: {} took {}ms", req.path(), end - start);
   * 
* * NOTE: Don't forget to call {@link Route.Chain#next(Request, Response)} if next route handler * need to be executed. * * @author edgar * @since 0.1.0 */ public interface Filter { /** * The handle method of the Filter is called by the server each time a * request/response pair is passed through the chain due to a client request for a resource at * the end of the chain. * The {@link Route.Chain} passed in to this method allows the Filter to pass on the request and * response to the next entity in the chain. * *

* A typical implementation of this method would follow the following pattern: *

*
    *
  • Examine the request
  • *
  • Optionally wrap the request object with a custom implementation to filter content or * headers for input filtering
  • *
  • Optionally wrap the response object with a custom implementation to filter content or * headers for output filtering
  • *
  • *
      *
    • Either invoke the next entity in the chain using the {@link Route.Chain} * object (chain.next(req, rsp)),
    • *
    • or not pass on the request/response pair to the next entity in the * filter chain to block the request processing
    • *
    *
  • Directly set headers on the response after invocation of the next entity in the filter * chain.
  • *
* * @param req A HTTP request. * @param rsp A HTTP response. * @param chain A route chain. * @throws Throwable If something goes wrong. */ void handle(Request req, Response rsp, Route.Chain chain) throws Throwable; } /** * Allow to customize an asset handler. * * @author edgar */ class AssetDefinition extends Definition { private Boolean etag; private String cdn; private Object maxAge; private Boolean lastModifiedSince; private Integer statusCode; /** * Creates a new route definition. * * @param method A HTTP verb or *. * @param pattern A path pattern. * @param handler A callback to execute. * @param caseSensitiveRouting Configure case for routing algorithm. */ public AssetDefinition(final String method, final String pattern, final Route.Filter handler, boolean caseSensitiveRouting) { super(method, pattern, handler, caseSensitiveRouting); } @Nonnull @Override public AssetHandler filter() { return (AssetHandler) super.filter(); } /** * Indicates what to do when an asset is missing (not resolved). Default action is to resolve them * as 404 (NOT FOUND) request. * * If you specify a status code <= 0, missing assets are ignored and the next handler on pipeline * will be executed. * * @param statusCode HTTP code or 0. * @return This route definition. */ public AssetDefinition onMissing(final int statusCode) { if (this.statusCode == null) { filter().onMissing(statusCode); this.statusCode = statusCode; } return this; } /** * @param etag Turn on/off etag support. * @return This route definition. */ public AssetDefinition etag(final boolean etag) { if (this.etag == null) { filter().etag(etag); this.etag = etag; } return this; } /** * @param enabled Turn on/off last modified support. * @return This route definition. */ public AssetDefinition lastModified(final boolean enabled) { if (this.lastModifiedSince == null) { filter().lastModified(enabled); this.lastModifiedSince = enabled; } return this; } /** * @param cdn If set, every resolved asset will be serve from it. * @return This route definition. */ public AssetDefinition cdn(final String cdn) { if (this.cdn == null) { filter().cdn(cdn); this.cdn = cdn; } return this; } /** * @param maxAge Set the cache header max-age value. * @return This route definition. */ public AssetDefinition maxAge(final Duration maxAge) { if (this.maxAge == null) { filter().maxAge(maxAge); this.maxAge = maxAge; } return this; } /** * @param maxAge Set the cache header max-age value in seconds. * @return This route definition. */ public AssetDefinition maxAge(final long maxAge) { if (this.maxAge == null) { filter().maxAge(maxAge); this.maxAge = maxAge; } return this; } /** * Parse value as {@link Duration}. If the value is already a number then it uses as seconds. * Otherwise, it parse expressions like: 8m, 1h, 365d, etc... * * @param maxAge Set the cache header max-age value in seconds. * @return This route definition. */ public AssetDefinition maxAge(final String maxAge) { if (this.maxAge == null) { filter().maxAge(maxAge); this.maxAge = maxAge; } return this; } } /** * A route handler that always call {@link Chain#next(Request, Response)}. * *
   * public class MyApp extends Jooby {
   *   {
   *      get("/", (req, rsp) {@literal ->} rsp.send("Hello"));
   *   }
   * }
   * 
* * @author edgar * @since 0.1.0 */ interface Handler extends Filter { @Override default void handle(final Request req, final Response rsp, final Route.Chain chain) throws Throwable { handle(req, rsp); chain.next(req, rsp); } /** * Callback method for a HTTP request. * * @param req A HTTP request. * @param rsp A HTTP response. * @throws Throwable If something goes wrong. The exception will processed by Jooby. */ void handle(Request req, Response rsp) throws Throwable; } /** * A handler for a MVC route, it extends {@link Handler} by adding a reference to the method * and class behind this route. * * @author edgar * @since 0.6.2 */ interface MethodHandler extends Handler { /** * Target method. * * @return Target method. */ @Nonnull Method method(); /** * Target class. * * @return Target class. */ @Nonnull Class implementingClass(); } /** * A functional route handler that use the return value as HTTP response. * *
   *   {
   *      get("/",(req {@literal ->} "Hello");
   *   }
   * 
* * @author edgar * @since 0.1.1 */ interface OneArgHandler extends Filter { @Override default void handle(final Request req, final Response rsp, final Route.Chain chain) throws Throwable { Object result = handle(req); rsp.send(result); chain.next(req, rsp); } /** * Callback method for a HTTP request. * * @param req A HTTP request. * @return Message to send. * @throws Throwable If something goes wrong. The exception will processed by Jooby. */ Object handle(Request req) throws Throwable; } /** * A functional handler that use the return value as HTTP response. * *
   * public class MyApp extends Jooby {
   *   {
   *      get("/", () {@literal ->} "Hello");
   *   }
   * }
   * 
* * @author edgar * @since 0.1.1 */ interface ZeroArgHandler extends Filter { @Override default void handle(final Request req, final Response rsp, final Route.Chain chain) throws Throwable { Object result = handle(); rsp.send(result); chain.next(req, rsp); } /** * Callback method for a HTTP request. * * @return Message to send. * @throws Throwable If something goes wrong. The exception will processed by Jooby. */ Object handle() throws Throwable; } /** *

before

* * Allows for customized handler execution chains. It will be invoked before the actual handler. * *
{@code
   * {
   *   before((req, rsp) -> {
   *     // your code goes here
   *   });
   * }
   * }
* * You are allowed to modify the request and response objects. * * Please note that the before handler is just syntax sugar for {@link Route.Filter}. * For example, the before handler was implemented as: * *
{@code
   * {
   *   use("*", "*", (req, rsp, chain) -> {
   *     before(req, rsp);
   *     // your code goes here
   *     chain.next(req, rsp);
   *   });
   * }
   * }
* * A before handler must to be registered before the actual handler you want to * intercept. * *
{@code
   * {
   *   before((req, rsp) -> {
   *     // your code goes here
   *   });
   *
   *   get("/path", req -> {
   *     // your code goes here
   *     return ...;
   *   });
   * }
   * }
* * If you reverse the order then it won't work. * *

* Remember: routes are executed in the order they are defined and the pipeline * is executed as long you don't generate a response. *

* * @author edgar * @since 1.0.0.CR */ interface Before extends Route.Filter { @Override default void handle(final Request req, final Response rsp, final Chain chain) throws Throwable { handle(req, rsp); chain.next(req, rsp); } /** * Allows for customized handler execution chains. It will be invoked before the actual handler. * * @param req Request. * @param rsp Response * @throws Throwable If something goes wrong. */ void handle(Request req, Response rsp) throws Throwable; } /** *

after

* * Allows for customized response before send it. It will be invoked at the time a response need * to be send. * *
{@code
   * {
   *   after("GET", "*", (req, rsp, result) -> {
   *     // your code goes here
   *     return result;
   *   });
   * }
   * }
* * You are allowed to modify the request, response and result objects. The handler returns a * {@link Result} which can be the same or an entirely new {@link Result}. * * Please note that the after handler is just syntax sugar for * {@link Route.Filter}. * For example, the after handler was implemented as: * *
{@code
   * {
   *   use("GET", "*", (req, rsp, chain) -> {
   *     chain.next(req, new Response.Forwarding(rsp) {
   *       public void send(Result result) {
   *         rsp.send(after(req, rsp, result);
   *       }
   *     });
   *   });
   * }
   * }
* * Due after is implemented by wrapping the {@link Response} object. A * after handler must to be registered before the actual handler you want to * intercept. * *
{@code
   * {
   *   after("GET", "/path", (req, rsp, result) -> {
   *     // your code goes here
   *     return result;
   *   });
   *
   *   get("/path", req -> {
   *     return "hello";
   *   });
   * }
   * }
* * If you reverse the order then it won't work. * *

* Remember: routes are executed in the order they are defined and the pipeline * is executed as long you don't generate a response. *

* * @author edgar * @since 1.0.0.CR */ interface After extends Filter { @Override default void handle(final Request req, final Response rsp, final Chain chain) throws Throwable { rsp.after(this); chain.next(req, rsp); } /** * Allows for customized response before send it. It will be invoked at the time a response need * to be send. * * @param req Request. * @param rsp Response * @param result Result. * @return Same or new result. * @throws Exception If something goes wrong. */ Result handle(Request req, Response rsp, Result result) throws Exception; } /** *

complete

* * Allows for log and cleanup a request. It will be invoked after we send a response. * *
{@code
   * {
   *   complete((req, rsp, cause) -> {
   *     // your code goes here
   *   });
   * }
   * }
* * You are NOT allowed to modify the request and response objects. The cause is an * {@link Optional} with a {@link Throwable} useful to identify problems. * * The goal of the complete handler is to probably cleanup request object and log * responses. * * Please note that the complete handler is just syntax sugar for * {@link Route.Filter}. * For example, the complete handler was implemented as: * *
{@code
   * {
   *   use("*", "*", (req, rsp, chain) -> {
   *     Optional err = Optional.empty();
   *     try {
   *       chain.next(req, rsp);
   *     } catch (Throwable cause) {
   *       err = Optional.of(cause);
   *     } finally {
   *       complete(req, rsp, err);
   *     }
   *   });
   * }
   * }
* * An complete handler must to be registered before the actual handler you want to * intercept. * *
{@code
   * {
   *   complete((req, rsp, cause) -> {
   *     // your code goes here
   *   });
   *
   *   get(req -> {
   *     return "hello";
   *   });
   * }
   * }
* * If you reverse the order then it won't work. * *

* Remember: routes are executed in the order they are defined and the pipeline * is executed as long you don't generate a response. *

* *

example

*

* Suppose you have a transactional resource, like a database connection. The next example shows * you how to implement a simple and effective transaction-per-request pattern: *

* *
{@code
   * {
   *   // start transaction
   *   before((req, rsp) -> {
   *     DataSource ds = req.require(DataSource.class);
   *     Connection connection = ds.getConnection();
   *     Transaction trx = connection.getTransaction();
   *     trx.begin();
   *     req.set("connection", connection);
   *     return true;
   *   });
   *
   *   // commit/rollback transaction
   *   complete((req, rsp, cause) -> {
   *     // unbind connection from request
   *     try(Connection connection = req.unset("connection").get()) {
   *       Transaction trx = connection.getTransaction();
   *       if (cause.ifPresent()) {
   *         trx.rollback();
   *       } else {
   *         trx.commit();
   *       }
   *     }
   *   });
   *
   *   // your transactional routes goes here
   *   get("/api/something", req -> {
   *     Connection connection = req.get("connection");
   *     // work with connection
   *   });
   * }
   * }
* * @author edgar * @since 1.0.0.CR */ interface Complete extends Filter { @Override default void handle(final Request req, final Response rsp, final Chain chain) throws Throwable { rsp.complete(this); chain.next(req, rsp); } /** * Allows for log and cleanup a request. It will be invoked after we send a response. * * @param req Request. * @param rsp Response * @param cause Empty optional on success. Otherwise, it contains the exception. */ void handle(Request req, Response rsp, Optional cause); } /** * Chain of routes to be executed. It invokes the next route in the chain. * * @author edgar * @since 0.1.0 */ interface Chain { /** * Invokes the next route in the chain where {@link Route#name()} starts with the given prefix. * * @param prefix Iterates over the route chain and keep routes that start with the given prefix. * @param req A HTTP request. * @param rsp A HTTP response. * @throws Throwable If invocation goes wrong. */ void next(@Nullable String prefix, Request req, Response rsp) throws Throwable; /** * Invokes the next route in the chain. * * @param req A HTTP request. * @param rsp A HTTP response. * @throws Throwable If invocation goes wrong. */ default void next(final Request req, final Response rsp) throws Throwable { next(null, req, rsp); } /** * All the pending/next routes from pipeline. Example: * *
{@code
     *   use("*", (req, rsp, chain) -> {
     *     List routes = chain.routes();
     *     assertEquals(2, routes.size());
     *     assertEquals("/r2", routes.get(0).name());
     *     assertEquals("/r3", routes.get(1).name());
     *     assertEquals("/786/:id", routes.get(routes.size() - 1).pattern());
     *
     *     chain.next(req, rsp);
     *   }).name("r1");
     *
     *   use("/786/**", (req, rsp, chain) -> {
     *     List routes = chain.routes();
     *     assertEquals(1, routes.size());
     *     assertEquals("/r3", routes.get(0).name());
     *     assertEquals("/786/:id", routes.get(routes.size() - 1).pattern());
     *     chain.next(req, rsp);
     *   }).name("r2");
     *
     *   get("/786/:id", req -> {
     *     return req.param("id").value();
     *   }).name("r3");
     * }
* * @return Next routes in the pipeline or empty list. */ List routes(); } /** Route key. */ Key> KEY = Key.get(new TypeLiteral>() { }); char OUT_OF_PATH = '\u200B'; String GET = "GET"; String POST = "POST"; String PUT = "PUT"; String DELETE = "DELETE"; String PATCH = "PATCH"; String HEAD = "HEAD"; String CONNECT = "CONNECT"; String OPTIONS = "OPTIONS"; String TRACE = "TRACE"; /** * Well known HTTP methods. */ List METHODS = ImmutableList.builder() .add(GET, POST, PUT, DELETE, PATCH, HEAD, CONNECT, OPTIONS, TRACE) .build(); /** * @return Current request path. */ @Nonnull String path(); /** * @return Current HTTP method. */ @Nonnull String method(); /** * @return The currently matched pattern. */ @Nonnull String pattern(); /** * Route's name, helpful for debugging but also to implement dynamic and advanced routing. See * {@link Route.Chain#next(String, Request, Response)} * * @return Route name, defaults to "anonymous" */ @Nonnull String name(); /** * Path variables, either named or by index (capturing group). * *
   *   /path/:var
   * 
* * Variable var is accessible by name: var or index: 0. * * @return The currently matched path variables (if any). */ @Nonnull Map vars(); /** * @return List all the types this route can consumes, defaults is: {@code * / *}. */ @Nonnull List consumes(); /** * @return List all the types this route can produces, defaults is: {@code * / *}. */ @Nonnull List produces(); /** * True, when route's name starts with the given prefix. Useful for dynamic routing. See * {@link Route.Chain#next(String, Request, Response)} * * @param prefix Prefix to check for. * @return True, when route's name starts with the given prefix. */ default boolean apply(final String prefix) { return name().startsWith(prefix); } /** * @return All the available attributes in the execution chain. */ @Nonnull Map attributes(); /** * Attribute by name. * * @param name Attribute's name. * @param Attribute's type. * @return Attribute value. */ @SuppressWarnings("unchecked") @Nonnull default T attr(final String name) { return (T) attributes().get(name); } /** * Explicit renderer to use or null. * * @return Explicit renderer to use or null. */ @Nonnull String renderer(); /** * Indicates if the {@link #pattern()} contains a glob character, like ?, * * or **. * * @return Indicates if the {@link #pattern()} contains a glob charecter, like ?, * * or **. */ boolean glob(); /** * Recreate a route path and apply the given variables. * * @param vars Path variables. * @return A route pattern. */ @Nonnull String reverse(final Map vars); /** * Recreate a route path and apply the given variables. * * @param values Path variable values. * @return A route pattern. */ @Nonnull String reverse(final Object... values); /** * Normalize a path by removing double or trailing slashes. * * @param path A path to normalize. * @return A normalized path. */ @Nonnull static String normalize(final String path) { return RoutePattern.normalize(path); } /** * Remove invalid path mark when present. * * @param path Path. * @return Original path. */ @Nonnull static String unerrpath(final String path) { if (path.charAt(0) == OUT_OF_PATH) { return path.substring(1); } return path; } /** * Mark a path as invalid. * * @param path Path. * @return Invalid path. */ @Nonnull static String errpath(final String path) { return OUT_OF_PATH + path; } /** * Source information (where the route was defined). * * @return Source information (where the route was defined). */ @Nonnull Route.Source source(); /** * Print route information like: method, path, source, etc... Useful for debugging. * * @param indent Indent level * @return Output. */ @Nonnull default String print(final int indent) { StringBuilder buff = new StringBuilder(); String[] header = {"Method", "Path", "Source", "Name", "Pattern", "Consumes", "Produces"}; String[] values = {method(), path(), source().toString(), name(), pattern(), consumes().toString(), produces().toString()}; BiConsumer, Character> format = (v, s) -> { buff.append(Strings.padEnd("", indent, ' ')) .append("|").append(s); for (int i = 0; i < header.length; i++) { buff .append(Strings.padEnd(v.apply(i), Math.max(header[i].length(), values[i].length()), s)) .append(s).append("|").append(s); } buff.setLength(buff.length() - 1); }; format.accept(i -> header[i], ' '); buff.append("\n"); format.accept(i -> "-", '-'); buff.append("\n"); format.accept(i -> values[i], ' '); return buff.toString(); } /** * Print route information like: method, path, source, etc... Useful for debugging. * * @return Output. */ @Nonnull default String print() { return print(0); } }