Added stream manager and TCP listener
[WeStealzYourDataz.git] / src / uk / ac / ntu / n0521366 / wsyd / server / ServerSocial.java
1 /*
2  * The MIT License
3  *
4  * Copyright 2015 TJ <hacker@iam.tj>.
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22  * THE SOFTWARE.
23  */
24 package uk.ac.ntu.n0521366.wsyd.server;
25
26 import java.awt.event.ActionEvent;
27 import java.awt.event.ActionListener;
28 import java.util.Date;
29 import java.util.ArrayList;
30 import java.text.SimpleDateFormat;
31 import java.text.ParseException;
32 import java.util.TreeSet;
33 import java.util.SortedMap;
34 import java.util.TreeMap;
35 import java.util.Map;
36 import java.util.Collections;
37 import java.util.logging.Logger;
38 import java.util.logging.Level;
39 import java.io.BufferedReader;
40 import java.io.BufferedWriter;
41 import java.io.File;
42 import java.io.FileInputStream;
43 import java.io.FileOutputStream;
44 import java.io.InputStreamReader;
45 import java.io.OutputStreamWriter;
46 import java.io.ObjectInputStream;
47 import java.io.ObjectOutputStream;
48 import java.io.IOException;
49 import java.io.FileNotFoundException;
50 import java.net.InetSocketAddress;
51 import java.net.SocketException;
52 import java.util.Arrays;
53 import java.util.logging.LogRecord;
54 import javax.swing.Timer;
55 import uk.ac.ntu.n0521366.wsyd.libs.WSYD_Member;
56 import uk.ac.ntu.n0521366.wsyd.libs.WSYD_Member_Comparator_UserID;
57 import uk.ac.ntu.n0521366.wsyd.libs.message.MessagePresence;
58 import uk.ac.ntu.n0521366.wsyd.libs.message.MessageServerControl;
59 import uk.ac.ntu.n0521366.wsyd.libs.net.Network;
60 import uk.ac.ntu.n0521366.wsyd.libs.net.NetworkMessage;
61 import uk.ac.ntu.n0521366.wsyd.libs.net.NetworkMessageEvent;
62 import uk.ac.ntu.n0521366.wsyd.libs.net.NetworkServerUDPMulticast;
63 import uk.ac.ntu.n0521366.wsyd.libs.net.WSYD_SocketAddress;
64 import uk.ac.ntu.n0521366.wsyd.libs.net.NetworkMessageEventListener;
65 import uk.ac.ntu.n0521366.wsyd.libs.net.NetworkServerTCP;
66 import uk.ac.ntu.n0521366.wsyd.libs.net.NetworkServerUDP;
67 import uk.ac.ntu.n0521366.wsyd.libs.net.NetworkSocketClosing;
68 import uk.ac.ntu.n0521366.wsyd.libs.net.NetworkStream;
69 import uk.ac.ntu.n0521366.wsyd.libs.net.NetworkStreamManager;
70 import uk.ac.ntu.n0521366.wsyd.libs.net.ServiceAddressMap;
71 import uk.ac.ntu.n0521366.wsyd.libs.net.ServiceAddressMap.LastSeenHost;
72
73 /**
74  * The main Social Network server.
75  *
76  * Can be restarted or stopped using the class static attributes
77  * exitRequested and restartRequested. This can be done by an optional
78  * Management GUI application.
79  *
80  * @author TJ <hacker@iam.tj>
81  */
82 public final class ServerSocial implements NetworkMessageEventListener {
83     /**
84      * Persistent storage in file-system when server exits.
85      */
86     static final String _membersFile = "WSYD_Members.serialized";
87
88     /**
89      * CSV test data file.
90      * 
91      * If it exists in the file system, only used if there is no _membersFile
92      */
93     static final String _testData = "WSYD_TestData.csv";
94     
95     /**
96      * Readable/displayable name of this application
97      */
98     final String _title = "ServerSocial";
99
100     /**
101      * Network services to address map.
102      */
103     ServiceAddressMap _serviceToAddressMap;
104
105     /**
106      * Indicates to start() loop and main() methods to exit completely.
107      */
108     public static boolean exitRequested = false;
109
110     /**
111      * Indicates to start() loop to exit, and to main() to restart the server.
112      */
113     public static boolean restartRequested = true;
114
115     /**
116      * Handles display and sending of log messages.
117      */
118     @SuppressWarnings("NonConstantLogger")
119     private static Logger LOGGER;
120
121     /**
122      * SortedMap wraps a TreeMap that has been made thread-safe by
123      * Collections.synchronizedSortedMap() in readMembers().
124      *
125      * Long key, the userID
126      * WSYD_Member member record
127      */
128     SortedMap<Long, WSYD_Member> _members;
129
130     /**
131      * userIDs of members currently logged in
132      */
133     ArrayList<Long> _membersOnline;
134
135     /**
136      * 
137      */
138     WSYD_SocketAddress _multicastAdvertiserSA;
139     
140     /**
141      * 
142      */
143     NetworkServerUDPMulticast _multicastService;
144     
145     Timer _servicesAnnounce;
146     
147     WSYD_SocketAddress _udpControlServiceSA;
148     
149     NetworkServerUDP _udpControlService;
150     
151     WSYD_SocketAddress _tcpListeningServiceSA;
152     
153     NetworkServerTCP _tcpListeningService;
154     
155     NetworkStreamManager _tcpStreamManager;
156    
157
158     /**
159      * 
160      * Default constructor.
161      */
162     public ServerSocial() {
163         String[] className = this.getClass().getName().split("\\.");
164         LOGGER = Logger.getLogger(className[className.length - 1]);
165         LOGGER.setLevel(Level.ALL);
166         if (LOGGER.getParent() != null) {
167             LOGGER.getParent().setLevel(Level.ALL);
168             System.out.println("Parent Logger level " + LOGGER.getParent().getLevel().toString());
169         }
170         _serviceToAddressMap = new ServiceAddressMap(_title, LOGGER);
171         readMembers(_membersFile);
172         _membersOnline = new ArrayList<>();
173         _tcpStreamManager = new NetworkStreamManager();
174     }
175     
176     /**
177      * Init listener
178      */
179     ServerSocial InitListeners()
180     {
181         _multicastAdvertiserSA = new WSYD_SocketAddress(Network.MULTICAST_IP, Network.PORTS_MULTICAST_DISCOVERY, WSYD_SocketAddress.Protocol.UDP);
182         _multicastService = new NetworkServerUDPMulticast(_multicastAdvertiserSA, _title + "MC", _serviceToAddressMap, LOGGER);
183         _multicastService.addNetworkMessageEventListener(this, "Neighbour");
184         _multicastService.execute();
185         _serviceToAddressMap.put("all", new LastSeenHost(new InetSocketAddress(Network.MULTICAST_IP, Network.PORTS_MULTICAST_DISCOVERY), LastSeenHost.STATE.STATIC));
186         
187         _udpControlServiceSA = new WSYD_SocketAddress(Network.IPv4_WILDCARD, Network.PORTS_EPHEMERAL, WSYD_SocketAddress.Protocol.UDP);
188         _udpControlService = new NetworkServerUDP(_udpControlServiceSA, _title + "Control", _serviceToAddressMap, LOGGER);
189         _udpControlService.addNetworkMessageEventListener(this, "Control");
190         _udpControlService.execute();
191         
192         _tcpListeningServiceSA = new WSYD_SocketAddress(Network.IPv4_WILDCARD, Network.PORTS_SERVER_SOCIAL, WSYD_SocketAddress.Protocol.TCP);
193         _tcpListeningService = new NetworkServerTCP(_tcpListeningServiceSA, _title + "Listener", _serviceToAddressMap, _tcpStreamManager, LOGGER);
194         _tcpListeningService.addNetworkMessageEventListener(this, "ServerSocial");
195         _tcpListeningService.execute();
196         
197         ActionListener servicesAnnounceActionListener = new ActionListener() {
198             /**
199              * Activated by timer events to send multi-cast neighbour announcements and other regular notifications.
200              * @param e 
201              */
202             @Override
203             public void actionPerformed(ActionEvent e) {
204
205                 // Announce the Social Server Neighbour service
206                 MessagePresence mp = new MessagePresence(_title, _multicastService.getSocketAddress());
207                 NetworkMessage nm = NetworkMessage.createNetworkMessage("Neighbour", "all", mp);
208                 nm.setSender(_title + "MC");
209                 _multicastService.queueMessage(nm);
210                 LOGGER.log(Level.INFO, "Neighbour advert");
211
212                 // Notify ServerManagement of the Social Server Control service
213                 String target = "ServerManagementControl";
214                 LastSeenHost targetHost = _serviceToAddressMap.get(target);
215                 if (targetHost != null) {
216                     mp = new MessagePresence(_title + "Control", _udpControlService.getSocketAddress());
217                     nm = NetworkMessage.createNetworkMessage("Control", target, mp);
218                     nm.setSender(_title + "Control");
219                     try {
220                         _udpControlService.queueMessage(nm);
221                         LOGGER.log(Level.INFO, "Control notification sent to ServerManagement");
222                     } catch (IllegalArgumentException ex) {
223                         // Not fatal - ServerManagement may not be currently known
224                     }
225                 }
226                 
227                 // clean up the known hosts map
228                 ArrayList<String> servicesRemoved = _serviceToAddressMap.cleanServiceAddressMap(5000);
229                 for (String service: servicesRemoved) {
230                     // FIXME: does the process care if hosts have been removed? if not, remove this array iteration
231                     switch (service) {
232                     }
233                 }
234
235             }
236         };
237         
238         _servicesAnnounce = new Timer(1000, servicesAnnounceActionListener);
239         _servicesAnnounce.setInitialDelay(100);
240         _servicesAnnounce.start();
241         
242         return this;
243     }
244     
245     /**
246      * Main execution loop of the server
247      *
248      * @return true if no errors encountered
249      * @throws java.lang.InterruptedException
250      */
251     @SuppressWarnings("SleepWhileInLoop")
252     public boolean start() throws InterruptedException {
253         boolean result;
254
255         // TODO: start() create TCP listener
256         // TODO: start() create UDP Multicast group listener and broadcast adverts
257         // wait for connections
258         int loopCount = 200;
259         while (!ServerSocial.exitRequested && ! ServerSocial.restartRequested) {
260             Thread.sleep(1000); // wait a second
261             System.out.println("start() loop " + loopCount);
262             if (loopCount-- == 0)
263                 ServerSocial.exitRequested = true;
264         }
265
266         _servicesAnnounce.stop();
267         _multicastService.cancel(true);
268         _udpControlService.cancel(true);
269         
270         result = writeMembers(_membersFile);
271
272         return result;
273     }
274
275     /**
276      * Deserialize the collection of WSYD_Members from file
277      *
278      * @param fileName serialized members data file
279      * @return true if successfully deserialized
280      */
281     @SuppressWarnings("CallToPrintStackTrace")
282     public boolean readMembers(String fileName) {
283         boolean result = false;
284
285         try (
286             FileInputStream f = new FileInputStream(fileName);
287             ObjectInputStream in  = new ObjectInputStream(f);
288             )
289         {
290             if (_members == null)
291                 /* XXX: do not pass a Comparator to the constructor if collection is being deserialized as one was already saved during serialization.
292                  *      If the Comparator is passed to the constructor the serialized object will 'grow' by ~17 bytes each time as multi Comparator
293                  *      objects are written each time the collection is serialized.
294                 */
295                 _members = Collections.synchronizedSortedMap( new TreeMap<Long, WSYD_Member>() );
296             if (!_members.isEmpty())
297                 _members.clear();
298             /* Need explicit cast to SortedMap for Object type returned by readObject()
299              * but this can cause an "unchecked cast" compiler warning since the compiler
300              * cannot be sure the Object returned from readObject() is really a
301              * SortedMap<Long, WSYD_Member> so need to tell the compiler that in this case
302              * we are sure it is. The following for() iteration will cause a
303              * ClassCastException if the type is not as expected.
304              */
305             @SuppressWarnings("unchecked")
306             SortedMap<Long, WSYD_Member> temp = (SortedMap<Long, WSYD_Member>) in.readObject();
307             _members = Collections.synchronizedSortedMap( temp );
308             for (Map.Entry<Long, WSYD_Member> e : _members.entrySet()) {
309                 System.out.println(e.getKey() + ": " + e.getValue().toString());
310             }
311             LOGGER.log(Level.INFO, "Members database read from {0}", fileName);
312             result = true;
313         }
314         catch(FileNotFoundException e) {
315             _members = Collections.synchronizedSortedMap( new TreeMap<Long, WSYD_Member>( new WSYD_Member_Comparator_UserID() ) );
316             LOGGER.log(Level.INFO, "Starting new members database: no database file found ({0})", fileName);
317             result = true;
318
319             // if test data CSV exists import it
320             File csv = new File(_testData);
321             if (csv.exists() && csv.isFile()) {
322                 LOGGER.log(Level.INFO, "Importing test data from {0}", _testData);
323                 importCSV(_testData);
324             }
325
326         }
327         catch(IOException e) {
328             LOGGER.log(Level.SEVERE, "Unable to read database file {0}", fileName);
329             e.printStackTrace();
330         }
331         catch(ClassNotFoundException e) {
332             LOGGER.log(Level.SEVERE, "Unable to deserialize database file {0}", fileName);
333             e.printStackTrace();
334         }
335
336         return result;
337     }
338
339     /**
340      * Serialize the WSYD_Members collection to a file
341      *
342      * @param fileName database file
343      * @return true if collection was successfully serialized
344      */
345     @SuppressWarnings("CallToPrintStackTrace")
346     public boolean writeMembers(String fileName) {
347         boolean result = false;
348
349         if (!_members.isEmpty()) { // don't write an empty database
350             try (
351                 FileOutputStream f = new FileOutputStream(fileName);
352                 ObjectOutputStream out = new ObjectOutputStream(f);
353             )
354             {
355                 out.writeObject(_members);
356
357                 LOGGER.log(Level.INFO, "Members database written to {0}", fileName);
358                 result = true;
359             }
360             catch(IOException e) {
361                 LOGGER.log(Level.SEVERE, "Unable to write database file {0}", fileName);
362                 e.printStackTrace();
363             }
364         }
365         else
366             result = true;
367
368         return result;
369     }
370
371     /**
372      * Read a CSV file containing WSYD_Member records and add it to the in-memory
373      * collection.
374      * 
375      * @param fileName name of CSV file
376      * @return true if successfully imported
377      */
378     @SuppressWarnings("CallToPrintStackTrace")
379     public boolean importCSV(String fileName) {
380         boolean result = false;
381
382         try (
383             FileInputStream fis = new FileInputStream(fileName);
384             InputStreamReader isr = new InputStreamReader(fis);
385             BufferedReader br = new BufferedReader(isr);
386         )
387         {
388             String line;
389             while ((line = br.readLine()) != null) {
390                 LOGGER.log(Level.FINEST, line);
391                 try {
392                     WSYD_Member temp = WSYD_Member.createWSYD_Member(line);
393                     if (temp != null)
394                         _members.put(temp._userID, temp); // add new member to collection
395                 } catch (IllegalArgumentException e) {     
396                     LOGGER.log(Level.WARNING, "Ignoring bad CSV import line");
397                 }
398             }
399         }
400         catch(IOException e) {
401                 LOGGER.log(Level.SEVERE, "Unable to import CSV file {0}", fileName);
402                 e.printStackTrace();
403         }
404
405         return result;
406     }
407
408     /**
409      * Export WSYD_Members collection to a CSV file.
410      * 
411      * @param fileName name of the CSV file to write
412      * @return true if successful
413      */
414     @SuppressWarnings("CallToPrintStackTrace")
415     public boolean exportCSV(String fileName) {
416         boolean result = false;
417
418         try (
419             FileOutputStream fos = new FileOutputStream(fileName);
420             OutputStreamWriter osw = new OutputStreamWriter(fos, "utf-8");
421             BufferedWriter bw = new BufferedWriter(osw);
422         )
423         {
424             bw.write("# 0     , 1       , 2       , 3              , 4  , 5        , 6        , 7      , 8                  , 9");
425             bw.write("# userID, userName, password, currentLocation, bio, birthDate, interests, friends, friendsRequestsSent, friendsRequestsReceived");
426             for (Map.Entry<Long, WSYD_Member> e: _members.entrySet()) {
427                 bw.write(e.getKey() + ": " + e.getValue().toString());
428             }
429         }
430         catch(IOException e) {
431             LOGGER.log(Level.SEVERE, "Unable to export to CSV file {0}", fileName);
432             e.printStackTrace();
433         }
434
435         return result;
436     }
437     
438     @Override
439     public void NetworkMessageReceived(NetworkMessageEvent event)
440     {
441         //TODO: NetworkMessageReceived: Handle Messages
442         NetworkMessage nm = event.getNetworkMessage();
443         if (nm == null)
444             return;
445         //Exit or Restart?
446         System.err.println("Packet Received for intent " + nm.getIntent());
447         if ("Control".equals(nm.getIntent()))
448         {
449             String type = nm.getMessage().getMessageType();
450             if (type.equals(MessageServerControl.getType())) { // ServerControl
451                 if ("ServerManagement".equals(nm.getSender())) {
452                     MessageServerControl mp = (MessageServerControl)nm.getMessage();
453                     if (mp.exitReq == MessageServerControl.EXIT.YES) ServerSocial.exitRequested = true;
454                     if (mp.restartReq == MessageServerControl.RESTART.YES) ServerSocial.restartRequested = true;
455                 }
456             }
457         }
458     }
459
460     /**
461      * Entry point which starts, restarts, and exits the application.
462      * 
463      * @param args the command line arguments
464      * @throws java.lang.InterruptedException
465      */
466     public static void main(String[] args) throws InterruptedException {
467         while (!ServerSocial.exitRequested && ServerSocial.restartRequested) {
468             ServerSocial app = new ServerSocial().InitListeners();
469             ServerSocial.restartRequested = false;
470             if (!app.start()) {
471                 System.err.println("Encountered error running Social Server");
472                 break; // leave the while loop
473             }
474         }
475     }
476 }
477     
478