1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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);
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
81 /***
82 * Main server entry point.
83 */
84 public static void main(final String[] args) {
85 try {
86 new Server().startServer(PORTNR);
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
106 log("Server", "Opening port " + port);
107 tripTrackerSocket = new ServerSocket(port);
108
109
110 log("Server", "Listening for connections...");
111
112
113 while (true) {
114 client = tripTrackerSocket.accept();
115
116 log("Server", "Connection accepted from " + client.getInetAddress().
117 getHostAddress());
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
128
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
171
172
173
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();
240
241
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 }