836c6d39e3c0b2f978b5176110a1bd4cc9b066a0
[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.util.Date;
27 import java.util.ArrayList;
28 import java.text.SimpleDateFormat;
29 import java.text.ParseException;
30 import java.util.TreeSet;
31 import java.util.SortedMap;
32 import java.util.TreeMap;
33 import java.util.Map;
34 import java.util.Collections;
35 import java.util.logging.Logger;
36 import java.util.logging.Level;
37 import java.io.BufferedReader;
38 import java.io.BufferedWriter;
39 import java.io.File;
40 import java.io.FileInputStream;
41 import java.io.FileOutputStream;
42 import java.io.InputStreamReader;
43 import java.io.OutputStreamWriter;
44 import java.io.ObjectInputStream;
45 import java.io.ObjectOutputStream;
46 import java.io.IOException;
47 import java.io.FileNotFoundException;
48 import java.util.Arrays;
49 import uk.ac.ntu.n0521366.wsyd.libs.WSYD_Member;
50 import uk.ac.ntu.n0521366.wsyd.libs.WSYD_Member_Comparator_UserID;
51
52 /**
53  * The main Social Network server.
54  *
55  * Can be restarted or stopped using the class static attributes
56  * exitRequested and restartRequested. This can be done by an optional
57  * Management GUI application.
58  *
59  * @author TJ <hacker@iam.tj>
60  */
61 public final class ServerSocial {
62     /**
63      * Persistent storage in file-system when server exits.
64      */
65     static final String _membersFile = "WSYD_Members.serialized";
66
67     /**
68      * CSV test data file.
69      * 
70      * If it exists in the file system, only used if there is no _membersFile
71      */
72     static final String _testData = "WSYD_TestData.csv";
73     
74     /**
75      * Indicates to start() loop and main() methods to exit completely.
76      */
77     public static boolean exitRequested = false;
78
79     /**
80      * Indicates to start() loop to exit, and to main() to restart the server.
81      */
82     public static boolean restartRequested = true;
83
84     /**
85      * Handles display and sending of log messages.
86      */
87     @SuppressWarnings("NonConstantLogger")
88     private static Logger LOGGER;
89
90     /**
91      * SortedMap wraps a TreeMap that has been made thread-safe by
92      * Collections.synchronizedSortedMap() in readMembers().
93      *
94      * Long key, the userID
95      * WSYD_Member member record
96      */
97     SortedMap<Long, WSYD_Member> _members;
98
99     /**
100      * userIDs of members currently logged in
101      */
102     ArrayList<Long> _membersOnline;
103     
104
105     /**
106      * Default constructor.
107      */
108     public ServerSocial() {
109         String[] className = this.getClass().getName().split("\\.");
110         LOGGER = Logger.getLogger(className[className.length - 1]);
111         LOGGER.setLevel(Level.ALL);
112         readMembers(_membersFile);
113         _membersOnline = new ArrayList<>();
114     }
115
116     /**
117      * Main execution loop of the server
118      *
119      * @return true if no errors encountered
120      * @throws java.lang.InterruptedException
121      */
122     @SuppressWarnings("SleepWhileInLoop")
123     public boolean start() throws InterruptedException {
124         boolean result;
125
126         // TODO: start() create TCP listener
127         // TODO: start() create UDP Multicast group listener and broadcast adverts
128         // wait for connections
129         int loopCount = 10;
130         while (!ServerSocial.exitRequested && ! ServerSocial.restartRequested) {
131             Thread.sleep(1000); // wait a second
132             System.out.println("start() loop " + loopCount);
133             if (loopCount-- == 0)
134                 ServerSocial.exitRequested = true;
135         }
136         result = writeMembers(_membersFile);
137
138         return result;
139     }
140
141     /**
142      * Deserialize the collection of WSYD_Members from file
143      *
144      * @param fileName serialized members data file
145      * @return true if successfully deserialized
146      */
147     @SuppressWarnings("CallToPrintStackTrace")
148     public boolean readMembers(String fileName) {
149         boolean result = false;
150
151         try (
152             FileInputStream f = new FileInputStream(fileName);
153             ObjectInputStream in  = new ObjectInputStream(f);
154             )
155         {
156             if (_members == null)
157                 /* XXX: do not pass a Comparator to the constructor if collection is being deserialized as one was already saved during serialization.
158                  *      If the Comparator is passed to the constructor the serialized object will 'grow' by ~17 bytes each time as multi Comparator
159                  *      objects are written each time the collection is serialized.
160                 */
161                 _members = Collections.synchronizedSortedMap( new TreeMap<Long, WSYD_Member>() );
162             if (!_members.isEmpty())
163                 _members.clear();
164             /* Need explicit cast to SortedMap for Object type returned by readObject()
165              * but this can cause an "unchecked cast" compiler warning since the compiler
166              * cannot be sure the Object returned from readObject() is really a
167              * SortedMap<Long, WSYD_Member> so need to tell the compiler that in this case
168              * we are sure it is. The following for() iteration will cause a
169              * ClassCastException if the type is not as expected.
170              */
171             @SuppressWarnings("unchecked")
172             SortedMap<Long, WSYD_Member> temp = (SortedMap<Long, WSYD_Member>) in.readObject();
173             _members = Collections.synchronizedSortedMap( temp );
174             for (Map.Entry<Long, WSYD_Member> e : _members.entrySet()) {
175                 System.out.println(e.getKey() + ": " + e.getValue().toString());
176             }
177             LOGGER.log(Level.INFO, "Members database read from {0}", fileName);
178             result = true;
179         }
180         catch(FileNotFoundException e) {
181             _members = Collections.synchronizedSortedMap( new TreeMap<Long, WSYD_Member>( new WSYD_Member_Comparator_UserID() ) );
182             LOGGER.log(Level.INFO, "Starting new members database: no database file found ({0})", fileName);
183             result = true;
184
185             // if test data CSV exists import it
186             File csv = new File(_testData);
187             if (csv.exists() && csv.isFile()) {
188                 LOGGER.log(Level.INFO, "Importing test data from {0}", _testData);
189                 importCSV(_testData);
190             }
191
192         }
193         catch(IOException e) {
194             LOGGER.log(Level.SEVERE, "Unable to read database file {0}", fileName);
195             e.printStackTrace();
196         }
197         catch(ClassNotFoundException e) {
198             LOGGER.log(Level.SEVERE, "Unable to deserialize database file {0}", fileName);
199             e.printStackTrace();
200         }
201
202         return result;
203     }
204
205     /**
206      * Serialize the WSYD_Members collection to a file
207      *
208      * @param fileName database file
209      * @return true if collection was successfully serialized
210      */
211     @SuppressWarnings("CallToPrintStackTrace")
212     public boolean writeMembers(String fileName) {
213         boolean result = false;
214
215         if (!_members.isEmpty()) { // don't write an empty database
216             try (
217                 FileOutputStream f = new FileOutputStream(fileName);
218                 ObjectOutputStream out = new ObjectOutputStream(f);
219             )
220             {
221                 out.writeObject(_members);
222
223                 LOGGER.log(Level.INFO, "Members database written to {0}", fileName);
224                 result = true;
225             }
226             catch(IOException e) {
227                 LOGGER.log(Level.SEVERE, "Unable to write database file {0}", fileName);
228                 e.printStackTrace();
229             }
230         }
231         else
232             result = true;
233
234         return result;
235     }
236
237     /**
238      * Read a CSV file containing WSYD_Member records and add it to the in-memory
239      * collection.
240      * 
241      * @param fileName name of CSV file
242      * @return true if successfully imported
243      */
244     @SuppressWarnings("CallToPrintStackTrace")
245     public boolean importCSV(String fileName) {
246         boolean result = false;
247
248         try (
249             FileInputStream fis = new FileInputStream(fileName);
250             InputStreamReader isr = new InputStreamReader(fis);
251             BufferedReader br = new BufferedReader(isr);
252         )
253         {
254             String line;
255             while ((line = br.readLine()) != null) {
256                 LOGGER.log(Level.FINEST, line);
257                 try {
258                     WSYD_Member temp = WSYD_Member.createWSYD_Member(line);
259                     if (temp != null)
260                         _members.put(temp._userID, temp); // add new member to collection
261                 } catch (IllegalArgumentException e) {     
262                     LOGGER.log(Level.WARNING, "Ignoring bad CSV import line");
263                 }
264             }
265         }
266         catch(IOException e) {
267                 LOGGER.log(Level.SEVERE, "Unable to import CSV file {0}", fileName);
268                 e.printStackTrace();
269         }
270
271         return result;
272     }
273
274     /**
275      * Export WSYD_Members collection to a CSV file.
276      * 
277      * @param fileName name of the CSV file to write
278      * @return true if successful
279      */
280     @SuppressWarnings("CallToPrintStackTrace")
281     public boolean exportCSV(String fileName) {
282         boolean result = false;
283
284         try (
285             FileOutputStream fos = new FileOutputStream(fileName);
286             OutputStreamWriter osw = new OutputStreamWriter(fos, "utf-8");
287             BufferedWriter bw = new BufferedWriter(osw);
288         )
289         {
290             bw.write("# 0     , 1       , 2       , 3              , 4  , 5        , 6        , 7      , 8                  , 9");
291             bw.write("# userID, userName, password, currentLocation, bio, birthDate, interests, friends, friendsRequestsSent, friendsRequestsReceived");
292             for (Map.Entry<Long, WSYD_Member> e: _members.entrySet()) {
293                 bw.write(e.getKey() + ": " + e.getValue().toString());
294             }
295         }
296         catch(IOException e) {
297             LOGGER.log(Level.SEVERE, "Unable to export to CSV file {0}", fileName);
298             e.printStackTrace();
299         }
300
301         return result;
302     }
303
304     /**
305      * Entry point which starts, restarts, and exits the application.
306      * 
307      * @param args the command line arguments
308      * @throws java.lang.InterruptedException
309      */
310     public static void main(String[] args) throws InterruptedException {
311         while (!ServerSocial.exitRequested && ServerSocial.restartRequested) {
312             ServerSocial app = new ServerSocial();
313             ServerSocial.restartRequested = false;
314             if (!app.start()) {
315                 System.err.println("Encountered error running Social Server");
316                 break; // leave the while loop
317             }
318         }
319     }
320 }
321     
322