4 * Copyright 2015 TJ <hacker@iam.tj>.
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:
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
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
24 package uk.ac.ntu.n0521366.wsyd.server;
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;
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;
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;
53 * The main Social Network server.
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.
59 * @author TJ <hacker@iam.tj>
61 public final class ServerSocial {
63 * Persistent storage in file-system when server exits.
65 static final String _membersFile = "WSYD_Members.serialized";
70 * If it exists in the file system, only used if there is no _membersFile
72 static final String _testData = "WSYD_TestData.csv";
75 * Indicates to start() loop and main() methods to exit completely.
77 public static boolean exitRequested = false;
80 * Indicates to start() loop to exit, and to main() to restart the server.
82 public static boolean restartRequested = true;
85 * Handles display and sending of log messages.
87 @SuppressWarnings("NonConstantLogger")
88 private static Logger LOGGER;
91 * SortedMap wraps a TreeMap that has been made thread-safe by
92 * Collections.synchronizedSortedMap() in readMembers().
94 * Long key, the userID
95 * WSYD_Member member record
97 SortedMap<Long, WSYD_Member> _members;
100 * userIDs of members currently logged in
102 ArrayList<Long> _membersOnline;
106 * Default constructor.
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<>();
117 * Main execution loop of the server
119 * @return true if no errors encountered
120 * @throws java.lang.InterruptedException
122 @SuppressWarnings("SleepWhileInLoop")
123 public boolean start() throws InterruptedException {
126 // TODO: start() create TCP listener
127 // TODO: start() create UDP Multicast group listener and broadcast adverts
128 // wait for connections
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;
136 result = writeMembers(_membersFile);
142 * Deserialize the collection of WSYD_Members from file
144 * @param fileName serialized members data file
145 * @return true if successfully deserialized
147 @SuppressWarnings("CallToPrintStackTrace")
148 public boolean readMembers(String fileName) {
149 boolean result = false;
152 FileInputStream f = new FileInputStream(fileName);
153 ObjectInputStream in = new ObjectInputStream(f);
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.
161 _members = Collections.synchronizedSortedMap( new TreeMap<Long, WSYD_Member>() );
162 if (!_members.isEmpty())
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.
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());
177 LOGGER.log(Level.INFO, "Members database read from {0}", fileName);
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);
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);
193 catch(IOException e) {
194 LOGGER.log(Level.SEVERE, "Unable to read database file {0}", fileName);
197 catch(ClassNotFoundException e) {
198 LOGGER.log(Level.SEVERE, "Unable to deserialize database file {0}", fileName);
206 * Serialize the WSYD_Members collection to a file
208 * @param fileName database file
209 * @return true if collection was successfully serialized
211 @SuppressWarnings("CallToPrintStackTrace")
212 public boolean writeMembers(String fileName) {
213 boolean result = false;
215 if (!_members.isEmpty()) { // don't write an empty database
217 FileOutputStream f = new FileOutputStream(fileName);
218 ObjectOutputStream out = new ObjectOutputStream(f);
221 out.writeObject(_members);
223 LOGGER.log(Level.INFO, "Members database written to {0}", fileName);
226 catch(IOException e) {
227 LOGGER.log(Level.SEVERE, "Unable to write database file {0}", fileName);
238 * Read a CSV file containing WSYD_Member records and add it to the in-memory
241 * @param fileName name of CSV file
242 * @return true if successfully imported
244 @SuppressWarnings("CallToPrintStackTrace")
245 public boolean importCSV(String fileName) {
246 boolean result = false;
249 FileInputStream fis = new FileInputStream(fileName);
250 InputStreamReader isr = new InputStreamReader(fis);
251 BufferedReader br = new BufferedReader(isr);
255 while ((line = br.readLine()) != null) {
256 LOGGER.log(Level.FINEST, line);
258 WSYD_Member temp = WSYD_Member.createWSYD_Member(line);
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");
266 catch(IOException e) {
267 LOGGER.log(Level.SEVERE, "Unable to import CSV file {0}", fileName);
275 * Export WSYD_Members collection to a CSV file.
277 * @param fileName name of the CSV file to write
278 * @return true if successful
280 @SuppressWarnings("CallToPrintStackTrace")
281 public boolean exportCSV(String fileName) {
282 boolean result = false;
285 FileOutputStream fos = new FileOutputStream(fileName);
286 OutputStreamWriter osw = new OutputStreamWriter(fos, "utf-8");
287 BufferedWriter bw = new BufferedWriter(osw);
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());
296 catch(IOException e) {
297 LOGGER.log(Level.SEVERE, "Unable to export to CSV file {0}", fileName);
305 * Entry point which starts, restarts, and exits the application.
307 * @param args the command line arguments
308 * @throws java.lang.InterruptedException
310 public static void main(String[] args) throws InterruptedException {
311 while (!ServerSocial.exitRequested && ServerSocial.restartRequested) {
312 ServerSocial app = new ServerSocial();
313 ServerSocial.restartRequested = false;
315 System.err.println("Encountered error running Social Server");
316 break; // leave the while loop