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