06e2c62d27701b27ccb76eaf20648391707825a4
[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(String.format("No Entries found for '%s/%s:%s'",
240                     (aaf_locator_uri==null?aaf_locator_host:aaf_locator_uri.toString()),
241                     name,
242                     version));
243         }
244         List<EP> lep = new ArrayList<>();
245         EP first = null;
246         // Note: Deque is sorted on the way by closest distance
247         Iterator<EP> iter = getIterator();
248         EP ep;
249         while (iter.hasNext()) {
250             ep = iter.next();
251             if (ep.valid) {
252                 if (first==null) {
253                     first = ep;
254                     lep.add(first);
255                 } else {
256                     if (Math.abs(ep.distance-first.distance)<.1) { // allow for nearby/precision issues.
257                         lep.add(ep);
258                     } else {
259                         break;
260                     }
261                 }
262             }
263         }
264         switch(lep.size()) {
265             case 0:
266                 return null;
267             case 1:
268                 return new AAFLItem(iter,first);
269             default:
270                 int rand = sr.nextInt(); // Sonar chokes without.
271                 int i = Math.abs(rand)%lep.size();
272                 if (i<0) {
273                     return null;
274                 } else {
275                     return new AAFLItem(iter,lep.get(i));
276                 }
277
278         }
279     }
280
281     private Iterator<EP> getIterator() {
282         Object[] epa = epList.toArray();
283         if (epa.length==0) {
284             _refresh();
285             epa = epList.toArray();
286         }
287         return new EPIterator(epa, epList);
288     }
289
290     public class EPIterator implements Iterator<EP> {
291         private final Object[] epa;
292         private final List<EP> epList;
293         private int idx;
294
295         public EPIterator(Object[] epa, List<EP> epList) {
296             this.epa = epa;
297             this.epList = epList;
298             idx = epa.length>0?0:-1;
299         }
300
301         @Override
302         public boolean hasNext() {
303             if (idx<0) {
304                 return false;
305             } else {
306                 Object obj;
307                 while (idx<epa.length) {
308                     if ((obj=epa[idx])==null || !((EP)obj).valid) {
309                         ++idx;
310                         continue;
311                     }
312                     break;
313                 }
314                 return idx<epa.length;
315             }
316         }
317
318         @Override
319         public EP next() {
320             if (!hasNext() ) {
321                 throw new NoSuchElementException();
322             }
323             return (EP)epa[idx++];
324         }
325
326         @Override
327         public void remove() {
328             if (idx>=0 && idx<epa.length) {
329                 synchronized(epList) {
330                     epList.remove(epa[idx]);
331                 }
332             }
333         }
334     }
335
336     @Override
337     public Item first()  {
338         Iterator<EP> iter = getIterator();
339         EP ep = AAFLItem.next(iter);
340         if (ep==null) {
341             return null;
342         }
343         return new AAFLItem(iter,ep);
344     }
345
346     @Override
347     public Item next(Item prev) throws LocatorException {
348         if (prev==null) {
349             StringBuilder sb = new StringBuilder("Locator Item passed in next(item) is null.");
350             int lines = 0;
351             for (StackTraceElement st : Thread.currentThread().getStackTrace()) {
352                 sb.append("\n\t");
353                 sb.append(st.toString());
354                 if (++lines > 5) {
355                     sb.append("\n\t...");
356                     break;
357                 }
358             }
359             access.log(Level.ERROR, sb);
360         } else {
361             if (prev instanceof AAFLItem) {
362                 AAFLItem ali = (AAFLItem)prev;
363                 EP ep = AAFLItem.next(ali.iter);
364                 if (ep!=null) {
365                     return new AAFLItem(ali.iter,ep);
366                 }
367             } else {
368                 throw new LocatorException(prev.getClass().getName() + " does not belong to AAFLocator");
369             }
370         }
371         return null;
372     }
373
374     protected static class AAFLItem implements Item {
375             private Iterator<EP> iter;
376             private URI uri;
377             private EP ep;
378
379             public AAFLItem(Iterator<EP> iter, EP ep) {
380                 this.iter = iter;
381                 this.ep = ep;
382                 uri = ep.uri;
383             }
384
385             private static EP next(Iterator<EP> iter) {
386                 EP ep=null;
387                 while (iter.hasNext() && (ep==null || !ep.valid)) {
388                     ep = iter.next();
389                 }
390                 return ep;
391             }
392
393             public String toString() {
394                 return ep==null?"Locator Item Invalid":ep.toString();
395             }
396         }
397
398     protected static class EP implements Comparable<EP> {
399         private URI uri;
400         private final double distance;
401         private boolean valid;
402
403         public EP(final Endpoint ep, double latitude, double longitude) throws URISyntaxException {
404             uri = new URI(ep.getProtocol(),null,ep.getHostname(),ep.getPort(),null,null,null);
405             distance = GreatCircle.calc(latitude, longitude, ep.getLatitude(), ep.getLongitude());
406             valid = true;
407         }
408
409         public void invalid() {
410             valid = false;
411         }
412
413         @Override
414         public int compareTo(EP o) {
415             if (distance<o.distance) {
416                 return -1;
417             } else if (distance>o.distance) {
418                 return 1;
419             } else {
420                 return 0;
421             }
422         }
423
424         @Override
425         public String toString() {
426             return distance + ": " + uri + (valid?" valid":" invalidate");
427         }
428     }
429
430     /* (non-Javadoc)
431      * @see org.onap.aaf.cadi.Locator#destroy()
432      */
433     @Override
434     public void destroy() {
435         // Nothing to do
436     }
437
438     @Override
439     public String toString() {
440         return "AAFLocator for " + name + " on " + getURI();
441     }
442
443     public AbsAAFLocator<TRANS> setPathInfo(String pathInfo) {
444         this.pathInfo = pathInfo;
445         additional=true;
446         return this;
447     }
448
449     public AbsAAFLocator<TRANS> setQuery(String query) {
450         this.query = query;
451         additional=true;
452         return this;
453     }
454
455     public AbsAAFLocator<TRANS>  setFragment(String fragment) {
456         this.fragment = fragment;
457         additional=true;
458         return this;
459     }
460
461     // Core URI, for reporting purposes
462     protected abstract URI getURI();
463
464     protected URI getURI(URI rv) throws LocatorException {
465         if (additional) {
466             try {
467                 return new URI(rv.getScheme(),rv.getUserInfo(),rv.getHost(),rv.getPort(),pathInfo,query,fragment);
468             } catch (URISyntaxException e) {
469                 throw new LocatorException("Error copying URL", e);
470             }
471         }
472         return rv;
473     }
474
475     protected void clear() {
476         epList.clear();
477         earliest=0L;
478     }
479
480
481 }