/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; 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 static org.jooby.Route.GET; import static org.jooby.Route.HEAD; 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 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.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; import javax.inject.Singleton; import javax.net.ssl.SSLContext; import org.jooby.Route.Definition; import org.jooby.Route.Mapper; import org.jooby.Session.Store; 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.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.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 com.google.common.base.Joiner; import com.google.common.base.Throwables; 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 com.typesafe.config.ConfigValueFactory; import javaslang.Predicates; import javaslang.control.Try; import javaslang.control.Try.CheckedConsumer; import javaslang.control.Try.CheckedRunnable; /** * <h1>jooby</h1> * <h2>getting started</h2> * * <pre> * 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! * } * } * </pre> * * <h2>application.conf</h2> * <p> * Jooby delegate configuration management to <a * href="https://github.com/typesafehub/config">TypeSafe Config</a>. * </p> * * <p> * By default Jooby looks for an <code>application.conf</code>. If * you want to specify a different file or location, you can do it with {@link #conf(String)}. * </p> * * <p> * <a href="https://github.com/typesafehub/config">TypeSafe Config</a> uses a hierarchical model to * define and override properties. * </p> * <p> * 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. * </p> * For example: * * <pre> * use(new M1()); * use(new M2()); * use(new M3()); * </pre> * * Previous example had the following order (first-listed are higher priority): * <ul> * <li>arguments properties</li> * <li>System properties</li> * <li>application.conf</li> * <li>M3 properties</li> * <li>M2 properties</li> * <li>M1 properties</li> * </ul> * <p> * Command line argmuents or system properties takes precedence over any application specific * property. * </p> * * <h2>env</h2> * <p> * Jooby defines one mode or environment: <strong>dev</strong>. In Jooby, <strong>dev</strong> * is special and some modules could apply special settings while running in <strong>dev</strong>. * Any other env is usually considered a <code>prod</code> like env. But that depends on module * implementor. * </p> * <p> * An <code>environment</code> can be defined in your <code>.conf</code> file using the * <code>application.env</code> property. If missing, Jooby set the <code>env</code> for you to * <strong>dev</strong>. * </p> * <p> * There is more at {@link Env} please read the {@link Env} javadoc. * </p> * * <h2>modules: the jump to full-stack framework</h2> * <p> * {@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}. * </p> * * <pre> * public class MyModule implements Jooby.Module { * public void configure(Env env, Config config, Binder binder) { * } * } * </pre> * * From the configure callback you can bind your services as you usually do in a Guice app. * <p> * There is more at {@link Jooby.Module} so please read the {@link Jooby.Module} javadoc. * </p> * * <h2>path patterns</h2> * <p> * Jooby supports Ant-style path patterns: * </p> * <p> * Some examples: * </p> * <ul> * <li>{@code com/t?st.html} - matches {@code com/test.html} but also {@code com/tast.html} or * {@code com/txst.html}</li> * <li>{@code com/*.html} - matches all {@code .html} files in the {@code com} directory</li> * <li><code>com/{@literal **}/test.html</code> - matches all {@code test.html} files underneath the * {@code com} path</li> * <li>{@code **}/{@code *} - matches any path at any level.</li> * <li>{@code *} - matches any path at any level, shorthand for {@code **}/{@code *}.</li> * </ul> * * <h3>variables</h3> * <p> * Jooby supports path parameters too: * </p> * <p> * Some examples: * </p> * <ul> * <li><code> /user/{id}</code> - /user/* and give you access to the <code>id</code> var.</li> * <li><code> /user/:id</code> - /user/* and give you access to the <code>id</code> var.</li> * <li><code> /user/{id:\\d+}</code> - /user/[digits] and give you access to the numeric * <code>id</code> var.</li> * </ul> * * <h2>routes</h2> * <p> * Routes perform actions in response to a server HTTP request. * </p> * <p> * Routes are executed in the order they are defined, for example: * </p> * * <pre> * 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! * }); * </pre> * * Previous example can be rewritten using {@link Route.Filter}: * * <pre> * 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! * }); * </pre> * * 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: * * <pre> * 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); * }); * </pre> * * <h3>mvc routes</h3> * <p> * A Mvc route use annotations to define routes: * </p> * * <pre> * use(MyRoute.class); * ... * * // MyRoute.java * {@literal @}Path("/") * public class MyRoute { * * {@literal @}GET * public String hello() { * return "Hello Jooby"; * } * } * </pre> * <p> * Programming model is quite similar to JAX-RS/Jersey with some minor differences and/or * simplifications. * </p> * * <p> * To learn more about Mvc Routes, please check {@link org.jooby.mvc.Path}, * {@link org.jooby.mvc.Produces} {@link org.jooby.mvc.Consumes} javadoc. * </p> * * <h2>static files</h2> * <p> * Static files, like: *.js, *.css, ..., etc... can be served with: * </p> * * <pre> * assets("assets/**"); * </pre> * <p> * Classpath resources under the <code>/assets</code> folder will be accessible from client/browser. * </p> * * <h2>lifecyle</h2> * <p> * We do provide {@link #onStart(CheckedConsumer)} and {@link #onStop(CheckedConsumer)} callbacks. * These callbacks are executed are application startup or shutdown time: * </p> * * <pre>{@code * { * onStart(() -> { * log.info("Welcome!"); * }); * * onStop(() -> { * log.info("Bye!"); * }); * } * }</pre> * * <p> * From life cycle callbacks you can access to application services: * </p> * * <pre>{@code * { * onStart(registry -> { * MyDatabase db = registry.require(MyDatabase.class); * // do something with databse: * }); * } * }</pre> * * @author edgar * @see Jooby.Module * @since 0.1.0 */ public class Jooby implements Router, LifeCycle, Registry { /** * <pre>{@code * { * on("dev", () -> { * // run something on dev * }).orElse(() -> { * // run something on prod * }); * } * }</pre> */ public interface EnvPredicate { /** * <pre>{@code * { * on("dev", () -> { * // run something on dev * }).orElse(() -> { * // run something on prod * }); * } * }</pre> * * @param callback Env callback. */ default void orElse(final Runnable callback) { orElse(conf -> callback.run()); } /** * <pre>{@code * { * on("dev", () -> { * // run something on dev * }).orElse(conf -> { * // run something on prod * }); * } * }</pre> * * @param callback Env callback. */ void orElse(Consumer<Config> 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. * <p> * It is similar to {@link com.google.inject.Module} except for the callback method receives a * {@link Env}, {@link Config} and {@link Binder}. * </p> * * <p> * A module can provide his own set of properties through the {@link #config()} method. By * default, this method returns an empty config object. * </p> * For example: * * <pre> * use(new M1()); * use(new M2()); * use(new M3()); * </pre> * * Previous example had the following order (first-listed are higher priority): * <ul> * <li>System properties</li> * <li>application.conf</li> * <li>M3 properties</li> * <li>M2 properties</li> * <li>M1 properties</li> * </ul> * * <p> * A module can provide start/stop methods in order to start or close resources. * </p> * * @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. */ 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. */ void configure(Env env, Config conf, Binder binder) throws Throwable; } private static class MvcClass implements Route.Props<MvcClass> { Class<?> routeClass; String path; ImmutableMap.Builder<String, Object> attrs = ImmutableMap.builder(); private List<MediaType> consumes; private String name; private List<MediaType> produces; private List<String> excludes; private Mapper<?> mapper; private String prefix; 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<MediaType> consumes) { this.consumes = consumes; return this; } @Override public MvcClass produces(final List<MediaType> produces) { this.produces = produces; return this; } @Override public MvcClass excludes(final List<String> excludes) { this.excludes = excludes; return this; } @Override public MvcClass map(final Mapper<?> mapper) { this.mapper = mapper; 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); } return route; } } private static class EnvDep { Predicate<String> predicate; Consumer<Config> callback; public EnvDep(final Predicate<String> predicate, final Consumer<Config> 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<Object> 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<CheckedConsumer<Registry>> onStart = new ArrayList<>(); private transient List<CheckedConsumer<Registry>> onStarted = new ArrayList<>(); /** stop callback . */ private transient List<CheckedConsumer<Registry>> onStop = new ArrayList<>(); /** Mappers . */ @SuppressWarnings("rawtypes") private transient Mapper mapper; /** Don't add same mapper twice . */ private transient Set<String> mappers = new HashSet<>(); /** Bean parser . */ private transient Optional<Parser> 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<Consumer<Binder>> executors = new ArrayList<>(); private transient boolean defaultExecSet; private boolean throwBootstrapException; /** * creates the injector */ private transient BiFunction<Stage, com.google.inject.Module, Injector> injectorFactory = Guice::createInjector; /** * 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); } @Override public Jooby use(final Jooby app) { return use(Optional.empty(), app); } @Override public Jooby use(final String path, final Jooby app) { return use(Optional.of(path), app); } /** * Use the provided HTTP server. * * @param server Server. * @return This jooby instance. */ public Jooby server(final Class<? extends Server> server) { requireNonNull(server, "Server required."); // remove server lookup List<Object> 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<String> path, final Jooby app) { requireNonNull(app, "App is required."); Function<Route.Definition, Route.Definition> 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.<Object> 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); } 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 CheckedRunnable callback) { LifeCycle.super.onStart(callback); return this; } @Override public Jooby onStart(final CheckedConsumer<Registry> callback) { requireNonNull(callback, "Callback is required."); onStart.add(callback); return this; } @Override public Jooby onStarted(final CheckedRunnable callback) { LifeCycle.super.onStarted(callback); return this; } @Override public Jooby onStarted(final CheckedConsumer<Registry> callback) { requireNonNull(callback, "Callback is required."); onStarted.add(callback); return this; } @Override public Jooby onStop(final CheckedRunnable callback) { LifeCycle.super.onStop(callback); return this; } @Override public Jooby onStop(final CheckedConsumer<Registry> 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. * * <pre> * { * on("dev", () {@literal ->} { * use(new DevModule()); * }); * } * </pre> * * There is an else clause which is the opposite version of the env predicate: * * <pre> * { * on("dev", () {@literal ->} { * use(new DevModule()); * }).orElse(() {@literal ->} { * use(new RealModule()); * }); * } * </pre> * * @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. * * <pre> * { * on("dev", () {@literal ->} { * use(new DevModule()); * }); * } * </pre> * * There is an else clause which is the opposite version of the env predicate: * * <pre> * { * on("dev", conf {@literal ->} { * use(new DevModule()); * }).orElse(conf {@literal ->} { * use(new RealModule()); * }); * } * </pre> * * @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<Config> 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. * * <pre> * { * on("dev", "test", () {@literal ->} { * use(new DevModule()); * }); * } * </pre> * * There is an else clause which is the opposite version of the env predicate: * * <pre> * { * on(env {@literal ->} env.equals("dev"), () {@literal ->} { * use(new DevModule()); * }).orElse(() {@literal ->} { * use(new RealModule()); * }); * } * </pre> * * @param predicate Predicate to check the environment. * @param callback An env callback. * @return This jooby instance. */ public EnvPredicate on(final Predicate<String> 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. * * <pre> * { * on(env {@literal ->} env.equals("dev"), conf {@literal ->} { * use(new DevModule()); * }); * } * </pre> * * @param predicate Predicate to check the environment. * @param callback An env callback. * @return This jooby instance. */ public EnvPredicate on(final Predicate<String> predicate, final Consumer<Config> 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. * * <pre> * { * on("dev", "test", "mock", () {@literal ->} { * use(new DevModule()); * }); * } * </pre> * * @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> T require(final Key<T> type) { checkState(injector != null, "App didn't start yet"); 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 * <a href="http://jooby.org/doc/session">session modules</a>. * * 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<? extends Session.Store> 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 <code>application.secret</code> property, so you must * provide an <code>application.secret</code> value. On dev environment you can set it in your * <code>.conf</code> 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 * <a href="http://jooby.org/doc/session">session modules</a>. * * 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.Collection before(final String method, final String pattern, final Route.Before handler, final Route.Before... chain) { Route.Definition[] routes = javaslang.collection.List.of(handler) .appendAll(Arrays.asList(chain)) .map(before -> appendDefinition(new Route.Definition(method, pattern, before))) .toJavaArray(Route.Definition.class); return new Route.Collection(routes); } @Override public Route.Collection after(final String method, final String pattern, final Route.After handler, final Route.After... chain) { Route.Definition[] routes = javaslang.collection.List.of(handler) .appendAll(Arrays.asList(chain)) .map(after -> appendDefinition(new Route.Definition(method, pattern, after))) .toJavaArray(Route.Definition.class); return new Route.Collection(routes); } @Override public Route.Collection complete(final String method, final String pattern, final Route.Complete handler, final Route.Complete... chain) { Route.Definition[] routes = javaslang.collection.List.of(handler) .appendAll(Arrays.asList(chain)) .map(complete -> appendDefinition(new Route.Definition(method, pattern, complete))) .toJavaArray(Route.Definition.class); return new Route.Collection(routes); } @Override public Route.Definition use(final String path, final Route.Filter filter) { return appendDefinition(new Route.Definition("*", path, filter)); } @Override public Route.Definition use(final String verb, final String path, final Route.Filter filter) { return appendDefinition(new Route.Definition(verb, path, filter)); } @Override public Route.Definition use(final String verb, final String path, final Route.Handler handler) { return appendDefinition(new Route.Definition(verb, path, handler)); } @Override public Route.Definition use(final String path, final Route.Handler handler) { return appendDefinition(new Route.Definition("*", path, handler)); } @Override public Route.Definition use(final String path, final Route.OneArgHandler handler) { return appendDefinition(new Route.Definition("*", path, handler)); } @Override public Route.Definition get(final String path, final Route.Handler handler) { return appendDefinition(new Route.Definition(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(new Route.Definition(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(new Route.Definition(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(new Route.Definition(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(new Route.Definition(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(new Route.Definition(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(new Route.Definition(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(new Route.Definition(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(new Route.Definition(HEAD, path, handler)); } @Override public Route.Definition head(final String path, final Route.OneArgHandler handler) { return appendDefinition(new Route.Definition(HEAD, path, handler)); } @Override public Route.Definition head(final String path, final Route.ZeroArgHandler handler) { return appendDefinition(new Route.Definition(HEAD, path, handler)); } @Override public Route.Definition head(final String path, final Route.Filter filter) { return appendDefinition(new Route.Definition(HEAD, path, filter)); } @Override public Route.Definition head() { return appendDefinition(new Route.Definition(HEAD, "*", filter(HeadHandler.class)) .name("*.head")); } @Override public Route.Definition options(final String path, final Route.Handler handler) { return appendDefinition(new Route.Definition(OPTIONS, path, handler)); } @Override public Route.Definition options(final String path, final Route.OneArgHandler handler) { return appendDefinition(new Route.Definition(OPTIONS, path, handler)); } @Override public Route.Definition options(final String path, final Route.ZeroArgHandler handler) { return appendDefinition(new Route.Definition(OPTIONS, path, handler)); } @Override public Route.Definition options(final String path, final Route.Filter filter) { return appendDefinition(new Route.Definition(OPTIONS, path, filter)); } @Override public Route.Definition options() { return appendDefinition(new Route.Definition(OPTIONS, "*", handler(OptionsHandler.class)) .name("*.options")); } @Override public Route.Definition put(final String path, final Route.Handler handler) { return appendDefinition(new Route.Definition(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(new Route.Definition(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(new Route.Definition(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(new Route.Definition(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(new Route.Definition(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(new Route.Definition(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(new Route.Definition(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(new Route.Definition(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(new Route.Definition(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(new Route.Definition(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(new Route.Definition(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(new Route.Definition(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(new Route.Definition(TRACE, path, handler)); } @Override public Route.Definition trace(final String path, final Route.OneArgHandler handler) { return appendDefinition(new Route.Definition(TRACE, path, handler)); } @Override public Route.Definition trace(final String path, final Route.ZeroArgHandler handler) { return appendDefinition(new Route.Definition(TRACE, path, handler)); } @Override public Route.Definition trace(final String path, final Route.Filter filter) { return appendDefinition(new Route.Definition(TRACE, path, filter)); } @Override public Route.Definition trace() { return appendDefinition(new Route.Definition(TRACE, "*", handler(TraceHandler.class)) .name("*.trace")); } @Override public Route.Definition connect(final String path, final Route.Handler handler) { return appendDefinition(new Route.Definition(CONNECT, path, handler)); } @Override public Route.Definition connect(final String path, final Route.OneArgHandler handler) { return appendDefinition(new Route.Definition(CONNECT, path, handler)); } @Override public Route.Definition connect(final String path, final Route.ZeroArgHandler handler) { return appendDefinition(new Route.Definition(CONNECT, path, handler)); } @Override public Route.Definition connect(final String path, final Route.Filter filter) { return appendDefinition(new Route.Definition(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. * * <pre> * 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 * }); * </pre> * * 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<? extends Route.Handler> 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. * * <pre> * 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 * }); * </pre> * * 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<? extends Route.Filter> filter) { requireNonNull(filter, "Filter is required."); return (req, rsp, chain) -> req.require(filter).handle(req, rsp, chain); } @Override public Definition assets(final String path, final Path basedir) { AssetHandler handler = new AssetHandler(basedir); configureAssetHandler(handler); return assets(path, handler); } @Override public Route.Definition assets(final String path, final String location) { AssetHandler handler = new AssetHandler(location); configureAssetHandler(handler); return assets(path, handler); } @Override public Route.Definition assets(final String path, final AssetHandler handler) { return appendDefinition(new Route.Definition(GET, path, handler)); } @Override public Route.Collection use(final Class<?> routeClass) { requireNonNull(routeClass, "Route class is required."); MvcClass mvc = new MvcClass(routeClass, "", prefix); bag.add(mvc); return new Route.Collection(mvc); } /** * Keep track of routes in the order user define them. * * @param route A route definition to append. * @return The same route definition. */ private Route.Definition appendDefinition(final Route.Definition route) { 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 <code>application.conf</code> * file. * * @param path Classpath location. * @return This jooby instance. */ public Jooby conf(final String path) { use(ConfigFactory.parseResources(path)); return this; } /** * Set/specify a custom .conf file, useful when you don't want a <code>application.conf</code> * file. * * @param path File system location. * @return This jooby instance. */ public Jooby conf(final File path) { use(ConfigFactory.parseFile(path)); return this; } /** * Set the application configuration object. You must call this method when the default file * name: <code>application.conf</code> 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 <T> WebSocket.Definition ws(final String path, final Class<? extends WebSocket.OnMessage<T>> 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(new Route.Definition(GET, path, handler)).consumes(MediaType.sse); } @Override public Route.Definition sse(final String path, final Sse.Handler1 handler) { return appendDefinition(new Route.Definition(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<Route.Props> local = this.bag.stream() .skip(size) .filter(Predicates.instanceOf(Route.Props.class)) .map(r -> (Route.Props) r) .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<? extends Jooby> 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<? extends Jooby> app, final String... args) { run(() -> Try.of(() -> 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 <code>empty</code> conf on error. */ public static Config exportConf(final Jooby app) { AtomicReference<Config> 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<Definition> exportRoutes(final Jooby app) { @SuppressWarnings("serial") class Success extends RuntimeException { List<Definition> routes; Success(final List<Route.Definition> routes) { this.routes = routes; } } List<Definition> 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. */ public void start() { start(new String[0]); } /** * Start an application. * * @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<List<Route.Definition>> 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((CheckedConsumer<Registry>) internalOnStart); } // start services for (CheckedConsumer<Registry> onStart : this.onStart) { onStart.accept(this); } // route mapper Set<Route.Definition> routeDefs = injector.getInstance(Route.KEY); Set<WebSocket.Definition> 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 (CheckedConsumer<Registry> 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<Object>) 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<Stage, com.google.inject.Module, Injector> injectorFactory) { this.injectorFactory = injectorFactory; return this; } /** * Bind the provided abstract type to the given implementation: * * <pre> * { * bind(MyInterface.class, MyImplementation.class); * } * </pre> * * @param type Service interface. * @param implementation Service implementation. * @param <T> Service type. * @return This instance. */ public <T> Jooby bind(final Class<T> type, final Class<? extends T> implementation) { use((env, conf, binder) -> { binder.bind(type).to(implementation); }); return this; } /** * Bind the provided abstract type to the given implementation: * * <pre> * { * bind(MyInterface.class, MyImplementation::new); * } * </pre> * * @param type Service interface. * @param implementation Service implementation. * @param <T> Service type. * @return This instance. */ public <T> Jooby bind(final Class<T> type, final Supplier<T> implementation) { use((env, conf, binder) -> { binder.bind(type).toInstance(implementation.get()); }); return this; } /** * Bind the provided type: * * <pre> * { * bind(MyInterface.class); * } * </pre> * * @param type Service interface. * @param <T> Service type. * @return This instance. */ public <T> Jooby bind(final Class<T> type) { use((env, conf, binder) -> { binder.bind(type); }); return this; } /** * Bind the provided type: * * <pre> * { * bind(new MyService()); * } * </pre> * * @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: * * <pre>{@code * { * bind(MyService.class, conf -> new MyService(conf.getString("service.url"))); * } * }</pre> * * @param type Service type. * @param provider Service provider. * @param <T> Service type. * @return This instance. */ public <T> Jooby bind(final Class<T> type, final Function<Config, ? extends T> 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: * * <pre>{@code * { * bind(conf -> new MyService(conf.getString("service.url"))); * } * }</pre> * * @param provider Service provider. * @param <T> Service type. * @return This instance. */ @SuppressWarnings({"unchecked", "rawtypes" }) public <T> Jooby bind(final Function<Config, T> 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. * * <p> * Keep in mind this work as a default port and can be reset via <code>application.port</code> * property. * </p> * * @param port HTTP port. * @return This instance. */ public Jooby port(final int port) { this.port = port; return this; } /** * <p> * Set the HTTPS port to use. * </p> * * <p> * Keep in mind this work as a default port and can be reset via <code>application.port</code> * property. * </p> * * <h2>HTTPS</h2> * <p> * Jooby comes with a self-signed certificate, useful for development and test. But of course, you * should NEVER use it in the real world. * </p> * * <p> * In order to setup HTTPS with a secure certificate, you need to set these properties: * </p> * * <ul> * <li> * <code>ssl.keystore.cert</code>: An X.509 certificate chain file in PEM format. It can be an * absolute path or a classpath resource. * </li> * <li> * <code>ssl.keystore.key</code>: A PKCS#8 private key file in PEM format. It can be an absolute * path or a classpath resource. * </li> * </ul> * * <p> * Optionally, you can set these too: * </p> * * <ul> * <li> * <code>ssl.keystore.password</code>: Password of the keystore.key (if any). Default is: * null/empty. * </li> * <li> * <code>ssl.trust.cert</code>: 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. * </li> * <li> * <code>ssl.session.cacheSize</code>: Set the size of the cache used for storing SSL session * objects. 0 to use the default value. * </li> * <li> * <code>ssl.session.timeout</code>: Timeout for the cached SSL session objects, in seconds. 0 to * use the default value. * </li> * </ul> * * <p> * As you can see setup is very simple. All you need is your <code>.crt</code> and * <code>.key</code> files. * </p> * * @param port HTTPS port. * @return This instance. */ public Jooby securePort(final int port) { this.securePort = port; return this; } /** * <p> * Enable <code>HTTP/2</code> protocol. Some servers require special configuration, others just * works. It is a good idea to check the server documentation about * <a href="http://jooby.org/doc/servers">HTTP/2</a>. * </p> * * <p> * In order to use HTTP/2 from a browser you must configure HTTPS, see {@link #securePort(int)} * documentation. * </p> * * <p> * 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. * </p> * * @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<? extends Provider<Executor>> 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: * * <pre> * public class App extends Jooby { * { * throwBootstrapException(); * ... * } * } * * App app = new App(); * * try { * app.start(); * } catch (Err err) { * Throwable cause = err.getCause(); * } * </pre> * * @return */ public Jooby throwBootstrapException() { this.throwBootstrapException = true; return this; } private static List<Object> normalize(final List<Object> services, final Env env, final RouteMetadata classInfo, final String prefix) { List<Object> result = new ArrayList<>(); List<Object> 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, mvcClass) .forEach(route -> result.add(mvcRoute.apply(route))); } else { result.add(candidate); } }); return result; } private static List<Object> processEnvDep(final Set<Object> src, final Env env) { List<Object> result = new ArrayList<>(); List<Object> 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<List<Route.Definition>> rcallback) throws Throwable { Config appconf = ConfigFactory.parseResources("application.conf"); Config initconf = srcconf == null ? appconf : srcconf.withFallback(appconf); List<Config> modconf = modconf(this.bag); Config conf = buildConfig(initconf, args, modconf); final List<Locale> 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); 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<Object> realbag = processEnvDep(this.bag, env); List<Config> realmodconf = modconf(realbag); List<Object> bag = normalize(realbag, env, rm, prefix); // collect routes and fire route callback if (rcallback != null) { List<Route.Definition> routes = bag.stream() .filter(it -> it instanceof Route.Definition) .map(it -> (Route.Definition) it) .collect(Collectors.<Route.Definition> 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<List<Locale>> localeType = (TypeLiteral<List<Locale>>) 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<Definition> definitions = Multibinder .newSetBinder(binder, Definition.class); /** web sockets */ Multibinder<WebSocket.Definition> 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<Err.Handler> ehandlers = Multibinder .newSetBinder(binder, Err.Handler.class); /** parsers & renderers */ Multibinder<Parser> parsers = Multibinder .newSetBinder(binder, Parser.class); Multibinder<Renderer> 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.Upload); 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<Object> routeClasses = new HashSet<>(); for (Object it : bag) { Try.run(() -> bindService( this.bag, finalConfig, finalEnv, rm, binder, definitions, sockets, ehandlers, parsers, renderers, routeClasses).accept(it)) .getOrElseThrow(Throwables::propagate); } parsers.addBinding().toInstance(new DateParser(dateFormat)); parsers.addBinding().toInstance(new LocalDateParser(dateTimeFormatter)); 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 */ boolean stacktrace = finalConfig.hasPath("err.stacktrace") ? finalConfig.getBoolean("err.stacktrace") : "dev".equals(envname); renderers.addBinding().toInstance(new DefaulErrRenderer(stacktrace)); 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<? extends Store>) sstore) .asEagerSingleton(); } else { binder.bind(Store.class).toInstance((Store) sstore); } } binder.bind(Request.class).toProvider(Providers.outOfScope(Request.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); 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.Definition> 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 CheckedConsumer<? super Object> bindService(final Set<Object> src, final Config conf, final Env env, final RouteMetadata rm, final Binder binder, final Multibinder<Route.Definition> definitions, final Multibinder<WebSocket.Definition> sockets, final Multibinder<Err.Handler> ehandlers, final Multibinder<Parser> parsers, final Multibinder<Renderer> renderers, final Set<Object> routeClasses) { 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<Object> elements = normalize(new ArrayList<>(src).subList(from, to), env, rm, null); for (Object e : elements) { bindService(src, conf, env, rm, binder, definitions, sockets, ehandlers, parsers, renderers, routeClasses).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).method().getDeclaringClass(); if (routeClasses.add(routeClass)) { binder.bind(routeClass); } } 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<Config> modconf(final Collection<Object> 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, close all the modules and stop the web server. */ 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<CheckedConsumer<Registry>> 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<Config> 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 envcof = envConf(source, env); // application.[env].conf -> application.conf Config conf = envcof.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<String, String> 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: <code>[application].[env].[conf]</code>. * Stack looks like * * <pre> * (file://[origin].[env].[conf])? * (cp://[origin].[env].[conf])? * file://application.[env].[conf] * /application.[env].[conf] * </pre> * * @param source App source to use. * @param env Application env. * @return A config env. */ private Config envConf(final Config source, final String env) { String origin = source.origin().resource(); Config result = ConfigFactory.empty(); if (origin != null) { // load [resource].[env].[ext] int dot = origin.lastIndexOf('.'); String originConf = origin.substring(0, dot) + "." + env + origin.substring(dot); result = fileConfig(originConf).withFallback(ConfigFactory.parseResources(originConf)); } String appConfig = "application." + env + ".conf"; return result .withFallback(fileConfig(appConfig)) .withFallback(fileConfig("application.conf")) .withFallback(ConfigFactory.parseResources(appConfig)); } /** * 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 = getClass().getPackage().getName(); String[] parts = ns.split("\\."); String appname = parts[parts.length - 1]; // locale final List<Locale> 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().getImplementationVersion()) .orElse("0.0.0"); Config defs = ConfigFactory.parseResources(Jooby.class, "jooby.conf") .withValue("contextPath", ConfigValueFactory.fromAnyRef(cpath.equals("/") ? "" : cpath)) .withValue("application.name", ConfigValueFactory.fromAnyRef(appname)) .withValue("application.version", ConfigValueFactory.fromAnyRef(version)) .withValue("application.class", ConfigValueFactory.fromAnyRef(getClass().getName())) .withValue("application.ns", ConfigValueFactory.fromAnyRef(ns)) .withValue("application.lang", ConfigValueFactory.fromAnyRef(lang)) .withValue("application.tz", ConfigValueFactory.fromAnyRef(tz)) .withValue("application.numberFormat", ConfigValueFactory.fromAnyRef(nf)) .withValue("server.http2.enabled", ConfigValueFactory.fromAnyRef(http2)) .withValue("runtime.processors", ConfigValueFactory.fromAnyRef(processors)) .withValue("runtime.processors-plus1", ConfigValueFactory.fromAnyRef(processors + 1)) .withValue("runtime.processors-plus2", ConfigValueFactory.fromAnyRef(processors + 2)) .withValue("runtime.processors-x2", ConfigValueFactory.fromAnyRef(processors * 2)) .withValue("runtime.processors-x4", ConfigValueFactory.fromAnyRef(processors * 4)) .withValue("runtime.processors-x8", ConfigValueFactory.fromAnyRef(processors * 8)) .withValue("runtime.concurrencyLevel", ConfigValueFactory .fromAnyRef(Math.max(4, processors))); if (charset != null) { defs = defs.withValue("application.charset", ConfigValueFactory.fromAnyRef(charset.name())); } if (port != null) { defs = defs.withValue("application.port", ConfigValueFactory.fromAnyRef(port.intValue())); } if (securePort != null) { defs = defs.withValue("application.securePort", ConfigValueFactory.fromAnyRef(securePort.intValue())); } if (dateFormat != null) { defs = defs.withValue("application.dateFormat", ConfigValueFactory.fromAnyRef(dateFormat)); } return defs; } /** * Install a {@link JoobyModule}. * * @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<String, ConfigValue> entry : config.entrySet()) { String name = entry.getKey(); Named named = Names.named(name); Object value = entry.getValue().unwrapped(); if (value instanceof List) { List<Object> values = (List<Object>) value; Type listType = values.size() == 0 ? String.class : Types.listOf(values.iterator().next().getClass()); Key<Object> key = (Key<Object>) 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<String> 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<File> 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()); } public void configureAssetHandler(final AssetHandler 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")); }); } }