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