001/*
002 * Trident - A Multithreaded Server Alternative
003 * Copyright 2014 The TridentSDK Team
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *    http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package net.tridentsdk.server;
019
020import io.netty.bootstrap.ServerBootstrap;
021import io.netty.channel.ChannelOption;
022import io.netty.channel.EventLoopGroup;
023import io.netty.channel.nio.NioEventLoopGroup;
024import io.netty.channel.socket.nio.NioServerSocketChannel;
025import joptsimple.OptionException;
026import joptsimple.OptionParser;
027import joptsimple.OptionSet;
028import joptsimple.OptionSpec;
029import net.tridentsdk.Defaults;
030import net.tridentsdk.Trident;
031import net.tridentsdk.config.Config;
032import net.tridentsdk.docs.Policy;
033import net.tridentsdk.plugin.Plugins;
034import net.tridentsdk.registry.Implementation;
035import net.tridentsdk.registry.Registered;
036import net.tridentsdk.server.command.ServerCommandRegistrar;
037import net.tridentsdk.server.concurrent.TickSync;
038import net.tridentsdk.server.netty.ClientChannelInitializer;
039import net.tridentsdk.server.service.Statuses;
040import net.tridentsdk.server.service.TridentImpl;
041import net.tridentsdk.server.world.TridentWorld;
042import net.tridentsdk.server.world.TridentWorldLoader;
043import net.tridentsdk.util.TridentLogger;
044import net.tridentsdk.world.World;
045import org.apache.log4j.Level;
046
047import javax.annotation.concurrent.ThreadSafe;
048import java.io.File;
049import java.io.InputStream;
050import java.net.InetSocketAddress;
051import java.nio.file.Files;
052import java.util.NoSuchElementException;
053import java.util.Scanner;
054
055import static com.google.common.collect.Lists.newArrayList;
056
057/**
058 * Server class that starts the connection listener.
059 * <p/>
060 * <p>Despite the fact that this class is under protected access,
061 * it is documented anyways because of its significance in the server</p>
062 *
063 * @author The TridentSDK Team
064 */
065@ThreadSafe
066public final class TridentStart {
067    private static volatile EventLoopGroup bossGroup;
068    private static volatile EventLoopGroup workerGroup;
069
070    private TridentStart() {
071    } // Do not initialize
072
073    /**
074     * Starts the server up when the jarfile is run
075     *
076     * @param args the command line arguments
077     */
078    @Policy("Do not throw exceptions in this method")
079    public static void main(String... args) throws Exception {
080        OptionParser parser = new OptionParser();
081        parser.acceptsAll(newArrayList("h", "help"), "Show this help dialog.").forHelp();
082        parser.accepts("d", "Prints debug level logging output");
083        parser.acceptsAll(newArrayList("log-append"), "Whether to append to the log file")
084                .withRequiredArg()
085                .ofType(Boolean.class)
086                .defaultsTo(true)
087                .describedAs("Log append");
088        OptionSpec<File> properties = parser.acceptsAll(newArrayList("properties"),
089                "The location for the properties file")
090                .withRequiredArg()
091                .ofType(File.class)
092                .defaultsTo(new File("server.json"))
093                .describedAs("Properties file");
094
095        OptionSet options;
096        try {
097            options = parser.parse(args);
098        } catch (OptionException ex) {
099            TridentLogger.get().error(ex);
100            return;
101        }
102
103        boolean d = options.has("d");
104        TridentLogger.init(d ? Level.DEBUG : Level.INFO);
105        TickSync.DEBUG = d;
106
107        TridentLogger.get().log("Open source software by TridentSDK - https://github.com/TridentSDK");
108        TridentLogger.get().log("Starting Trident server");
109
110        TridentLogger.get().log("Looking for server files...");
111        File f = properties.value(options);
112        if (!f.exists()) {
113            TridentLogger.get().warn("Server properties not found, creating one for you...");
114            InputStream link = TridentServer.class.getResourceAsStream("/server.json");
115            Files.copy(link, f.getAbsoluteFile().toPath());
116        }
117
118        TridentLogger.get().log("Initializing the API implementations");
119        Implementation implementation = new TridentImpl();
120        Registered.setProvider(implementation);
121        TridentLogger.get().success("Loaded API implementations.");
122
123        ((Statuses) Registered.statuses()).loadAll();
124        TridentLogger.get().success("Loaded the server files");
125
126        TridentLogger.get().log("Starting server process...");
127        init(new Config(f));
128    }
129
130    /**
131     * Initializes the server with the configuration file
132     *
133     * @param config the configuration to use for option lookup
134     */
135    private static void init(final Config config) throws InterruptedException {
136        bossGroup = new NioEventLoopGroup(4, Defaults.ERROR_HANDLED);
137        workerGroup = new NioEventLoopGroup(4, Defaults.ERROR_HANDLED);
138
139        try {
140            TridentLogger.get().log("Creating server...");
141            TridentServer.createServer(config);
142            TridentLogger.get().success("Server created.");
143
144            // Required before loading worlds to find all class files in case the plugin has a world generator
145            TridentLogger.get().log("Loading plugins...");
146            File fi = new File(System.getProperty("user.dir") + File.separator + "plugins");
147            if (!fi.exists())
148                fi.mkdir();
149
150            for (File file : new File(System.getProperty("user.dir") + File.separator + "plugins")
151                    .listFiles((dir, name) -> name.endsWith(".jar")))
152                Registered.plugins().load(file);
153            TridentLogger.get().success("Loaded plugins.");
154
155            TridentWorldLoader.loadAll();
156            TridentServer.WORLD = (TridentWorld) Registered.worlds().get("world");
157
158            if (TridentServer.WORLD == null) {
159                World world = TridentServer.instance().rootWorldLoader.createWorld("world");
160                TridentServer.WORLD = (TridentWorld) world;
161            }
162
163            TridentLogger.get().log("Setting server commands...");
164            ServerCommandRegistrar.registerAll();
165            TridentLogger.get().success("Server commands set.");
166
167            TridentLogger.get().log("Enabling plugins...");
168            Plugins handler = Registered.plugins();
169            handler.forEach(handler::enable);
170            TridentLogger.get().success("Enabled plugins.");
171
172            ////////////////////////////////// NETTY SETUP //////////////////////////////////////////
173
174            TridentLogger.get().log("Creating server connections...");
175            String ip = config.getString("address", Defaults.ADDRESS);
176            int port = config.getInt("port", Defaults.PORT);
177
178            // FIXME double shutdown for no reason...
179            // Runtime.getRuntime().addShutdownHook(new Thread(() -> TridentServer.instance().shutdown()));
180
181            TridentLogger.get().log("Binding socket to server address, using address:port " + ip + ":" + port);
182
183            new ServerBootstrap().group(bossGroup, workerGroup)
184                    .channel(NioServerSocketChannel.class)
185                    .childHandler(new ClientChannelInitializer())
186                    .option(ChannelOption.TCP_NODELAY, true)
187                    .bind(new InetSocketAddress(ip, port))
188                    .sync();
189
190            TridentLogger.get().success("Server started.");
191
192            /////////////////////////// Console command handling ////////////////////////////////////
193            Thread thread = new Thread(() -> {
194                Scanner scanner = new Scanner(System.in);
195
196                while (true) {
197                    String command = scanner.nextLine();
198                    System.out.print("$ ");
199
200                    Trident.console().invokeCommand(command);
201                }
202            });
203            thread.setName("Trident - Console");
204            thread.setDaemon(true);
205            thread.start();
206        } catch (InterruptedException e) {
207            // This exception is caught if server is closed.
208        } catch (NoSuchElementException e) {
209            // For some reason, this is thrown when the server is quit
210        } catch (Exception e) {
211            TridentLogger.get().error("Server closed, error occurred");
212            TridentLogger.get().error(e);
213            Trident.shutdown();
214        }
215    }
216
217    /**
218     * Shuts down the backed event loops
219     */
220    public static void close() {
221        workerGroup.shutdownGracefully().awaitUninterruptibly();
222        bossGroup.shutdownGracefully().awaitUninterruptibly();
223    }
224}