package io.statik.report; import com.trendrr.beanstalk.BeanstalkClient; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.timeout.ReadTimeoutHandler; import io.statik.report.processing.ProcessThread; import java.io.File; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.logging.ConsoleHandler; import java.util.logging.Formatter; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; /** * Main class. Runs the report server for Statik. */ public class ReportServer { private final static Logger logger = Logger.getLogger("io.statik.report"); private final Configuration c; private final MongoDB mdb; private final List<Client> clients = Collections.synchronizedList(new ArrayList<Client>()); /** * Starts the ReportServer with the given configuration file. * * @param configFileName File name to load as the configuration */ public ReportServer(final String configFileName) { this.setUpLogger(); this.c = new Configuration(new File(configFileName)); this.mdb = new MongoDB(this); this.startBeanstalkProcessors(); final EventLoopGroup masterGroup = new NioEventLoopGroup(); final EventLoopGroup slaveGroup = new NioEventLoopGroup(); try { final ServerBootstrap sb = new ServerBootstrap(); sb.group(masterGroup, slaveGroup).channel(NioServerSocketChannel.class); sb.option(ChannelOption.SO_BACKLOG, 128).childOption(ChannelOption.SO_KEEPALIVE, true); sb.childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(final SocketChannel ch) throws Exception { ch.pipeline().addLast(new ReadTimeoutHandler(10, TimeUnit.SECONDS)); ch.pipeline().addLast(new ReportHandler(ReportServer.this)); ch.pipeline().addLast(new EndOfTheLine(ReportServer.this)); } }); final ChannelFuture cf = sb.bind( new InetSocketAddress( this.getConfiguration().getString("config.bind.hostname", "localhost"), this.getConfiguration().getInt("config.bind.port", 12345) ) ).sync(); cf.channel().closeFuture().sync(); } catch (final Throwable t) { this.getLogger().severe("An exception was thrown during server setup:"); this.getLogger().log(Level.SEVERE, t.getMessage(), t); } finally { masterGroup.shutdownGracefully(); slaveGroup.shutdownGracefully(); } } /** * Entry point. Creates a new {@link io.statik.report.ReportServer}, catching any exceptions. * <p/> * If any exception is caught, the application will exit with status 1. * * @param args Command-line arguments */ public static void main(final String[] args) { try { new ReportServer(args.length > 0 ? args[0] : "config.json"); } catch (final Throwable t) { ReportServer.logger.severe("Could not start the report server due to the following exception: "); ReportServer.logger.log(Level.SEVERE, t.getMessage(), t); System.exit(1); } } /** * Sets up the main {@link java.util.logging.Logger}. * <p/> * Example output: <code>[WARNING] Some message!</code> */ private void setUpLogger() { final ConsoleHandler ch = new ConsoleHandler(); ch.setFormatter(new Formatter() { @Override public String format(final LogRecord logRecord) { return "[" + logRecord.getLevel().getLocalizedName() + "] " + logRecord.getMessage() + "\n"; } }); this.getLogger().setUseParentHandlers(false); this.getLogger().addHandler(ch); } /** * Starts a configurable amount of processors (if not configured, 4 is the default) in new threads. Processors sit * and wait for beanstalkd to feed them jobs. Once receiving the job, the processor will process it and store it in * the MongoDB. */ private void startBeanstalkProcessors() { for (int i = 0; i < this.getConfiguration().getInt("config.beanstalkd.processors", 4); i++) { new ProcessThread(this).start(); } } public Client getClient(final InetSocketAddress isa) { synchronized (this.getClients()) { for (final Client client : this.getClients()) { if (client.getRemoteAddress().equals(isa)) return client; } } return null; } public List<Client> getClients() { synchronized (this.clients) { return this.clients; } } /** * Gets the {@link io.statik.report.Configuration} this server is running with. * * @return Configuration */ public Configuration getConfiguration() { return this.c; } /** * Gets the main {@link java.util.logging.Logger} for this server. This should be used whenever it is necessary to * output to the console. * * @return Logger */ public Logger getLogger() { return ReportServer.logger; } /** * Gets the MongoDB link for this server. * * @return MongoDB */ public MongoDB getMongoDB() { return this.mdb; } /** * Gets a new BeanstalkClient for immediate use. * * @return BeanstalkClient */ public BeanstalkClient getNewBeanstalkClient() { return new BeanstalkClient( this.getConfiguration().getString("config.beanstalkd.hostname", null), this.getConfiguration().getInt("config.beanstalkd.port", -1), "processing" ); } }