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 }