Adding UI extensibility
[aai/sparky-be.git] / src / main / java / org / onap / aai / sparky / dal / ActiveInventoryAdapter.java
1 /**
2  * ============LICENSE_START=======================================================
3  * org.onap.aai
4  * ================================================================================
5  * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
6  * Copyright © 2017 Amdocs
7  * ================================================================================
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  *       http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  * ============LICENSE_END=========================================================
20  *
21  * ECOMP is a trademark and service mark of AT&T Intellectual Property.
22  */
23 package org.onap.aai.sparky.dal;
24
25 import java.io.IOException;
26 import java.net.URISyntaxException;
27 import java.net.URLEncoder;
28 import java.util.ArrayList;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.NoSuchElementException;
33
34 import javax.ws.rs.core.MediaType;
35 import javax.ws.rs.core.UriBuilder;
36
37 import org.apache.http.NameValuePair;
38 import org.apache.http.client.utils.URIBuilder;
39 import org.onap.aai.cl.api.Logger;
40 import org.onap.aai.cl.eelf.LoggerFactory;
41 import org.onap.aai.restclient.client.OperationResult;
42 import org.onap.aai.restclient.client.RestClient;
43 import org.onap.aai.restclient.enums.RestAuthenticationMode;
44 import org.onap.aai.sparky.config.oxm.OxmEntityDescriptor;
45 import org.onap.aai.sparky.config.oxm.OxmEntityLookup;
46 import org.onap.aai.sparky.config.oxm.OxmModelLoader;
47 import org.onap.aai.sparky.dal.exception.ElasticSearchOperationException;
48 import org.onap.aai.sparky.logging.AaiUiMsgs;
49 import org.onap.aai.sparky.util.Encryptor;
50 import org.onap.aai.sparky.util.NodeUtils;
51 import org.onap.aai.sparky.viewandinspect.config.TierSupportUiConstants;
52
53
54
55 /**
56  * The Class ActiveInventoryAdapter.
57  */
58
59 public class ActiveInventoryAdapter {
60
61   private static final Logger LOG =
62       LoggerFactory.getInstance().getLogger(ActiveInventoryAdapter.class);
63
64   private static final String HEADER_TRANS_ID = "X-TransactionId";
65   private static final String HEADER_FROM_APP_ID = "X-FromAppId";
66   private static final String HEADER_AUTHORIZATION = "Authorization";
67
68   private static final String HTTP_SCHEME = "http";
69   private static final String HTTPS_SCHEME = "https";
70
71   private static final String TRANSACTION_ID_PREFIX = "txnId-";
72   private static final String UI_APP_NAME = "AAI-UI";
73
74   private OxmModelLoader oxmModelLoader;
75   private OxmEntityLookup oxmEntityLookup;
76
77   private RestClient restClient;
78
79   private String activeInventoryIpAddress;
80   private String activeInventoryServerPort;
81   private int numRequestRetries;
82   private String basicAuthUserName;
83   private String basicAuthPassword;
84   private RestAuthenticationMode restAuthenticationMode;
85   private int connectTimeoutInMs;
86   private int readTimeoutInMs;
87
88   /**
89    * Instantiates a new active inventory adapter.
90    *
91    */
92
93   public ActiveInventoryAdapter(OxmModelLoader oxmModelLoader,
94       RestAuthenticationMode authenticationMode, boolean validateServerHostname,
95       boolean validateServerCertChain, String certFileName, String certPassword,
96       String truststoreFileName, int connectTimeoutInMs, int readTimeoutInMs)
97       throws ElasticSearchOperationException, IOException {
98
99     this.oxmModelLoader = oxmModelLoader;
100     this.restAuthenticationMode = authenticationMode;
101     this.connectTimeoutInMs = connectTimeoutInMs;
102     this.readTimeoutInMs = readTimeoutInMs;
103
104
105     Encryptor enc = new Encryptor();
106     String certFileNameFullPath = TierSupportUiConstants.CONFIG_AUTH_LOCATION + certFileName;
107     String decryptedCertPassword = enc.decryptValue(certPassword);
108     String truststoreFileNameFullPath =
109         TierSupportUiConstants.CONFIG_AUTH_LOCATION + truststoreFileName;
110
111     this.restClient = new RestClient().authenticationMode(authenticationMode)
112         .validateServerCertChain(validateServerCertChain)
113         .validateServerHostname(validateServerHostname).clientCertFile(certFileNameFullPath)
114         .clientCertPassword(decryptedCertPassword).trustStore(truststoreFileNameFullPath)
115         .connectTimeoutMs(connectTimeoutInMs).readTimeoutMs(readTimeoutInMs);
116
117   }
118
119   public ActiveInventoryAdapter(OxmModelLoader oxmModelLoader,
120       RestAuthenticationMode authenticationMode, boolean validateServerHostname,
121       boolean validateServerCertChain, String basicAuthUserName, String basicAuthPassword,
122       int connectTimeoutInMs, int readTimeoutInMs)
123       throws ElasticSearchOperationException, IOException {
124
125     this.oxmModelLoader = oxmModelLoader;
126     this.restAuthenticationMode = authenticationMode;
127
128     this.restClient = new RestClient().authenticationMode(authenticationMode)
129         .validateServerCertChain(validateServerCertChain)
130         .validateServerHostname(validateServerHostname).connectTimeoutMs(connectTimeoutInMs)
131         .readTimeoutMs(readTimeoutInMs);
132
133     this.basicAuthUserName = basicAuthUserName;
134     this.basicAuthPassword = basicAuthPassword;
135
136   }
137
138
139   protected Map<String, List<String>> getMessageHeaders() {
140
141     Map<String, List<String>> headers = new HashMap<String, List<String>>();
142
143     headers.putIfAbsent(HEADER_FROM_APP_ID, new ArrayList<String>());
144     headers.get(HEADER_FROM_APP_ID).add(UI_APP_NAME);
145
146     headers.putIfAbsent(HEADER_TRANS_ID, new ArrayList<String>());
147     headers.get(HEADER_TRANS_ID).add(TRANSACTION_ID_PREFIX + NodeUtils.getRandomTxnId());
148
149     if (restAuthenticationMode == RestAuthenticationMode.SSL_BASIC) {
150
151       headers.putIfAbsent(HEADER_AUTHORIZATION, new ArrayList<String>());
152       headers.get(HEADER_AUTHORIZATION).add(getBasicAuthenticationCredentials());
153
154     }
155
156     return headers;
157   }
158
159   protected String getBasicAuthenticationCredentials() {
160     String usernameAndPassword = String.join(":", basicAuthUserName, basicAuthPassword);
161     return "Basic " + java.util.Base64.getEncoder().encodeToString(usernameAndPassword.getBytes());
162   }
163
164   public int getNumRequestRetries() {
165     return numRequestRetries;
166   }
167
168
169
170   public void setNumRequestRetries(int numRequestRetries) {
171     this.numRequestRetries = numRequestRetries;
172   }
173
174   public OxmEntityLookup getOxmEntityLookup() {
175     return oxmEntityLookup;
176   }
177
178   public void setOxmEntityLookup(OxmEntityLookup oxmEntityLookup) {
179     this.oxmEntityLookup = oxmEntityLookup;
180   }
181
182   public String getActiveInventoryIpAddress() {
183     return activeInventoryIpAddress;
184   }
185
186   public void setActiveInventoryIpAddress(String activeInventoryIpAddress) {
187     this.activeInventoryIpAddress = activeInventoryIpAddress;
188   }
189
190   public String getActiveInventoryServerPort() {
191     return activeInventoryServerPort;
192   }
193
194   public void setActiveInventoryServerPort(String activeInventoryServerPort) {
195     this.activeInventoryServerPort = activeInventoryServerPort;
196   }
197
198   protected String getResourceBasePath() {
199
200     String versionStr = null;
201     if (oxmModelLoader != null) {
202       versionStr = String.valueOf(oxmModelLoader.getLatestVersionNum());
203     }
204
205     return "/aai/v" + versionStr;
206
207   }
208
209   public int getConnectTimeoutInMs() {
210     return this.connectTimeoutInMs;
211   }
212
213   public int getReadTimeoutInMs() {
214     return this.readTimeoutInMs;
215   }
216
217   /**
218    * Gets the full url.
219    *
220    * @param resourceUrl the resource url
221    * @return the full url
222    * @throws Exception the exception
223    */
224   private String getFullUrl(String resourceUrl) throws Exception {
225     final String basePath = getResourceBasePath();
226     return String.format("https://%s:%s%s%s", activeInventoryIpAddress, activeInventoryServerPort,
227         basePath, resourceUrl);
228   }
229
230   public String getGenericQueryForSelfLink(String startNodeType, List<String> queryParams)
231       throws Exception {
232
233     URIBuilder urlBuilder = new URIBuilder(getFullUrl("/search/generic-query"));
234
235     for (String queryParam : queryParams) {
236       urlBuilder.addParameter("key", queryParam);
237     }
238
239     urlBuilder.addParameter("start-node-type", startNodeType);
240     urlBuilder.addParameter("include", startNodeType);
241
242     final String constructedLink = urlBuilder.toString();
243
244     return constructedLink;
245
246   }
247
248
249   public OperationResult getSelfLinksByEntityType(String entityType) throws Exception {
250
251     /*
252      * For this one, I want to dynamically construct the nodes-query for self-link discovery as a
253      * utility method that will use the OXM model entity data to drive the query as well.
254      */
255
256     if (entityType == null) {
257       throw new NullPointerException(
258           "Failed to getSelfLinksByEntityType() because entityType is null");
259     }
260
261     OxmEntityDescriptor entityDescriptor = oxmEntityLookup.getEntityDescriptors().get(entityType);
262
263     if (entityDescriptor == null) {
264       throw new NoSuchElementException("Failed to getSelfLinksByEntityType() because could"
265           + " not find entity descriptor from OXM with type = " + entityType);
266     }
267
268     String link = null;
269     final String primaryKeyStr =
270         NodeUtils.concatArray(entityDescriptor.getPrimaryKeyAttributeNames(), "/");
271
272     link = getFullUrl("/search/nodes-query?search-node-type=" + entityType + "&filter="
273         + primaryKeyStr + ":EXISTS");
274
275
276     return restClient.get(link, getMessageHeaders(), MediaType.APPLICATION_JSON_TYPE);
277
278   }
279
280   public OperationResult getSelfLinkForEntity(String entityType, String primaryKeyName,
281       String primaryKeyValue) throws Exception {
282
283     if (entityType == null) {
284       throw new NullPointerException("Failed to getSelfLinkForEntity() because entityType is null");
285     }
286
287     if (primaryKeyName == null) {
288       throw new NullPointerException(
289           "Failed to getSelfLinkForEntity() because primaryKeyName is null");
290     }
291
292     if (primaryKeyValue == null) {
293       throw new NullPointerException(
294           "Failed to getSelfLinkForEntity() because primaryKeyValue is null");
295     }
296
297     /*
298      * Try to protect ourselves from illegal URI formatting exceptions caused by characters that
299      * aren't natively supported in a URI, but can be escaped to make them legal.
300      */
301
302     String encodedEntityType = URLEncoder.encode(entityType, "UTF-8");
303     String encodedPrimaryKeyName = URLEncoder.encode(primaryKeyName, "UTF-8");
304     String encodedPrimaryKeyValue = URLEncoder.encode(primaryKeyValue, "UTF-8");
305
306     String link = null;
307
308     if ("service-instance".equals(entityType)) {
309
310       link = getFullUrl("/search/generic-query?key=" + encodedEntityType + "."
311           + encodedPrimaryKeyName + ":" + encodedPrimaryKeyValue + "&start-node-type="
312           + encodedEntityType + "&include=customer&depth=2");
313
314     } else {
315
316       link =
317           getFullUrl("/search/generic-query?key=" + encodedEntityType + "." + encodedPrimaryKeyName
318               + ":" + encodedPrimaryKeyValue + "&start-node-type=" + encodedEntityType);
319
320     }
321
322     return queryActiveInventoryWithRetries(link, "application/json", numRequestRetries);
323
324   }
325
326
327   /**
328    * Our retry conditions should be very specific.
329    *
330    * @param r the r
331    * @return true, if successful
332    */
333   private boolean shouldRetryRequest(OperationResult r) {
334
335     if (r == null) {
336       return true;
337     }
338
339     int rc = r.getResultCode();
340
341     if (rc == 200) {
342       return false;
343     }
344
345     if (rc == 404) {
346       return false;
347     }
348
349     return true;
350
351   }
352
353   /**
354    * Query active inventory.
355    *
356    * @param url the url
357    * @param acceptContentType the accept content type
358    * @return the operation result
359    */
360   // package protected for test classes instead of private
361   OperationResult queryActiveInventory(String url, String acceptContentType) {
362
363     return restClient.get(url, getMessageHeaders(), MediaType.APPLICATION_JSON_TYPE);
364
365   }
366
367   public OperationResult queryActiveInventoryWithRetries(String url, String responseType,
368       int numRetries) {
369
370     OperationResult result = null;
371
372     for (int retryCount = 0; retryCount < numRetries; retryCount++) {
373
374       LOG.debug(AaiUiMsgs.QUERY_AAI_RETRY_SEQ, url, String.valueOf(retryCount + 1));
375
376       result = queryActiveInventory(url, responseType);
377
378       /**
379        * Record number of times we have attempted the request to later summarize how many times we
380        * are generally retrying over thousands of messages in a sync.
381        * 
382        * If the number of retries is surprisingly high, then we need to understand why that is as
383        * the number of retries is also causing a heavier load on AAI beyond the throttling controls
384        * we already have in place in term of the transaction rate controller and number of
385        * parallelized threads per task processor.
386        */
387
388       result.setNumRetries(retryCount);
389
390       if (!shouldRetryRequest(result)) {
391
392         result.setFromCache(false);
393         LOG.debug(AaiUiMsgs.QUERY_AAI_RETRY_DONE_SEQ, url, String.valueOf(retryCount + 1));
394
395         return result;
396       }
397
398       try {
399         /*
400          * Sleep between re-tries to be nice to the target system.
401          */
402         Thread.sleep(50);
403       } catch (InterruptedException exc) {
404         LOG.error(AaiUiMsgs.QUERY_AAI_WAIT_INTERRUPTION, exc.getLocalizedMessage());
405         break;
406       }
407       LOG.error(AaiUiMsgs.QUERY_AAI_RETRY_FAILURE_WITH_SEQ, url, String.valueOf(retryCount + 1));
408
409     }
410
411     LOG.info(AaiUiMsgs.QUERY_AAI_RETRY_MAXED_OUT, url);
412
413     return result;
414
415   }
416
417   public String repairSelfLink(String selfLink) {
418     return repairSelfLink(selfLink, null);
419   }
420
421   /**
422    * This method adds a scheme, host and port (if missing) to the passed-in URI. If these parts of
423    * the URI are already present, they will not be duplicated.
424    * 
425    * @param selflink The URI to repair
426    * @param queryParams The query parameters as a single string
427    * @return The corrected URI (i.e. includes a scheme/host/port)
428    */
429   public String repairSelfLink(String selflink, String queryParams) {
430     if (selflink == null) {
431       return selflink;
432     }
433
434     UriBuilder builder = UriBuilder.fromPath(selflink).host(activeInventoryIpAddress)
435         .port(Integer.parseInt(activeInventoryServerPort));
436
437     switch (restAuthenticationMode) {
438
439       case SSL_BASIC:
440       case SSL_CERT: {
441         builder.scheme(HTTPS_SCHEME);
442         break;
443       }
444
445       default: {
446         builder.scheme(HTTP_SCHEME);
447       }
448     }
449
450     boolean includeQueryParams = ((null != queryParams) && (!"".equals(queryParams)));
451
452     /*
453      * builder.build().toString() will encode special characters to hexadecimal pairs prefixed with
454      * a '%' so we're adding the query parameters separately, in their UTF-8 representations, so
455      * that characters such as '?', '&', etc. remain intact as needed by the synchronizer
456      */
457     return (builder.build().toString() + (includeQueryParams ? queryParams : ""));
458   }
459
460 }