9f61713646242c70b73264bdafcae76d329e5412
[aaf/authz.git] / cadi / aaf / src / main / java / org / onap / aaf / cadi / aaf / v2_0 / AbsAAFLocator.java
1 /**
2  * ============LICENSE_START====================================================
3  * org.onap.aaf
4  * ===========================================================================
5  * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved.
6  * ===========================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  * 
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  * 
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END====================================================
19  *
20  */
21
22 package org.onap.aaf.cadi.aaf.v2_0;
23
24 import java.net.URI;
25 import java.net.URISyntaxException;
26 import java.security.SecureRandom;
27 import java.util.ArrayList;
28 import java.util.Iterator;
29 import java.util.LinkedList;
30 import java.util.List;
31 import java.util.NoSuchElementException;
32
33 import org.onap.aaf.cadi.Access;
34 import org.onap.aaf.cadi.Access.Level;
35 import org.onap.aaf.cadi.Locator;
36 import org.onap.aaf.cadi.LocatorException;
37 import org.onap.aaf.cadi.aaf.Defaults;
38 import org.onap.aaf.cadi.config.Config;
39 import org.onap.aaf.cadi.routing.GreatCircle;
40 import org.onap.aaf.misc.env.Trans;
41 import org.onap.aaf.misc.env.util.Split;
42
43 import locate.v1_0.Endpoint;
44
45 public abstract class AbsAAFLocator<TRANS extends Trans> implements Locator<URI> {
46     protected static final SecureRandom sr = new SecureRandom();
47     private static LocatorCreator locatorCreator;
48     protected final Access access;
49
50     protected final double latitude;
51     protected final double longitude;
52     protected List<EP> epList;
53     protected final String name, version;
54     private String pathInfo = null;
55     private String query = null;
56     private String fragment = null;
57     private boolean additional = false;
58     protected String myhostname;
59     protected int myport;
60     protected final String aaf_locator_host;
61     protected final URI aaf_locator_uri;
62     private long earliest;
63     private final long refreshWait;
64
65
66     public AbsAAFLocator(Access access, String name, final long refreshMin) throws LocatorException {
67         aaf_locator_host = access.getProperty(Config.AAF_LOCATE_URL, null);
68         if (aaf_locator_host==null) {
69             aaf_locator_uri = null;
70         } else {
71             try {
72                 aaf_locator_uri = new URI(aaf_locator_host);
73             } catch (URISyntaxException e) {
74                 throw new LocatorException(e);
75             }
76         }
77
78         epList = new LinkedList<>();
79         refreshWait = refreshMin;
80
81         this.access = access;
82         String lat = access.getProperty(Config.CADI_LATITUDE,null);
83         String lng = access.getProperty(Config.CADI_LONGITUDE,null);
84         if (lat==null || lng==null) {
85             throw new LocatorException(Config.CADI_LATITUDE + " and " + Config.CADI_LONGITUDE + " properties are required.");
86         } else {
87             latitude = Double.parseDouble(lat);
88             longitude = Double.parseDouble(lng);
89         }
90         if (name.startsWith(Defaults.AAF_NS)) {
91             String root_ns = access.getProperty(Config.AAF_ROOT_NS, null);
92             if (root_ns!=null) {
93                 name=name.replace(Defaults.AAF_NS, root_ns);
94             }
95         }
96         if (name.startsWith("http")) { // simple URL
97             this.name = name;
98             this.version = access.getProperty(Config.AAF_API_VERSION,Config.AAF_DEFAULT_API_VERSION);
99         } else {
100             String[] split = Split.split(':', name);
101             this.name = split[0];
102             this.version = (split.length > 1) ? split[1] : access.getProperty(Config.AAF_API_VERSION,Config.AAF_DEFAULT_API_VERSION);
103         }
104         
105     }
106
107     /**
108      * This is the way to setup specialized AAFLocators ahead of time.
109      * @param preload
110      */
111     public static void setCreator(LocatorCreator lc) {
112         locatorCreator = lc; 
113     }
114     
115     /*public static Locator<URI> create(String key) throws LocatorException {
116         String name = null;
117         String version = Config.AAF_DEFAULT_API_VERSION;
118         String pathInfo = null;
119         int prev = key.indexOf("/locate");
120         if (prev>0) {
121             prev = key.indexOf('/',prev+6);
122             if (prev>0) {
123                 int next = key.indexOf('/',++prev);
124                 if (next>0) {
125                     name = key.substring(prev, next);
126                     pathInfo=key.substring(next);
127                 } else {
128                     name = key.substring(prev);
129                 }
130                 String[] split = Split.split(':', name);
131                 switch(split.length) {
132                     case 3:
133                     case 2:
134                         version = split[1];
135                         name = split[0];
136                         break;
137                     default:
138                         break;
139                 }
140             }
141         }
142
143         if (key.startsWith("http")) {
144             if (name!=null) {
145                 if (locatorCreator != null) {
146                     AbsAAFLocator<?> aal = locatorCreator.create(name, version);
147                     if (pathInfo!=null) {
148                         aal.setPathInfo(pathInfo);
149                     }
150                     return aal;
151                 }
152             } else {
153                 return new PropertyLocator(key);
154             }
155         }
156         return null;
157     }
158     */
159     
160     public static Locator<URI> create(final String name, final String version) throws LocatorException {
161         return locatorCreator.create(name, version);
162     }
163
164     public interface LocatorCreator {
165         public AbsAAFLocator<?> create(String key, String version) throws LocatorException;
166         public void setSelf(String hostname, int port);
167     }
168
169     protected static String nameFromLocatorURI(URI locatorURI) {
170         String[] path = Split.split('/', locatorURI.getPath());
171         if (path.length>1 && "locate".equals(path[1])) {
172            return path[2];
173         } else if(path.length>1) {
174                 return path[1];
175         } else {
176             return locatorURI.toString();
177         }
178     }
179     
180     /**
181      * Setting "self" excludes this service from the list.  Critical for contacting peers. 
182      */
183     public void setSelf(final String hostname, final int port) {
184         myhostname=hostname;
185         myport=port;
186     }
187
188
189     public static void setCreatorSelf(final String hostname, final int port) {
190         if (locatorCreator!=null) {
191             locatorCreator.setSelf(hostname,port);
192         }
193     }
194
195     protected final synchronized void replace(List<EP> list) {
196         epList = list;
197     }
198     
199     /**
200      * Call _refresh as needed during calls, but actual refresh will not occur if there
201      * are existing entities or if it has been called in the last 10 (settable) seconds.  
202      * Timed Refreshes happen by Scheduled Thread
203      */
204     private final boolean _refresh() {
205         boolean rv = false;
206         long now=System.currentTimeMillis();
207         if (noEntries()) {
208             if (earliest<now) {
209                 synchronized(epList) {
210                     rv = refresh();
211                     earliest = now + refreshWait; // call only up to 10 seconds.
212                 }
213             } else {
214                 access.log(Level.ERROR, "Must wait at least " + refreshWait/1000 + " seconds for Locator Refresh");
215             }
216         }
217         return rv;
218     }
219
220     private boolean noEntries() {
221         return epList.isEmpty();
222     }
223
224     @Override
225     public URI get(Item item) throws LocatorException {
226         if (item==null) {
227             return null;
228         } else if (item instanceof AAFLItem) {
229             return getURI(((AAFLItem)item).uri);
230         } else {
231             throw new LocatorException(item.getClass().getName() + " does not belong to AAFLocator");
232         }
233     }
234
235     @Override
236     public boolean hasItems() {
237         boolean isEmpty = epList.isEmpty();
238         if (!isEmpty) {
239             for (Iterator<EP> iter = epList.iterator(); iter.hasNext(); ) {
240                 EP ep = iter.next();
241                 if (ep.valid) {
242                     return true;
243                 }
244             }
245             isEmpty = true;
246         }
247         if (_refresh()) { // is refreshed... check again
248             isEmpty = epList.isEmpty();
249         }
250         return !isEmpty;
251     }
252
253     @Override
254     public void invalidate(Item item) throws LocatorException {
255         if (item!=null) {
256             if (item instanceof AAFLItem) {
257                 AAFLItem ali =(AAFLItem)item; 
258                 EP ep = ali.ep;
259                 synchronized(epList) {
260                     epList.remove(ep);
261                 }
262                 ep.invalid();
263                 ali.iter = getIterator(); // for next guy... fresh iterator
264             } else {
265                 throw new LocatorException(item.getClass().getName() + " does not belong to AAFLocator");
266             }
267         }
268     }
269
270     @Override
271     public Item best() throws LocatorException {
272         if (!hasItems()) {
273             throw new LocatorException("No Entries found for '" + aaf_locator_uri.toString() + "/locate/" + name + ':' + version + '\'');
274         }
275         List<EP> lep = new ArrayList<>();
276         EP first = null;
277         // Note: Deque is sorted on the way by closest distance
278         Iterator<EP> iter = getIterator();
279         EP ep;
280         while (iter.hasNext()) {
281             ep = iter.next();
282             if (ep.valid) {
283                 if (first==null) {
284                     first = ep;
285                     lep.add(first);
286                 } else {
287                     if (Math.abs(ep.distance-first.distance)<.1) { // allow for nearby/precision issues.
288                         lep.add(ep);
289                     } else {
290                         break;
291                     }
292                 }
293             }
294         }
295         switch(lep.size()) {
296             case 0:
297                 return null;
298             case 1:
299                 return new AAFLItem(iter,first);
300             default:
301                 int rand = sr.nextInt(); // Sonar chokes without.
302                 int i = Math.abs(rand)%lep.size();
303                 if (i<0) {
304                     return null;
305                 } else {
306                     return new AAFLItem(iter,lep.get(i));
307                 }
308             
309         }
310     }
311
312     private Iterator<EP> getIterator() {
313         Object[] epa = epList.toArray();
314         if (epa.length==0) {
315             _refresh();
316             epa = epList.toArray();
317         }
318         return new EPIterator(epa, epList);
319     }
320
321     public class EPIterator implements Iterator<EP> {
322         private final Object[] epa;
323         private final List<EP> epList;
324         private int idx;
325         
326         public EPIterator(Object[] epa, List<EP> epList) {
327             this.epa = epa;
328             this.epList = epList;
329             idx = epa.length>0?0:-1;
330         }
331
332         @Override
333         public boolean hasNext() {
334             if (idx<0) {
335                 return false;
336             } else {
337                 Object obj;
338                 while (idx<epa.length) {
339                     if ((obj=epa[idx])==null || !((EP)obj).valid) {
340                         ++idx;
341                         continue;
342                     }
343                     break;
344                 }
345                 return idx<epa.length;
346             }
347         }
348
349         @Override
350         public EP next() {
351             if (!hasNext() ) {
352                 throw new NoSuchElementException();
353             }
354             return (EP)epa[idx++];
355         }
356
357         @Override
358         public void remove() {
359             if (idx>=0 && idx<epa.length) {
360                 synchronized(epList) {
361                     epList.remove(epa[idx]);
362                 }
363             }
364         }
365     }
366     
367     @Override
368     public Item first()  {
369         Iterator<EP> iter = getIterator();
370         EP ep = AAFLItem.next(iter);
371         if (ep==null) {
372             return null;
373         }
374         return new AAFLItem(iter,ep);
375     }
376
377     @Override
378     public Item next(Item prev) throws LocatorException {
379         if (prev==null) {
380             StringBuilder sb = new StringBuilder("Locator Item passed in next(item) is null.");
381             int lines = 0;
382             for (StackTraceElement st : Thread.currentThread().getStackTrace()) {
383                 sb.append("\n\t");
384                 sb.append(st.toString());
385                 if (++lines > 5) {
386                     sb.append("\n\t...");
387                     break;
388                 }
389             }
390             access.log(Level.ERROR, sb);
391         } else {
392             if (prev instanceof AAFLItem) {
393                 AAFLItem ali = (AAFLItem)prev;
394                 EP ep = AAFLItem.next(ali.iter);
395                 if (ep!=null) {
396                     return new AAFLItem(ali.iter,ep);
397                 }
398             } else {
399                 throw new LocatorException(prev.getClass().getName() + " does not belong to AAFLocator");
400             }
401         }
402         return null;
403     }
404
405     protected static class AAFLItem implements Item {
406             private Iterator<EP> iter;
407             private URI uri;
408             private EP ep;
409     
410             public AAFLItem(Iterator<EP> iter, EP ep) {
411                 this.iter = iter;
412                 this.ep = ep;
413                 uri = ep.uri;
414             }
415             
416             private static EP next(Iterator<EP> iter) {
417                 EP ep=null;
418                 while (iter.hasNext() && (ep==null || !ep.valid)) {
419                     ep = iter.next();
420                 }
421                 return ep;
422             }
423             
424             public String toString() {
425                 return ep==null?"Locator Item Invalid":ep.toString();
426             }
427         }
428
429     protected static class EP implements Comparable<EP> {
430         private URI uri;
431         private final double distance;
432         private boolean valid;
433         
434         public EP(final Endpoint ep, double latitude, double longitude) throws URISyntaxException {
435             uri = new URI(ep.getProtocol(),null,ep.getHostname(),ep.getPort(),null,null,null);
436             distance = GreatCircle.calc(latitude, longitude, ep.getLatitude(), ep.getLongitude());
437             valid = true;
438         }
439
440         public void invalid() {
441             valid = false;
442         }
443
444         @Override
445         public int compareTo(EP o) {
446             if (distance<o.distance) {
447                 return -1;
448             } else if (distance>o.distance) {
449                 return 1;
450             } else {
451                 return 0;
452             }
453         }
454         
455         @Override
456         public String toString() {
457             return distance + ": " + uri + (valid?" valid":" invalidate");
458         }
459     }
460     
461     /* (non-Javadoc)
462      * @see org.onap.aaf.cadi.Locator#destroy()
463      */
464     @Override
465     public void destroy() {
466         // Nothing to do
467     }
468     
469     @Override
470     public String toString() {
471         return "AAFLocator for " + name + " on " + getURI();
472     }
473
474     public AbsAAFLocator<TRANS> setPathInfo(String pathInfo) {
475         this.pathInfo = pathInfo;
476         additional=true;
477         return this;
478     }
479
480     public AbsAAFLocator<TRANS> setQuery(String query) {
481         this.query = query;
482         additional=true;
483         return this;
484     }
485
486     public AbsAAFLocator<TRANS>  setFragment(String fragment) {
487         this.fragment = fragment;
488         additional=true;
489         return this;
490     }
491
492     // Core URI, for reporting purposes
493     protected abstract URI getURI();
494
495     protected URI getURI(URI rv) throws LocatorException {
496         if (additional) {
497             try {
498                 return new URI(rv.getScheme(),rv.getUserInfo(),rv.getHost(),rv.getPort(),pathInfo,query,fragment);
499             } catch (URISyntaxException e) {
500                 throw new LocatorException("Error copying URL", e);
501             }
502         }
503         return rv;
504     }
505
506
507 }