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