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