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