/**
* 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"));
});
}
}