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

org.jooby.Jooby 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.Joiner;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.escape.Escaper;
import com.google.common.html.HtmlEscapers;
import com.google.common.net.UrlEscapers;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.inject.Binder;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.Stage;
import com.google.inject.TypeLiteral;
import com.google.inject.multibindings.Multibinder;
import com.google.inject.name.Named;
import com.google.inject.name.Names;
import com.google.inject.util.Types;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import com.typesafe.config.ConfigObject;
import com.typesafe.config.ConfigValue;
import static com.typesafe.config.ConfigValueFactory.fromAnyRef;
import static java.util.Objects.requireNonNull;
import static org.jooby.Route.CONNECT;
import static org.jooby.Route.DELETE;
import org.jooby.Route.Definition;
import static org.jooby.Route.GET;
import static org.jooby.Route.HEAD;
import org.jooby.Route.Mapper;
import static org.jooby.Route.OPTIONS;
import static org.jooby.Route.PATCH;
import static org.jooby.Route.POST;
import static org.jooby.Route.PUT;
import static org.jooby.Route.TRACE;
import org.jooby.Session.Store;
import org.jooby.funzy.Throwing;
import org.jooby.funzy.Try;
import org.jooby.handlers.AssetHandler;
import org.jooby.internal.AppPrinter;
import org.jooby.internal.BuiltinParser;
import org.jooby.internal.BuiltinRenderer;
import org.jooby.internal.CookieSessionManager;
import org.jooby.internal.DefaulErrRenderer;
import org.jooby.internal.HttpHandlerImpl;
import org.jooby.internal.JvmInfo;
import org.jooby.internal.LocaleUtils;
import org.jooby.internal.ParameterNameProvider;
import org.jooby.internal.RequestScope;
import org.jooby.internal.RouteMetadata;
import org.jooby.internal.ServerExecutorProvider;
import org.jooby.internal.ServerLookup;
import org.jooby.internal.ServerSessionManager;
import org.jooby.internal.SessionManager;
import org.jooby.internal.SourceProvider;
import org.jooby.internal.TypeConverters;
import org.jooby.internal.handlers.HeadHandler;
import org.jooby.internal.handlers.OptionsHandler;
import org.jooby.internal.handlers.TraceHandler;
import org.jooby.internal.mvc.MvcRoutes;
import org.jooby.internal.mvc.MvcWebSocket;
import org.jooby.internal.parser.BeanParser;
import org.jooby.internal.parser.DateParser;
import org.jooby.internal.parser.LocalDateParser;
import org.jooby.internal.parser.LocaleParser;
import org.jooby.internal.parser.ParserExecutor;
import org.jooby.internal.parser.StaticMethodParser;
import org.jooby.internal.parser.StringConstructorParser;
import org.jooby.internal.parser.ZonedDateTimeParser;
import org.jooby.internal.ssl.SslContextProvider;
import org.jooby.mvc.Consumes;
import org.jooby.mvc.Produces;
import org.jooby.scope.Providers;
import org.jooby.scope.RequestScoped;
import org.jooby.spi.HttpHandler;
import org.jooby.spi.Server;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.inject.Singleton;
import javax.net.ssl.SSLContext;
import java.io.File;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * 

jooby

*

getting started

* *
 * public class MyApp extends Jooby {
 *
 *   {
 *      use(new Jackson()); // 1. JSON serializer.
 *
 *      // 2. Define a route
 *      get("/", req {@literal ->} {
 *        Map{@literal <}String, Object{@literal >} model = ...;
 *        return model;
 *      }
 *   }
 *
 *  public static void main(String[] args) {
 *    run(MyApp::new, args); // 3. Done!
 *  }
 * }
 * 
* *

application.conf

*

* Jooby delegate configuration management to TypeSafe Config. *

* *

* By default Jooby looks for an application.conf. If * you want to specify a different file or location, you can do it with {@link #conf(String)}. *

* *

* TypeSafe Config uses a hierarchical model to * define and override properties. *

*

* A {@link Jooby.Module} might provides his own set of properties through the * {@link Jooby.Module#config()} method. By default, this method returns an empty config object. *

* For example: * *
 *   use(new M1());
 *   use(new M2());
 *   use(new M3());
 * 
* * Previous example had the following order (first-listed are higher priority): *
    *
  • arguments properties
  • *
  • System properties
  • *
  • application.conf
  • *
  • M3 properties
  • *
  • M2 properties
  • *
  • M1 properties
  • *
*

* Command line argmuents or system properties takes precedence over any application specific * property. *

* *

env

*

* Jooby defines one mode or environment: dev. In Jooby, dev * is special and some modules could apply special settings while running in dev. * Any other env is usually considered a prod like env. But that depends on module * implementor. *

*

* An environment can be defined in your .conf file using the * application.env property. If missing, Jooby set the env for you to * dev. *

*

* There is more at {@link Env} please read the {@link Env} javadoc. *

* *

modules: the jump to full-stack framework

*

* {@link Jooby.Module Modules} are quite similar to a Guice modules except that the configure * callback has been complementing with {@link Env} and {@link Config}. *

* *
 *   public class MyModule implements Jooby.Module {
 *     public void configure(Env env, Config config, Binder binder) {
 *     }
 *   }
 * 
* * From the configure callback you can bind your services as you usually do in a Guice app. *

* There is more at {@link Jooby.Module} so please read the {@link Jooby.Module} javadoc. *

* *

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.html} 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

*

* Routes perform actions in response to a server HTTP request. *

*

* 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!
 *   });
 * 
* * Previous example can be rewritten using {@link Route.Filter}: * *
 *   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!
 *   });
 * 
* * 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 <}{@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 routes

*

* 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} javadoc. *

* *

static files

*

* Static files, like: *.js, *.css, ..., etc... can be served with: *

* *
 *   assets("assets/**");
 * 
*

* Classpath resources under the /assets folder will be accessible from client/browser. *

* *

lifecyle

*

* We do provide {@link #onStart(Throwing.Consumer)} and {@link #onStop(Throwing.Consumer)} callbacks. * These callbacks are executed are application startup or shutdown time: *

* *
{@code
 * {
 *   onStart(() -> {
 *     log.info("Welcome!");
 *   });
 *
 *   onStop(() -> {
 *     log.info("Bye!");
 *   });
 * }
 * }
* *

* From life cycle callbacks you can access to application services: *

* *
{@code
 * {
 *   onStart(registry -> {
 *     MyDatabase db = registry.require(MyDatabase.class);
 *     // do something with databse:
 *   });
 * }
 * }
* * @author edgar * @see Jooby.Module * @since 0.1.0 */ public class Jooby implements Router, LifeCycle, Registry { /** *
{@code
   * {
   *   on("dev", () -> {
   *     // run something on dev
   *   }).orElse(() -> {
   *     // run something on prod
   *   });
   * }
   * }
*/ public interface EnvPredicate { /** *
{@code
     * {
     *   on("dev", () -> {
     *     // run something on dev
     *   }).orElse(() -> {
     *     // run something on prod
     *   });
     * }
     * }
* * @param callback Env callback. */ default void orElse(final Runnable callback) { orElse(conf -> callback.run()); } /** *
{@code
     * {
     *   on("dev", () -> {
     *     // run something on dev
     *   }).orElse(conf -> {
     *     // run something on prod
     *   });
     * }
     * }
* * @param callback Env callback. */ void orElse(Consumer callback); } /** * A module can publish or produces: {@link Route.Definition routes}, {@link Parser}, * {@link Renderer}, and any other application specific service or contract of your choice. *

* It is similar to {@link com.google.inject.Module} except for the callback method receives a * {@link Env}, {@link Config} and {@link Binder}. *

* *

* A module can provide his own set of properties through the {@link #config()} method. By * default, this method returns an empty config object. *

* For example: * *
   *   use(new M1());
   *   use(new M2());
   *   use(new M3());
   * 
* * Previous example had the following order (first-listed are higher priority): *
    *
  • System properties
  • *
  • application.conf
  • *
  • M3 properties
  • *
  • M2 properties
  • *
  • M1 properties
  • *
* *

* A module can provide start/stop methods in order to start or close resources. *

* * @author edgar * @see Jooby#use(Jooby.Module) * @since 0.1.0 */ public interface Module { /** * @return Produces a module config object (when need it). By default a module doesn't produce * any configuration object. */ @Nonnull default Config config() { return ConfigFactory.empty(); } /** * Configure and produces bindings for the underlying application. A module can optimize or * customize a service by checking current the {@link Env application env} and/or the current * application properties available from {@link Config}. * * @param env The current application's env. Not null. * @param conf The current config object. Not null. * @param binder A guice binder. Not null. * @throws Throwable If something goes wrong. */ void configure(Env env, Config conf, Binder binder) throws Throwable; } static class MvcClass implements Route.Props { Class routeClass; String path; ImmutableMap.Builder attrs = ImmutableMap.builder(); private List consumes; private String name; private List produces; private List excludes; private Mapper mapper; private String prefix; private String renderer; public MvcClass(final Class routeClass, final String path, final String prefix) { this.routeClass = routeClass; this.path = path; this.prefix = prefix; } @Override public MvcClass attr(final String name, final Object value) { attrs.put(name, value); return this; } @Override public MvcClass name(final String name) { this.name = name; return this; } @Override public MvcClass consumes(final List consumes) { this.consumes = consumes; return this; } @Override public MvcClass produces(final List produces) { this.produces = produces; return this; } @Override public MvcClass excludes(final List excludes) { this.excludes = excludes; return this; } @Override public MvcClass map(final Mapper mapper) { this.mapper = mapper; return this; } @Override public String renderer() { return renderer; } @Override public MvcClass renderer(final String name) { this.renderer = name; return this; } public Route.Definition apply(final Route.Definition route) { attrs.build().forEach(route::attr); if (name != null) { route.name(name); } if (prefix != null) { route.name(prefix + "/" + route.name()); } if (consumes != null) { route.consumes(consumes); } if (produces != null) { route.produces(produces); } if (excludes != null) { route.excludes(excludes); } if (mapper != null) { route.map(mapper); } if (renderer != null) { route.renderer(renderer); } return route; } } private static class EnvDep { Predicate predicate; Consumer callback; public EnvDep(final Predicate predicate, final Consumer callback) { this.predicate = predicate; this.callback = callback; } } static { // set pid as system property String pid = System.getProperty("pid", JvmInfo.pid() + ""); System.setProperty("pid", pid); } /** * Keep track of routes. */ private transient Set bag = new LinkedHashSet<>(); /** * The override config. Optional. */ private transient Config srcconf; private final transient AtomicBoolean started = new AtomicBoolean(false); /** Keep the global injector instance. */ private transient Injector injector; /** Session store. */ private transient Session.Definition session = new Session.Definition(Session.Mem.class); /** Env builder. */ private transient Env.Builder env = Env.DEFAULT; /** Route's prefix. */ private transient String prefix; /** startup callback . */ private transient List> onStart = new ArrayList<>(); private transient List> onStarted = new ArrayList<>(); /** stop callback . */ private transient List> onStop = new ArrayList<>(); /** Mappers . */ @SuppressWarnings("rawtypes") private transient Mapper mapper; /** Don't add same mapper twice . */ private transient Set mappers = new HashSet<>(); /** Bean parser . */ private transient Optional beanParser = Optional.empty(); private transient ServerLookup server = new ServerLookup(); private transient String dateFormat; private transient Charset charset; private transient String[] languages; private transient ZoneId zoneId; private transient Integer port; private transient Integer securePort; private transient String numberFormat; private transient boolean http2; private transient List> executors = new ArrayList<>(); private transient boolean defaultExecSet; private boolean throwBootstrapException; /** * creates the injector */ private transient BiFunction injectorFactory = Guice::createInjector; private transient List apprefs; private transient LinkedList path = new LinkedList<>(); private transient String confname; private transient boolean caseSensitiveRouting = true; private transient String classname; /** * Creates a new {@link Jooby} application. */ public Jooby() { this(null); } /** * Creates a new application and prefix all the names of the routes with the given prefix. Useful, * for dynamic/advanced routing. See {@link Route.Chain#next(String, Request, Response)}. * * @param prefix Route name prefix. */ public Jooby(final String prefix) { this.prefix = prefix; use(server); this.classname = classname(getClass().getName()); } @Override public Route.Collection path(String path, Runnable action) { this.path.addLast(Route.normalize(path)); Route.Collection collection = with(action); this.path.removeLast(); return collection; } @Override public Jooby use(final Jooby app) { return use(prefixPath(null), app); } private Optional prefixPath(String tail) { return path.size() == 0 ? tail == null ? Optional.empty() : Optional.of(Route.normalize(tail)) : Optional.of(path.stream() .collect(Collectors.joining("", "", tail == null ? "" : Route.normalize(tail)))); } @Override public Jooby use(final String path, final Jooby app) { return use(prefixPath(path), app); } /** * Use the provided HTTP server. * * @param server Server. * @return This jooby instance. */ public Jooby server(final Class server) { requireNonNull(server, "Server required."); // remove server lookup List tmp = bag.stream() .skip(1) .collect(Collectors.toList()); tmp.add(0, (Module) (env, conf, binder) -> binder.bind(Server.class).to(server).asEagerSingleton()); bag.clear(); bag.addAll(tmp); return this; } private Jooby use(final Optional path, final Jooby app) { requireNonNull(app, "App is required."); Function rewrite = r -> { return path.map(p -> { Route.Definition result = new Route.Definition(r.method(), p + r.pattern(), r.filter()); result.consumes(r.consumes()); result.produces(r.produces()); result.excludes(r.excludes()); return result; }).orElse(r); }; app.bag.forEach(it -> { if (it instanceof Route.Definition) { this.bag.add(rewrite.apply((Definition) it)); } else if (it instanceof Route.Group) { ((Route.Group) it).routes().forEach(r -> this.bag.add(rewrite.apply(r))); } else if (it instanceof MvcClass) { Object routes = path.map(p -> new MvcClass(((MvcClass) it).routeClass, p, prefix)) .orElse(it); this.bag.add(routes); } else { // everything else this.bag.add(it); } }); // start/stop callback app.onStart.forEach(this.onStart::add); app.onStarted.forEach(this.onStarted::add); app.onStop.forEach(this.onStop::add); // mapper if (app.mapper != null) { this.map(app.mapper); } if (apprefs == null) { apprefs = new ArrayList<>(); } apprefs.add(app); return this; } @Override public Route.Group use(final String pattern) { Route.Group group = new Route.Group(pattern, prefix); this.bag.add(group); return group; } /** * Set a custom {@link Env.Builder} to use. * * @param env A custom env builder. * @return This jooby instance. */ public Jooby env(final Env.Builder env) { this.env = requireNonNull(env, "Env builder is required."); return this; } @Override public Jooby onStart(final Throwing.Runnable callback) { LifeCycle.super.onStart(callback); return this; } @Override public Jooby onStart(final Throwing.Consumer callback) { requireNonNull(callback, "Callback is required."); onStart.add(callback); return this; } @Override public Jooby onStarted(final Throwing.Runnable callback) { LifeCycle.super.onStarted(callback); return this; } @Override public Jooby onStarted(final Throwing.Consumer callback) { requireNonNull(callback, "Callback is required."); onStarted.add(callback); return this; } @Override public Jooby onStop(final Throwing.Runnable callback) { LifeCycle.super.onStop(callback); return this; } @Override public Jooby onStop(final Throwing.Consumer callback) { requireNonNull(callback, "Callback is required."); onStop.add(callback); return this; } /** * Run the given callback if and only if, application runs in the given environment. * *
   * {
   *   on("dev", () {@literal ->} {
   *     use(new DevModule());
   *   });
   * }
   * 
* * There is an else clause which is the opposite version of the env predicate: * *
   * {
   *   on("dev", () {@literal ->} {
   *     use(new DevModule());
   *   }).orElse(() {@literal ->} {
   *     use(new RealModule());
   *   });
   * }
   * 
* * @param env Environment where we want to run the callback. * @param callback An env callback. * @return This jooby instance. */ public EnvPredicate on(final String env, final Runnable callback) { requireNonNull(env, "Env is required."); return on(envpredicate(env), callback); } /** * Run the given callback if and only if, application runs in the given environment. * *
   * {
   *   on("dev", () {@literal ->} {
   *     use(new DevModule());
   *   });
   * }
   * 
* * There is an else clause which is the opposite version of the env predicate: * *
   * {
   *   on("dev", conf {@literal ->} {
   *     use(new DevModule());
   *   }).orElse(conf {@literal ->} {
   *     use(new RealModule());
   *   });
   * }
   * 
* * @param env Environment where we want to run the callback. * @param callback An env callback. * @return This jooby instance. */ public EnvPredicate on(final String env, final Consumer callback) { requireNonNull(env, "Env is required."); return on(envpredicate(env), callback); } /** * Run the given callback if and only if, application runs in the given envirobment. * *
   * {
   *   on("dev", "test", () {@literal ->} {
   *     use(new DevModule());
   *   });
   * }
   * 
* * There is an else clause which is the opposite version of the env predicate: * *
   * {
   *   on(env {@literal ->} env.equals("dev"), () {@literal ->} {
   *     use(new DevModule());
   *   }).orElse(() {@literal ->} {
   *     use(new RealModule());
   *   });
   * }
   * 
* * @param predicate Predicate to check the environment. * @param callback An env callback. * @return This jooby instance. */ public EnvPredicate on(final Predicate predicate, final Runnable callback) { requireNonNull(predicate, "Predicate is required."); requireNonNull(callback, "Callback is required."); return on(predicate, conf -> callback.run()); } /** * Run the given callback if and only if, application runs in the given environment. * *
   * {
   *   on(env {@literal ->} env.equals("dev"), conf {@literal ->} {
   *     use(new DevModule());
   *   });
   * }
   * 
* * @param predicate Predicate to check the environment. * @param callback An env callback. * @return This jooby instance. */ public EnvPredicate on(final Predicate predicate, final Consumer callback) { requireNonNull(predicate, "Predicate is required."); requireNonNull(callback, "Callback is required."); this.bag.add(new EnvDep(predicate, callback)); return otherwise -> this.bag.add(new EnvDep(predicate.negate(), otherwise)); } /** * Run the given callback if and only if, application runs in the given environment. * *
   * {
   *   on("dev", "test", "mock", () {@literal ->} {
   *     use(new DevModule());
   *   });
   * }
   * 
* * @param env1 Environment where we want to run the callback. * @param env2 Environment where we want to run the callback. * @param env3 Environment where we want to run the callback. * @param callback An env callback. * @return This jooby instance. */ public Jooby on(final String env1, final String env2, final String env3, final Runnable callback) { on(envpredicate(env1).or(envpredicate(env2)).or(envpredicate(env3)), callback); return this; } @Override public T require(final Key type) { checkState(injector != null, "Registry is not ready. Require calls are available at application startup time, see http://jooby.org/doc/#application-life-cycle"); return injector.getInstance(type); } @Override public Route.OneArgHandler promise(final Deferred.Initializer initializer) { return req -> { return new Deferred(initializer); }; } @Override public Route.OneArgHandler promise(final String executor, final Deferred.Initializer initializer) { return req -> new Deferred(executor, initializer); } @Override public Route.OneArgHandler promise(final Deferred.Initializer0 initializer) { return req -> { return new Deferred(initializer); }; } @Override public Route.OneArgHandler promise(final String executor, final Deferred.Initializer0 initializer) { return req -> new Deferred(executor, initializer); } /** * Setup a session store to use. Useful if you want/need to persist sessions between shutdowns, * apply timeout policies, etc... * * Jooby comes with a dozen of {@link Session.Store}, checkout the * session modules. * * This method returns a {@link Session.Definition} objects that let you customize the session * cookie. * * @param store A session store. * @return A session store definition. */ public Session.Definition session(final Class store) { this.session = new Session.Definition(requireNonNull(store, "A session store is required.")); return this.session; } /** * Setup a session store that saves data in a the session cookie. It makes the application * stateless, which help to scale easily. Keep in mind that a cookie has a limited size (up to * 4kb) so you must pay attention to what you put in the session object (don't use as cache). * * Cookie session signed data using the application.secret property, so you must * provide an application.secret value. On dev environment you can set it in your * .conf file. In prod is probably better to provide as command line argument and/or * environment variable. Just make sure to keep it private. * * Please note {@link Session#id()}, {@link Session#accessedAt()}, etc.. make no sense for cookie * sessions, just the {@link Session#attributes()}. * * This method returns a {@link Session.Definition} objects that let you customize the session * cookie. * * @return A session definition/configuration object. */ public Session.Definition cookieSession() { this.session = new Session.Definition(); return this.session; } /** * Setup a session store to use. Useful if you want/need to persist sessions between shutdowns, * apply timeout policies, etc... * * Jooby comes with a dozen of {@link Session.Store}, checkout the * session modules. * * This method returns a {@link Session.Definition} objects that let you customize the session * cookie. * * @param store A session store. * @return A session store definition. */ public Session.Definition session(final Session.Store store) { this.session = new Session.Definition(requireNonNull(store, "A session store is required.")); return this.session; } /** * Register a new param/body converter. See {@link Parser} for more details. * * @param parser A parser. * @return This jooby instance. */ public Jooby parser(final Parser parser) { if (parser instanceof BeanParser) { beanParser = Optional.of(parser); } else { bag.add(requireNonNull(parser, "A parser is required.")); } return this; } /** * Append a response {@link Renderer} for write HTTP messages. * * @param renderer A renderer renderer. * @return This jooby instance. */ public Jooby renderer(final Renderer renderer) { this.bag.add(requireNonNull(renderer, "A renderer is required.")); return this; } @Override public Route.Definition before(final String method, final String pattern, final Route.Before handler) { return appendDefinition(method, pattern, handler); } @Override public Route.Definition after(final String method, final String pattern, final Route.After handler) { return appendDefinition(method, pattern, handler); } @Override public Route.Definition complete(final String method, final String pattern, final Route.Complete handler) { return appendDefinition(method, pattern, handler); } @Override public Route.Definition use(final String path, final Route.Filter filter) { return appendDefinition("*", path, filter); } @Override public Route.Definition use(final String verb, final String path, final Route.Filter filter) { return appendDefinition(verb, path, filter); } @Override public Route.Definition use(final String verb, final String path, final Route.Handler handler) { return appendDefinition(verb, path, handler); } @Override public Route.Definition use(final String path, final Route.Handler handler) { return appendDefinition("*", path, handler); } @Override public Route.Definition use(final String path, final Route.OneArgHandler handler) { return appendDefinition("*", path, handler); } @Override public Route.Definition get(final String path, final Route.Handler handler) { return appendDefinition(GET, path, handler); } @Override public Route.Collection get(final String path1, final String path2, final Route.Handler handler) { return new Route.Collection( new Route.Definition[]{get(path1, handler), get(path2, handler)}); } @Override public Route.Collection get(final String path1, final String path2, final String path3, final Route.Handler handler) { return new Route.Collection( new Route.Definition[]{get(path1, handler), get(path2, handler), get(path3, handler)}); } @Override public Route.Definition get(final String path, final Route.OneArgHandler handler) { return appendDefinition(GET, path, handler); } @Override public Route.Collection get(final String path1, final String path2, final Route.OneArgHandler handler) { return new Route.Collection( new Route.Definition[]{get(path1, handler), get(path2, handler)}); } @Override public Route.Collection get(final String path1, final String path2, final String path3, final Route.OneArgHandler handler) { return new Route.Collection( new Route.Definition[]{get(path1, handler), get(path2, handler), get(path3, handler)}); } @Override public Route.Definition get(final String path, final Route.ZeroArgHandler handler) { return appendDefinition(GET, path, handler); } @Override public Route.Collection get(final String path1, final String path2, final Route.ZeroArgHandler handler) { return new Route.Collection( new Route.Definition[]{get(path1, handler), get(path2, handler)}); } @Override public Route.Collection get(final String path1, final String path2, final String path3, final Route.ZeroArgHandler handler) { return new Route.Collection( new Route.Definition[]{get(path1, handler), get(path2, handler), get(path3, handler)}); } @Override public Route.Definition get(final String path, final Route.Filter filter) { return appendDefinition(GET, path, filter); } @Override public Route.Collection get(final String path1, final String path2, final Route.Filter filter) { return new Route.Collection(new Route.Definition[]{get(path1, filter), get(path2, filter)}); } @Override public Route.Collection get(final String path1, final String path2, final String path3, final Route.Filter filter) { return new Route.Collection( new Route.Definition[]{get(path1, filter), get(path2, filter), get(path3, filter)}); } @Override public Route.Definition post(final String path, final Route.Handler handler) { return appendDefinition(POST, path, handler); } @Override public Route.Collection post(final String path1, final String path2, final Route.Handler handler) { return new Route.Collection( new Route.Definition[]{post(path1, handler), post(path2, handler)}); } @Override public Route.Collection post(final String path1, final String path2, final String path3, final Route.Handler handler) { return new Route.Collection( new Route.Definition[]{post(path1, handler), post(path2, handler), post(path3, handler)}); } @Override public Route.Definition post(final String path, final Route.OneArgHandler handler) { return appendDefinition(POST, path, handler); } @Override public Route.Collection post(final String path1, final String path2, final Route.OneArgHandler handler) { return new Route.Collection( new Route.Definition[]{post(path1, handler), post(path2, handler)}); } @Override public Route.Collection post(final String path1, final String path2, final String path3, final Route.OneArgHandler handler) { return new Route.Collection( new Route.Definition[]{post(path1, handler), post(path2, handler), post(path3, handler)}); } @Override public Route.Definition post(final String path, final Route.ZeroArgHandler handler) { return appendDefinition(POST, path, handler); } @Override public Route.Collection post(final String path1, final String path2, final Route.ZeroArgHandler handler) { return new Route.Collection( new Route.Definition[]{post(path1, handler), post(path2, handler)}); } @Override public Route.Collection post(final String path1, final String path2, final String path3, final Route.ZeroArgHandler handler) { return new Route.Collection( new Route.Definition[]{post(path1, handler), post(path2, handler), post(path3, handler)}); } @Override public Route.Definition post(final String path, final Route.Filter filter) { return appendDefinition(POST, path, filter); } @Override public Route.Collection post(final String path1, final String path2, final Route.Filter filter) { return new Route.Collection( new Route.Definition[]{post(path1, filter), post(path2, filter)}); } @Override public Route.Collection post(final String path1, final String path2, final String path3, final Route.Filter filter) { return new Route.Collection( new Route.Definition[]{post(path1, filter), post(path2, filter), post(path3, filter)}); } @Override public Route.Definition head(final String path, final Route.Handler handler) { return appendDefinition(HEAD, path, handler); } @Override public Route.Definition head(final String path, final Route.OneArgHandler handler) { return appendDefinition(HEAD, path, handler); } @Override public Route.Definition head(final String path, final Route.ZeroArgHandler handler) { return appendDefinition(HEAD, path, handler); } @Override public Route.Definition head(final String path, final Route.Filter filter) { return appendDefinition(HEAD, path, filter); } @Override public Route.Definition head() { return appendDefinition(HEAD, "*", filter(HeadHandler.class)).name("*.head"); } @Override public Route.Definition options(final String path, final Route.Handler handler) { return appendDefinition(OPTIONS, path, handler); } @Override public Route.Definition options(final String path, final Route.OneArgHandler handler) { return appendDefinition(OPTIONS, path, handler); } @Override public Route.Definition options(final String path, final Route.ZeroArgHandler handler) { return appendDefinition(OPTIONS, path, handler); } @Override public Route.Definition options(final String path, final Route.Filter filter) { return appendDefinition(OPTIONS, path, filter); } @Override public Route.Definition options() { return appendDefinition(OPTIONS, "*", handler(OptionsHandler.class)).name("*.options"); } @Override public Route.Definition put(final String path, final Route.Handler handler) { return appendDefinition(PUT, path, handler); } @Override public Route.Collection put(final String path1, final String path2, final Route.Handler handler) { return new Route.Collection( new Route.Definition[]{put(path1, handler), put(path2, handler)}); } @Override public Route.Collection put(final String path1, final String path2, final String path3, final Route.Handler handler) { return new Route.Collection( new Route.Definition[]{put(path1, handler), put(path2, handler), put(path3, handler)}); } @Override public Route.Definition put(final String path, final Route.OneArgHandler handler) { return appendDefinition(PUT, path, handler); } @Override public Route.Collection put(final String path1, final String path2, final Route.OneArgHandler handler) { return new Route.Collection( new Route.Definition[]{put(path1, handler), put(path2, handler)}); } @Override public Route.Collection put(final String path1, final String path2, final String path3, final Route.OneArgHandler handler) { return new Route.Collection( new Route.Definition[]{put(path1, handler), put(path2, handler), put(path3, handler)}); } @Override public Route.Definition put(final String path, final Route.ZeroArgHandler handler) { return appendDefinition(PUT, path, handler); } @Override public Route.Collection put(final String path1, final String path2, final Route.ZeroArgHandler handler) { return new Route.Collection( new Route.Definition[]{put(path1, handler), put(path2, handler)}); } @Override public Route.Collection put(final String path1, final String path2, final String path3, final Route.ZeroArgHandler handler) { return new Route.Collection( new Route.Definition[]{put(path1, handler), put(path2, handler), put(path3, handler)}); } @Override public Route.Definition put(final String path, final Route.Filter filter) { return appendDefinition(PUT, path, filter); } @Override public Route.Collection put(final String path1, final String path2, final Route.Filter filter) { return new Route.Collection( new Route.Definition[]{put(path1, filter), put(path2, filter)}); } @Override public Route.Collection put(final String path1, final String path2, final String path3, final Route.Filter filter) { return new Route.Collection( new Route.Definition[]{put(path1, filter), put(path2, filter), put(path3, filter)}); } @Override public Route.Definition patch(final String path, final Route.Handler handler) { return appendDefinition(PATCH, path, handler); } @Override public Route.Collection patch(final String path1, final String path2, final Route.Handler handler) { return new Route.Collection( new Route.Definition[]{patch(path1, handler), patch(path2, handler)}); } @Override public Route.Collection patch(final String path1, final String path2, final String path3, final Route.Handler handler) { return new Route.Collection( new Route.Definition[]{patch(path1, handler), patch(path2, handler), patch(path3, handler)}); } @Override public Route.Definition patch(final String path, final Route.OneArgHandler handler) { return appendDefinition(PATCH, path, handler); } @Override public Route.Collection patch(final String path1, final String path2, final Route.OneArgHandler handler) { return new Route.Collection( new Route.Definition[]{patch(path1, handler), patch(path2, handler)}); } @Override public Route.Collection patch(final String path1, final String path2, final String path3, final Route.OneArgHandler handler) { return new Route.Collection( new Route.Definition[]{patch(path1, handler), patch(path2, handler), patch(path3, handler)}); } @Override public Route.Definition patch(final String path, final Route.ZeroArgHandler handler) { return appendDefinition(PATCH, path, handler); } @Override public Route.Collection patch(final String path1, final String path2, final Route.ZeroArgHandler handler) { return new Route.Collection( new Route.Definition[]{patch(path1, handler), patch(path2, handler)}); } @Override public Route.Collection patch(final String path1, final String path2, final String path3, final Route.ZeroArgHandler handler) { return new Route.Collection( new Route.Definition[]{patch(path1, handler), patch(path2, handler), patch(path3, handler)}); } @Override public Route.Definition patch(final String path, final Route.Filter filter) { return appendDefinition(PATCH, path, filter); } @Override public Route.Collection patch(final String path1, final String path2, final Route.Filter filter) { return new Route.Collection( new Route.Definition[]{patch(path1, filter), patch(path2, filter)}); } @Override public Route.Collection patch(final String path1, final String path2, final String path3, final Route.Filter filter) { return new Route.Collection( new Route.Definition[]{patch(path1, filter), patch(path2, filter), patch(path3, filter)}); } @Override public Route.Definition delete(final String path, final Route.Handler handler) { return appendDefinition(DELETE, path, handler); } @Override public Route.Collection delete(final String path1, final String path2, final Route.Handler handler) { return new Route.Collection( new Route.Definition[]{delete(path1, handler), delete(path2, handler)}); } @Override public Route.Collection delete(final String path1, final String path2, final String path3, final Route.Handler handler) { return new Route.Collection( new Route.Definition[]{delete(path1, handler), delete(path2, handler), delete(path3, handler)}); } @Override public Route.Definition delete(final String path, final Route.OneArgHandler handler) { return appendDefinition(DELETE, path, handler); } @Override public Route.Collection delete(final String path1, final String path2, final Route.OneArgHandler handler) { return new Route.Collection( new Route.Definition[]{delete(path1, handler), delete(path2, handler)}); } @Override public Route.Collection delete(final String path1, final String path2, final String path3, final Route.OneArgHandler handler) { return new Route.Collection( new Route.Definition[]{delete(path1, handler), delete(path2, handler), delete(path3, handler)}); } @Override public Route.Definition delete(final String path, final Route.ZeroArgHandler handler) { return appendDefinition(DELETE, path, handler); } @Override public Route.Collection delete(final String path1, final String path2, final Route.ZeroArgHandler handler) { return new Route.Collection( new Route.Definition[]{delete(path1, handler), delete(path2, handler)}); } @Override public Route.Collection delete(final String path1, final String path2, final String path3, final Route.ZeroArgHandler handler) { return new Route.Collection( new Route.Definition[]{delete(path1, handler), delete(path2, handler), delete(path3, handler)}); } @Override public Route.Definition delete(final String path, final Route.Filter filter) { return appendDefinition(DELETE, path, filter); } @Override public Route.Collection delete(final String path1, final String path2, final Route.Filter filter) { return new Route.Collection( new Route.Definition[]{delete(path1, filter), delete(path2, filter)}); } @Override public Route.Collection delete(final String path1, final String path2, final String path3, final Route.Filter filter) { return new Route.Collection( new Route.Definition[]{delete(path1, filter), delete(path2, filter), delete(path3, filter)}); } @Override public Route.Definition trace(final String path, final Route.Handler handler) { return appendDefinition(TRACE, path, handler); } @Override public Route.Definition trace(final String path, final Route.OneArgHandler handler) { return appendDefinition(TRACE, path, handler); } @Override public Route.Definition trace(final String path, final Route.ZeroArgHandler handler) { return appendDefinition(TRACE, path, handler); } @Override public Route.Definition trace(final String path, final Route.Filter filter) { return appendDefinition(TRACE, path, filter); } @Override public Route.Definition trace() { return appendDefinition(TRACE, "*", handler(TraceHandler.class)).name("*.trace"); } @Override public Route.Definition connect(final String path, final Route.Handler handler) { return appendDefinition(CONNECT, path, handler); } @Override public Route.Definition connect(final String path, final Route.OneArgHandler handler) { return appendDefinition(CONNECT, path, handler); } @Override public Route.Definition connect(final String path, final Route.ZeroArgHandler handler) { return appendDefinition(CONNECT, path, handler); } @Override public Route.Definition connect(final String path, final Route.Filter filter) { return appendDefinition(CONNECT, path, filter); } /** * Creates a new {@link Route.Handler} that delegate the execution to the given handler. This is * useful when the target handler requires some dependencies. * *
   *   public class MyHandler implements Route.Handler {
   *     @Inject
   *     public MyHandler(Dependency d) {
   *     }
   *
   *     public void handle(Request req, Response rsp) throws Exception {
   *      // do something
   *     }
   *   }
   *   ...
   *   // external route
   *   get("/", handler(MyHandler.class));
   *
   *   // inline version route
   *   get("/", (req, rsp) {@literal ->} {
   *     Dependency d = req.getInstance(Dependency.class);
   *     // do something
   *   });
   * 
* * You can access to a dependency from a in-line route too, so the use of external route it is * more or less a matter of taste. * * @param handler The external handler class. * @return A new inline route handler. */ private Route.Handler handler(final Class handler) { requireNonNull(handler, "Route handler is required."); return (req, rsp) -> req.require(handler).handle(req, rsp); } /** * Creates a new {@link Route.Filter} that delegate the execution to the given filter. This is * useful when the target handler requires some dependencies. * *
   *   public class MyFilter implements Filter {
   *     @Inject
   *     public MyFilter(Dependency d) {
   *     }
   *
   *     public void handle(Request req, Response rsp, Route.Chain chain) throws Exception {
   *      // do something
   *     }
   *   }
   *   ...
   *   // external filter
   *   get("/", filter(MyFilter.class));
   *
   *   // inline version route
   *   get("/", (req, rsp, chain) {@literal ->} {
   *     Dependency d = req.getInstance(Dependency.class);
   *     // do something
   *   });
   * 
* * You can access to a dependency from a in-line route too, so the use of external filter it is * more or less a matter of taste. * * @param filter The external filter class. * @return A new inline route. */ private Route.Filter filter(final Class filter) { requireNonNull(filter, "Filter is required."); return (req, rsp, chain) -> req.require(filter).handle(req, rsp, chain); } @Override public Route.AssetDefinition assets(final String path, final Path basedir) { return assets(path, new AssetHandler(basedir)); } @Override public Route.AssetDefinition assets(final String path, final String location) { return assets(path, new AssetHandler(location)); } @Override public Route.AssetDefinition assets(final String path, final AssetHandler handler) { Route.AssetDefinition route = appendDefinition(GET, path, handler, Route.AssetDefinition::new); return configureAssetHandler(route); } @Override public Route.Collection use(final Class routeClass) { return use("", routeClass); } @Override public Route.Collection use(final String path, final Class routeClass) { requireNonNull(routeClass, "Route class is required."); requireNonNull(path, "Path is required"); MvcClass mvc = new MvcClass(routeClass, path, prefix); bag.add(mvc); return new Route.Collection(mvc); } /** * Keep track of routes in the order user define them. * * @param method Route method. * @param pattern Route pattern. * @param filter Route filter. * @return The same route definition. */ private Route.Definition appendDefinition(String method, String pattern, Route.Filter filter) { return appendDefinition(method, pattern, filter, Route.Definition::new); } /** * Keep track of routes in the order user define them. * * @param method Route method. * @param pattern Route pattern. * @param filter Route filter. * @param creator Route creator. * @return The same route definition. */ private T appendDefinition(String method, String pattern, Route.Filter filter, Throwing.Function4 creator) { String pathPattern = prefixPath(pattern).orElse(pattern); T route = creator.apply(method, pathPattern, filter, caseSensitiveRouting); if (prefix != null) { route.prefix = prefix; // reset name will update the name if prefix != null route.name(route.name()); } bag.add(route); return route; } /** * Import an application {@link Module}. * * @param module The module to import. * @return This jooby instance. * @see Jooby.Module */ public Jooby use(final Jooby.Module module) { requireNonNull(module, "A module is required."); bag.add(module); return this; } /** * Set/specify a custom .conf file, useful when you don't want a application.conf * file. * * @param path Classpath location. * @return This jooby instance. */ public Jooby conf(final String path) { this.confname = path; use(ConfigFactory.parseResources(path)); return this; } /** * Set/specify a custom .conf file, useful when you don't want a application.conf * file. * * @param path File system location. * @return This jooby instance. */ public Jooby conf(final File path) { this.confname = path.getName(); use(ConfigFactory.parseFile(path)); return this; } /** * Set the application configuration object. You must call this method when the default file * name: application.conf doesn't work for you or when you need/want to register two * or more files. * * @param config The application configuration object. * @return This jooby instance. * @see Config */ public Jooby use(final Config config) { this.srcconf = requireNonNull(config, "Config required."); return this; } @Override public Jooby err(final Err.Handler err) { this.bag.add(requireNonNull(err, "An err handler is required.")); return this; } @Override public WebSocket.Definition ws(final String path, final WebSocket.OnOpen handler) { WebSocket.Definition ws = new WebSocket.Definition(path, handler); checkArgument(bag.add(ws), "Duplicated path: '%s'", path); return ws; } @Override public WebSocket.Definition ws(final String path, final Class> handler) { String fpath = Optional.ofNullable(handler.getAnnotation(org.jooby.mvc.Path.class)) .map(it -> path + "/" + it.value()[0]) .orElse(path); WebSocket.Definition ws = ws(fpath, MvcWebSocket.newWebSocket(handler)); Optional.ofNullable(handler.getAnnotation(Consumes.class)) .ifPresent(consumes -> Arrays.asList(consumes.value()).forEach(ws::consumes)); Optional.ofNullable(handler.getAnnotation(Produces.class)) .ifPresent(produces -> Arrays.asList(produces.value()).forEach(ws::produces)); return ws; } @Override public Route.Definition sse(final String path, final Sse.Handler handler) { return appendDefinition(GET, path, handler).consumes(MediaType.sse); } @Override public Route.Definition sse(final String path, final Sse.Handler1 handler) { return appendDefinition(GET, path, handler).consumes(MediaType.sse); } @SuppressWarnings("rawtypes") @Override public Route.Collection with(final Runnable callback) { // hacky way of doing what we want... but we do simplify developer life int size = this.bag.size(); callback.run(); // collect latest routes and apply route props List local = this.bag.stream() .skip(size) .filter(Route.Props.class::isInstance) .map(Route.Props.class::cast) .collect(Collectors.toList()); return new Route.Collection(local.toArray(new Route.Props[local.size()])); } /** * Prepare and startup a {@link Jooby} application. * * @param app Application supplier. * @param args Application arguments. */ public static void run(final Supplier app, final String... args) { Config conf = ConfigFactory.systemProperties() .withFallback(args(args)); System.setProperty("logback.configurationFile", logback(conf)); app.get().start(args); } /** * Prepare and startup a {@link Jooby} application. * * @param app Application supplier. * @param args Application arguments. */ public static void run(final Class app, final String... args) { run(() -> Try.apply(() -> app.newInstance()).get(), args); } /** * Export configuration from an application. Useful for tooling, testing, debugging, etc... * * @param app Application to extract/collect configuration. * @return Application conf or empty conf on error. */ public static Config exportConf(final Jooby app) { AtomicReference conf = new AtomicReference<>(ConfigFactory.empty()); app.on("*", c -> { conf.set(c); }); exportRoutes(app); return conf.get(); } /** * Export routes from an application. Useful for route analysis, testing, debugging, etc... * * @param app Application to extract/collect routes. * @return Application routes. */ public static List exportRoutes(final Jooby app) { @SuppressWarnings("serial") class Success extends RuntimeException { List routes; Success(final List routes) { this.routes = routes; } } List routes = Collections.emptyList(); try { app.start(new String[0], r -> { throw new Success(r); }); } catch (Success success) { routes = success.routes; } catch (Throwable x) { logger(app).debug("Failed bootstrap: {}", app, x); } return routes; } /** * Start an application. Fire the {@link #onStart(Throwing.Runnable)} event and the * {@link #onStarted(Throwing.Runnable)} events. */ public void start() { start(new String[0]); } /** * Start an application. Fire the {@link #onStart(Throwing.Runnable)} event and the * {@link #onStarted(Throwing.Runnable)} events. * * @param args Application arguments. */ public void start(final String... args) { try { start(args, null); } catch (Throwable x) { stop(); String msg = "An error occurred while starting the application:"; if (throwBootstrapException) { throw new Err(Status.SERVICE_UNAVAILABLE, msg, x); } else { logger(this).error(msg, x); } } } @SuppressWarnings("unchecked") private void start(final String[] args, final Consumer> routes) throws Throwable { long start = System.currentTimeMillis(); started.set(true); this.injector = bootstrap(args(args), routes); // shutdown hook Runtime.getRuntime().addShutdownHook(new Thread(() -> stop())); Config conf = injector.getInstance(Config.class); Logger log = logger(this); // inject class injector.injectMembers(this); // onStart callbacks via .conf if (conf.hasPath("jooby.internal.onStart")) { ClassLoader loader = getClass().getClassLoader(); Object internalOnStart = loader.loadClass(conf.getString("jooby.internal.onStart")) .newInstance(); onStart.add((Throwing.Consumer) internalOnStart); } // start services for (Throwing.Consumer onStart : this.onStart) { onStart.accept(this); } // route mapper Set routeDefs = injector.getInstance(Route.KEY); Set sockets = injector.getInstance(WebSocket.KEY); if (mapper != null) { routeDefs.forEach(it -> it.map(mapper)); } AppPrinter printer = new AppPrinter(routeDefs, sockets, conf); printer.printConf(log, conf); // Start server Server server = injector.getInstance(Server.class); String serverName = server.getClass().getSimpleName().replace("Server", "").toLowerCase(); server.start(); long end = System.currentTimeMillis(); log.info("[{}@{}]: Server started in {}ms\n\n{}\n", conf.getString("application.env"), serverName, end - start, printer); // started services for (Throwing.Consumer onStarted : this.onStarted) { onStarted.accept(this); } boolean join = conf.hasPath("server.join") ? conf.getBoolean("server.join") : true; if (join) { server.join(); } } @Override @SuppressWarnings("unchecked") public Jooby map(final Mapper mapper) { requireNonNull(mapper, "Mapper is required."); if (mappers.add(mapper.name())) { this.mapper = Optional.ofNullable(this.mapper) .map(next -> Route.Mapper.chain(mapper, next)) .orElse((Mapper) mapper); } return this; } /** * Use the injection provider to create the Guice injector * * @param injectorFactory the injection provider * @return this instance. */ public Jooby injector( final BiFunction injectorFactory) { this.injectorFactory = injectorFactory; return this; } /** * Bind the provided abstract type to the given implementation: * *
   * {
   *   bind(MyInterface.class, MyImplementation.class);
   * }
   * 
* * @param type Service interface. * @param implementation Service implementation. * @param Service type. * @return This instance. */ public Jooby bind(final Class type, final Class implementation) { use((env, conf, binder) -> { binder.bind(type).to(implementation); }); return this; } /** * Bind the provided abstract type to the given implementation: * *
   * {
   *   bind(MyInterface.class, MyImplementation::new);
   * }
   * 
* * @param type Service interface. * @param implementation Service implementation. * @param Service type. * @return This instance. */ public Jooby bind(final Class type, final Supplier implementation) { use((env, conf, binder) -> { binder.bind(type).toInstance(implementation.get()); }); return this; } /** * Bind the provided type: * *
   * {
   *   bind(MyInterface.class);
   * }
   * 
* * @param type Service interface. * @param Service type. * @return This instance. */ public Jooby bind(final Class type) { use((env, conf, binder) -> { binder.bind(type); }); return this; } /** * Bind the provided type: * *
   * {
   *   bind(new MyService());
   * }
   * 
* * @param service Service. * @return This instance. */ @SuppressWarnings({"rawtypes", "unchecked"}) public Jooby bind(final Object service) { use((env, conf, binder) -> { Class type = service.getClass(); binder.bind(type).toInstance(service); }); return this; } /** * Bind the provided type and object that requires some type of configuration: * *
{@code
   * {
   *   bind(MyService.class, conf -> new MyService(conf.getString("service.url")));
   * }
   * }
* * @param type Service type. * @param provider Service provider. * @param Service type. * @return This instance. */ public Jooby bind(final Class type, final Function provider) { use((env, conf, binder) -> { T service = provider.apply(conf); binder.bind(type).toInstance(service); }); return this; } /** * Bind the provided type and object that requires some type of configuration: * *
{@code
   * {
   *   bind(conf -> new MyService(conf.getString("service.url")));
   * }
   * }
* * @param provider Service provider. * @param Service type. * @return This instance. */ @SuppressWarnings({"unchecked", "rawtypes"}) public Jooby bind(final Function provider) { use((env, conf, binder) -> { Object service = provider.apply(conf); Class type = service.getClass(); binder.bind(type).toInstance(service); }); return this; } /** * Set application date format. * * @param dateFormat A date format. * @return This instance. */ public Jooby dateFormat(final String dateFormat) { this.dateFormat = requireNonNull(dateFormat, "DateFormat required."); return this; } /** * Set application number format. * * @param numberFormat A number format. * @return This instance. */ public Jooby numberFormat(final String numberFormat) { this.numberFormat = requireNonNull(numberFormat, "NumberFormat required."); return this; } /** * Set application/default charset. * * @param charset A charset. * @return This instance. */ public Jooby charset(final Charset charset) { this.charset = requireNonNull(charset, "Charset required."); return this; } /** * Set application locale (first listed are higher priority). * * @param languages List of locale using the language tag format. * @return This instance. */ public Jooby lang(final String... languages) { this.languages = languages; return this; } /** * Set application time zone. * * @param zoneId ZoneId. * @return This instance. */ public Jooby timezone(final ZoneId zoneId) { this.zoneId = requireNonNull(zoneId, "ZoneId required."); return this; } /** * Set the HTTP port. * *

* Keep in mind this work as a default port and can be reset via application.port * property. *

* * @param port HTTP port. * @return This instance. */ public Jooby port(final int port) { this.port = port; return this; } /** *

* Set the HTTPS port to use. *

* *

* Keep in mind this work as a default port and can be reset via application.port * property. *

* *

HTTPS

*

* Jooby comes with a self-signed certificate, useful for development and test. But of course, you * should NEVER use it in the real world. *

* *

* In order to setup HTTPS with a secure certificate, you need to set these properties: *

* *
    *
  • * ssl.keystore.cert: An X.509 certificate chain file in PEM format. It can be an * absolute path or a classpath resource. *
  • *
  • * ssl.keystore.key: A PKCS#8 private key file in PEM format. It can be an absolute * path or a classpath resource. *
  • *
* *

* Optionally, you can set these too: *

* *
    *
  • * ssl.keystore.password: Password of the keystore.key (if any). Default is: * null/empty. *
  • *
  • * ssl.trust.cert: Trusted certificates for verifying the remote endpoint’s * certificate. The file should contain an X.509 certificate chain in PEM format. Default uses the * system default. *
  • *
  • * ssl.session.cacheSize: Set the size of the cache used for storing SSL session * objects. 0 to use the default value. *
  • *
  • * ssl.session.timeout: Timeout for the cached SSL session objects, in seconds. 0 to * use the default value. *
  • *
* *

* As you can see setup is very simple. All you need is your .crt and * .key files. *

* * @param port HTTPS port. * @return This instance. */ public Jooby securePort(final int port) { this.securePort = port; return this; } /** *

* Enable HTTP/2 protocol. Some servers require special configuration, others just * works. It is a good idea to check the server documentation about * HTTP/2. *

* *

* In order to use HTTP/2 from a browser you must configure HTTPS, see {@link #securePort(int)} * documentation. *

* *

* If HTTP/2 clear text is supported then you may skip the HTTPS setup, but of course you won't be * able to use HTTP/2 with browsers. *

* * @return This instance. */ public Jooby http2() { this.http2 = true; return this; } /** * Set the default executor to use from {@link Deferred Deferred API}. * * Default executor runs each task in the thread that invokes {@link Executor#execute execute}, * that's a Jooby worker thread. A worker thread in Jooby can block. * * The {@link ExecutorService} will automatically shutdown. * * @param executor Executor to use. * @return This jooby instance. */ public Jooby executor(final ExecutorService executor) { executor((Executor) executor); onStop(r -> executor.shutdown()); return this; } /** * Set the default executor to use from {@link Deferred Deferred API}. * * Default executor runs each task in the thread that invokes {@link Executor#execute execute}, * that's a Jooby worker thread. A worker thread in Jooby can block. * * The {@link ExecutorService} will automatically shutdown. * * @param executor Executor to use. * @return This jooby instance. */ public Jooby executor(final Executor executor) { this.defaultExecSet = true; this.executors.add(binder -> { binder.bind(Key.get(String.class, Names.named("deferred"))).toInstance("deferred"); binder.bind(Key.get(Executor.class, Names.named("deferred"))).toInstance(executor); }); return this; } /** * Set a named executor to use from {@link Deferred Deferred API}. Useful for override the * default/global executor. * * Default executor runs each task in the thread that invokes {@link Executor#execute execute}, * that's a Jooby worker thread. A worker thread in Jooby can block. * * The {@link ExecutorService} will automatically shutdown. * * @param name Name of the executor. * @param executor Executor to use. * @return This jooby instance. */ public Jooby executor(final String name, final ExecutorService executor) { executor(name, (Executor) executor); onStop(r -> executor.shutdown()); return this; } /** * Set a named executor to use from {@link Deferred Deferred API}. Useful for override the * default/global executor. * * Default executor runs each task in the thread that invokes {@link Executor#execute execute}, * that's a Jooby worker thread. A worker thread in Jooby can block. * * The {@link ExecutorService} will automatically shutdown. * * @param name Name of the executor. * @param executor Executor to use. * @return This jooby instance. */ public Jooby executor(final String name, final Executor executor) { this.executors.add(binder -> { binder.bind(Key.get(Executor.class, Names.named(name))).toInstance(executor); }); return this; } /** * Set the default executor to use from {@link Deferred Deferred API}. This works as reference to * an executor, application directly or via module must provide an named executor. * * Default executor runs each task in the thread that invokes {@link Executor#execute execute}, * that's a Jooby worker thread. A worker thread in Jooby can block. * * @param name Executor to use. * @return This jooby instance. */ public Jooby executor(final String name) { defaultExecSet = true; this.executors.add(binder -> { binder.bind(Key.get(String.class, Names.named("deferred"))).toInstance(name); }); return this; } /** * Set a named executor to use from {@link Deferred Deferred API}. Useful for override the * default/global executor. * * Default executor runs each task in the thread that invokes {@link Executor#execute execute}, * that's a Jooby worker thread. A worker thread in Jooby can block. * * @param name Name of the executor. * @param provider Provider for the executor. * @return This jooby instance. */ private Jooby executor(final String name, final Class> provider) { this.executors.add(binder -> { binder.bind(Key.get(Executor.class, Names.named(name))).toProvider(provider) .in(Singleton.class); }); return this; } /** * If the application fails to start all the services are shutdown. Also, the exception is logged * and usually the application is going to exit. * * This options turn off logging and rethrow the exception as {@link Err}. Here is an example: * *
   * public class App extends Jooby {
   *   {
   *     throwBootstrapException();
   *     ...
   *   }
   * }
   *
   * App app = new App();
   *
   * try {
   *   app.start();
   * } catch (Err err) {
   *   Throwable cause = err.getCause();
   * }
   * 
* * @return This instance. */ public Jooby throwBootstrapException() { this.throwBootstrapException = true; return this; } /** * Configure case for routing algorithm. Default is case sensitive. * * @param enabled True for case sensitive, false otherwise. * @return This instance. */ public Jooby caseSensitiveRouting(boolean enabled) { this.caseSensitiveRouting = enabled; return this; } private static List normalize(final List services, final Env env, final RouteMetadata classInfo, final boolean caseSensitiveRouting) { List result = new ArrayList<>(); List snapshot = services; /** modules, routes, parsers, renderers and websockets */ snapshot.forEach(candidate -> { if (candidate instanceof Route.Definition) { result.add(candidate); } else if (candidate instanceof Route.Group) { ((Route.Group) candidate).routes() .forEach(r -> result.add(r)); } else if (candidate instanceof MvcClass) { MvcClass mvcRoute = ((MvcClass) candidate); Class mvcClass = mvcRoute.routeClass; String path = ((MvcClass) candidate).path; MvcRoutes.routes(env, classInfo, path, caseSensitiveRouting, mvcClass) .forEach(route -> result.add(mvcRoute.apply(route))); } else { result.add(candidate); } }); return result; } private static List processEnvDep(final Set src, final Env env) { List result = new ArrayList<>(); List bag = new ArrayList<>(src); bag.forEach(it -> { if (it instanceof EnvDep) { EnvDep envdep = (EnvDep) it; if (envdep.predicate.test(env.name())) { int from = src.size(); envdep.callback.accept(env.config()); int to = src.size(); result.addAll(new ArrayList<>(src).subList(from, to)); } } else { result.add(it); } }); return result; } private Injector bootstrap(final Config args, final Consumer> rcallback) throws Throwable { Config initconf = Optional.ofNullable(srcconf) .orElseGet(() -> ConfigFactory.parseResources("application.conf")); List modconf = modconf(this.bag); Config conf = buildConfig(initconf, args, modconf); final List locales = LocaleUtils.parse(conf.getString("application.lang")); Env env = this.env.build(conf, this, locales.get(0)); String envname = env.name(); final Charset charset = Charset.forName(conf.getString("application.charset")); String dateFormat = conf.getString("application.dateFormat"); ZoneId zoneId = ZoneId.of(conf.getString("application.tz")); DateTimeFormatter dateTimeFormatter = DateTimeFormatter .ofPattern(dateFormat, locales.get(0)) .withZone(zoneId); DateTimeFormatter zonedDateTimeFormat = DateTimeFormatter .ofPattern(conf.getString("application.zonedDateTimeFormat")); DecimalFormat numberFormat = new DecimalFormat(conf.getString("application.numberFormat")); // Guice Stage Stage stage = "dev".equals(envname) ? Stage.DEVELOPMENT : Stage.PRODUCTION; // expand and normalize bag RouteMetadata rm = new RouteMetadata(env); List realbag = processEnvDep(this.bag, env); List realmodconf = modconf(realbag); List bag = normalize(realbag, env, rm, caseSensitiveRouting); // collect routes and fire route callback if (rcallback != null) { List routes = bag.stream() .filter(it -> it instanceof Route.Definition) .map(it -> (Route.Definition) it) .collect(Collectors.toList()); rcallback.accept(routes); } // final config ? if we add a mod that depends on env Config finalConfig; Env finalEnv; if (modconf.size() != realmodconf.size()) { finalConfig = buildConfig(initconf, args, realmodconf); finalEnv = this.env.build(finalConfig, this, locales.get(0)); } else { finalConfig = conf; finalEnv = env; } boolean cookieSession = session.store() == null; if (cookieSession && !finalConfig.hasPath("application.secret")) { throw new IllegalStateException("Required property 'application.secret' is missing"); } /** executors: */ if (!defaultExecSet) { // default executor executor(MoreExecutors.directExecutor()); } executor("direct", MoreExecutors.directExecutor()); executor("server", ServerExecutorProvider.class); /** Some basic xss functions. */ xss(finalEnv); /** dependency injection */ @SuppressWarnings("unchecked") com.google.inject.Module joobyModule = binder -> { /** type converters */ new TypeConverters().configure(binder); /** bind config */ bindConfig(binder, finalConfig); /** bind env */ binder.bind(Env.class).toInstance(finalEnv); /** bind charset */ binder.bind(Charset.class).toInstance(charset); /** bind locale */ binder.bind(Locale.class).toInstance(locales.get(0)); TypeLiteral> localeType = (TypeLiteral>) TypeLiteral .get(Types.listOf(Locale.class)); binder.bind(localeType).toInstance(locales); /** bind time zone */ binder.bind(ZoneId.class).toInstance(zoneId); binder.bind(TimeZone.class).toInstance(TimeZone.getTimeZone(zoneId)); /** bind date format */ binder.bind(DateTimeFormatter.class).toInstance(dateTimeFormatter); /** bind number format */ binder.bind(NumberFormat.class).toInstance(numberFormat); binder.bind(DecimalFormat.class).toInstance(numberFormat); /** bind ssl provider. */ binder.bind(SSLContext.class).toProvider(SslContextProvider.class); /** routes */ Multibinder definitions = Multibinder .newSetBinder(binder, Definition.class); /** web sockets */ Multibinder sockets = Multibinder .newSetBinder(binder, WebSocket.Definition.class); /** tmp dir */ File tmpdir = new File(finalConfig.getString("application.tmpdir")); tmpdir.mkdirs(); binder.bind(File.class).annotatedWith(Names.named("application.tmpdir")) .toInstance(tmpdir); binder.bind(ParameterNameProvider.class).toInstance(rm); /** err handler */ Multibinder ehandlers = Multibinder .newSetBinder(binder, Err.Handler.class); /** parsers & renderers */ Multibinder parsers = Multibinder .newSetBinder(binder, Parser.class); Multibinder renderers = Multibinder .newSetBinder(binder, Renderer.class); /** basic parser */ parsers.addBinding().toInstance(BuiltinParser.Basic); parsers.addBinding().toInstance(BuiltinParser.Collection); parsers.addBinding().toInstance(BuiltinParser.Optional); parsers.addBinding().toInstance(BuiltinParser.Enum); parsers.addBinding().toInstance(BuiltinParser.Bytes); /** basic render */ renderers.addBinding().toInstance(BuiltinRenderer.asset); renderers.addBinding().toInstance(BuiltinRenderer.bytes); renderers.addBinding().toInstance(BuiltinRenderer.byteBuffer); renderers.addBinding().toInstance(BuiltinRenderer.file); renderers.addBinding().toInstance(BuiltinRenderer.charBuffer); renderers.addBinding().toInstance(BuiltinRenderer.stream); renderers.addBinding().toInstance(BuiltinRenderer.reader); renderers.addBinding().toInstance(BuiltinRenderer.fileChannel); /** modules, routes, parsers, renderers and websockets */ Set routeClasses = new HashSet<>(); for (Object it : bag) { Try.run(() -> bindService( this.bag, finalConfig, finalEnv, rm, binder, definitions, sockets, ehandlers, parsers, renderers, routeClasses, caseSensitiveRouting) .accept(it)) .throwException(); } parsers.addBinding().toInstance(new DateParser(dateFormat)); parsers.addBinding().toInstance(new LocalDateParser(dateTimeFormatter)); parsers.addBinding().toInstance(new ZonedDateTimeParser(zonedDateTimeFormat)); parsers.addBinding().toInstance(new LocaleParser()); parsers.addBinding().toInstance(new StaticMethodParser("valueOf")); parsers.addBinding().toInstance(new StaticMethodParser("fromString")); parsers.addBinding().toInstance(new StaticMethodParser("forName")); parsers.addBinding().toInstance(new StringConstructorParser()); parsers.addBinding().toInstance(beanParser.orElseGet(() -> new BeanParser(false))); binder.bind(ParserExecutor.class).in(Singleton.class); /** override(able) renderer */ renderers.addBinding().toInstance(new DefaulErrRenderer()); renderers.addBinding().toInstance(BuiltinRenderer.text); binder.bind(HttpHandler.class).to(HttpHandlerImpl.class).in(Singleton.class); RequestScope requestScope = new RequestScope(); binder.bind(RequestScope.class).toInstance(requestScope); binder.bindScope(RequestScoped.class, requestScope); /** session manager */ binder.bind(Session.Definition.class) .toProvider(session(finalConfig.getConfig("session"), session)) .asEagerSingleton(); Object sstore = session.store(); if (cookieSession) { binder.bind(SessionManager.class).to(CookieSessionManager.class) .asEagerSingleton(); } else { binder.bind(SessionManager.class).to(ServerSessionManager.class).asEagerSingleton(); if (sstore instanceof Class) { binder.bind(Store.class).to((Class) sstore) .asEagerSingleton(); } else { binder.bind(Store.class).toInstance((Store) sstore); } } binder.bind(Request.class).toProvider(Providers.outOfScope(Request.class)) .in(RequestScoped.class); binder.bind(Route.Chain.class).toProvider(Providers.outOfScope(Route.Chain.class)) .in(RequestScoped.class); binder.bind(Response.class).toProvider(Providers.outOfScope(Response.class)) .in(RequestScoped.class); /** server sent event */ binder.bind(Sse.class).toProvider(Providers.outOfScope(Sse.class)) .in(RequestScoped.class); binder.bind(Session.class).toProvider(Providers.outOfScope(Session.class)) .in(RequestScoped.class); /** def err */ ehandlers.addBinding().toInstance(new Err.DefHandler()); /** executors. */ executors.forEach(it -> it.accept(binder)); }; Injector injector = injectorFactory.apply(stage, joobyModule); if (apprefs != null) { apprefs.forEach(app -> app.injector = injector); apprefs.clear(); apprefs = null; } onStart.addAll(0, finalEnv.startTasks()); onStarted.addAll(0, finalEnv.startedTasks()); onStop.addAll(finalEnv.stopTasks()); // clear bag and freeze it this.bag.clear(); this.bag = ImmutableSet.of(); this.executors.clear(); this.executors = ImmutableList.of(); return injector; } private void xss(final Env env) { Escaper ufe = UrlEscapers.urlFragmentEscaper(); Escaper fpe = UrlEscapers.urlFormParameterEscaper(); Escaper pse = UrlEscapers.urlPathSegmentEscaper(); Escaper html = HtmlEscapers.htmlEscaper(); env.xss("urlFragment", ufe::escape) .xss("formParam", fpe::escape) .xss("pathSegment", pse::escape) .xss("html", html::escape); } private static Provider session(final Config $session, final Session.Definition session) { return () -> { // save interval session.saveInterval(session.saveInterval() .orElse($session.getDuration("saveInterval", TimeUnit.MILLISECONDS))); // build cookie Cookie.Definition source = session.cookie(); source.name(source.name() .orElse($session.getString("cookie.name"))); if (!source.comment().isPresent() && $session.hasPath("cookie.comment")) { source.comment($session.getString("cookie.comment")); } if (!source.domain().isPresent() && $session.hasPath("cookie.domain")) { source.domain($session.getString("cookie.domain")); } source.httpOnly(source.httpOnly() .orElse($session.getBoolean("cookie.httpOnly"))); Object maxAge = $session.getAnyRef("cookie.maxAge"); if (maxAge instanceof String) { maxAge = $session.getDuration("cookie.maxAge", TimeUnit.SECONDS); } source.maxAge(source.maxAge() .orElse(((Number) maxAge).intValue())); source.path(source.path() .orElse($session.getString("cookie.path"))); source.secure(source.secure() .orElse($session.getBoolean("cookie.secure"))); return session; }; } private static Throwing.Consumer bindService(final Set src, final Config conf, final Env env, final RouteMetadata rm, final Binder binder, final Multibinder definitions, final Multibinder sockets, final Multibinder ehandlers, final Multibinder parsers, final Multibinder renderers, final Set routeClasses, final boolean caseSensitiveRouting) { return it -> { if (it instanceof Jooby.Module) { int from = src.size(); install((Jooby.Module) it, env, conf, binder); int to = src.size(); // collect any route a module might add if (to > from) { List elements = normalize(new ArrayList<>(src).subList(from, to), env, rm, caseSensitiveRouting); for (Object e : elements) { bindService(src, conf, env, rm, binder, definitions, sockets, ehandlers, parsers, renderers, routeClasses, caseSensitiveRouting).accept(e); } } } else if (it instanceof Route.Definition) { Route.Definition rdef = (Definition) it; Route.Filter h = rdef.filter(); if (h instanceof Route.MethodHandler) { Class routeClass = ((Route.MethodHandler) h).implementingClass(); if (routeClasses.add(routeClass)) { binder.bind(routeClass); } definitions.addBinding().toInstance(rdef); } else { definitions.addBinding().toInstance(rdef); } } else if (it instanceof WebSocket.Definition) { sockets.addBinding().toInstance((WebSocket.Definition) it); } else if (it instanceof Parser) { parsers.addBinding().toInstance((Parser) it); } else if (it instanceof Renderer) { renderers.addBinding().toInstance((Renderer) it); } else { ehandlers.addBinding().toInstance((Err.Handler) it); } }; } private static List modconf(final Collection bag) { return bag.stream() .filter(it -> it instanceof Jooby.Module) .map(it -> ((Jooby.Module) it).config()) .filter(c -> !c.isEmpty()) .collect(Collectors.toList()); } /** * Test if the application is up and running. * * @return True if the application is up and running. */ public boolean isStarted() { return started.get(); } /** * Stop the application, fire the {@link #onStop(Throwing.Runnable)} event and shutdown the * web server. * * Stop listeners run in the order they were added: * *
{@code
   * {
   *
   *   onStop(() -> System.out.println("first"));
   *
   *   onStop(() -> System.out.println("second"));
   *
   *   ...
   * }
   * }
* * */ public void stop() { if (started.compareAndSet(true, false)) { Logger log = logger(this); fireStop(this, log, onStop); if (injector != null) { try { injector.getInstance(Server.class).stop(); } catch (Throwable ex) { log.debug("server.stop() resulted in exception", ex); } } injector = null; log.info("Stopped"); } } private static void fireStop(final Jooby app, final Logger log, final List> onStop) { // stop services onStop.forEach(c -> Try.run(() -> c.accept(app)) .onFailure(x -> log.error("shutdown of {} resulted in error", c, x))); } /** * Build configuration properties, it configure system, app and modules properties. * * @param source Source config to use. * @param args Args conf. * @param modules List of modules. * @return A configuration properties ready to use. */ private Config buildConfig(final Config source, final Config args, final List modules) { // normalize tmpdir Config system = ConfigFactory.systemProperties(); Config tmpdir = source.hasPath("java.io.tmpdir") ? source : system; // system properties system = system // file encoding got corrupted sometimes, override it. .withValue("file.encoding", fromAnyRef(System.getProperty("file.encoding"))) .withValue("java.io.tmpdir", fromAnyRef(Paths.get(tmpdir.getString("java.io.tmpdir")).normalize().toString())); // set module config Config moduleStack = ConfigFactory.empty(); for (Config module : ImmutableList.copyOf(modules).reverse()) { moduleStack = moduleStack.withFallback(module); } String env = Arrays.asList(system, args, source).stream() .filter(it -> it.hasPath("application.env")) .findFirst() .map(c -> c.getString("application.env")) .orElse("dev"); String cpath = Arrays.asList(system, args, source).stream() .filter(it -> it.hasPath("application.path")) .findFirst() .map(c -> c.getString("application.path")) .orElse("/"); Config envconf = envConf(source, env); // application.[env].conf -> application.conf Config conf = envconf.withFallback(source); return system .withFallback(args) .withFallback(conf) .withFallback(moduleStack) .withFallback(MediaType.types) .withFallback(defaultConfig(conf, Route.normalize(cpath))) .resolve(); } /** * Build a conf from arguments. * * @param args Application arguments. * @return A conf. */ static Config args(final String[] args) { if (args == null || args.length == 0) { return ConfigFactory.empty(); } Map conf = new HashMap<>(); for (String arg : args) { String[] values = arg.split("="); String name; String value; if (values.length == 2) { name = values[0]; value = values[1]; } else { name = "application.env"; value = values[0]; } if (name.indexOf(".") == -1) { conf.put("application." + name, value); } conf.put(name, value); } return ConfigFactory.parseMap(conf, "args"); } /** * Build a env config: [application].[env].[conf]. * Stack looks like * *
   *   (file://[origin].[env].[conf])?
   *   (cp://[origin].[env].[conf])?
   *   file://application.[env].[conf]
   *   /application.[env].[conf]
   * 
* * @param source App source to use. * @param env Application env. * @return A config env. */ private Config envConf(final Config source, final String env) { String name = Optional.ofNullable(this.confname).orElse(source.origin().resource()); Config result = ConfigFactory.empty(); if (name != null) { // load [resource].[env].[ext] int dot = name.lastIndexOf('.'); name = name.substring(0, dot); } else { name = "application"; } String envconfname = name + "." + env + ".conf"; Config envconf = fileConfig(envconfname); Config appconf = fileConfig(name + ".conf"); return result // file system: .withFallback(envconf) .withFallback(appconf) // classpath: .withFallback(ConfigFactory.parseResources(envconfname)); } /** * Config from file system. * * @param fname A file name. * @return A config for the file name. */ static Config fileConfig(final String fname) { File dir = new File(System.getProperty("user.dir")); File froot = new File(dir, fname); if (froot.exists()) { return ConfigFactory.parseFile(froot); } else { File fconfig = new File(new File(dir, "conf"), fname); if (fconfig.exists()) { return ConfigFactory.parseFile(fconfig); } } return ConfigFactory.empty(); } /** * Build default application.* properties. * * @param conf A source config. * @param cpath Application path. * @return default properties. */ private Config defaultConfig(final Config conf, final String cpath) { String ns = Optional.ofNullable(getClass().getPackage()) .map(Package::getName) .orElse("default." + getClass().getName()); String[] parts = ns.split("\\."); String appname = parts[parts.length - 1]; // locale final List locales; if (!conf.hasPath("application.lang")) { locales = Optional.ofNullable(this.languages) .map(langs -> LocaleUtils.parse(Joiner.on(",").join(langs))) .orElse(ImmutableList.of(Locale.getDefault())); } else { locales = LocaleUtils.parse(conf.getString("application.lang")); } Locale locale = locales.iterator().next(); String lang = locale.toLanguageTag(); // time zone final String tz; if (!conf.hasPath("application.tz")) { tz = Optional.ofNullable(zoneId).orElse(ZoneId.systemDefault()).getId(); } else { tz = conf.getString("application.tz"); } // number format final String nf; if (!conf.hasPath("application.numberFormat")) { nf = Optional.ofNullable(numberFormat) .orElseGet(() -> ((DecimalFormat) DecimalFormat.getInstance(locale)).toPattern()); } else { nf = conf.getString("application.numberFormat"); } int processors = Runtime.getRuntime().availableProcessors(); String version = Optional.ofNullable(getClass().getPackage()) .map(Package::getImplementationVersion) .filter(Objects::nonNull) .orElse("0.0.0"); Config defs = ConfigFactory.parseResources(Jooby.class, "jooby.conf") .withValue("contextPath", fromAnyRef(cpath.equals("/") ? "" : cpath)) .withValue("application.name", fromAnyRef(appname)) .withValue("application.version", fromAnyRef(version)) .withValue("application.class", fromAnyRef(classname)) .withValue("application.ns", fromAnyRef(ns)) .withValue("application.lang", fromAnyRef(lang)) .withValue("application.tz", fromAnyRef(tz)) .withValue("application.numberFormat", fromAnyRef(nf)) .withValue("server.http2.enabled", fromAnyRef(http2)) .withValue("runtime.processors", fromAnyRef(processors)) .withValue("runtime.processors-plus1", fromAnyRef(processors + 1)) .withValue("runtime.processors-plus2", fromAnyRef(processors + 2)) .withValue("runtime.processors-x2", fromAnyRef(processors * 2)) .withValue("runtime.processors-x4", fromAnyRef(processors * 4)) .withValue("runtime.processors-x8", fromAnyRef(processors * 8)) .withValue("runtime.concurrencyLevel", fromAnyRef(Math.max(4, processors))) .withValue("server.threads.Min", fromAnyRef(Math.max(4, processors))) .withValue("server.threads.Max", fromAnyRef(Math.max(32, processors * 8))); if (charset != null) { defs = defs.withValue("application.charset", fromAnyRef(charset.name())); } if (port != null) { defs = defs.withValue("application.port", fromAnyRef(port.intValue())); } if (securePort != null) { defs = defs.withValue("application.securePort", fromAnyRef(securePort.intValue())); } if (dateFormat != null) { defs = defs.withValue("application.dateFormat", fromAnyRef(dateFormat)); } return defs; } /** * Install a {@link Jooby.Module}. * * @param module The module to install. * @param env Application env. * @param config The configuration object. * @param binder A Guice binder. * @throws Throwable If module bootstrap fails. */ private static void install(final Jooby.Module module, final Env env, final Config config, final Binder binder) throws Throwable { module.configure(env, config, binder); } /** * Bind a {@link Config} and make it available for injection. Each property of the config is also * binded it and ready to be injected with {@link javax.inject.Named}. * * @param binder Guice binder. * @param config App config. */ @SuppressWarnings("unchecked") private void bindConfig(final Binder binder, final Config config) { // root nodes traverse(binder, "", config.root()); // terminal nodes for (Entry entry : config.entrySet()) { String name = entry.getKey(); Named named = Names.named(name); Object value = entry.getValue().unwrapped(); if (value instanceof List) { List values = (List) value; Type listType = values.size() == 0 ? String.class : Types.listOf(values.iterator().next().getClass()); Key key = (Key) Key.get(listType, Names.named(name)); binder.bind(key).toInstance(values); } else { binder.bindConstant().annotatedWith(named).to(value.toString()); } } // bind config binder.bind(Config.class).toInstance(config); } private static void traverse(final Binder binder, final String p, final ConfigObject root) { root.forEach((n, v) -> { if (v instanceof ConfigObject) { ConfigObject child = (ConfigObject) v; String path = p + n; Named named = Names.named(path); binder.bind(Config.class).annotatedWith(named).toInstance(child.toConfig()); traverse(binder, path + ".", child); } }); } private static Predicate envpredicate(final String candidate) { return env -> env.equalsIgnoreCase(candidate) || candidate.equals("*"); } static String logback(final Config conf) { // Avoid warning message from logback when multiples files are present String logback; if (conf.hasPath("logback.configurationFile")) { logback = conf.getString("logback.configurationFile"); } else { String env = conf.hasPath("application.env") ? conf.getString("application.env") : null; ImmutableList.Builder files = ImmutableList.builder(); File userdir = new File(System.getProperty("user.dir")); File confdir = new File(userdir, "conf"); if (env != null) { files.add(new File(userdir, "logback." + env + ".xml")); files.add(new File(confdir, "logback." + env + ".xml")); } files.add(new File(userdir, "logback.xml")); files.add(new File(confdir, "logback.xml")); logback = files.build() .stream() .filter(f -> f.exists()) .map(f -> f.getAbsolutePath()) .findFirst() .orElseGet(() -> { return Optional.ofNullable(Jooby.class.getResource("/logback." + env + ".xml")) .map(Objects::toString) .orElse("logback.xml"); }); } return logback; } private static Logger logger(final Jooby app) { return LoggerFactory.getLogger(app.getClass()); } private Route.AssetDefinition configureAssetHandler(final Route.AssetDefinition handler) { onStart(r -> { Config conf = r.require(Config.class); handler .cdn(conf.getString("assets.cdn")) .lastModified(conf.getBoolean("assets.lastModified")) .etag(conf.getBoolean("assets.etag")) .maxAge(conf.getString("assets.cache.maxAge")); }); return handler; } /** * Class name is this, except for script bootstrap. * * @param name Default classname. * @return Classname. */ private String classname(String name) { if (name.equals(Jooby.class.getName()) || name.equals("org.jooby.Kooby")) { return SourceProvider.INSTANCE.get() .map(StackTraceElement::getClassName) .orElse(name); } return name; } }