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