001    /**
002     * Jetrix TetriNET Server
003     * Copyright (C) 2001-2010  Emmanuel Bourg
004     *
005     * This program is free software; you can redistribute it and/or
006     * modify it under the terms of the GNU General Public License
007     * as published by the Free Software Foundation; either version 2
008     * of the License, or (at your option) any later version.
009     *
010     * This program is distributed in the hope that it will be useful,
011     * but WITHOUT ANY WARRANTY; without even the implied warranty of
012     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013     * GNU General Public License for more details.
014     *
015     * You should have received a copy of the GNU General Public License
016     * along with this program; if not, write to the Free Software
017     * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
018     */
019    
020    package net.jetrix;
021    
022    import java.io.*;
023    import java.util.*;
024    import java.util.concurrent.*;
025    import java.util.logging.*;
026    
027    import net.jetrix.clients.*;
028    import net.jetrix.commands.*;
029    import net.jetrix.config.*;
030    import net.jetrix.messages.*;
031    import net.jetrix.messages.channel.*;
032    import net.jetrix.services.VersionService;
033    import net.jetrix.listeners.ShutdownListener;
034    import net.jetrix.mail.MailSessionManager;
035    
036    /**
037     * Main class, starts the server components and handle the server level messages.
038     *
039     * @author Emmanuel Bourg
040     * @version $Revision: 860 $, $Date: 2010-05-06 13:21:05 +0200 (jeu., 06 mai 2010) $
041     */
042    public class Server implements Runnable, Destination
043    {
044        private static Server instance;
045    
046        private Logger log = Logger.getLogger("net.jetrix");
047    
048        private File configFile;
049        private ServerConfig config;
050        private BlockingQueue<Message> queue = new LinkedBlockingQueue<Message>();
051        private ChannelManager channelManager;
052        private Client console;
053    
054        private Server() { }
055    
056        /**
057         * Register the shutdown hooks to stop the server properly when the JVM is stopped.
058         */
059        private void registerHooks()
060        {
061            Thread hook = new Thread("StopHook")
062            {
063                public void run()
064                {
065                    if (config != null && config.isRunning())
066                    {
067                        log.info("Shutdown command received from the system");
068                        instance.stop();
069                    }
070                }
071            };
072            Runtime.getRuntime().addShutdownHook(hook);
073    
074            try
075            {
076                SystemSignal.handle("INT", hook);
077                SystemSignal.handle("TERM", hook);
078            }
079            catch (Throwable e)
080            {
081                log.warning("Unable to hook the system signals: " + e.getMessage());
082            }
083        }
084    
085        /**
086         * Return the unique instance of the server.
087         */
088        public static Server getInstance()
089        {
090            if (instance == null)
091            {
092                instance = new Server();
093            }
094    
095            return instance;
096        }
097    
098        /**
099         * Server initialization.
100         */
101        private void init()
102        {
103            registerHooks();
104            
105            // read the server configuration
106            config = new ServerConfig();
107            config.load(configFile);
108            config.setRunning(true);
109    
110            // prepare the loggers
111            LogManager.init();
112            
113            // open the database connections
114            if (!config.getDataSources().isEmpty())
115            {
116                log.info("Initializing the datasources...");
117                for (DataSourceConfig datasource : config.getDataSources())
118                {
119                    DataSourceManager.getInstance().setDataSource(datasource, datasource.getName());
120                }
121            }
122    
123            if (config.getMailSessionConfig() != null)
124            {
125                log.info("Initializing the mail session...");
126                MailSessionManager.getInstance().setConfiguration(config.getMailSessionConfig());
127                MailSessionManager.getInstance().checkSession();
128            }
129    
130            // display the systray icon (windows only)
131            SystrayManager.open();
132    
133            // spawning persistent channels
134            channelManager = ChannelManager.getInstance();
135            channelManager.clear();
136    
137            for (ChannelConfig cc : config.getChannels())
138            {
139                cc.setPersistent(true);
140                channelManager.createChannel(cc);
141            }
142    
143            // start the client listeners
144            for (Listener listener : config.getListeners())
145            {
146                if (listener.isAutoStart())
147                {
148                    listener.start();
149                }
150            }
151    
152            // start the shutdown listener
153            new ShutdownListener().start();
154    
155            // start the services
156            for (Service service : config.getServices())
157            {
158                if (service.isAutoStart())
159                {
160                    log.info("Starting service " + service.getName());
161                    service.start();
162                }
163            }
164    
165            // check the availability of a new release
166            VersionService.updateLatestVersion();
167            if (VersionService.isNewVersionAvailable())
168            {
169                log.warning("A new version is available (" + VersionService.getLatestVersion() + "), download it on http://jetrix.sf.net now!");
170            }
171    
172            // start the server console
173            console = new ConsoleClient();
174            new Thread(console).start();
175    
176            log.info("Server ready!");
177        }
178    
179        /**
180         * Start the server.
181         */
182        public void start()
183        {
184            Thread server = new Thread(this, "server");
185            server.start();
186        }
187    
188        /**
189         * Stop the server.
190         */
191        public void stop()
192        {
193            config.setRunning(false);
194    
195            // stop the listeners
196            for (Listener listener : config.getListeners())
197            {
198                if (listener.isRunning())
199                {
200                    listener.stop();
201                }
202            }
203    
204            // stop the services
205            for (Service service : config.getServices())
206            {
207                if (service.isRunning())
208                {
209                    service.stop();
210                }
211            }
212    
213            // disconnect all clients
214            disconnectAll();
215    
216            // close the channels
217            ChannelManager.getInstance().closeAll();
218    
219            // stop the server thread
220            send(new ShutdownMessage());
221        }
222    
223        /**
224         * Disconnect all clients from the server.
225         */
226        private void disconnectAll()
227        {
228            ClientRepository repository = ClientRepository.getInstance();
229    
230            for (Client client : repository.getClients())
231            {
232                client.disconnect();
233            }
234    
235            // disconnect the console client as well
236            if (console != null)
237            {
238                console.disconnect();
239            }
240        }
241    
242        public void run()
243        {
244            init();
245    
246            while (config.isRunning())
247            {
248                try
249                {
250                    // fetching next message waiting in the queue
251                    Message message = queue.take();
252    
253                    if (log.isLoggable(Level.FINEST))
254                    {
255                        log.finest("[server] processing " + message);
256                    }
257    
258                    // processing message
259    
260                    if (message instanceof AddPlayerMessage)
261                    {
262                        // look for a suitable channel
263                        Client client = ((AddPlayerMessage) message).getClient();
264                        int level = client.getUser().getAccessLevel();
265                        Channel channel = channelManager.getHomeChannel(level, client.getProtocol().getName());
266                        
267                        if (channel != null)
268                        {
269                            if (log.isLoggable(Level.FINEST))
270                            {
271                                log.finest("[server] assigning client to channel " + channel);
272                            }
273                            channel.send(message);
274                        }
275                        else
276                        {
277                            // send server full message or create a new channel
278                            if (log.isLoggable(Level.FINEST))
279                            {
280                                log.finest("[server] no available channels!");
281                            }
282                        }
283                    }
284                    else if (message instanceof CommandMessage)
285                    {
286                        CommandManager.getInstance().execute((CommandMessage) message);
287                    }
288                    else if (!(message instanceof ShutdownMessage))
289                    {
290                        log.info("[server] Message not processed " + message);
291                    }
292                }
293                catch (InterruptedException e)
294                {
295                    log.log(Level.WARNING, e.getMessage(), e);
296                }
297            }
298    
299            // remove the system tray icon
300            SystrayManager.close();
301        }
302    
303        /**
304         * Add a message to the server message queue.
305         */
306        public void send(Message message)
307        {
308            queue.add(message);
309        }
310    
311        /**
312         * Return the server configuration.
313         */
314        public ServerConfig getConfig()
315        {
316            return config;
317        }
318    
319        /**
320         * Set the server configuration file.
321         */
322        public void setConfigFile(File configFile)
323        {
324            this.configFile = configFile;
325        }
326    
327        /**
328         * Server entry point.
329         *
330         * @param args start parameters
331         */
332        public static void main(String[] args)
333        {
334            System.out.println("Jetrix TetriNET Server " + ServerConfig.VERSION + ", Copyright (C) 2001-2010 Emmanuel Bourg\n");
335    
336            Server server = Server.getInstance();
337    
338            List<String> params = Arrays.asList(args);
339    
340            // read the path of the server configuration file
341            int p = params.indexOf("--conf");
342            if (p != -1 && p + 1 < params.size())
343            {
344                server.setConfigFile(new File(params.get(p + 1)));
345            }
346            else
347            {
348                server.setConfigFile(new File("conf/server.xml"));
349            }
350    
351            server.start();
352        }
353    
354    }