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