/* * Copyright 2012 Jason Miller * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package jj.repl; import static java.util.concurrent.TimeUnit.SECONDS; import static jj.server.ServerLocation.Virtual; import java.lang.Thread.UncaughtExceptionHandler; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.util.concurrent.Future; import javax.inject.Inject; import javax.inject.Singleton; import jj.ServerStarting; import jj.ServerStopping; import jj.configuration.ConfigurationLoaded; import jj.event.Listener; import jj.event.Publisher; import jj.event.Subscriber; import jj.logging.Emergency; import jj.resource.ResourceLoader; /** * starts up with the server (bound as an eager singleton) * * listens for configuration to be loaded * * if the repl is activated, starts the listener on the configured port or 9955 if unspecified * * if the repl is not activated and the listener is running, stops it * * shuts down the listener when the server goes down * * @author jason * */ @Singleton @Subscriber class ReplServer { public static final int DEFAULT_PORT = 9955; private final ReplConfiguration configuration; private final ReplServerChannelInitializer channelInitializer; private final Publisher publisher; private final ResourceLoader resourceLoader; private final UncaughtExceptionHandler uncaughtExceptionHandler; private volatile ServerBootstrap server; private volatile int port; @Inject ReplServer( final ReplConfiguration configuration, final ReplServerChannelInitializer channelInitializer, final Publisher publisher, final ResourceLoader resourceLoader, final UncaughtExceptionHandler uncaughtExceptionHandler ) { this.configuration = configuration; this.channelInitializer = channelInitializer; this.publisher = publisher; this.resourceLoader = resourceLoader; this.uncaughtExceptionHandler = uncaughtExceptionHandler; } @Listener void on(ServerStarting serverStarting) { resourceLoader.loadResource(ReplScriptEnvironment.class, Virtual, ReplScriptEnvironment.NAME); } @Listener void on(ServerStopping serverStopping) { if (server != null) { shutdown(); } } @Listener void on(ConfigurationLoaded configurationLoaded) { if (configuration.activate()) { if (server != null && port != configuration.port()) { server.group().terminationFuture().addListener((Future<Object> future) -> { if (future.isSuccess()) { start(); } else { publisher.publish(new Emergency("couldn't restart the REPL server. The server may need to be restarted", future.cause())); } }); shutdown(); } else if (server == null) { start(); } } else if (server != null) { shutdown(); } } private void start() { port = (configuration.port() < 1023 || configuration.port() > 65535) ? DEFAULT_PORT : configuration.port(); final ServerBootstrap bootstrap = new ServerBootstrap() .group( new NioEventLoopGroup(1, executorService(uncaughtExceptionHandler, "JibbrJabbr REPL Boss ")), new NioEventLoopGroup(1, executorService(uncaughtExceptionHandler, "JibbrJabbr REPL Worker ")) ) .channel(NioServerSocketChannel.class) .childHandler(channelInitializer); bootstrap.bind("localhost", port).addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { if (future.isSuccess()) { publisher.publish(new ReplListening(port)); server = bootstrap; } else { publisher.publish(new Emergency("could not start the REPL server", future.cause())); bootstrap.group().shutdownGracefully(0, 0, SECONDS); bootstrap.childGroup().shutdownGracefully(0, 0, SECONDS); } } }); } private void shutdown() { server.group().shutdownGracefully(); server.childGroup().shutdownGracefully(); server = null; publisher.publish(new ReplStopped()); } private static ExecutorService executorService(final UncaughtExceptionHandler uncaughtExceptionHandler, final String name) { return Executors.newFixedThreadPool(1, new ThreadFactory() { private final AtomicInteger id = new AtomicInteger(); @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r, name + id.incrementAndGet()); thread.setUncaughtExceptionHandler(uncaughtExceptionHandler); return thread; } }); } }