filter a&ai query to discard relationship list
[vid.git] / vid-app-common / src / main / java / org / onap / vid / aai / AaiClient.java
index e1a1e70..00137b6 100644 (file)
@@ -1,23 +1,91 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * VID
+ * ================================================================================
+ * Copyright (C) 2017 - 2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
 package org.onap.vid.aai;
 
+import static com.fasterxml.jackson.module.kotlin.ExtensionsKt.jacksonObjectMapper;
+import static java.util.Collections.emptyList;
+import static java.util.Comparator.comparing;
+import static java.util.stream.Collectors.toMap;
+import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
+import static org.apache.commons.lang3.StringUtils.equalsIgnoreCase;
+import static org.apache.commons.lang3.StringUtils.isEmpty;
+import static org.onap.vid.utils.KotlinUtilsKt.JACKSON_OBJECT_MAPPER;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.io.IOException;
+import java.io.Serializable;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URLEncoder;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import javax.inject.Inject;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.exception.ExceptionUtils;
 import org.apache.http.HttpStatus;
 import org.apache.http.client.utils.URIBuilder;
+import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 import org.json.simple.JSONArray;
 import org.json.simple.JSONObject;
 import org.json.simple.parser.JSONParser;
 import org.onap.portalsdk.core.logging.logic.EELFLoggerDelegate;
 import org.onap.vid.aai.exceptions.InvalidAAIResponseException;
 import org.onap.vid.aai.model.AaiGetAicZone.AicZones;
-import org.onap.vid.aai.model.*;
-import org.onap.vid.aai.model.AaiGetNetworkCollectionDetails.*;
+import org.onap.vid.aai.model.AaiGetInstanceGroupsByCloudRegion;
+import org.onap.vid.aai.model.AaiGetNetworkCollectionDetails.AaiGetNetworkCollectionDetails;
+import org.onap.vid.aai.model.AaiGetNetworkCollectionDetails.AaiGetNetworkCollectionDetailsHelper;
+import org.onap.vid.aai.model.AaiGetNetworkCollectionDetails.AaiGetRelatedInstanceGroupsByVnfId;
+import org.onap.vid.aai.model.AaiGetNetworkCollectionDetails.CloudRegion;
+import org.onap.vid.aai.model.AaiGetNetworkCollectionDetails.InstanceGroup;
+import org.onap.vid.aai.model.AaiGetNetworkCollectionDetails.Network;
 import org.onap.vid.aai.model.AaiGetOperationalEnvironments.OperationalEnvironmentList;
+import org.onap.vid.aai.model.AaiGetPnfResponse;
 import org.onap.vid.aai.model.AaiGetPnfs.Pnf;
 import org.onap.vid.aai.model.AaiGetServicesRequestModel.GetServicesAAIRespone;
 import org.onap.vid.aai.model.AaiGetTenatns.GetTenantsResponse;
+import org.onap.vid.aai.model.CustomQuerySimpleResult;
+import org.onap.vid.aai.model.GetServiceModelsByDistributionStatusResponse;
+import org.onap.vid.aai.model.LogicalLinkResponse;
+import org.onap.vid.aai.model.ModelVer;
+import org.onap.vid.aai.model.ModelVersions;
+import org.onap.vid.aai.model.OwningEntityResponse;
+import org.onap.vid.aai.model.PortDetailsTranslator;
+import org.onap.vid.aai.model.ProjectResponse;
+import org.onap.vid.aai.model.Properties;
+import org.onap.vid.aai.model.ResourceType;
+import org.onap.vid.aai.model.ServiceRelationships;
+import org.onap.vid.aai.model.SimpleResult;
 import org.onap.vid.aai.util.AAIRestInterface;
 import org.onap.vid.aai.util.CacheProvider;
 import org.onap.vid.aai.util.VidObjectMapperType;
@@ -31,31 +99,10 @@ import org.onap.vid.utils.Unchecked;
 import org.springframework.http.HttpMethod;
 import org.springframework.web.util.UriUtils;
 
-import javax.inject.Inject;
-import javax.ws.rs.WebApplicationException;
-import javax.ws.rs.core.Response;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.net.URI;
-import java.net.URLEncoder;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-import java.util.function.Function;
-
-import static java.util.Collections.emptyList;
-import static java.util.stream.Collectors.toMap;
-import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
-
-/**
-
- * Created by Oren on 7/4/17.
- */
 public class AaiClient implements AaiClientInterface {
 
 
-    private static final String QUERY_FORMAT_RESOURCE = "query?format=resource";
+    public static final String QUERY_FORMAT_RESOURCE = "query?format=resource";
     private static final String SERVICE_SUBSCRIPTIONS_PATH = "/service-subscriptions/service-subscription/";
     private static final String MODEL_INVARIANT_ID = "&model-invariant-id=";
     private static final String QUERY_FORMAT_SIMPLE = "query?format=simple";
@@ -71,14 +118,14 @@ public class AaiClient implements AaiClientInterface {
 
     private final CacheProvider cacheProvider;
 
-    ObjectMapper objectMapper = new ObjectMapper();
+    ObjectMapper objectMapper = jacksonObjectMapper();
 
     /**
      * The logger
      */
     EELFLoggerDelegate logger = EELFLoggerDelegate.getLogger(AaiClient.class);
 
-    private static final String GET_SERVICE_MODELS_RESPONSE_BODY = "{\"start\" : \"service-design-and-creation/models/\", \"query\" : \"query/serviceModels-byDistributionStatus?distributionStatus=DISTRIBUTION_COMPLETE_OK\"}";
+    private static final String GET_SERVICE_MODELS_REQUEST_BODY = "{\"start\" : \"service-design-and-creation/models/\", \"query\" : \"query/serviceModels-byDistributionStatus?distributionStatus=DISTRIBUTION_COMPLETE_OK\"}";
 
     @Inject
     public AaiClient(AAIRestInterface restController, PortDetailsTranslator portDetailsTranslator, CacheProvider cacheProvider) {
@@ -97,13 +144,13 @@ public class AaiClient implements AaiClientInterface {
     }
 
     @Override
-    public AaiResponse getServicesByOwningEntityId(List<String> owningEntityIds){
+    public AaiResponse<OwningEntityResponse> getServicesByOwningEntityId(List<String> owningEntityIds){
         Response resp = doAaiGet(getUrlFromLIst("business/owning-entities?", "owning-entity-id=", owningEntityIds), false);
         return processAaiResponse(resp, OwningEntityResponse.class, null);
     }
 
     @Override
-    public AaiResponse getServicesByProjectNames(List<String> projectNames){
+    public AaiResponse<ProjectResponse> getServicesByProjectNames(List<String> projectNames){
         Response resp = doAaiGet(getUrlFromLIst("business/projects?", "project-name=",  projectNames), false);
         return processAaiResponse(resp, ProjectResponse.class, null);
     }
@@ -116,7 +163,7 @@ public class AaiClient implements AaiClientInterface {
 
     private AaiResponse getServiceModelsByDistributionStatusNonCached(boolean propagateExceptions) {
         GetServiceModelsByDistributionStatusResponse response = typedAaiRest(QUERY_FORMAT_RESOURCE, GetServiceModelsByDistributionStatusResponse.class,
-                GET_SERVICE_MODELS_RESPONSE_BODY, HttpMethod.PUT, propagateExceptions);
+                GET_SERVICE_MODELS_REQUEST_BODY, HttpMethod.PUT, propagateExceptions);
         return new AaiResponse(response, "", HttpStatus.SC_OK);
     }
 
@@ -158,6 +205,7 @@ public class AaiClient implements AaiClientInterface {
                 return new AaiResponse(e.getCause(), "AAI response parsing Error" , HttpStatus.SC_INTERNAL_SERVER_ERROR);
             }
             catch (Exception e) {
+                logger.error(EELFLoggerDelegate.errorLogger,"Exception in aai response parsing", e);
                 return new AaiResponse(e.getCause(), "Got " + aaiResponse.getHttpCode() + " from a&ai" , aaiResponse.getHttpCode());
             }
         }
@@ -213,7 +261,7 @@ public class AaiClient implements AaiClientInterface {
 
     @Override
     public boolean isNodeTypeExistsByName(String name, ResourceType type) {
-        if (StringUtils.isEmpty(name)) {
+        if (isEmpty(name)) {
             throw new GenericUncheckedException("Empty resource-name provided to searchNodeTypeByName; request is rejected as this will cause full resources listing");
         }
 
@@ -241,6 +289,60 @@ public class AaiClient implements AaiClientInterface {
                 .collect(toMap(SimpleResult::getNodeType, SimpleResult::getProperties));
     }
 
+    @Override
+    public AaiResponse<AaiGetVnfResponse> getVnfsByParamsForChangeManagement(String subscriberId, String serviceType, @Nullable String nfRole,
+        @Nullable String cloudRegion) {
+        String payloadAsString = "";
+        ResponseWithRequestInfo response;
+        ImmutableMap<String, Serializable> payload = getMapForAAIQueryByParams(subscriberId, serviceType,
+            nfRole, cloudRegion);
+        try {
+            payloadAsString = JACKSON_OBJECT_MAPPER.writeValueAsString(payload);
+        } catch (JsonProcessingException e) {
+            logger.error(e.getMessage());
+            ExceptionUtils.rethrow(e);
+        }
+        response = doAaiPut(QUERY_FORMAT_SIMPLE, payloadAsString, false, false);
+        AaiResponseWithRequestInfo<AaiGetVnfResponse> aaiResponse = processAaiResponse(response, AaiGetVnfResponse.class, false);
+        verifyAaiResponseValidityOrThrowExc(aaiResponse, aaiResponse.getAaiResponse().getHttpCode());
+        return aaiResponse.getAaiResponse();
+    }
+
+    private ImmutableMap<String, Serializable> getMapForAAIQueryByParams(String subscriberId,
+        String serviceType, @Nullable String nfRole, @Nullable String cloudRegion) {
+        // in a case cloudRegion is null using query/vnfs-fromServiceInstance-filter,
+        // otherwise using query/vnfs-fromServiceInstance-filterByCloudRegion
+        if (nfRole != null){
+            if (cloudRegion != null){
+                return ImmutableMap.of(
+                    "start", ImmutableList
+                        .of("/business/customers/customer/" + encodePathSegment(subscriberId) + "/service-subscriptions/service-subscription/" + encodePathSegment(serviceType) + "/service-instances"),
+                    "query",  "query/vnfs-fromServiceInstance-filterByCloudRegion?nfRole=" + nfRole + "&cloudRegionID=" + cloudRegion + ""
+                );
+            }else {
+                return ImmutableMap.of(
+                    "start", ImmutableList
+                        .of("/business/customers/customer/" + encodePathSegment(subscriberId) + "/service-subscriptions/service-subscription/" + encodePathSegment(serviceType) + "/service-instances"),
+                    "query",  "query/vnfs-fromServiceInstance-filter?nfRole=" + nfRole + ""
+                );
+            }
+        }
+
+        if (cloudRegion != null){
+            return ImmutableMap.of(
+                "start", ImmutableList
+                    .of("/business/customers/customer/" + encodePathSegment(subscriberId) + "/service-subscriptions/service-subscription/" + encodePathSegment(serviceType) + "/service-instances"),
+                "query",  "query/vnfs-fromServiceInstance-filterByCloudRegion?cloudRegionID=" + cloudRegion + ""
+            );
+        }
+
+        return ImmutableMap.of(
+            "start", ImmutableList
+                .of("/business/customers/customer/" + encodePathSegment(subscriberId) + "/service-subscriptions/service-subscription/" + encodePathSegment(serviceType) + "/service-instances"),
+            "query", "query/vnfs-fromServiceInstance-filter"
+        );
+    }
+
     private boolean isResourceExistByStatusCode(ResponseWithRequestInfo responseWithRequestInfo) {
         // 200 - is found
         // 404 - resource not found
@@ -275,19 +377,20 @@ public class AaiClient implements AaiClientInterface {
         }
 
         final AaiResponseWithRequestInfo<T> aaiResponse = processAaiResponse(responseWithRequestInfo, clz, VidObjectMapperType.FASTERXML, true);
+        verifyAaiResponseValidityOrThrowExc(aaiResponse, responseWithRequestInfo.getResponse().getStatus());
+        return aaiResponse.getAaiResponse().getT();
+    }
 
+    private void verifyAaiResponseValidityOrThrowExc(AaiResponseWithRequestInfo aaiResponse, int httpCode) {
         if (aaiResponse.getAaiResponse().getHttpCode() > 399 || aaiResponse.getAaiResponse().getT() == null) {
             throw new ExceptionWithRequestInfo(aaiResponse.getHttpMethod(),
-                    aaiResponse.getRequestedUrl(),
-                    aaiResponse.getRawData(),
-                    responseWithRequestInfo.getResponse().getStatus(),
-                    new InvalidAAIResponseException(aaiResponse.getAaiResponse()));
+                aaiResponse.getRequestedUrl(),
+                aaiResponse.getRawData(),
+                httpCode,
+                new InvalidAAIResponseException(aaiResponse.getAaiResponse()));
         }
-
-        return aaiResponse.getAaiResponse().getT();
     }
 
-
     private String getUrlFromLIst(String url, String paramKey, List<String> params){
         int i = 0;
         for(String param: params){
@@ -298,8 +401,8 @@ public class AaiClient implements AaiClientInterface {
                 encodedParam= URLEncoder.encode(param, "UTF-8");
             } catch (UnsupportedEncodingException e) {
                 String methodName = "getUrlFromList";
-                logger.error(EELFLoggerDelegate.errorLogger, methodName + e.toString());
-                logger.debug(EELFLoggerDelegate.debugLogger, methodName + e.toString());
+                logger.error(EELFLoggerDelegate.errorLogger, methodName, e);
+                logger.debug(EELFLoggerDelegate.debugLogger, methodName, e);
             }
             url = url.concat(encodedParam);
             if(i != params.size()){
@@ -357,7 +460,7 @@ public class AaiClient implements AaiClientInterface {
     }
 
     @Override
-    public AaiResponse getVNFData(String globalSubscriberId, String serviceType) {
+    public AaiResponse<AaiGetVnfResponse> getVNFData(String globalSubscriberId, String serviceType) {
         String payload = "{\"start\": [\"business/customers/customer/" + globalSubscriberId + SERVICE_SUBSCRIPTIONS_PATH + encodePathSegment(serviceType) +"/service-instances\"]," +
                 "\"query\": \"query/vnf-topology-fromServiceInstance\"}";
         Response resp = doAaiPut(QUERY_FORMAT_SIMPLE, payload, false);
@@ -383,14 +486,71 @@ public class AaiClient implements AaiClientInterface {
             sb.append(id);
 
         }
-        return doAaiGet("service-design-and-creation/models?depth=2"+ sb.toString(), false);
+        return doAaiGet("service-design-and-creation/models?depth=2" + sb.toString(), false);
+    }
+
+    @Override
+    public ModelVer getLatestVersionByInvariantId(String modelInvariantId) {
+        return maxModelVer(getAllVersionsByInvariantId(modelInvariantId));
     }
 
     @Override
-    public AaiResponse getSubscriberData(String subscriberId) {
-        String depth = "2";
-        AaiResponse subscriberDataResponse;
-        Response resp = doAaiGet(BUSINESS_CUSTOMERS_CUSTOMER + subscriberId + "?depth=" + depth, false);
+    public List<ModelVer> getSortedVersionsByInvariantId(String modelInvariantId) {
+        return sortedModelVer(getAllVersionsByInvariantId(modelInvariantId));
+    }
+
+    private Stream<ModelVer> getAllVersionsByInvariantId(String modelInvariantId) {
+        if (modelInvariantId.isEmpty()) {
+            throw new GenericUncheckedException("no invariant-id provided to getLatestVersionByInvariantId; request is rejected");
+        }
+
+        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);
+        AaiResponse<ModelVersions> aaiResponse = processAaiResponse(response, ModelVersions.class, null, VidObjectMapperType.FASTERXML);
+
+        return toModelVerStream(aaiResponse.getT());
+    }
+
+    protected Stream<ModelVer> toModelVerStream(ModelVersions modelVersions) {
+        if (modelVersions == null)
+            return null;
+
+        return Stream.of(modelVersions)
+                .map(ModelVersions::getResults)
+                .flatMap(java.util.Collection::stream)
+                .flatMap(map -> map.entrySet().stream())
+                .filter(kv -> StringUtils.equals(kv.getKey(), "model-ver"))
+                .map(Map.Entry::getValue);
+
+    }
+
+    protected ModelVer maxModelVer(Stream<ModelVer> modelVerStream) {
+        if (modelVerStream == null)
+            return null;
+
+        return modelVerStream
+                .filter(modelVer -> StringUtils.isNotEmpty(modelVer.getModelVersion()))
+                .max(comparing(ModelVer::getModelVersion, comparing(DefaultArtifactVersion::new)))
+                .orElseThrow(() -> new GenericUncheckedException("Could not find any version"));
+    }
+
+    protected List<ModelVer> sortedModelVer(Stream<ModelVer> modelVerStream) {
+        if (modelVerStream == null)
+            return emptyList();
+
+        return modelVerStream
+            .sorted(comparing(ModelVer::getModelVersion, comparing(DefaultArtifactVersion::new)).reversed())
+            .collect(Collectors.toList());
+    }
+
+    @Override
+    public AaiResponse<Services> getSubscriberData(String subscriberId, boolean omitServiceInstances) {
+        String depth = omitServiceInstances ? "1" : "2";
+        AaiResponse<Services> subscriberDataResponse;
+       String query = depth.equals("1") ?
+                BUSINESS_CUSTOMERS_CUSTOMER + subscriberId + "?depth=" + depth :
+                BUSINESS_CUSTOMERS_CUSTOMER + subscriberId + "?depth=" + depth +"&nodes-only";
+                               
+        Response resp = doAaiGet(query, false);
         subscriberDataResponse = processAaiResponse(resp, Services.class, null);
         return subscriberDataResponse;
     }
@@ -416,7 +576,7 @@ public class AaiClient implements AaiClientInterface {
 
     @Override
     public AaiResponse getTenants(String globalCustomerId, String serviceType) {
-        if ((globalCustomerId == null || globalCustomerId.isEmpty()) || ((serviceType == null) || (serviceType.isEmpty())) ){
+        if ((globalCustomerId == null || globalCustomerId.isEmpty()) || ((serviceType == null) || (serviceType.isEmpty()))){
             return buildAaiResponseForGetTenantsFailure(" Failed to retrieve LCP Region & Tenants from A&AI, Subscriber ID or Service Type is missing.");
         }
         try {
@@ -476,7 +636,7 @@ public class AaiClient implements AaiClientInterface {
         }
     }
 
-    private AaiResponse processAaiResponse(Response resp, Class classType, String responseBody) {
+    private <T> AaiResponse<T> processAaiResponse(Response resp, Class<? extends T> classType, String responseBody) {
         return processAaiResponse(resp, classType, responseBody, VidObjectMapperType.CODEHAUS);
     }
 
@@ -677,7 +837,7 @@ public class AaiClient implements AaiClientInterface {
 
         String propKey = checkForNull((String) innerObj.get("property-key"));
         String propVal = checkForNull((String) innerObj.get("property-value"));
-        if (propKey.equalsIgnoreCase("tenant.tenant-name")) {
+        if (equalsIgnoreCase(propKey, "tenant.tenant-name")) {
             tenantNewObj.put("tenantName", propVal);
         }
     }
@@ -688,25 +848,21 @@ public class AaiClient implements AaiClientInterface {
 
         String rShipKey = checkForNull((String) inner2Obj.get("relationship-key"));
         String rShipVal = checkForNull((String) inner2Obj.get("relationship-value"));
-        if (rShipKey.equalsIgnoreCase("cloud-region.cloud-owner")) {
+        if (equalsIgnoreCase(rShipKey, "cloud-region.cloud-owner")) {
             tenantNewObj.put("cloudOwner", rShipVal);
-        } else if (rShipKey.equalsIgnoreCase("cloud-region.cloud-region-id")) {
+        } else if (equalsIgnoreCase(rShipKey, "cloud-region.cloud-region-id")) {
             tenantNewObj.put("cloudRegionID", rShipVal);
-        } else if (rShipKey.equalsIgnoreCase("tenant.tenant-id")) {
+        } else if (equalsIgnoreCase(rShipKey, "tenant.tenant-id")) {
             tenantNewObj.put("tenantID", rShipVal);
         }
     }
 
     private static String encodePathSegment(String segmentToEncode) {
-        try {
-            return UriUtils.encodePathSegment(segmentToEncode, "UTF-8");
-        } catch (UnsupportedEncodingException e) {
-            throw new GenericUncheckedException("URI encoding failed unexpectedly", e);
-        }
+        return UriUtils.encodePathSegment(segmentToEncode, "UTF-8");
     }
 
     @Override
-    public ExternalComponentStatus probeAaiGetAllSubscribers(){
+    public ExternalComponentStatus probeComponent(){
         long startTime = System.currentTimeMillis();
         try {
             AaiResponseWithRequestInfo<SubscriberList> responseWithRequestInfo = getAllSubscribers(true);
@@ -748,12 +904,12 @@ public class AaiClient implements AaiClientInterface {
     @Override
     public GetTenantsResponse getHomingDataByVfModule(String vnfInstanceId, String vfModuleId) {
 
-        if (StringUtils.isEmpty(vnfInstanceId)||StringUtils.isEmpty(vfModuleId)){
+        if (isEmpty(vnfInstanceId)|| isEmpty(vfModuleId)){
             throw new GenericUncheckedException("Failed to retrieve homing data associated to vfModule from A&AI, VNF InstanceId or VF Module Id is missing.");
         }
         Response resp = doAaiGet("network/generic-vnfs/generic-vnf/" + vnfInstanceId +"/vf-modules/vf-module/"+ vfModuleId, false);
         String responseAsString = parseForTenantsByServiceSubscription("vserver",resp.readEntity(String.class));
-        if (responseAsString.equals("")){
+        if (isEmpty(responseAsString)){
             throw new GenericUncheckedException( String.format("A&AI has no homing data associated to vfModule '%s' of vnf '%s'", vfModuleId, vnfInstanceId));
         }
         else {
@@ -802,7 +958,7 @@ public class AaiClient implements AaiClientInterface {
 
         Response resp = doAaiGet(url, false);
         String responseAsString = parseForTenantsByServiceSubscription("tenant",resp.readEntity(String.class));
-        if (StringUtils.isEmpty(responseAsString)){
+        if (isEmpty(responseAsString)){
            throw new ParsingGetTenantsResponseFailure(String.format("A&AI has no LCP Region & Tenants associated to subscriber '%s' and service type '%s'", globalCustomerId, serviceType));
         }
         else {
@@ -829,4 +985,4 @@ public class AaiClient implements AaiClientInterface {
             return array[i];
         }
     }
-}
\ No newline at end of file
+}