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