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