View Javadoc

1   /*
2    * Trip Tracker, a real-time position tracking system for the Internet.
3    * Copyright (C) 2006  Team Trip Tracker
4    *
5    * This program is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License as published by the
7    * Free Software Foundation; either version 2 of the License, or (at your
8    * option) any later version.
9    *
10   * This program is distributed in the hope that it will be useful, but
11   * WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13   * General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License along
16   * with this program; if not, write to the Free Software Foundation, Inc.,
17   * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
18   */
19  
20  package triptracker.server;
21  
22  import static triptracker.core.Protocol.*;
23  
24  import java.io.File;
25  import java.io.FileWriter;
26  import java.io.IOException;
27  import java.io.PrintWriter;
28  import java.net.ServerSocket;
29  import java.net.Socket;
30  import java.util.ArrayList;
31  import java.util.Date;
32  import java.util.List;
33  import java.util.Set;
34  import java.util.concurrent.CopyOnWriteArraySet;
35  
36  import triptracker.core.Route;
37  import triptracker.core.Utils;
38  
39  /***
40   * Main server class.
41   */
42  public class Server {
43  	/*** Main listening server socket. */
44  	private ServerSocket tripTrackerSocket;
45  
46  	/*** Connected clients sending coordinates. */
47  	private List<GPSHandler> gpsHandlers = new ArrayList<GPSHandler>();
48  
49  	/*** Connected clients receiving coordinates. */
50  	private List<MapHandler> mapHandlers = new ArrayList<MapHandler>();
51  
52  	/*** Listener set for Model View Controller (MVC) separation. */
53  	private Set<ServerListener> listeners =
54  			new CopyOnWriteArraySet<ServerListener>();
55  
56  	/*** Default server log file. */
57  	private final String SERVER_LOG = "server.log";
58  
59  	/*** Server log. */
60  	private final File logFile;
61  	
62  	/***
63  	 * Default constructor.
64  	 */
65  	public Server() {
66  		logFile = new File(SERVER_LOG); // TODO Move to properties file
67  	}
68  	
69  	/***
70  	 * Method being run when server is set for stopping.
71  	 * 
72  	 * @throws IOException server dies on <code>IOException</code>
73  	 */
74  	public final void stopServer() throws IOException {
75  		log("Server", "Closing down server");
76  		
77  		tripTrackerSocket.close();
78  	}
79  
80  	// TODO Remove method when ConsoleServer is complete.
81  	/***
82  	 * Main server entry point. 
83  	 */
84  	public static void main(final String[] args) {
85  		try {
86  			new Server().startServer(PORTNR); // TODO Move to properties file
87  		} catch (IOException e) {
88  			System.err.println("Server terminated by fatal IOException");
89  			e.printStackTrace();
90  		}
91  	}
92  	
93  	/***
94  	 * Main method will open the server socket, and go into a loop, where it
95  	 * recieves clients and starts handlers.
96  	 * 
97  	 * @param port listening port
98  	 * @throws IOException server dies on <code>IOException</code>
99  	 */
100 	public final void startServer(final int port) throws IOException {
101 		Socket client;
102 
103 		log("Server", "Starting server.");
104 
105 		// Opening socket port
106 		log("Server", "Opening port " + port); // TODO MVC listener call
107 		tripTrackerSocket = new ServerSocket(port);
108 
109 		// TODO MVC listener call
110 		log("Server", "Listening for connections...");
111 		
112 		// Process connection requests in an infinite loop.
113 		while (true) {
114 			client = tripTrackerSocket.accept();
115 
116 			log("Server", "Connection accepted from " + client.getInetAddress().
117 					getHostAddress()); // TODO MVC listener call
118 			new Thread(new CheckThread(this, client)).start();
119 		}
120 	}
121 
122 	/***
123 	 * Returns a GPSHandler to listen to.
124 	 * @Param route - Route to search for
125 	 * @return GPS handler
126 	 */
127 	// TODO (jani) Add compareTo in GPSHandler to use binSearch or SortedSet 
128 //	protected final GPSHandler getGPSHandler(final int routeId){
129 	public GPSHandler getGPSHandler(final Route route) {
130 		for(GPSHandler handler : gpsHandlers){
131 			if (handler.getActiveRoute() != null
132 					&& route.getRouteId() == handler.getActiveRoute()
133 					.getRouteId()) {
134 				return handler;
135 			}
136 		}
137 		return null;
138 	}
139 	
140 	/***
141 	 * Adds a GPS client handler to the main server handler list.
142 	 * 
143 	 * @param handler handler
144 	 */
145 	protected final void addGPSHandler(final GPSHandler handler) {
146 		log("Server", "Adding gpsHandler: " + handler);
147 
148 		clientListUpdate(handler, true);
149 
150 		gpsHandlers.add(handler);
151 	}
152 
153 	/***
154 	 * Removes a GPS client handler from the main server handler list.
155 	 * 
156 	 * @param handler handler
157 	 */
158 	protected final void removeGPSHandler(final GPSHandler handler) {
159 		clientListUpdate(handler, false);
160 		log("Server", "Removing gpsHandler: " + handler);
161 		gpsHandlers.remove(handler);
162 	}
163 
164 	/***
165 	 * Checks if any mapHandlers are listening to the route gpsHandler will log
166 	 * @param gpsHandler
167 	 */
168 	protected final void checkMapHandler(GPSHandler gpsHandler){
169 		for(MapHandler mapHandler : mapHandlers){
170 //			System.out.println("Maphandler route: "
171 //					+ mapHandler.getActiveRoute());
172 //			System.out.println("GpsHandler route: "
173 //					+ gpsHandler.getActiveRoute());
174 			if (mapHandler.getActiveRoute() != null && 
175 					mapHandler.getActiveRoute().getRouteId() == gpsHandler
176 							.getActiveRoute().getRouteId()) {
177 				gpsHandler.addCoordListener(mapHandler);
178 				mapHandler.startLogging(gpsHandler);
179 				log("Server", "mapHandler will now start listening to route: "
180 						+ gpsHandler.getActiveRoute().getRouteId());
181 			}
182 		}
183 	}
184 	
185 	/***
186 	 * Adds a map client handler from the main server handler list.
187 	 * 
188 	 * @param handler handler
189 	 */
190 	protected final void addMapHandler(final MapHandler handler) {
191 		clientListUpdate(handler, true);
192 		mapHandlers.add(handler);
193 	}
194 
195 	/***
196 	 * Removes a map client handler from the main server handler list.
197 	 * 
198 	 * @param handler handler
199 	 */
200 	protected final void removeMapHandler(final MapHandler handler) {
201 		clientListUpdate(handler, false);
202 		log("Server", "Removing mapHandler: " + handler);
203 		mapHandlers.remove(handler);
204 	}
205 	
206 	public List<GPSHandler> getGPSHandlers(){		
207 		return gpsHandlers;
208 	}
209 	
210 	/***
211 	 * Register a listener for server events.
212 	 * 
213 	 * @param listener event receiver 
214 	 */
215 	public final void addListener(final ServerListener listener) {
216 		listeners.add(listener);
217 	}
218 	
219 	/***
220 	 * Remove listener from listener queue.
221 	 * 
222 	 * @param listener event receiver 
223 	 */
224 	public final void removeListener(final ServerListener listener) {
225 		listeners.remove(listener);
226 	}
227 	
228 	/***
229 	 * Reports a broken listener, removes it from the listener queue and logs
230 	 * the error.
231 	 * 
232 	 * @param listener broken listener
233 	 * @param exception exception thrown by listener
234 	 */
235 	private void brokenListener(final ServerListener listener,
236 			final RuntimeException exception) {
237 		log("Server", "Unexpected exception in listener: "
238 				+ exception.toString());
239 		exception.printStackTrace(); // XXX Temp error logging? Add log to file?
240 		
241 		// Remove listener from queue.
242 		listeners.remove(listener);
243 	}
244 	
245 	/***
246 	 * Publish a "socket error" event to all listeners. This generally means
247 	 * that the socket associated with <code>client</code> has been closed and
248 	 * the client has been removed from any client list it might be a member of. 
249 	 * 
250 	 * @param client disconnected client
251 	 * @param exception socket <code>IOException</code> thrown by client
252 	 */
253 	protected final void socketErrorEvent(final ClientHandler client,
254 			final IOException exception) {
255 		for (ServerListener listener : listeners) {
256 			try {
257 				listener.socketError(client, exception);
258 			} catch (RuntimeException e) {
259 				brokenListener(listener, e);
260 			}
261 		}
262 	}
263 
264 	/***
265 	 * Publish an "invalid message" event to all listeners. This signals an
266 	 * unsupported or malformed message recieved from <code>client</code>.
267 	 * Clients are usually not disconnected for sending invalid messages, unless
268 	 * the amount of invalid messages is significant and the behaviour of the
269 	 * client appears to be malicious.
270 	 * 
271 	 * @param client misbehaving client
272 	 * @param message full message
273 	 */
274 	protected final void invalidMessage(final ClientHandler client,
275 			final String message) {
276 		for (ServerListener listener : listeners) {
277 			try {
278 				listener.invalidMessage(client, message);
279 			} catch (RuntimeException e) {
280 				brokenListener(listener, e);
281 			}
282 		}
283 	}
284 	
285 	/***
286 	 * Publish a "socket error" event to all listeners. This generally means
287 	 * that the socket associated with <code>client</code> has been closed and
288 	 * the client has been removed from any client list it might be a member of. 
289 	 * 
290 	 * @param client joined or disconnected client
291 	 * @param joined true if joined, false if disconnected
292 	 */
293 	protected final void clientListUpdate(final ClientHandler client,
294 			final boolean joined) {
295 		for (ServerListener listener : listeners) {
296 			try {
297 				listener.clientListUpdate(client, joined);
298 			} catch (RuntimeException e) {
299 				brokenListener(listener, e);
300 			}
301 		}
302 	}
303 	
304 	/***
305 	 * Log a status message to the server log file. Each log entry is given
306 	 * a timestamp and appended to the end of the file.
307 	 * 
308 	 * @param source in what context the log message was recorded
309 	 * @param message status message
310 	 */
311 	protected final void log(final String source, final String message) {
312 		System.out.println(source  + " [" + Thread.currentThread().getId() + "]"
313 				+ ": " + message);
314 		try {
315 			if (!(logFile.exists())) {
316 			   logFile.createNewFile();
317 			}
318 			
319 			String date = Utils.dateToString(new Date());
320 			
321 			PrintWriter logOut = new PrintWriter(new FileWriter(logFile, true));
322 			logOut.println("[" + date + "] " + source  + " ["
323 					+ Thread.currentThread().getId() + "]" + ": " + message);
324 			logOut.flush();
325 		} catch (IOException e) {
326 			System.out.println("With logging: " + e);
327 			e.printStackTrace();
328 		}
329 	}
330 }