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