/*
* The MIT License
*
* Copyright 2013 Tim Boudreau.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.mastfrog.acteur.server;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.inject.AbstractModule;
import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.Scopes;
import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
import com.google.inject.name.Names;
import com.mastfrog.acteur.Acteur;
import com.mastfrog.acteur.Application;
import com.mastfrog.acteur.BuiltInPageAnnotationHandler;
import com.mastfrog.acteur.Closables;
import com.mastfrog.acteur.Event;
import com.mastfrog.acteur.HttpEvent;
import com.mastfrog.acteur.ImplicitBindings;
import com.mastfrog.acteur.Page;
import com.mastfrog.acteur.ResponseHeaders;
import com.mastfrog.acteur.errors.Err;
import com.mastfrog.acteur.errors.ErrorResponse;
import com.mastfrog.acteur.errors.ExceptionEvaluator;
import com.mastfrog.acteur.errors.ExceptionEvaluatorRegistry;
import com.mastfrog.acteur.headers.Method;
import com.mastfrog.acteur.server.ServerModule.TF;
import com.mastfrog.acteur.spi.ApplicationControl;
import com.mastfrog.acteur.sse.EventChannelName;
import com.mastfrog.acteur.util.BasicCredentials;
import com.mastfrog.acteur.util.HttpMethod;
import com.mastfrog.acteur.util.RequestID;
import com.mastfrog.acteur.util.Server;
import com.mastfrog.acteur.util.ServerControl;
import com.mastfrog.acteurbase.ActeurBaseModule;
import com.mastfrog.acteurbase.Chain;
import com.mastfrog.giulius.Dependencies;
import com.mastfrog.guicy.annotations.Defaults;
import com.mastfrog.guicy.scope.ReentrantScope;
import com.mastfrog.parameters.KeysValues;
import com.mastfrog.settings.MutableSettings;
import com.mastfrog.settings.Settings;
import com.mastfrog.settings.SettingsBuilder;
import com.mastfrog.url.Path;
import com.mastfrog.util.Codec;
import com.mastfrog.util.ConfigurationError;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.buffer.UnpooledByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.Cookie;
import io.netty.handler.codec.http.CookieDecoder;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.util.CharsetUtil;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.Thread.UncaughtExceptionHandler;
import java.lang.reflect.InvocationTargetException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinWorkerThread;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import javax.inject.Inject;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.netbeans.validation.api.InvalidInputException;
/**
* Guice module for creating a server; also defines settings keys which can
* affect behavior.
*
* @author Tim Boudreau
*/
@Defaults("realm=Users")
public class ServerModule<A extends Application> extends AbstractModule {
/**
* Name of the @Named parameter that should be used in an annotation if
* you want Guice to inject the specific thread pool used for processing
* requests.
*/
public static final String BACKGROUND_THREAD_POOL_NAME = "background";
/**
* Name of the @Named parameter that should be used in an annotation if
* you want Guice to inject the specific thread pool used for processing
* requests.
*/
public static final String WORKER_THREAD_POOL_NAME = "workers";
/**
* Name of the @Named parameter that should be used in an annotation if
* you want Guice to inject the specific thread pool used for processing
* requests, but wrappered so that all runnables are run within the
* application's request scope and have whatever context they were submitted
* with.
*/
public static final String SCOPED_WORKER_THREAD_POOL_NAME = "scopedWorkers";
/**
* Name of the @Named parameter that should be used in an annotation if
* you want Guice to inject the specific thread pool used for processing
* requests, but wrappered so that all runnables are run within the
* application's request scope and have whatever context they were submitted
* with.
*/
public static final String SCOPED_BACKGROUND_THREAD_POOL_NAME = "scopedBackground";
/**
* Property name for setting which byte buffer allocator Netty uses (heap,
* direct, pooled).
*/
public static final String BYTEBUF_ALLOCATOR_SETTINGS_KEY = "acteur.bytebuf.allocator";
/**
* Property value for telling the server to use the direct byte buffer
* allocator (non-heap).
*/
public static final String DIRECT_ALLOCATOR = "direct";
/**
* Property value for telling the server to use the heap byte buffer
* allocator.
*/
public static final String HEAP_ALLOCATOR = "heap";
/**
* Property value for telling the server to use the pooled byte buffer
* allocator.
*/
public static final String POOLED_ALLOCATOR = "pooled";
/**
* Property value for telling the server to use the heap or direct byte
* buffer allocator as decided by Netty's PlatformDependent class.
*/
public static final String DIRECT_OR_HEAP_BY_PLATFORM = "directOrHeap";
/**
* The default allocator to use if none is specified
*/
public static final String DEFAULT_ALLOCATOR = POOLED_ALLOCATOR;
/**
* Settings key for the nnumber of worker threads to use.
*/
public static final String WORKER_THREADS = "workerThreads";
/**
* Number of event threads
*/
public static final String EVENT_THREADS = "eventThreads";
/**
* Number of background thread pool threads. The background thread pool is
* used by a few things which chunk responses.
*/
public static final String BACKGROUND_THREADS = "backgroundThreads";
/**
* The port to run on
*/
public static final String PORT = "port";
/**
* Settings key for enabling HTTP compression.
*/
public static final String HTTP_COMPRESSION = "httpCompression";
/**
* Settings key for the maximum content length.
*/
public static final String MAX_CONTENT_LENGTH = "maxContentLength";
/**
* Guice binding for
* <code>@Named(DELAY_EXECUTOR) ScheduledExecutorService</code> to get
* a scheduled executor service which shares a ThreadFactory with the worker
* thread pool.
*/
public static final String DELAY_EXECUTOR = "delayExecutor";
/**
* Number of threads to process delayed responses (see Acteur.setDelay()).
* These threads are typically not busy and can be 1-2 threads.
*/
public static final String SETTINGS_KEY_DELAY_THREAD_POOL_THREADS = "delay.response.threads";
/**
* The default number of delay threads.
*/
private static final int DEFAULT_DELAY_THREADS = 2;
/**
* If true, the return value of Event.getRemoteAddress() will prefer the
* headers X-Forwarded-For or X-Real-IP if present, so that running an
* acteur application behind a reverse proxy does not mask the actual IP
* address.
*/
public static final String SETTINGS_KEY_DECODE_REAL_IP = "decodeRealIP";
/**
* Settings key if true, do CORS responses on OPTIONS requests
*/
public static final String SETTINGS_KEY_CORS_ENABLED = "cors.enabled";
/**
* If true (the default), a ForkJoinPool will be used for dispatching work
* to acteurs; if not, a fixed thread ExecutorService will be used.
* The default is correct for most appliations; applications which require
* an extremely small memory footprint (7-10Mb) will reduce their memory
* requirements under load by turning this off.
*/
public static final String SETTINGS_KEY_USE_FORK_JOIN_POOL = "acteur.fork.join";
/**
* If the default support for CORS requests is enabled, this is the max age
* in minutes that the browser should regard the response as valid.
*/
public static final String SETTINGS_KEY_CORS_MAX_AGE_MINUTES = "cors.max.age.minutes";
/**
* If the default support for CORS requests is enabled, this is the value
* of what hosts the response is valid for (what sites can use scripts from
* this server without the browser blocking them). The default is *.
*/
public static final String SETTINGS_KEY_CORS_ALLOW_ORIGIN = "cors.allow.origin";
/**
* Default value for @link(ServerModule.SETTINGS_KEY_CORS_ENABLED}
*/
public static final boolean DEFAULT_CORS_ENABLED = true;
/**
* Default value for @link(ServerModule.SETTINGS_KEY_CORS_MAX_AGE_MINUTES}
*/
public static final long DEFAULT_CORS_MAX_AGE_MINUTES = 5;
/**
* Default value for @link(ServerModule.SETTINGS_KEY_CORS_ALLOW_ORIGIN}
*/
public static final String DEFAULT_CORS_ALLOW_ORIGIN = "*";
/**
* Determine if the application should exit if an exception is thrown when binding
* the server socket (usually because the port is in use). The default is
* true, but in cases where multiple servers are started in one JVM and the failure
* of one should not cause the JVM to exit, it can be set to false and the JVM
* will continue running if there are any live non-daemon threads.
*/
public static final String SETTINGS_KEY_SYSTEM_EXIT_ON_BIND_FAILURE = "system.exit.on.bind.failure";
protected final Class<A> appType;
protected final ReentrantScope scope;
private final int eventThreads;
private final int workerThreads;
private final int backgroundThreads;
private final List<Module> otherModules = new ArrayList<>();
public ServerModule(Class<A> appType, int workerThreadCount, int eventThreadCount, int backgroundThreadCount) {
this(new ReentrantScope(), appType, workerThreadCount, eventThreadCount, backgroundThreadCount);
}
public ServerModule(ReentrantScope scope, Class<A> appType, int workerThreadCount, int eventThreadCount, int backgroundThreadCount) {
if (!Application.class.isAssignableFrom(appType)) {
throw new ClassCastException(appType.getName() + " is not a subclass of " + Application.class.getName());
}
this.appType = appType;
this.workerThreads = workerThreadCount;
this.eventThreads = eventThreadCount;
this.backgroundThreads = backgroundThreadCount;
this.scope = scope;
}
public ServerModule(Class<A> appType) {
this(appType, -1, -1, -1);
}
/**
* Get the Guice scope used for injecting dynamic request-related
* objects into Acteur constructors.
* @return The scope
*/
public final ReentrantScope applicationScope() {
return scope;
}
@Override
@SuppressWarnings("deprecation")
protected void configure() {
bind(Server.class).to(ServerImpl.class);
bind(ReentrantScope.class).toInstance(scope);
bind(Application.class).to(appType).asEagerSingleton();
bind(ChannelHandler.class).to(UpstreamHandlerImpl.class);
bind(new CISC()).to(PipelineFactoryImpl.class);
bind(ServerBootstrap.class).toProvider(new ServerBootstrapProvider(
binder().getProvider(Settings.class),
binder().getProvider(ByteBufAllocator.class)));
scope.bindTypes(binder(), Event.class, HttpEvent.class, RequestID.class,
Page.class, BasicCredentials.class, Closables.class);
ImplicitBindings implicit = appType.getAnnotation(ImplicitBindings.class);
if (implicit != null) {
scope.bindTypes(binder(), implicit.value());
}
scope.bindTypesAllowingNulls(binder(), EventChannelName.class);
// Acteurs can ask for a Deferral to pause execution while some
// other operation completes, such as making an external HTTP request
// to another server
install(new ActeurBaseModule(scope));
Provider<ApplicationControl> appControlProvider = binder().getProvider(ApplicationControl.class);
Provider<Settings> set = binder().getProvider(Settings.class);
TF eventThreadFactory = new TF(EVENT_THREADS, appControlProvider);
TF workerThreadFactory = new TF(WORKER_THREADS, appControlProvider);
TF backgroundThreadFactory = new TF(BACKGROUND_THREAD_POOL_NAME, appControlProvider);
bind(ThreadGroup.class).annotatedWith(Names.named(BACKGROUND_THREAD_POOL_NAME)).toInstance(backgroundThreadFactory.tg);
bind(ThreadGroup.class).annotatedWith(Names.named(WORKER_THREADS)).toInstance(workerThreadFactory.tg);
bind(ThreadGroup.class).annotatedWith(Names.named(EVENT_THREADS)).toInstance(eventThreadFactory.tg);
ThreadCount workerThreadCount = new ThreadCount(set, 8, workerThreads, WORKER_THREADS);
ThreadCount eventThreadCount = new ThreadCount(set, 8, eventThreads, EVENT_THREADS);
ThreadCount backgroundThreadCount = new ThreadCount(set, 128, backgroundThreads, BACKGROUND_THREADS);
bind(ThreadCount.class).annotatedWith(Names.named(EVENT_THREADS)).toInstance(eventThreadCount);
bind(ThreadCount.class).annotatedWith(Names.named(WORKER_THREADS)).toInstance(workerThreadCount);
bind(ThreadCount.class).annotatedWith(Names.named(BACKGROUND_THREAD_POOL_NAME)).toInstance(backgroundThreadCount);
bind(ThreadFactory.class).annotatedWith(Names.named(WORKER_THREADS)).toInstance(workerThreadFactory);
bind(ThreadFactory.class).annotatedWith(Names.named(EVENT_THREADS)).toInstance(eventThreadFactory);
bind(ThreadFactory.class).annotatedWith(Names.named(BACKGROUND_THREAD_POOL_NAME)).toInstance(backgroundThreadFactory);
Provider<ExecutorService> workerProvider
= new ExecutorServiceProvider(workerThreadFactory, workerThreadCount, set);
Provider<ExecutorService> backgroundProvider
= new ExecutorServiceProvider(backgroundThreadFactory, backgroundThreadCount, set);
bind(ExecutorService.class).annotatedWith(Names.named(
WORKER_THREAD_POOL_NAME)).toProvider(workerProvider);
bind(ExecutorService.class).annotatedWith(Names.named(
BACKGROUND_THREAD_POOL_NAME)).toProvider(backgroundProvider);
bind(ExecutorService.class).annotatedWith(Names.named(
SCOPED_WORKER_THREAD_POOL_NAME)).toProvider(
new WrappedWorkerThreadPoolProvider(workerProvider, scope));
bind(ExecutorService.class).annotatedWith(Names.named(
SCOPED_BACKGROUND_THREAD_POOL_NAME)).toProvider(
new WrappedWorkerThreadPoolProvider(backgroundProvider, scope));
bind(DateTime.class).toInstance(DateTime.now());
bind(Duration.class).toProvider(UptimeProvider.class);
bind(new CKTL()).toProvider(CookiesProvider.class);
//XXX anything using this?
bind(String.class).annotatedWith(Names.named("application")).toInstance(this.appType.getSimpleName());
bind(ServerImpl.class).asEagerSingleton();
for (Module m : otherModules) {
install(m);
}
bind(Charset.class).toProvider(CharsetProvider.class);
bind(ByteBufAllocator.class).toProvider(ByteBufAllocatorProvider.class);
bind(new ETL()).toProvider(EventProvider.class).in(scope);
bind(Codec.class).to(CodecImpl.class);
bind(ApplicationControl.class).toProvider(ApplicationControlProvider.class).in(Scopes.SINGLETON);
bind(ExceptionEvaluatorRegistry.class).asEagerSingleton();
bind(KeysValues.class).toProvider(KeysValuesProvider.class);
bind(InvalidInputExceptionEvaluator.class).asEagerSingleton();
bind(Channel.class).toProvider(ChannelProvider.class);
bind(HttpMethod.class).toProvider(MethodProvider.class);
bind(Method.class).toProvider(MethodProvider2.class);
bind(Path.class).toProvider(PathProvider.class);
bind(BuiltInPageAnnotationHandler.class).asEagerSingleton();
bind(ResponseHeaders.class).toProvider(ResponseHeadersProvider.class);
bind(ScheduledExecutorService.class).annotatedWith(Names.named(DELAY_EXECUTOR)).toProvider(DelayExecutorProvider.class);
// allow Chain<Acteur> to be injected
bind(new CL()).toProvider(ChainProvider.class);
}
static class CL extends TypeLiteral<Chain<Acteur>> {
}
static class ChainProvider implements Provider<Chain<Acteur>> {
@SuppressWarnings("unchecked")
private final Provider<Chain> chain;
@SuppressWarnings("unchecked")
@Inject
ChainProvider(Provider<Chain> chain) {
this.chain = chain;
}
@Override
@SuppressWarnings("unchecked")
public Chain<Acteur> get() {
return chain.get();
}
}
@Singleton
private static final class DelayExecutorProvider implements Provider<ScheduledExecutorService> {
private final ThreadFactory workerThreadFactory;
private final int count;
private volatile ScheduledExecutorService exe;
@Inject
DelayExecutorProvider(@Named(ServerModule.WORKER_THREADS) ThreadFactory workerThreadFactory, Settings settings) {
this.workerThreadFactory = workerThreadFactory;
count = settings.getInt(SETTINGS_KEY_DELAY_THREAD_POOL_THREADS, DEFAULT_DELAY_THREADS);
}
@Override
public ScheduledExecutorService get() {
if (exe == null) {
synchronized (this) {
if (exe == null) {
exe = Executors.newScheduledThreadPool(count, workerThreadFactory);
}
}
}
return exe;
}
}
@Singleton
private static final class ResponseHeadersProvider implements Provider<ResponseHeaders> {
private final Provider<Page> page;
@Inject
public ResponseHeadersProvider(Provider<Page> page) {
this.page = page;
}
@Override
public ResponseHeaders get() {
return page.get().getResponseHeaders();
}
}
private static final class PathProvider implements Provider<Path> {
private final Provider<HttpEvent> evt;
@Inject
PathProvider(Provider<HttpEvent> evt) {
this.evt = evt;
}
@Override
public Path get() {
return evt.get().getPath();
}
}
private static final class MethodProvider implements Provider<HttpMethod> {
private final Provider<HttpEvent> evt;
@Inject
MethodProvider(Provider<HttpEvent> evt) {
this.evt = evt;
}
@Override
public HttpMethod get() {
return evt.get().getMethod();
}
}
private static final class MethodProvider2 implements Provider<Method> {
private final Provider<HttpEvent> evt;
@Inject
MethodProvider2(Provider<HttpEvent> evt) {
this.evt = evt;
}
@Override
public Method get() {
HttpEvent e = evt.get();
if (e == null) {
return null;
}
return e == null || !(e.getMethod() instanceof Method) ? null : (Method) evt.get().getMethod();
}
}
private static final class ChannelProvider implements Provider<Channel> {
private final Provider<HttpEvent> evt;
@Inject
ChannelProvider(Provider<HttpEvent> evt) {
this.evt = evt;
}
@Override
public Channel get() {
return evt.get().getChannel();
}
}
private static final class KeysValuesProvider implements Provider<KeysValues> {
private final Provider<HttpEvent> evt;
@Inject
public KeysValuesProvider(Provider<HttpEvent> evt) {
this.evt = evt;
}
@Override
public KeysValues get() {
return new KeysValues.MapAdapter(evt.get().getParametersAsMap());
}
}
private static final class InvalidInputExceptionEvaluator extends ExceptionEvaluator {
@Inject
public InvalidInputExceptionEvaluator(ExceptionEvaluatorRegistry registry) {
super(registry);
}
@Override
public ErrorResponse evaluate(Throwable t, Acteur acteur, Page page, HttpEvent evt) {
if (t instanceof InvalidInputException) {
InvalidInputException iie = (InvalidInputException) t;
return Err.badRequest(iie.getProblems().toString());
}
return null;
}
}
private static final class EventProvider implements Provider<Event<?>> {
@SuppressWarnings("unchecked")
private final Provider<Event> eventProvider;
@SuppressWarnings("unchecked")
@Inject
public EventProvider(Provider<Event> eventProvider) {
this.eventProvider = eventProvider;
}
@Override
public Event<?> get() {
return eventProvider.get();
}
}
private static final class ETL extends TypeLiteral<Event<?>> {
}
static class ApplicationControlProvider implements Provider<ApplicationControl> {
private final ApplicationControl control;
@Inject
public ApplicationControlProvider(Provider<Application> app) throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
// In order to separate the API and SPI of Application, so Application
// is not polluted with methods a subclasser should never call,
// we do this:
Application a = app.get();
java.lang.reflect.Method method = Application.class.getDeclaredMethod("control");
method.setAccessible(true);
control = (ApplicationControl) method.invoke(a);
}
@Override
public ApplicationControl get() {
return control;
}
}
static class CodecImpl implements Codec {
private final Provider<ObjectMapper> mapper;
@Inject
public CodecImpl(Provider<ObjectMapper> mapper) {
this.mapper = mapper;
}
@Override
public <T> String writeValueAsString(T object) throws IOException {
return mapper.get().writeValueAsString(object);
}
@Override
public <T> void writeValue(T object, OutputStream out) throws IOException {
mapper.get().writeValue(out, object);
}
@Override
public <T> T readValue(InputStream byteBufInputStream, Class<T> type) throws IOException {
return mapper.get().readValue(byteBufInputStream, type);
}
@Override
public <T> byte[] writeValueAsBytes(T object) throws IOException {
return mapper.get().writeValueAsBytes(object);
}
}
@Singleton
private static final class CharsetProvider implements Provider<Charset> {
private final Charset charset;
@Inject
CharsetProvider(Settings settings) {
String set = settings.getString("charset");
if (set == null) {
charset = CharsetUtil.UTF_8;
} else {
charset = Charset.forName(set);
}
}
@Override
public Charset get() {
return charset;
}
}
/**
* Add another module to be installed with this one
*
* @param module
*/
public ServerModule<A> add(Module module) {
otherModules.add(module);
return this;
}
/**
* Override to configure options on the bootstrap which will start the
* server. The default implementation sets up the allocator.
*
* @param bootstrap The server bootstrap
* @param settings The application settings
* @return The same bootstrap or optionally another one
*/
protected ServerBootstrap configureServerBootstrap(ServerBootstrap bootstrap, Settings settings) {
return bootstrap;
}
@Singleton
private static final class ByteBufAllocatorProvider implements Provider<ByteBufAllocator> {
private final Settings settings;
private volatile ByteBufAllocator allocator;
@Inject
public ByteBufAllocatorProvider(Settings settings) {
this.settings = settings;
}
public ByteBufAllocator get() {
String s = settings.getString(BYTEBUF_ALLOCATOR_SETTINGS_KEY, DEFAULT_ALLOCATOR);
ByteBufAllocator result = this.allocator;
if (result == null) {
synchronized (this) {
result = this.allocator;
if (result == null) {
switch (s) {
case DIRECT_OR_HEAP_BY_PLATFORM:
result = UnpooledByteBufAllocator.DEFAULT;
break;
case DIRECT_ALLOCATOR:
result = new UnpooledByteBufAllocator(true);
break;
case HEAP_ALLOCATOR:
result = new UnpooledByteBufAllocator(false);
break;
case POOLED_ALLOCATOR:
result = PooledByteBufAllocator.DEFAULT;
break;
default:
throw new ConfigurationError("Unknown value for " + BYTEBUF_ALLOCATOR_SETTINGS_KEY
+ " '" + s + "'; valid values are " + DIRECT_ALLOCATOR + ", "
+ HEAP_ALLOCATOR + ", " + POOLED_ALLOCATOR);
}
this.allocator = result;
}
}
}
return this.allocator;
}
}
private final class ServerBootstrapProvider implements Provider<ServerBootstrap> {
private final Provider<Settings> settings;
private final Provider<ByteBufAllocator> allocator;
public ServerBootstrapProvider(Provider<Settings> settings, Provider<ByteBufAllocator> allocator) {
this.settings = settings;
this.allocator = allocator;
}
@Override
public ServerBootstrap get() {
ServerBootstrap result = new ServerBootstrap();
result = result.childOption(ChannelOption.ALLOCATOR, allocator.get());
return configureServerBootstrap(result, settings.get());
}
}
private static final class WrappedWorkerThreadPoolProvider implements Provider<ExecutorService> {
private final Provider<ExecutorService> svc;
private final ReentrantScope scope;
public WrappedWorkerThreadPoolProvider(Provider<ExecutorService> svc, ReentrantScope scope) {
this.svc = svc;
this.scope = scope;
}
@Override
public ExecutorService get() {
return scope.wrapThreadPool(svc.get());
}
}
private static final class CookiesProvider implements Provider<Set<Cookie>> {
private final Provider<HttpEvent> ev;
@Inject
public CookiesProvider(Provider<HttpEvent> ev) {
this.ev = ev;
}
@Override
public Set<Cookie> get() {
HttpEvent evt = ev.get();
String h = evt.getHeader(HttpHeaders.Names.COOKIE);
if (h != null) {
Set<Cookie> result = CookieDecoder.decode(h);
if (result != null) {
return result;
}
}
return Collections.emptySet();
}
}
private static final class ExecutorServiceProvider implements Provider<ExecutorService> {
final TF tf;
private volatile ExecutorService svc;
private final ThreadCount count;
private final Provider<Settings> settings;
public ExecutorServiceProvider(TF tf, ThreadCount count, Provider<Settings> settings) {
this.tf = tf;
this.count = count;
this.settings = settings;
}
private ExecutorService create() {
boolean useForkJoin = settings.get().getBoolean(SETTINGS_KEY_USE_FORK_JOIN_POOL, true);
switch (tf.name()) {
case BACKGROUND_THREAD_POOL_NAME:
if (useForkJoin) {
return new ForkJoinPool(count.get(), tf, tf, true);
} else {
return Executors.newCachedThreadPool(tf);
}
default:
if (useForkJoin) {
return new ForkJoinPool(count.get(), tf, tf, true);
} else {
return Executors.newFixedThreadPool(count.get(), tf);
}
}
}
@Override
public ExecutorService get() {
if (svc == null) {
synchronized (this) {
if (svc == null) {
svc = create();
}
}
}
return svc;
}
}
static final class TF implements ThreadFactory, UncaughtExceptionHandler, ForkJoinPool.ForkJoinWorkerThreadFactory {
private final String name;
private final Provider<ApplicationControl> app;
private final AtomicInteger count = new AtomicInteger();
private final ThreadGroup tg;
public TF(String name, Provider<ApplicationControl> app) {
this.name = name;
this.app = app;
tg = new ThreadGroup(Thread.currentThread().getThreadGroup(), name + "s");
tg.setDaemon(true);
}
public String name() {
return name;
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
if ("event".equals(tg.getName())) {
t.setPriority(Thread.MAX_PRIORITY);
} else {
t.setPriority(Thread.NORM_PRIORITY);
}
t.setUncaughtExceptionHandler(this);
String nm = name + "-" + count.getAndIncrement();
t.setName(nm);
return t;
}
@Override
public void uncaughtException(Thread on, Throwable error) {
app.get().internalOnError(error);
}
public String toString() {
return "ThreadFactory " + name;
}
@Override
public ForkJoinWorkerThread newThread(ForkJoinPool pool) {
FWT t = new FWT(pool);
if ("event".equals(tg.getName())) {
t.setPriority(Thread.MAX_PRIORITY);
} else {
t.setPriority(Thread.NORM_PRIORITY - 1);
}
t.setUncaughtExceptionHandler(this);
String nm = name + "-" + count.getAndIncrement();
t.setName(nm);
return t;
}
static class FWT extends java.util.concurrent.ForkJoinWorkerThread {
public FWT(ForkJoinPool pool) {
super(pool);
}
}
}
private static class UptimeProvider implements Provider<Duration> {
private final DateTime dt;
@Inject
UptimeProvider(DateTime dt) {
this.dt = dt;
}
@Override
public Duration get() {
return new Duration(dt, new DateTime());
}
}
protected void onInit(Settings settings) {
}
protected void onBeforeStart(Server server, Dependencies deps) {
}
protected void onAfterStart(Server server, Dependencies deps) {
}
/**
* Start a server
*
* @return an object to wait on, which can be used to shut down the server
* @throws IOException if something goes wrong
* @throws InterruptedException if something goes wrong
* @deprecated Use ServerBuilder instead
*/
@Deprecated
public ServerControl start() throws IOException, InterruptedException {
return start(null);
}
/**
* Start a server
*
* @param port The port to start on
* @return an object to wait on, which can be used to shut down the server
* @throws IOException if something goes wrong
* @throws InterruptedException if something goes wrong
* @deprecated Use ServerBuilder instead
*/
@Deprecated
public ServerControl start(Integer port) throws IOException, InterruptedException {
MutableSettings settings = SettingsBuilder.createDefault().buildMutableSettings();
if (port != null) {
settings.setInt("port", port);
}
Integer pt = settings.getInt("port");
if (pt == null) {
settings.setInt("port", port == null ? 8080 : port);
pt = 8080;
}
onInit(settings);
Dependencies dependencies = new Dependencies(settings, this);
Server server = dependencies.getInstance(Server.class);
onBeforeStart(server, dependencies);
ServerControl result = server.start(pt);
onAfterStart(server, dependencies);
return result;
}
private static class CISC extends TypeLiteral<ChannelInitializer<SocketChannel>> {
}
private static class CKTL extends TypeLiteral<Set<Cookie>> {
}
}