2 * ============LICENSE_START=======================================================
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
12 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
21 package org.onap.aai.sparky.dal;
23 import java.io.IOException;
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;
31 import java.util.NoSuchElementException;
33 import javax.ws.rs.core.MediaType;
34 import javax.ws.rs.core.UriBuilder;
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;
55 * The Class ActiveInventoryAdapter.
58 public class ActiveInventoryAdapter {
60 private static final Logger LOG =
61 LoggerFactory.getInstance().getLogger(ActiveInventoryAdapter.class);
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";
67 private static final String HTTP_SCHEME = "http";
68 private static final String HTTPS_SCHEME = "https";
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";
74 private OxmModelLoader oxmModelLoader;
75 private OxmEntityLookup oxmEntityLookup;
76 private RestEndpointConfig endpointConfig;
78 private RestClient restClient;
79 private String domain;
82 private String appPartnerName = "";
83 private String syncPartnerName = "";
84 private Map<String, List<String>> messageHeaders;
87 * Instantiates a new active inventory adapter.
88 * @throws RestClientConstructionException
92 public ActiveInventoryAdapter(OxmModelLoader oxmModelLoader, OxmEntityLookup oxmEntityLookup,
93 RestEndpointConfig endpointConfig,String domain)
94 throws ElasticSearchOperationException, IOException, RestClientConstructionException {
96 this.oxmModelLoader = oxmModelLoader;
97 this.oxmEntityLookup = oxmEntityLookup;
98 this.endpointConfig = endpointConfig;
102 * Add support for de-obfuscating basic auth password (if obfuscated)
105 if (endpointConfig.getRestAuthenticationMode() == RestAuthenticationMode.SSL_BASIC) {
106 String basicAuthPassword = endpointConfig.getBasicAuthPassword();
108 if (basicAuthPassword != null
109 && basicAuthPassword.startsWith(SparkyConstants.OBFUSCATION_PREFIX)) {
110 Encryptor enc = new Encryptor();
111 endpointConfig.setBasicAuthPassword(enc.decryptValue(basicAuthPassword));
115 this.restClient = RestClientFactory.buildClient(endpointConfig);
119 public String getAppPartnerName() {
120 return appPartnerName;
123 public void setAppPartnerName(String appPartnerName) {
124 this.appPartnerName = appPartnerName;
127 public String getSyncPartnerName() {
128 return syncPartnerName;
131 public void setSyncPartnerName(String syncPartnerName) {
132 this.syncPartnerName = syncPartnerName;
135 protected Map<String, List<String>> getMessageHeaders() {
137 Map<String, List<String>> headers = new HashMap<String, List<String>>();
139 headers.putIfAbsent(HEADER_FROM_APP_ID, new ArrayList<String>());
140 headers.get(HEADER_FROM_APP_ID).add(appPartnerName);
142 headers.putIfAbsent(HEADER_TRANS_ID, new ArrayList<String>());
143 headers.get(HEADER_TRANS_ID).add(TRANSACTION_ID_PREFIX + NodeUtils.getRandomTxnId());
145 if (endpointConfig.getRestAuthenticationMode() == RestAuthenticationMode.SSL_BASIC) {
146 headers.putIfAbsent(HEADER_AUTHORIZATION, new ArrayList<String>());
147 headers.get(HEADER_AUTHORIZATION).add(getBasicAuthenticationCredentials());
153 protected Map<String, List<String>> getSyncMessageHeaders() {
155 Map<String, List<String>> headers = new HashMap<String, List<String>>();
157 headers.putIfAbsent(HEADER_FROM_APP_ID, new ArrayList<String>());
158 headers.get(HEADER_FROM_APP_ID).add(syncPartnerName);
160 headers.putIfAbsent(HEADER_TRANS_ID, new ArrayList<String>());
161 headers.get(HEADER_TRANS_ID).add(TRANSACTION_ID_PREFIX + NodeUtils.getRandomTxnId());
163 if (endpointConfig.getRestAuthenticationMode() == RestAuthenticationMode.SSL_BASIC) {
164 headers.putIfAbsent(HEADER_AUTHORIZATION, new ArrayList<String>());
165 headers.get(HEADER_AUTHORIZATION).add(getBasicAuthenticationCredentials());
171 protected String getBasicAuthenticationCredentials() {
173 String usernameAndPassword = String.join(":", endpointConfig.getBasicAuthUserName(),
174 endpointConfig.getBasicAuthPassword());
175 return "Basic " + java.util.Base64.getEncoder().encodeToString(usernameAndPassword.getBytes());
178 public OxmEntityLookup getOxmEntityLookup() {
179 return oxmEntityLookup;
182 public void setOxmEntityLookup(OxmEntityLookup oxmEntityLookup) {
183 this.oxmEntityLookup = oxmEntityLookup;
186 protected String getResourceBasePath() {
189 if (oxmModelLoader != null) {
190 versionStr = String.valueOf(oxmModelLoader.getOxmApiVersion());
192 throw new RuntimeException("Unable to resolve aai version.");
195 return "/" + domain + "/" + versionStr.toLowerCase();
199 public static String extractResourcePath(String selflink) {
201 return new URI(selflink).getRawPath();
202 } catch (URISyntaxException uriSyntaxException) {
203 LOG.error(AaiUiMsgs.ERROR_EXTRACTING_RESOURCE_PATH_FROM_LINK,
204 uriSyntaxException.getMessage());
213 * @param resourceUrl the resource url
214 * @return the full url
215 * @throws Exception the exception
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);
223 public String getGenericQueryForSelfLink(String startNodeType, List<String> queryParams)
226 URIBuilder urlBuilder = new URIBuilder(getFullUrl("/search/generic-query"));
228 for (String queryParam : queryParams) {
229 urlBuilder.addParameter("key", queryParam);
232 urlBuilder.addParameter("start-node-type", startNodeType);
233 urlBuilder.addParameter("include", startNodeType);
235 final String constructedLink = urlBuilder.toString();
237 return constructedLink;
242 public OperationResult getSelfLinksByEntityType(String entityType) throws Exception {
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.
249 if (entityType == null) {
250 throw new NullPointerException(
251 "Failed to getSelfLinksByEntityType() because entityType is null");
254 OxmEntityDescriptor entityDescriptor = oxmEntityLookup.getEntityDescriptors().get(entityType);
256 if (entityDescriptor == null) {
257 throw new NoSuchElementException("Failed to getSelfLinksByEntityType() because could"
258 + " not find entity descriptor from OXM with type = " + entityType);
262 final String primaryKeyStr =
263 NodeUtils.concatArray(entityDescriptor.getPrimaryKeyAttributeNames(), "/");
265 link = getFullUrl("/search/nodes-query?search-node-type=" + entityType + "&filter="
266 + primaryKeyStr + ":EXISTS");
269 return restClient.get(link, getMessageHeaders(), MediaType.APPLICATION_JSON_TYPE);
273 public OperationResult getSelfLinkForEntity(String entityType, String primaryKeyName,
274 String primaryKeyValue) throws Exception {
276 if (entityType == null) {
277 throw new NullPointerException("Failed to getSelfLinkForEntity() because entityType is null");
280 if (primaryKeyName == null) {
281 throw new NullPointerException(
282 "Failed to getSelfLinkForEntity() because primaryKeyName is null");
285 if (primaryKeyValue == null) {
286 throw new NullPointerException(
287 "Failed to getSelfLinkForEntity() because primaryKeyValue is null");
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.
295 String encodedEntityType = URLEncoder.encode(entityType, "UTF-8");
296 String encodedPrimaryKeyName = URLEncoder.encode(primaryKeyName, "UTF-8");
297 String encodedPrimaryKeyValue = URLEncoder.encode(primaryKeyValue, "UTF-8");
301 if ("service-instance".equals(entityType)) {
303 link = getFullUrl("/search/generic-query?key=" + encodedEntityType + "."
304 + encodedPrimaryKeyName + ":" + encodedPrimaryKeyValue + "&start-node-type="
305 + encodedEntityType + "&include=customer&depth=2");
310 getFullUrl("/search/generic-query?key=" + encodedEntityType + "." + encodedPrimaryKeyName
311 + ":" + encodedPrimaryKeyValue + "&start-node-type=" + encodedEntityType);
315 return queryActiveInventoryWithRetries(link, "application/json",
316 endpointConfig.getNumRequestRetries(),"sync");
322 * Our retry conditions should be very specific.
325 * @return true, if successful
327 private boolean shouldRetryRequest(OperationResult r) {
333 int rc = r.getResultCode();
348 * Query active inventory.
351 * @param acceptContentType the accept content type
352 * @return the operation result
354 // package protected for test classes instead of private
355 OperationResult queryActiveInventory(String url, String acceptContentType, String uiRequestType) {
357 if (uiRequestType == UI_REQUEST_TYPE) {
358 messageHeaders = getMessageHeaders();
360 messageHeaders = getSyncMessageHeaders();
362 return restClient.get(url, messageHeaders, MediaType.APPLICATION_JSON_TYPE);
366 public RestEndpointConfig getEndpointConfig() {
367 return endpointConfig;
370 public void setEndpointConfig(RestEndpointConfig endpointConfig) {
371 this.endpointConfig = endpointConfig;
374 public OperationResult queryActiveInventoryWithRetries(String url, String responseType,
375 int numRetries,String uiRequestType) {
377 OperationResult result = null;
379 for (int retryCount = 0; retryCount < numRetries; retryCount++) {
381 LOG.debug(AaiUiMsgs.QUERY_AAI_RETRY_SEQ, url, String.valueOf(retryCount + 1));
383 result = queryActiveInventory(url, responseType,uiRequestType);
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.
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.
395 result.setNumRetries(retryCount);
397 if (!shouldRetryRequest(result)) {
399 result.setFromCache(false);
400 LOG.debug(AaiUiMsgs.QUERY_AAI_RETRY_DONE_SEQ, url, String.valueOf(retryCount + 1));
407 * Sleep between re-tries to be nice to the target system.
410 } catch (InterruptedException exc) {
411 LOG.error(AaiUiMsgs.QUERY_AAI_WAIT_INTERRUPTION, exc.getLocalizedMessage());
412 Thread.currentThread().interrupt();
415 LOG.error(AaiUiMsgs.QUERY_AAI_RETRY_FAILURE_WITH_SEQ, url, String.valueOf(retryCount + 1));
419 LOG.info(AaiUiMsgs.QUERY_AAI_RETRY_MAXED_OUT, url);
425 public String repairSelfLink(String selfLink) {
426 return repairSelfLink(selfLink, null);
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.
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)
437 public String repairSelfLink(String selflink, String queryParams) {
438 if (selflink == null) {
442 UriBuilder builder = UriBuilder.fromPath(selflink).host(endpointConfig.getEndpointIpAddress())
443 .port(Integer.parseInt(endpointConfig.getEndpointServerPort()));
445 switch (endpointConfig.getRestAuthenticationMode()) {
449 builder.scheme(HTTPS_SCHEME);
454 builder.scheme(HTTP_SCHEME);
458 boolean includeQueryParams = ( (null != queryParams) && (!"".equals(queryParams)) );
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 : ""));
466 public String getDomain() {