Merge from ECOMP's repository
[vid.git] / vid-app-common / src / main / java / org / onap / vid / aai / AaiClient.java
1 package org.onap.vid.aai;
2
3 import com.fasterxml.jackson.databind.JsonNode;
4 import com.fasterxml.jackson.databind.ObjectMapper;
5 import org.apache.commons.lang3.StringUtils;
6 import org.apache.http.HttpStatus;
7 import org.apache.http.client.utils.URIBuilder;
8 import org.jetbrains.annotations.NotNull;
9 import org.json.simple.JSONArray;
10 import org.json.simple.JSONObject;
11 import org.json.simple.parser.JSONParser;
12 import org.onap.portalsdk.core.logging.logic.EELFLoggerDelegate;
13 import org.onap.vid.aai.exceptions.InvalidAAIResponseException;
14 import org.onap.vid.aai.model.AaiGetAicZone.AicZones;
15 import org.onap.vid.aai.model.*;
16 import org.onap.vid.aai.model.AaiGetNetworkCollectionDetails.*;
17 import org.onap.vid.aai.model.AaiGetOperationalEnvironments.OperationalEnvironmentList;
18 import org.onap.vid.aai.model.AaiGetPnfs.Pnf;
19 import org.onap.vid.aai.model.AaiGetServicesRequestModel.GetServicesAAIRespone;
20 import org.onap.vid.aai.model.AaiGetTenatns.GetTenantsResponse;
21 import org.onap.vid.aai.util.AAIRestInterface;
22 import org.onap.vid.aai.util.CacheProvider;
23 import org.onap.vid.aai.util.VidObjectMapperType;
24 import org.onap.vid.exceptions.GenericUncheckedException;
25 import org.onap.vid.model.SubscriberList;
26 import org.onap.vid.model.probes.ErrorMetadata;
27 import org.onap.vid.model.probes.ExternalComponentStatus;
28 import org.onap.vid.model.probes.HttpRequestMetadata;
29 import org.onap.vid.utils.Logging;
30 import org.onap.vid.utils.Unchecked;
31 import org.springframework.http.HttpMethod;
32 import org.springframework.web.util.UriUtils;
33
34 import javax.inject.Inject;
35 import javax.ws.rs.WebApplicationException;
36 import javax.ws.rs.core.Response;
37 import java.io.IOException;
38 import java.io.UnsupportedEncodingException;
39 import java.net.URI;
40 import java.net.URLEncoder;
41 import java.util.LinkedHashMap;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.UUID;
45 import java.util.function.Function;
46
47 import static java.util.Collections.emptyList;
48 import static java.util.stream.Collectors.toMap;
49 import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
50
51 /**
52
53  * Created by Oren on 7/4/17.
54  */
55 public class AaiClient implements AaiClientInterface {
56
57
58     private static final String QUERY_FORMAT_RESOURCE = "query?format=resource";
59     private static final String SERVICE_SUBSCRIPTIONS_PATH = "/service-subscriptions/service-subscription/";
60     private static final String MODEL_INVARIANT_ID = "&model-invariant-id=";
61     private static final String QUERY_FORMAT_SIMPLE = "query?format=simple";
62     private static final String BUSINESS_CUSTOMER = "/business/customers/customer/";
63     private static final String SERVICE_INSTANCE = "/service-instances/service-instance/";
64     private static final String BUSINESS_CUSTOMERS_CUSTOMER = "business/customers/customer/";
65
66     protected String fromAppId = "VidAaiController";
67
68     private PortDetailsTranslator portDetailsTranslator;
69
70     private final AAIRestInterface restController;
71
72     private final CacheProvider cacheProvider;
73
74     ObjectMapper objectMapper = new ObjectMapper();
75
76     /**
77      * The logger
78      */
79     EELFLoggerDelegate logger = EELFLoggerDelegate.getLogger(AaiClient.class);
80
81     private static final String GET_SERVICE_MODELS_RESPONSE_BODY = "{\"start\" : \"service-design-and-creation/models/\", \"query\" : \"query/serviceModels-byDistributionStatus?distributionStatus=DISTRIBUTION_COMPLETE_OK\"}";
82
83     @Inject
84     public AaiClient(AAIRestInterface restController, PortDetailsTranslator portDetailsTranslator, CacheProvider cacheProvider) {
85         this.restController = restController;
86         this.portDetailsTranslator = portDetailsTranslator;
87         this.cacheProvider = cacheProvider;
88     }
89
90
91     private static String checkForNull(String local) {
92         if (local != null)
93             return local;
94         else
95             return "";
96
97     }
98
99     @Override
100     public AaiResponse getServicesByOwningEntityId(List<String> owningEntityIds){
101         Response resp = doAaiGet(getUrlFromLIst("business/owning-entities?", "owning-entity-id=", owningEntityIds), false);
102         return processAaiResponse(resp, OwningEntityResponse.class, null);
103     }
104
105     @Override
106     public AaiResponse getServicesByProjectNames(List<String> projectNames){
107         Response resp = doAaiGet(getUrlFromLIst("business/projects?", "project-name=",  projectNames), false);
108         return processAaiResponse(resp, ProjectResponse.class, null);
109     }
110
111     @Override
112     public AaiResponse getServiceModelsByDistributionStatus() {
113         return getFromCache("getServiceModelsByDistributionStatus", this::getServiceModelsByDistributionStatusNonCached,
114                 true, "Failed to get service models by distribution status");
115     }
116
117     private AaiResponse getServiceModelsByDistributionStatusNonCached(boolean propagateExceptions) {
118         GetServiceModelsByDistributionStatusResponse response = typedAaiRest(QUERY_FORMAT_RESOURCE, GetServiceModelsByDistributionStatusResponse.class,
119                 GET_SERVICE_MODELS_RESPONSE_BODY, HttpMethod.PUT, propagateExceptions);
120         return new AaiResponse(response, "", HttpStatus.SC_OK);
121     }
122
123     @Override
124     public AaiResponse getNetworkCollectionDetails(String serviceInstanceId) {
125         Response resp = doAaiPut(QUERY_FORMAT_RESOURCE, "{\"start\": [\"nodes/service-instances/service-instance/" + serviceInstanceId + "\"],\"query\": \"query/network-collection-ByServiceInstance\"}\n", false);
126         AaiResponse<AaiGetNetworkCollectionDetailsHelper> aaiResponse = processAaiResponse(resp, AaiGetNetworkCollectionDetailsHelper.class, null, VidObjectMapperType.FASTERXML);
127         return getNetworkCollectionDetailsResponse(aaiResponse);
128     }
129
130     @Override
131     public AaiResponse getInstanceGroupsByCloudRegion(String cloudOwner, String cloudRegionId, String networkFunction) {
132         Response resp = doAaiPut(QUERY_FORMAT_RESOURCE,
133                 "{\"start\": [\"cloud-infrastructure/cloud-regions/cloud-region/" + encodePathSegment(cloudOwner) + "/" + encodePathSegment(cloudRegionId) + "\"]," +
134                         "\"query\": \"query/instance-groups-byCloudRegion?type=L3-NETWORK&role=SUB-INTERFACE&function=" + encodePathSegment(networkFunction) + "\"}\n", false);
135         return processAaiResponse(resp, AaiGetInstanceGroupsByCloudRegion.class, null, VidObjectMapperType.FASTERXML);
136     }
137
138     private AaiResponse getNetworkCollectionDetailsResponse(AaiResponse<AaiGetNetworkCollectionDetailsHelper> aaiResponse){
139         if(aaiResponse.getHttpCode() == 200) {
140             ObjectMapper om = objectMapper;
141             AaiGetNetworkCollectionDetails aaiGetNetworkCollectionDetails = new AaiGetNetworkCollectionDetails();
142             try {
143                 for (int i = 0; i < aaiResponse.getT().getResults().size(); i++) {
144                     LinkedHashMap<String, Object> temp = ((LinkedHashMap) aaiResponse.getT().getResults().get(i));
145                     if (temp.get("service-instance") != null)
146                         aaiGetNetworkCollectionDetails.getResults().setServiceInstance(om.readValue(om.writeValueAsString(temp.get("service-instance")), org.onap.vid.aai.model.AaiGetNetworkCollectionDetails.ServiceInstance.class));
147                     else if (temp.get("collection") != null)
148                         aaiGetNetworkCollectionDetails.getResults().setCollection(om.readValue(om.writeValueAsString(temp.get("collection")), org.onap.vid.aai.model.AaiGetNetworkCollectionDetails.Collection.class));
149                     else if (temp.get("instance-group") != null)
150                         aaiGetNetworkCollectionDetails.getResults().setInstanceGroup(om.readValue(om.writeValueAsString(temp.get("instance-group")), InstanceGroup.class));
151                     else if (temp.get("l3-network") != null)
152                         aaiGetNetworkCollectionDetails.getResults().getNetworks().add(om.readValue(om.writeValueAsString(temp.get("l3-network")), Network.class));
153                 }
154                 return new AaiResponse(aaiGetNetworkCollectionDetails, null, HttpStatus.SC_OK);
155             }
156             catch (com.fasterxml.jackson.databind.JsonMappingException e) {
157                 logger.error(EELFLoggerDelegate.errorLogger, "AAI response parsing Error",  e);
158                 return new AaiResponse(e.getCause(), "AAI response parsing Error" , HttpStatus.SC_INTERNAL_SERVER_ERROR);
159             }
160             catch (Exception e) {
161                 return new AaiResponse(e.getCause(), "Got " + aaiResponse.getHttpCode() + " from a&ai" , aaiResponse.getHttpCode());
162             }
163         }
164         return aaiResponse;
165     }
166
167     @Override
168     public AaiResponse getPNFData(String globalCustomerId, String serviceType, String modelVersionId, String modelInvariantId, String cloudRegion, String equipVendor, String equipModel) {
169         String siQuery = BUSINESS_CUSTOMER + globalCustomerId + SERVICE_SUBSCRIPTIONS_PATH + encodePathSegment(serviceType) + "/service-instances?model-version-id=" + modelVersionId + MODEL_INVARIANT_ID + modelInvariantId;
170         String pnfQuery = "query/pnf-fromModel-byRegion?cloudRegionId=" + encodePathSegment(cloudRegion) + "&equipVendor=" + encodePathSegment(equipVendor) + "&equipModel=" + encodePathSegment(equipModel);
171         String payload = "{\"start\":\"" + siQuery + "\",\"query\":\"" + pnfQuery + "\"}";
172         Response resp = doAaiPut(QUERY_FORMAT_SIMPLE, payload, false);
173         return processAaiResponse(resp, AaiGetPnfResponse.class, null);
174     }
175
176
177     @Override
178     public AaiResponse<Pnf> getSpecificPnf(String pnfId) {
179         Response resp = doAaiGet("network/pnfs/pnf/"+pnfId, false);
180         return processAaiResponse(resp, Pnf.class, null);
181     }
182
183
184     public AaiResponse getInstanceGroupsByVnfInstanceId(String vnfInstanceId){
185         Response resp = doAaiGet("network/generic-vnfs/generic-vnf/" + vnfInstanceId + "?depth=0", false);
186         return processAaiResponse(resp, AaiGetRelatedInstanceGroupsByVnfId.class , null, null);
187     }
188
189
190     @Override
191     public List<PortDetailsTranslator.PortDetails> getPortMirroringSourcePorts(String configurationID) {
192         String payload = "{\"start\":\"/network/configurations/configuration/" + configurationID + "\",\"query\":\"query/pserver-fromConfiguration\"}";
193         Response resp = doAaiPut(QUERY_FORMAT_SIMPLE, payload, false);
194         resp.bufferEntity(); // avoid later "Entity input stream has already been closed" problems
195         String rawPayload = resp.readEntity(String.class);
196         AaiResponse<CustomQuerySimpleResult> aaiResponse = processAaiResponse(resp, CustomQuerySimpleResult.class, rawPayload);
197         return portDetailsTranslator.extractPortDetails(aaiResponse, rawPayload);
198     }
199
200
201
202     public AaiResponse getServiceInstance(String globalCustomerId, String serviceType, String serviceInstanceId) {
203         String getServiceInstancePath = BUSINESS_CUSTOMERS_CUSTOMER + globalCustomerId+ SERVICE_SUBSCRIPTIONS_PATH +serviceType+ SERVICE_INSTANCE +serviceInstanceId;
204         Response resp = doAaiGet(getServiceInstancePath , false);
205         return processAaiResponse(resp, ServiceRelationships.class, null);
206     }
207
208     @Override
209     public AaiResponse getLogicalLink(String link) {
210         Response resp = doAaiGet("network/logical-links/logical-link/" + link , false);
211         return processAaiResponse(resp, LogicalLinkResponse.class, null);
212     }
213
214     @Override
215     public boolean isNodeTypeExistsByName(String name, ResourceType type) {
216         if (StringUtils.isEmpty(name)) {
217             throw new GenericUncheckedException("Empty resource-name provided to searchNodeTypeByName; request is rejected as this will cause full resources listing");
218         }
219
220         URI path = Unchecked.toURI(String.format( // e.g. GET /aai/v$/nodes/vf-modules?vf-module-name={vf-module-name}
221                 "nodes/%s?%s=%s",
222                 type.getAaiFormat(),
223                 type.getNameFilter(),
224                 encodePathSegment(name)
225         ));
226         final ResponseWithRequestInfo responseWithRequestInfo = restController.RestGet(fromAppId, UUID.randomUUID().toString(), path, false, true);
227
228         return isResourceExistByStatusCode(responseWithRequestInfo);
229     }
230
231     public Map<String, Properties> getCloudRegionAndTenantByVnfId(String vnfId) {
232         String start = "/network/generic-vnfs/generic-vnf/" + vnfId;
233         String query = "/query/cloud-region-fromVnf";
234
235         String payload = "{\"start\":[\"" + start + "\"],\"query\":\"" + query + "\"}";
236         CustomQuerySimpleResult result = typedAaiRest(QUERY_FORMAT_SIMPLE, CustomQuerySimpleResult.class, payload, HttpMethod.PUT, false);
237
238         return result.getResults().stream()
239                 .filter(res -> StringUtils.equals(res.getNodeType(), "tenant") ||
240                         StringUtils.equals(res.getNodeType(), "cloud-region"))
241                 .collect(toMap(SimpleResult::getNodeType, SimpleResult::getProperties));
242     }
243
244     private boolean isResourceExistByStatusCode(ResponseWithRequestInfo responseWithRequestInfo) {
245         // 200 - is found
246         // 404 - resource not found
247         Response.Status statusInfo = responseWithRequestInfo.getResponse().getStatusInfo().toEnum();
248         switch (statusInfo) {
249             case OK:
250                 return true;
251             case NOT_FOUND:
252                 return false;
253             default:
254                 throw new GenericUncheckedException("Unexpected response-code (only OK and NOT_FOUND are expected): " +
255                         responseWithRequestInfo.getResponse().getStatusInfo());
256         }
257     }
258
259     @Override
260     public <T> T typedAaiGet(URI uri, Class<T> clz) {
261         return typedAaiRest(uri, clz, null, HttpMethod.GET, false);
262     }
263
264     public <T> T typedAaiRest(String path, Class<T> clz, String payload, HttpMethod method, boolean propagateExceptions) {
265         return typedAaiRest(Unchecked.toURI(path), clz, payload, method, propagateExceptions);
266     }
267
268
269     public <T> T typedAaiRest(URI path, Class<T> clz, String payload, HttpMethod method, boolean propagateExceptions) {
270         ResponseWithRequestInfo responseWithRequestInfo;
271         try {
272             responseWithRequestInfo = restController.doRest(fromAppId, UUID.randomUUID().toString(), path, payload, method, false, propagateExceptions);
273         } catch (Exception e) {
274             responseWithRequestInfo = handleExceptionFromRestCall(propagateExceptions, "doAai"+method.name(), e);
275         }
276
277         final AaiResponseWithRequestInfo<T> aaiResponse = processAaiResponse(responseWithRequestInfo, clz, VidObjectMapperType.FASTERXML, true);
278
279         if (aaiResponse.getAaiResponse().getHttpCode() > 399 || aaiResponse.getAaiResponse().getT() == null) {
280             throw new ExceptionWithRequestInfo(aaiResponse.getHttpMethod(),
281                     aaiResponse.getRequestedUrl(),
282                     aaiResponse.getRawData(),
283                     responseWithRequestInfo.getResponse().getStatus(),
284                     new InvalidAAIResponseException(aaiResponse.getAaiResponse()));
285         }
286
287         return aaiResponse.getAaiResponse().getT();
288     }
289
290
291     private String getUrlFromLIst(String url, String paramKey, List<String> params){
292         int i = 0;
293         for(String param: params){
294             i ++;
295             url = url.concat(paramKey);
296             String encodedParam= param;
297             try {
298                 encodedParam= URLEncoder.encode(param, "UTF-8");
299             } catch (UnsupportedEncodingException e) {
300                 String methodName = "getUrlFromList";
301                 logger.error(EELFLoggerDelegate.errorLogger, methodName + e.toString());
302                 logger.debug(EELFLoggerDelegate.debugLogger, methodName + e.toString());
303             }
304             url = url.concat(encodedParam);
305             if(i != params.size()){
306                 url = url.concat("&");
307             }
308         }
309         return url;
310     }
311
312
313
314     @Override
315     public AaiResponse<SubscriberList> getAllSubscribers() {
316         return getFromCache("getAllSubscribers", this::getAllSubscribersNonCached, true, "Failed to get all subscribers");
317     }
318
319     private <K> AaiResponse getFromCache(String cacheName, Function<K, AaiResponse> function, K argument, String errorMessage) {
320         try {
321             return cacheProvider
322                     .aaiClientCacheFor(cacheName, function)
323                     .get(argument);
324         } catch (ExceptionWithRequestInfo exception) {
325             logger.error(errorMessage, exception);
326             return new AaiResponse(null, exception.getRawData(), exception.getHttpCode());
327         }
328         catch (Exception exception) {
329             logger.error(errorMessage, exception);
330             return new AaiResponse(null, exception.getMessage(), HttpStatus.SC_INTERNAL_SERVER_ERROR);
331         }
332     }
333
334     private AaiResponse<SubscriberList> getAllSubscribersNonCached(boolean propagateExceptions) {
335         AaiResponse<SubscriberList> aaiResponse = getAllSubscribers(propagateExceptions).getAaiResponse();
336         if (propagateExceptions && (aaiResponse.getT() == null || aaiResponse.getT().customer == null || aaiResponse.getT().customer.isEmpty())) {
337             throw new GenericUncheckedException("Failed to get Subscribers data. The data is null or empty.");
338         } else {
339             return aaiResponse;
340         }
341     }
342
343     AaiResponseWithRequestInfo<SubscriberList> getAllSubscribers(boolean propagateExceptions){
344         String depth = "0";
345         ResponseWithRequestInfo aaiGetResult = doAaiGet("business/customers?subscriber-type=INFRA&depth=" + depth, false, propagateExceptions);
346         AaiResponseWithRequestInfo<SubscriberList> responseWithRequestInfo = processAaiResponse(aaiGetResult, SubscriberList.class, propagateExceptions);
347         responseWithRequestInfo.setRequestedUrl(aaiGetResult.getRequestUrl());
348         responseWithRequestInfo.setHttpMethod(aaiGetResult.getRequestHttpMethod());
349         return responseWithRequestInfo;
350     }
351
352
353     @Override
354     public AaiResponse getAllAicZones() {
355         Response resp = doAaiGet("network/zones", false);
356         return processAaiResponse(resp, AicZones.class, null);
357     }
358
359     @Override
360     public AaiResponse getVNFData(String globalSubscriberId, String serviceType) {
361         String payload = "{\"start\": [\"business/customers/customer/" + globalSubscriberId + SERVICE_SUBSCRIPTIONS_PATH + encodePathSegment(serviceType) +"/service-instances\"]," +
362                 "\"query\": \"query/vnf-topology-fromServiceInstance\"}";
363         Response resp = doAaiPut(QUERY_FORMAT_SIMPLE, payload, false);
364         return processAaiResponse(resp, AaiGetVnfResponse.class, null);
365     }
366
367     @Override
368     public AaiResponse getVNFData(String globalSubscriberId, String serviceType, String serviceInstanceId) {
369         String payload = "{\"start\": [\"/business/customers/customer/" + globalSubscriberId + SERVICE_SUBSCRIPTIONS_PATH + encodePathSegment(serviceType) + SERVICE_INSTANCE + serviceInstanceId + "\"],       \"query\": \"query/vnf-topology-fromServiceInstance\"}";
370         Response resp = doAaiPut(QUERY_FORMAT_SIMPLE, payload, false);
371         return processAaiResponse(resp, AaiGetVnfResponse.class, null);
372     }
373
374     @Override
375     public Response getVersionByInvariantId(List<String> modelInvariantId) {
376         if (modelInvariantId.isEmpty()) {
377             throw new GenericUncheckedException("Zero invariant-ids provided to getVersionByInvariantId; request is rejected as this will cause full models listing");
378         }
379
380         StringBuilder sb = new StringBuilder();
381         for (String id : modelInvariantId){
382             sb.append(MODEL_INVARIANT_ID);
383             sb.append(id);
384
385         }
386         return doAaiGet("service-design-and-creation/models?depth=2"+ sb.toString(), false);
387     }
388
389     @Override
390     public AaiResponse getSubscriberData(String subscriberId) {
391         String depth = "2";
392         AaiResponse subscriberDataResponse;
393         Response resp = doAaiGet(BUSINESS_CUSTOMERS_CUSTOMER + subscriberId + "?depth=" + depth, false);
394         subscriberDataResponse = processAaiResponse(resp, Services.class, null);
395         return subscriberDataResponse;
396     }
397
398     @Override
399     public AaiResponse getServices() {
400         Response resp = doAaiGet("service-design-and-creation/services", false);
401         return processAaiResponse(resp, GetServicesAAIRespone.class, null);
402     }
403
404     @Override
405     public AaiResponse getOperationalEnvironments(String operationalEnvironmentType, String operationalEnvironmentStatus) {
406         String url = "cloud-infrastructure/operational-environments";
407         URIBuilder urlBuilder  = new URIBuilder();
408         if (operationalEnvironmentType != null)
409             urlBuilder.addParameter("operational-environment-type", operationalEnvironmentType);
410         if (operationalEnvironmentStatus != null)
411             urlBuilder.addParameter("operational-environment-status", operationalEnvironmentStatus);
412         url += urlBuilder.toString();
413         Response resp = doAaiGet(url, false);
414         return processAaiResponse(resp, OperationalEnvironmentList.class, null);
415     }
416
417     @Override
418     public AaiResponse getTenants(String globalCustomerId, String serviceType) {
419         if ((globalCustomerId == null || globalCustomerId.isEmpty()) || ((serviceType == null) || (serviceType.isEmpty())) ){
420             return buildAaiResponseForGetTenantsFailure(" Failed to retrieve LCP Region & Tenants from A&AI, Subscriber ID or Service Type is missing.");
421         }
422         try {
423             return cacheProvider
424                     .aaiClientCacheFor("getTenants", this::getTenantsByKey)
425                     .get(CacheProvider.compileKey(globalCustomerId, serviceType));
426         }
427         catch (ParsingGetTenantsResponseFailure exception) {
428             logger.error("Failed to get tenants ", exception);
429             return buildAaiResponseForGetTenantsFailure(exception.getMessage());
430         }
431     }
432
433     @Override
434     public AaiResponse getNodeTemplateInstances(String globalCustomerId, String serviceType, String modelVersionId, String modelInvariantId, String cloudRegion) {
435
436         String siQuery = BUSINESS_CUSTOMER + globalCustomerId + SERVICE_SUBSCRIPTIONS_PATH + encodePathSegment(serviceType) + "/service-instances?model-version-id=" + modelVersionId + MODEL_INVARIANT_ID + modelInvariantId;
437         String vnfQuery = "query/queryvnfFromModelbyRegion?cloudRegionId=" + encodePathSegment(cloudRegion);
438         String payload1 = "{\"start\":\"" + siQuery + "\",\"query\":\"" + vnfQuery + "\"}";
439
440         Response resp1 = doAaiPut(QUERY_FORMAT_SIMPLE, payload1, false);
441         AaiResponse aaiResponse1 = processAaiResponse(resp1, AaiGetVnfResponse.class, null);
442         logger.debug(EELFLoggerDelegate.debugLogger, "getNodeTemplateInstances AAI's response: {}", aaiResponse1);
443         return aaiResponse1;
444     }
445
446     @Override
447     public AaiResponse<JsonNode> getCloudRegionAndSourceByPortMirroringConfigurationId(String configurationId) {
448         final String start = "[\"network/configurations/configuration/" + configurationId + "\"]";
449         final String query = "\"query/cloud-region-and-source-FromConfiguration\"";
450         String payload = "{\"start\":" + start + ",\"query\":" + query + "}";
451
452         Response response = doAaiPut("query?format=simple&nodesOnly=true", payload, false);
453         AaiResponse<JsonNode> aaiResponse = processAaiResponse(response, JsonNode.class, null);
454
455         logger.debug(EELFLoggerDelegate.debugLogger, "getNodeTemplateInstances AAI's response: {}", aaiResponse);
456         return aaiResponse;
457     }
458
459     private <T> AaiResponseWithRequestInfo<T> processAaiResponse(ResponseWithRequestInfo responseWithRequestInfo, Class<? extends T> classType, boolean propagateExceptions) {
460         return processAaiResponse(responseWithRequestInfo, classType, VidObjectMapperType.CODEHAUS, propagateExceptions);
461     }
462
463     private <T> AaiResponseWithRequestInfo<T> processAaiResponse(ResponseWithRequestInfo responseWithRequestInfo, Class<? extends T> classType, VidObjectMapperType omType, boolean propagateExceptions) {
464         String responseBody = null;
465         Integer responseHttpCode = null;
466         try {
467             Response response = responseWithRequestInfo.getResponse();
468             responseHttpCode = (response != null) ? response.getStatus() : null;
469             responseBody = (response != null) ? response.readEntity(String.class) : null;
470             AaiResponse<T> processedAaiResponse = processAaiResponse(response, classType, responseBody, omType, propagateExceptions);
471             return new AaiResponseWithRequestInfo<>(responseWithRequestInfo.getRequestHttpMethod(), responseWithRequestInfo.getRequestUrl(), processedAaiResponse,
472                     responseBody);
473         } catch (Exception e) {
474             throw new ExceptionWithRequestInfo(responseWithRequestInfo.getRequestHttpMethod(),
475                     responseWithRequestInfo.getRequestUrl(), responseBody, responseHttpCode, e);
476         }
477     }
478
479     private AaiResponse processAaiResponse(Response resp, Class classType, String responseBody) {
480         return processAaiResponse(resp, classType, responseBody, VidObjectMapperType.CODEHAUS);
481     }
482
483     private <T> AaiResponse<T> processAaiResponse(Response resp, Class<? extends T> classType, String responseBody, VidObjectMapperType omType) {
484         return processAaiResponse(resp, classType, responseBody, omType, false);
485     }
486
487     private  <T> AaiResponse<T> processAaiResponse(Response resp, Class<? extends T> classType, String responseBody, VidObjectMapperType omType, boolean propagateExceptions) {
488         AaiResponse<T> subscriberDataResponse;
489         if (resp == null) {
490             subscriberDataResponse = new AaiResponse<>(null, null, HttpStatus.SC_INTERNAL_SERVER_ERROR);
491             logger.debug(EELFLoggerDelegate.debugLogger, "Invalid response from AAI");
492         } else {
493             logger.debug(EELFLoggerDelegate.debugLogger, "getSubscribers() resp=" + resp.getStatusInfo().toString());
494             if (resp.getStatus() != HttpStatus.SC_OK) {
495                 subscriberDataResponse = processFailureResponse(resp,responseBody);
496             } else {
497                 subscriberDataResponse = processOkResponse(resp, classType, responseBody, omType, propagateExceptions);
498             }
499         }
500         return subscriberDataResponse;
501     }
502
503     private AaiResponse processFailureResponse(Response resp, String responseBody) {
504         logger.debug(EELFLoggerDelegate.debugLogger, "Invalid response from AAI");
505         String rawData;
506         if (responseBody != null) {
507             rawData = responseBody;
508         } else {
509             rawData = resp.readEntity(String.class);
510         }
511         return new AaiResponse<>(null, rawData, resp.getStatus());
512     }
513
514     private <T> AaiResponse<T> processOkResponse(Response resp, Class<? extends T> classType, String responseBody, VidObjectMapperType omType, boolean propagateExceptions) {
515         AaiResponse<T> subscriberDataResponse;
516         String finalResponse = null;
517         try {
518             if (responseBody != null) {
519                 finalResponse = responseBody;
520             } else {
521                 finalResponse = resp.readEntity(String.class);
522             }
523
524             if(omType == VidObjectMapperType.CODEHAUS)
525                 subscriberDataResponse = parseCodeHausObject(classType, finalResponse);
526             else
527                 subscriberDataResponse = parseFasterXmlObject(classType, finalResponse);
528
529         } catch(Exception e){
530             if (propagateExceptions) {
531                 throw new GenericUncheckedException(e);
532             } else {
533                 subscriberDataResponse = new AaiResponse<>(null, null, HttpStatus.SC_INTERNAL_SERVER_ERROR);
534                 logger.error("Failed to parse aai response: \"{}\" to class {}", finalResponse, classType, e);
535             }
536         }
537         return subscriberDataResponse;
538     }
539
540     private <T> AaiResponse<T> parseFasterXmlObject(Class<? extends T> classType, String finalResponse) throws IOException {
541         return new AaiResponse<>((objectMapper.readValue(finalResponse, classType)), null, HttpStatus.SC_OK);
542     }
543
544     private <T> AaiResponse<T> parseCodeHausObject(Class<? extends T> classType, String finalResponse) throws IOException {
545         return new AaiResponse<>((objectMapper.readValue(finalResponse, classType)), null, HttpStatus.SC_OK);
546     }
547
548     @Override
549     public Response doAaiGet(String uri, boolean xml) {
550         return doAaiGet(uri, xml, false).getResponse();
551     }
552
553
554     public ResponseWithRequestInfo doAaiGet(String uri, boolean xml, boolean propagateExceptions) {
555         return doAaiGet(Unchecked.toURI(uri), xml, propagateExceptions);
556     }
557
558     public ResponseWithRequestInfo doAaiGet(URI uri, boolean xml, boolean propagateExceptions) {
559         String methodName = "doAaiGet";
560         logger.debug(EELFLoggerDelegate.debugLogger, methodName + " start");
561
562         ResponseWithRequestInfo resp;
563         try {
564             resp = restController.RestGet(fromAppId, UUID.randomUUID().toString(), uri, xml, propagateExceptions);
565
566         } catch (Exception e) {
567             resp = handleExceptionFromRestCall(propagateExceptions, methodName, e);
568         }
569         return resp;
570     }
571
572     @NotNull
573     protected ResponseWithRequestInfo handleExceptionFromRestCall(boolean propagateExceptions, String methodName, Exception e) {
574         ResponseWithRequestInfo resp;
575         if (propagateExceptions) {
576             throw (e instanceof RuntimeException) ? (RuntimeException)e : new GenericUncheckedException(e);
577         } else {
578             final Exception actual =
579                     e instanceof ExceptionWithRequestInfo ? (Exception) e.getCause() : e;
580
581             final String message =
582                     actual instanceof WebApplicationException ? ((WebApplicationException) actual).getResponse().readEntity(String.class) : e.toString();
583
584             //ToDo: change parameter of requestUrl to real url from doRest function
585             resp = new ResponseWithRequestInfo(null, null, org.springframework.http.HttpMethod.GET);
586             logger.info(EELFLoggerDelegate.errorLogger, methodName + message);
587             logger.debug(EELFLoggerDelegate.debugLogger, methodName + message);
588         }
589         return resp;
590     }
591
592     private String parseForTenantsByServiceSubscription(String relatedToKey, String resp) {
593         String tenantList = "";
594
595         try {
596             JSONParser jsonParser = new JSONParser();
597
598             JSONObject jsonObject = (JSONObject) jsonParser.parse(resp);
599
600             return parseServiceSubscriptionObjectForTenants(relatedToKey, jsonObject);
601         } catch (Exception ex) {
602             logger.debug(EELFLoggerDelegate.debugLogger, "parseForTenantsByServiceSubscription error while parsing tenants by service subscription", ex);
603         }
604         return tenantList;
605     }
606
607     protected Response doAaiPut(String uri, String payload, boolean xml) {
608         return doAaiPut(uri, payload, xml, false).getResponse();
609     }
610
611     protected ResponseWithRequestInfo doAaiPut(String uri, String payload, boolean xml, boolean propagateExceptions) {
612         String methodName = "doAaiPut";
613         logger.debug(EELFLoggerDelegate.debugLogger, methodName + " start");
614
615         ResponseWithRequestInfo resp;
616         try {
617
618             resp = restController.RestPut(fromAppId, uri, payload, xml, propagateExceptions);
619
620         } catch (Exception e) {
621             resp = handleExceptionFromRestCall(propagateExceptions, methodName, e);
622         }
623         return resp;
624     }
625
626
627     private String parseServiceSubscriptionObjectForTenants(String relatedToKey, JSONObject jsonObject) {
628         JSONArray tenantArray = new JSONArray();
629         boolean bconvert = false;
630         try {
631             JSONObject relationShipListsObj = (JSONObject) jsonObject.get("relationship-list");
632             if (relationShipListsObj != null) {
633                 JSONArray rShipArray = (JSONArray) relationShipListsObj.get("relationship");
634                 for (Object innerObj : defaultIfNull(rShipArray, emptyList())) {
635                     if (innerObj != null) {
636                         bconvert = parseTenant(relatedToKey, tenantArray, bconvert, (JSONObject) innerObj);
637                     }
638                 }
639             }
640         } catch (NullPointerException ex) {
641             logger.debug(EELFLoggerDelegate.debugLogger, "parseServiceSubscriptionObjectForTenants. error while parsing service subscription object for tenants", ex);
642         }
643
644         if (bconvert)
645             return tenantArray.toJSONString();
646         else
647             return "";
648
649     }
650
651     private static boolean parseTenant(String relatedToKey, JSONArray tenantArray, boolean bconvert, JSONObject inner1Obj) {
652         String relatedTo = checkForNull((String) inner1Obj.get("related-to"));
653         if (relatedTo.equalsIgnoreCase(relatedToKey)) {
654             JSONObject tenantNewObj = new JSONObject();
655
656             String relatedLink = checkForNull((String) inner1Obj.get("related-link"));
657             tenantNewObj.put("link", relatedLink);
658
659             JSONArray rDataArray = (JSONArray) inner1Obj.get("relationship-data");
660             for (Object innerObj : defaultIfNull(rDataArray, emptyList())) {
661                 parseRelationShip(tenantNewObj, (JSONObject) innerObj);
662             }
663
664             JSONArray relatedTPropArray = (JSONArray) inner1Obj.get("related-to-property");
665             for (Object innerObj : defaultIfNull(relatedTPropArray, emptyList())) {
666                 parseRelatedTProp(tenantNewObj, (JSONObject) innerObj);
667             }
668             bconvert = true;
669             tenantArray.add(tenantNewObj);
670         }
671         return bconvert;
672     }
673
674     private static void parseRelatedTProp(JSONObject tenantNewObj, JSONObject innerObj) {
675         if (innerObj == null)
676             return;
677
678         String propKey = checkForNull((String) innerObj.get("property-key"));
679         String propVal = checkForNull((String) innerObj.get("property-value"));
680         if (propKey.equalsIgnoreCase("tenant.tenant-name")) {
681             tenantNewObj.put("tenantName", propVal);
682         }
683     }
684
685     private static void parseRelationShip(JSONObject tenantNewObj, JSONObject inner2Obj) {
686         if (inner2Obj == null)
687             return;
688
689         String rShipKey = checkForNull((String) inner2Obj.get("relationship-key"));
690         String rShipVal = checkForNull((String) inner2Obj.get("relationship-value"));
691         if (rShipKey.equalsIgnoreCase("cloud-region.cloud-owner")) {
692             tenantNewObj.put("cloudOwner", rShipVal);
693         } else if (rShipKey.equalsIgnoreCase("cloud-region.cloud-region-id")) {
694             tenantNewObj.put("cloudRegionID", rShipVal);
695         } else if (rShipKey.equalsIgnoreCase("tenant.tenant-id")) {
696             tenantNewObj.put("tenantID", rShipVal);
697         }
698     }
699
700     private static String encodePathSegment(String segmentToEncode) {
701         try {
702             return UriUtils.encodePathSegment(segmentToEncode, "UTF-8");
703         } catch (UnsupportedEncodingException e) {
704             throw new GenericUncheckedException("URI encoding failed unexpectedly", e);
705         }
706     }
707
708     @Override
709     public ExternalComponentStatus probeAaiGetAllSubscribers(){
710         long startTime = System.currentTimeMillis();
711         try {
712             AaiResponseWithRequestInfo<SubscriberList> responseWithRequestInfo = getAllSubscribers(true);
713             AaiResponse<SubscriberList> aaiResponse = responseWithRequestInfo.getAaiResponse();
714             long duration = System.currentTimeMillis() - startTime;
715
716             SubscriberList subscribersList = (aaiResponse != null) ? aaiResponse.getT() : null;
717             boolean isAvailable = subscribersList != null && subscribersList.customer != null && !subscribersList.customer.isEmpty();
718
719             HttpRequestMetadata metadata = new HttpRequestMetadata(
720                     responseWithRequestInfo.getHttpMethod(),
721                     (aaiResponse != null) ? aaiResponse.getHttpCode() : 0,
722                     responseWithRequestInfo.getRequestedUrl(),
723                     responseWithRequestInfo.getRawData(),
724                     isAvailable ? "OK" : "No subscriber received",
725                     duration
726             );
727             return new ExternalComponentStatus(ExternalComponentStatus.Component.AAI, isAvailable, metadata);
728
729         } catch (ExceptionWithRequestInfo e) {
730             long duration = System.currentTimeMillis() - startTime;
731             return new ExternalComponentStatus(ExternalComponentStatus.Component.AAI, false,
732                     new HttpRequestMetadata(e, duration));
733         } catch (Exception e) {
734             long duration = System.currentTimeMillis() - startTime;
735             return new ExternalComponentStatus(ExternalComponentStatus.Component.AAI, false,
736                     new ErrorMetadata(Logging.exceptionToDescription(e), duration));
737         }
738     }
739
740     @Override
741     public String getCloudOwnerByCloudRegionId(String cloudRegionId) {
742         return cacheProvider
743                 .aaiClientCacheFor("getCloudOwnerByCloudRegionId", this::getCloudOwnerByCloudRegionIdNonCached)
744                 .get(cloudRegionId);
745     }
746
747
748     @Override
749     public GetTenantsResponse getHomingDataByVfModule(String vnfInstanceId, String vfModuleId) {
750
751         if (StringUtils.isEmpty(vnfInstanceId)||StringUtils.isEmpty(vfModuleId)){
752             throw new GenericUncheckedException("Failed to retrieve homing data associated to vfModule from A&AI, VNF InstanceId or VF Module Id is missing.");
753         }
754         Response resp = doAaiGet("network/generic-vnfs/generic-vnf/" + vnfInstanceId +"/vf-modules/vf-module/"+ vfModuleId, false);
755         String responseAsString = parseForTenantsByServiceSubscription("vserver",resp.readEntity(String.class));
756         if (responseAsString.equals("")){
757             throw new GenericUncheckedException( String.format("A&AI has no homing data associated to vfModule '%s' of vnf '%s'", vfModuleId, vnfInstanceId));
758         }
759         else {
760             AaiResponse aaiResponse = processAaiResponse(resp, GetTenantsResponse[].class, responseAsString);
761             return ((GetTenantsResponse[])aaiResponse.getT())[0];
762         }
763     }
764
765     @Override
766     public void resetCache(String cacheName) {
767         cacheProvider.resetCache(cacheName);
768     }
769
770     String getCloudOwnerByCloudRegionIdNonCached(String cloudRegionId) {
771         String uri = "cloud-infrastructure/cloud-regions?cloud-region-id=" + encodePathSegment(cloudRegionId);
772
773         final CloudRegion.Collection cloudRegionCollection =
774                 typedAaiGet(Unchecked.toURI(uri), CloudRegion.Collection.class);
775
776         return cloudRegionCollection
777                 .getCloudRegions().stream()
778                 .map(CloudRegion::getCloudOwner)
779                 // from here we assure that the cloud owner is given, and not null
780                 // and non-empty, and that if more than one cloud-owner is given -
781                 // it is only a single value.
782                 // exception is thrown if none or more than a single values are
783                 // given.
784                 .filter(StringUtils::isNotEmpty)
785                 .distinct()
786                 .reduce((a, b) -> {
787                     // will be invoked only if distinct() leaves more than a single element
788                     throw new GenericUncheckedException("Conflicting cloud-owner found for " + cloudRegionId + ": '" + a + "' / '" + b + "'");
789                 })
790                 .orElseThrow(() -> new GenericUncheckedException("No cloud-owner found for " + cloudRegionId));
791     }
792
793     private AaiResponse getTenantsByKey(String key) {
794         String[] args = CacheProvider.decompileKey(key);
795         String globalCustomerId = safeGetFromArray(args, 0);
796         String serviceType = safeGetFromArray(args, 1);
797         return getTenantsNonCached(globalCustomerId, serviceType);
798     }
799
800     AaiResponse getTenantsNonCached(String globalCustomerId, String serviceType) {
801         String url = BUSINESS_CUSTOMERS_CUSTOMER + globalCustomerId + SERVICE_SUBSCRIPTIONS_PATH + serviceType;
802
803         Response resp = doAaiGet(url, false);
804         String responseAsString = parseForTenantsByServiceSubscription("tenant",resp.readEntity(String.class));
805         if (StringUtils.isEmpty(responseAsString)){
806            throw new ParsingGetTenantsResponseFailure(String.format("A&AI has no LCP Region & Tenants associated to subscriber '%s' and service type '%s'", globalCustomerId, serviceType));
807         }
808         else {
809             return processAaiResponse(resp, GetTenantsResponse[].class, responseAsString);
810         }
811     }
812
813     public static class ParsingGetTenantsResponseFailure extends GenericUncheckedException {
814
815         public ParsingGetTenantsResponseFailure(String message) {
816             super(message);
817         }
818     }
819
820     @NotNull
821     private AaiResponse<String> buildAaiResponseForGetTenantsFailure(String errorText) {
822         return new AaiResponse<>(null, String.format("{\"statusText\":\"%s\"}", errorText), HttpStatus.SC_INTERNAL_SERVER_ERROR);
823     }
824
825     private static String safeGetFromArray(String[] array, int i) {
826         if (i < 0 || i >= array.length) {
827             return null;
828         } else {
829             return array[i];
830         }
831     }
832 }