Add Registration functionality and tidy up
[WeStealzYourDataz.git] / src / uk / ac / ntu / n0521366 / wsyd / libs / net / NetworkServerAbstract.java
1 /*
2  * The MIT License
3  *
4  * Copyright 2015 Eddie Berrisford-Lynch <dev@fun2be.me>.
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.libs.net;
25
26 import java.text.MessageFormat;
27 import java.net.SocketException;
28 import java.util.concurrent.ConcurrentLinkedQueue;
29 import java.util.ArrayList;
30 import java.util.List;
31 import java.util.logging.Logger;
32 import java.util.logging.Level;
33 import javax.swing.SwingWorker;
34
35 /**
36  * Abstract dual-use multithreading network server that can be used stand-alone
37  * or in a Swing GUI application as a background worker thread.
38  * 
39  * Concrete classes are required to implement the Socket-specific functionality.
40  * 
41  * The arguments to the Generics superclass SwingWorker<T, V> are:
42  * 
43  *  < return-TYPE-of doInBackground(), publish(parameter-TYPE) >
44  * 
45  * Here doInBackground() returns an Integer connection counter and publish() takes
46  * a NetworkMessage type.
47  *
48  * Server sockets block in the operating system kernel waiting
49  * for connections or incoming packets.
50  *
51  * SwingWorker objects avoid using the GUI event dispatcher thread. Without that the
52  * user interface could be unresponsive for considerable periods whilst server
53  * sockets wait for incoming connections via the blocking in
54  * ServerSocket.accept() (TCP) or DatagramSocket.receive() (UDP) method.
55  *
56  * This design combines the multithreading support of the java.lang.Runnable
57  * interface with the javax.swing.SwingWorker inheritance so that this single class
58  * can be used in non-GUI daemon services and GUI applications, avoiding the need
59  * to write the same server code in more than one class.
60  * 
61  * The server registers NetworkMessageEventListener objects and notifies them
62  * when a new NetworkMessage has been received.
63  * 
64  * @see javax.swing.SwingWorker
65  * 
66  * @author Eddie Berrisford-Lynch <dev@fun2be.me>
67  */
68 public abstract class NetworkServerAbstract extends SwingWorker<Integer, NetworkMessage> {
69
70     /**
71      * Single Logger for the class used by all object instances.
72      * 
73      * Can be instantiated once by objects of any sub-class.
74      */
75     @SuppressWarnings("NonConstantLogger")
76     protected static Logger LOGGER = null;
77
78     /**
79      * Inject simulated received NetworkMessages.
80      * 
81      * A helpful tool for debugging.
82      */
83     protected boolean _simulate = false;
84
85     /**
86      * Count of packets or connections received.
87      */
88     int _connectionCount;
89
90     /**
91      * Service name for this server instance.
92      * 
93      * E.g. "ServerSocial", "ServerChat", "ServerControl", "ClientControl", "ClientChat", "ServerLog"
94      */
95     String _title;
96
97     /**
98      * Socket parameters for this server.
99      */
100     WSYD_SocketAddress _socketAddress;
101
102     protected ServiceAddressMap _serviceToHostMap;
103
104     private final NetworkMessageEventListenerManager _eventManager;
105     /**
106      * 
107      * @param level message importance
108      * @param title source identifier
109      * @param formatter parameter Formatter for log message
110      * @param parameters variable length list of replaceable parameters for formatter
111      */
112     protected static void log(Level level, String title, String formatter, ArrayList<String> parameters) {
113         if (LOGGER == null)
114             return;
115         // formatter = "{" + Integer.toString(parameters.size()) + "}: " + formatter;
116         // parameters.add(title);
117         LOGGER.logp(level, title, null, MessageFormat.format(formatter, parameters.toArray()));
118     }
119     /**
120      * 
121      * @param level message importance
122      * @param title source identifier
123      * @param message the log entry
124      */
125     protected static void log(Level level, String title, String message) {
126         if (LOGGER == null)
127             return;
128         LOGGER.logp(level, title, null, message);
129     }
130
131     /**
132      * Set the log level for the server
133      * @param level a new log level
134      * @return the old log level
135      */
136     public Level setLogLevel(Level level) {
137         Level result = Level.OFF;
138         if (LOGGER != null) {
139             Level temp = LOGGER.getLevel();
140             LOGGER.setLevel(level);
141             result = temp;
142         }
143         return result;
144     }
145
146     /**
147      * Default constructor.
148      */
149     NetworkServerAbstract() {
150         this._connectionCount = 0;
151         this._title = null;
152         this._socketAddress = null;
153         this._serviceToHostMap = null;
154         this._eventManager = null;
155     }
156     
157     /**
158      * Construct the server with a Logger.
159      * 
160      * No socket is opened.
161      * 
162      * @param socketAddress The socket to listen on
163      * @param title source identifier for use in log messages and sent NetworkMessage objects
164      * @param serviceToHostMap the map object used for host <> InetSocketAddress lookups
165      * @param logger An instance of Logger to be used by all objects of this class
166      */
167     public NetworkServerAbstract(WSYD_SocketAddress socketAddress, String title, ServiceAddressMap serviceToHostMap, Logger logger) {
168         this._connectionCount = 0;
169         this._title = title;
170         this._socketAddress = socketAddress;
171         this._serviceToHostMap = serviceToHostMap;
172         this._eventManager = new NetworkMessageEventListenerManager();
173         if (LOGGER == null) // do not replace existing logger reference
174             LOGGER = logger;
175     }
176
177     /**
178      * Construct the server without a Logger.
179      * 
180      * No socket is opened.
181      * 
182      * @param socketAddress The socket to listen on
183      * @param title source identifier for use in log messages and sent NetworkMessage objects
184      * @param serviceToHostMap the map object used for host <> InetSocketAddress lookups
185      */
186     public NetworkServerAbstract(WSYD_SocketAddress socketAddress, String title, ServiceAddressMap serviceToHostMap) {
187         this(socketAddress, title, serviceToHostMap, null);
188     }
189
190     /**
191      * Get the socket in use - not that (possibly wildcard/ephermeral) requested.
192      * 
193      * @return the port being used
194      */
195     public WSYD_SocketAddress getSocketAddress() {
196         return _socketAddress;
197     }
198
199     /**
200      * Enable or disable simulated received packet injection.
201      * 
202      * @param simulate true to simulate received messages
203      */
204     public void setSimulate(boolean simulate) {
205         this._simulate = simulate;
206     }
207
208     /**
209      * Get the simulation state.
210      * 
211      * @return true if simulation is enabled.
212      */
213     public boolean getSimulate() {
214         return this._simulate;
215     }
216
217
218     /* XXX: The following Methods execute on the background Worker Thread */
219     
220     /**
221      * The primary SwingWorker method, started on the Worker Thread when the Owner
222      * Thread calls execute().
223      * 
224      * Loops until isCancelled() == true. Within the loop calls serverListen() to
225      * allow reception of one packet or connection and if so counts it.
226      * Then  it checks if there are any messages to be sent out and if so calls
227      * serverSend().
228      * 
229      * @return the number of connections accepted
230      */
231     @Override
232     public Integer doInBackground() {
233         ArrayList<String> logMessages = new ArrayList<>();
234         try {
235             logMessages.add(_socketAddress.toString());
236             log(Level.INFO, _title, "Opening socket {0}", logMessages);
237             this.serverOpen();
238         }
239         catch(SocketException e) {
240             logMessages.clear();
241             logMessages.add(_socketAddress.getAddress().toString());
242             logMessages.add(Integer.toString(_socketAddress.getPort()));
243             logMessages.add(_socketAddress.getProtocol().toString());
244             log(Level.SEVERE, _title, "{0}: Unable to open socket on {1}:{2} {3}", logMessages);
245         }
246         
247         // unless cancelled keep waiting for new packets or connections
248         while (!this.isCancelled()) {
249             if (this.serverListen())
250                 this._connectionCount++;
251
252             // send a queued message
253             NetworkMessage temp =  this.sendMessage();
254             if (temp != null) {
255                 if (!this.serverSend(temp)) {
256                     logMessages.clear();
257                     logMessages.add(temp.getSender());
258                     logMessages.add(temp.getTarget());
259                     log(Level.WARNING, _title, "Unable to send message from {0} to {1}", logMessages);
260                 }
261             }
262         }
263      
264         try {
265             logMessages.clear();
266             logMessages.add(_socketAddress.toString());
267             log(Level.INFO, _title, "Closing socket {0}", logMessages);
268             this.serverClose();
269         }
270         catch(SocketException e) {
271             logMessages.clear();
272             logMessages.add(_socketAddress.getAddress().toString());
273             logMessages.add(Integer.toString(_socketAddress.getPort()));
274             logMessages.add(_socketAddress.getProtocol().toString());
275             log(Level.SEVERE, _title, "{0}: Unable to close socket on {1}:{2} {3}", logMessages);
276         }
277         
278         return this._connectionCount;
279     }
280
281     /**
282      * Removes a message from a queue of pending messages.
283      *
284      * This method is called on the Worker Thread by the doInBackground() main loop.
285      * 
286      * Sub-classes that have the ability to transmit messages should implement this fully.
287      *
288      * @return a message to be sent
289      */
290     protected abstract NetworkMessage sendMessage();
291     /**
292      * Open the socket ready for accepting data or connections.
293      * 
294      * It should also set a reasonable socket timeout with a call to setSoTimeout()
295      * 
296      * @see java.net.ServerSocket#setSoTimeout
297      * @see java.net.DatagramSocket#setSoTimeout
298      * @throws SocketException 
299      */
300     public abstract void serverOpen() throws SocketException;
301     
302     /**
303      * Close the socket.
304      * 
305      * @throws SocketException
306      */
307     public abstract void serverClose() throws SocketException;
308     
309     /**
310      * Send an unsolicited message to a remote service.
311      * 
312      * This method is called by the main worker loop if there is a message to
313      * be sent.
314      * 
315      * @param message must have its _serviceTarget parameter set
316      * @return true if the message was sent
317      */
318     protected abstract boolean serverSend(NetworkMessage message);
319
320     /**
321      * Accept packet or connection from remote hosts.
322      * 
323      * This method must wait for a single incoming connection or packet, process it,
324      * and then publish() it for consumption by process().
325      * 
326      * It must add newly seen remote service names to _serviceToHostMap so that
327      * methods on the Owner Thread can discover the destination service titles
328      * they can use in new NetworkMessage submissions.
329      * 
330      * @return true if the server should continue listening
331      */
332     public abstract boolean serverListen();
333
334     /* XXX: Methods below here all execute on the GUI Event Dispatch Thread */
335
336
337     public NetworkMessageEventListenerManager getEventManager() {
338         return this._eventManager;
339     }
340     
341     /**
342      * Fetch messages received by the server.
343      * 
344      * For delivery to event listeners; usually Swing GUI components. This method will run on the
345      * Owner Thread so must complete quickly as that is the GUI Event Dispatch Thread.
346      * 
347      * @param list messages received and queued
348      */
349     @Override
350     protected void process(List<NetworkMessage> list) {
351         for (NetworkMessage message: list) {
352             this._eventManager.fireNetworkMessageEvent(message);
353         }
354     }
355
356     /**
357      * Clean up after doInBackground() has returned.
358      * 
359      * This method will run on the GUI Event Dispatch Thread so must complete quickly.
360      */
361     @Override
362     protected abstract void done();
363 }