ServiceAddressMap: improve public API
[WeStealzYourDataz.git] / src / uk / ac / ntu / n0521366 / wsyd / libs / net / ServiceAddressMap.java
1 /*
2  * The MIT License
3  *
4  * Copyright 2015 TJ <hacker@iamtj>.
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.net.InetSocketAddress;
27 import java.text.MessageFormat;
28 import java.util.ArrayList;
29 import java.util.concurrent.ConcurrentHashMap;
30 import java.util.logging.Level;
31 import java.util.logging.Logger;
32
33 /**
34  * Maps service names on hosts to  an InetAddress:Port pair.
35  * 
36  * @author TJ <hacker@iamtj>
37  */
38 public class ServiceAddressMap {
39     /**
40      * Multi-thread safe map keyed on the service name.
41      */
42     protected ConcurrentHashMap<String, LastSeenHost> _serviceToAddressMap = new ConcurrentHashMap<>();
43
44     /**
45      * Encapsulates a unique network host and the last time it was seen.
46      */
47     public static class LastSeenHost {
48         /**
49          * State of a record:
50          * 
51          * STATIC = was manually entered and should not be removed
52          * DYNAMIC = was discovered via multicast announcement or new connection and can be removed
53          */
54         public static enum STATE {STATIC, DYNAMIC}
55         
56         final STATE state;
57         final long timeInMillis;
58         final InetSocketAddress address;
59         
60         /**
61          * Constructs a new instance of a host.
62          * 
63          * @param address The current address:port
64          * @param timeInMillis time last seen
65          * @param state whether record was added through dynamic discovery
66          */
67         public LastSeenHost(InetSocketAddress address, long timeInMillis, STATE state) {
68             this.address = address;
69             this.timeInMillis = timeInMillis;
70             this.state = state;
71         }
72         
73         /**
74          * Construct a new instance of a dynamically announced host.
75          * 
76          * @param host 
77          */
78         public LastSeenHost(InetSocketAddress host) {
79             this(host, System.currentTimeMillis(), STATE.DYNAMIC);
80         }
81         
82         /**
83          * Construct a new instance of with user-defined STATE.
84          * 
85          * @param host 
86          */
87         public LastSeenHost(InetSocketAddress host, STATE state) {
88             this(host, System.currentTimeMillis(), state);
89         }
90         /**
91          * Formatted string representation of InetAddress and timestamp.
92          * @return the representation
93          */
94         @Override
95         public String toString() {
96             return MessageFormat.format("{0}:{1,number,integer}@{2}", this.address.getHostString(), this.address.getPort(), this.timeInMillis);
97         }
98     };
99
100     /**
101      * The Logger to use.
102      */
103     private static Logger LOGGER = null;
104     
105     /**
106      * Facility name for logger
107      */
108     private String _facility = null;
109     
110     /**
111      * Construct a map with a Logger.
112      * 
113      * @param facility The log facility to tag messages with
114      * @param logger  The Logger
115      */
116     public ServiceAddressMap(String facility, Logger logger) {
117         this._facility = facility;
118         LOGGER = logger;
119     }
120
121     /**
122      * Log some message.
123      * @param level
124      * @param message 
125      */
126     private void log(Level level, String message) {
127         if (LOGGER == null)
128             return;
129         LOGGER.logp(level, _facility, null, message);
130         
131     }
132     /**
133      * Remove stale service records.
134      * 
135      * @param ageInMillis milliseconds since last seen to be considered stale
136      * @return quantity of records removed
137      */
138     public ArrayList<String> cleanServiceAddressMap(long ageInMillis) {
139         ArrayList<String> result = new ArrayList<>();
140         long expireTime = System.currentTimeMillis() - ageInMillis;
141         java.util.Enumeration<String> keys = this._serviceToAddressMap.keys();
142         while (keys.hasMoreElements()) {
143             String key = keys.nextElement();
144             LastSeenHost host = _serviceToAddressMap.get(key);
145             if (host != null && host.state == LastSeenHost.STATE.DYNAMIC) {
146                 if (host.timeInMillis < expireTime) {
147                     if (_serviceToAddressMap.remove(key, host)) {
148                         result.add(key);
149                         log(Level.INFO, MessageFormat.format("Removed \"{0}\" from service map", key));
150                     }
151                 }
152             }
153         }
154         return result;
155     }
156
157     /**
158      * Ensure service is in the map of known hosts.
159      * @param service the service name to check
160      * @return true is the target service is known
161      */
162     protected boolean isServiceValid(String service) {
163         return this._serviceToAddressMap.containsKey(service);
164     }
165
166     /**
167      * Get the current InetSocketAddress of a target.
168      * 
169      * @param target name of the service
170      * @return IP address and port of the service
171      */
172     public InetSocketAddress getServiceAddress(String target) {
173         InetSocketAddress result = null;
174         
175         if (target != null && target.length() > 0) {
176             LastSeenHost host = this._serviceToAddressMap.get(target);
177             if (host != null)
178                 result = host.address;
179         }
180         
181         return result;
182     }
183     
184     /**
185      * Add an entry to the known services list.
186      * @param service Service name at this unique host IP address/port combination
187      * @param host new host record
188      * @return Host record previously associated with this service, or null if key didn't exist
189      */
190     public LastSeenHost put(String service, LastSeenHost host) {
191         return this._serviceToAddressMap.put(service, host);
192     }
193     
194     /**
195      * Get the host record associated with a service.
196      * @param service Service name required
197      * @return Host record associated with this service, or null if service isn't known
198      */
199     public LastSeenHost get(String service) {
200         return this._serviceToAddressMap.get(service);
201     }
202     
203     /**
204      * 
205      * @param service
206      * @return 
207      */
208     public boolean containsService(String service) {
209         return this._serviceToAddressMap.containsKey(service);
210     }
211 }