add single transaction api to aaiclient 29/67029/1
authorBenjamin, Max (mb388a) <mb388a@us.att.com>
Mon, 17 Sep 2018 14:52:09 +0000 (10:52 -0400)
committerBenjamin, Max (mb388a) <mb388a@us.att.com>
Mon, 17 Sep 2018 14:54:05 +0000 (10:54 -0400)
fixed marshalling when issuing a patch request
added support for single transaction api in A&AI

Change-Id: Icf755f547523cc7dbf931e198177847a5a1c6ea1
Issue-ID: SO-1060
Signed-off-by: Benjamin, Max (mb388a) <mb388a@us.att.com>
22 files changed:
adapters/mso-openstack-adapters/src/main/java/org/onap/so/adapters/network/async/client/ObjectFactory.java
common/src/main/java/org/onap/so/client/RestClient.java
common/src/main/java/org/onap/so/client/aai/AAIObjectType.java
common/src/main/java/org/onap/so/client/aai/AAIPatchConverter.java [new file with mode: 0644]
common/src/main/java/org/onap/so/client/aai/AAIResourcesClient.java
common/src/main/java/org/onap/so/client/aai/AAIRestClient.java
common/src/main/java/org/onap/so/client/aai/AAISingleTransactionClient.java [new file with mode: 0644]
common/src/main/java/org/onap/so/client/aai/AAITransactionalClient.java
common/src/main/java/org/onap/so/client/aai/entities/bulkprocess/OperationBody.java
common/src/main/java/org/onap/so/client/aai/entities/bulkprocess/OperationBodySerializer.java [new file with mode: 0644]
common/src/main/java/org/onap/so/client/aai/entities/singletransaction/OperationBodyRequest.java [new file with mode: 0644]
common/src/main/java/org/onap/so/client/aai/entities/singletransaction/OperationBodyRequestSerializer.java [new file with mode: 0644]
common/src/main/java/org/onap/so/client/aai/entities/singletransaction/OperationBodyResponse.java [new file with mode: 0644]
common/src/main/java/org/onap/so/client/aai/entities/singletransaction/SingleTransactionRequest.java [new file with mode: 0644]
common/src/main/java/org/onap/so/client/aai/entities/singletransaction/SingleTransactionResponse.java [new file with mode: 0644]
common/src/test/java/org/onap/so/client/aai/AAIPatchConverterTest.java [new file with mode: 0644]
common/src/test/java/org/onap/so/client/aai/AAIRestClientTest.java
common/src/test/java/org/onap/so/client/aai/AAISingleTransactionClientTest.java [new file with mode: 0644]
common/src/test/java/org/onap/so/client/aai/AAITransactionalClientTest.java
common/src/test/resources/__files/aai/singletransaction/sample-request.json [new file with mode: 0644]
common/src/test/resources/__files/aai/singletransaction/sample-response-failure.json [new file with mode: 0644]
common/src/test/resources/__files/aai/singletransaction/sample-response.json [new file with mode: 0644]

index d65cdc4..f2238fc 100644 (file)
@@ -4,7 +4,6 @@
  * ================================================================================
  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
  * ================================================================================
- * Modifications Copyright 2018 IBM.
  * 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
index 631850a..1a453c6 100644 (file)
@@ -271,7 +271,7 @@ public abstract class RestClient {
                return format(method("DELETE", obj), resultClass).orElse(null);
        }
        
-       private Response method(String method, Object entity) {
+       public Response method(String method, Object entity) {
                RetryPolicy policy = new RetryPolicy();
                
                List<Predicate<Throwable>> items = retryOn();
index a5d8f12..4b646f9 100644 (file)
@@ -27,6 +27,7 @@ import org.onap.aai.annotations.Metadata;
 import org.onap.aai.domain.yang.AllottedResource;
 import org.onap.aai.domain.yang.CloudRegion;
 import org.onap.aai.domain.yang.Collection;
+import org.onap.aai.domain.yang.Complex;
 import org.onap.aai.domain.yang.Configuration;
 import org.onap.aai.domain.yang.Customer;
 import org.onap.aai.domain.yang.GenericVnf;
@@ -64,6 +65,7 @@ public enum AAIObjectType implements GraphInventoryObjectType {
        CUSTOMER(AAINamespaceConstants.BUSINESS, Customer.class),
        GENERIC_QUERY("/search", "/generic-query"),
        BULK_PROCESS("/bulkprocess", ""),
+       SINGLE_TRANSACTION("/bulk/single-transaction", ""),
        GENERIC_VNF(AAINamespaceConstants.NETWORK, GenericVnf.class),
        VF_MODULE(AAIObjectType.GENERIC_VNF.uriTemplate(), VfModule.class),
        L3_NETWORK(AAINamespaceConstants.NETWORK, L3Network.class),
@@ -98,6 +100,7 @@ public enum AAIObjectType implements GraphInventoryObjectType {
        COLLECTION(AAINamespaceConstants.NETWORK, Collection.class),
        VNFC(AAINamespaceConstants.NETWORK, Vnfc.class),
        VLAN_TAG(AAINamespaceConstants.NETWORK, VlanTag.class),
+       COMPLEX(AAINamespaceConstants.CLOUD_INFRASTRUCTURE, Complex.class),
        UNKNOWN("", "");
 
        private final String uriTemplate;
diff --git a/common/src/main/java/org/onap/so/client/aai/AAIPatchConverter.java b/common/src/main/java/org/onap/so/client/aai/AAIPatchConverter.java
new file mode 100644 (file)
index 0000000..6ccb592
--- /dev/null
@@ -0,0 +1,81 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 - 2018 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.so.client.aai;
+
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import org.onap.so.client.graphinventory.exceptions.GraphInventoryPatchDepthExceededException;
+import org.onap.so.jsonpath.JsonPathUtil;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+
+public class AAIPatchConverter {
+
+       private static final AAICommonObjectMapperProvider standardProvider = new AAICommonObjectMapperProvider();
+       private static final AAICommonObjectMapperPatchProvider patchProvider = new AAICommonObjectMapperPatchProvider();
+       private static final Pattern LOCATE_COMPLEX_OBJECT = Pattern.compile("^((?!relationship-list).)+?\\['[^\\[\\]]+?'\\]$");
+
+       
+       protected String convertPatchFormat(Object obj) {
+               return validatePatchObject(marshallObjectToPatchFormat(obj));
+       }
+       
+       protected String validatePatchObject(String payload) {
+               if (hasComplexObject(payload)) {
+                       throw new GraphInventoryPatchDepthExceededException(payload);
+               }
+               
+               return payload;
+       }
+       
+       /** validates client side that json does not include any complex objects
+        * relationship-list is omitted from this validation
+        */
+       protected boolean hasComplexObject(String json) {
+               if (json.isEmpty()) {
+                       return false;
+               }
+               String complex = "$.*.*";
+               String array = "$.*.*.*";
+               List<String> result = JsonPathUtil.getInstance().getPathList(json, complex);
+               List<String> result2 = JsonPathUtil.getInstance().getPathList(json, array);
+               
+               result.addAll(result2);
+               return result.stream().anyMatch(item -> LOCATE_COMPLEX_OBJECT.matcher(item).find());
+       }
+       
+       protected String marshallObjectToPatchFormat(Object obj) {
+               Object value = obj;
+               try {
+                       if (!(obj instanceof Map || obj instanceof String)) {
+                               value = patchProvider.getMapper().writeValueAsString(obj);
+                       } else if (obj instanceof Map) {
+                               value = standardProvider.getMapper().writeValueAsString(obj);
+                       }
+               } catch (JsonProcessingException e) {
+                       value = "{}";
+               }
+               
+               return (String)value;
+       }
+}
index 072534d..7e4397e 100644 (file)
@@ -309,6 +309,15 @@ public class AAIResourcesClient extends AAIClient {
                return new AAITransactionalClient(this.getVersion());
        }
        
+       /**
+        * Starts a transaction groups multiple A&AI mutations
+        * 
+        * @return
+        */
+       public AAISingleTransactionClient beginSingleTransaction() {
+               return new AAISingleTransactionClient(this.getVersion());
+       }
+       
        private AAIUri addParams(Optional<Depth> depth, boolean nodesOnly, AAIUri uri) {
                AAIUri clone = uri.clone();
                if (depth.isPresent()) {
index 2bd5f11..ac6e939 100644 (file)
@@ -20,6 +20,8 @@
 
 package org.onap.so.client.aai;
 
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+
 import java.net.URI;
 import java.util.List;
 import java.util.Map;
@@ -41,9 +43,9 @@ public class AAIRestClient extends RestClientSSL {
 
        private final AAIProperties aaiProperties;
        private static final AAICommonObjectMapperProvider standardProvider = new AAICommonObjectMapperProvider();
-       private static final AAICommonObjectMapperPatchProvider patchProvider = new AAICommonObjectMapperPatchProvider();
-       private static final Pattern LOCATE_COMPLEX_OBJECT = Pattern.compile("^((?!relationship-list).)+?\\['[^\\[\\]]+?'\\]$");
 
+       private final AAIPatchConverter patchConverter = new AAIPatchConverter();
+       
        protected AAIRestClient(AAIProperties props, URI uri) {
                super(props, Optional.of(uri));
                this.aaiProperties = props;
@@ -79,53 +81,20 @@ public class AAIRestClient extends RestClientSSL {
 
        @Override
        public Response patch(Object obj) {
-               String value = convertObjectToPatchFormat(obj);
-               validatePatchObject(value);
-               return super.patch(value);
+               return super.patch(convertToPatchFormat(obj));
        }
 
        @Override
        public <T> T patch(Object obj, Class<T> resultClass) {
-               String value = convertObjectToPatchFormat(obj);
-               validatePatchObject(value);
-               return super.patch(value, resultClass);
+               return super.patch(convertToPatchFormat(obj), resultClass);
        }
        
-       protected String convertObjectToPatchFormat(Object obj) {
-               Object value = obj;
-               try {
-                       if (!(obj instanceof Map || obj instanceof String)) {
-                               value = patchProvider.getMapper().writeValueAsString(obj);
-                       } else if (obj instanceof Map) {
-                               value = standardProvider.getMapper().writeValueAsString(obj);
-                       }
-               } catch (JsonProcessingException e) {
-                       value = "{}";
-               }
-               
-               return (String)value;
+       protected AAIPatchConverter getPatchConverter() {
+               return this.patchConverter;
        }
        
-       
-       protected void validatePatchObject(String payload) {
-               if (hasComplexObject(payload)) {
-                       throw new GraphInventoryPatchDepthExceededException(payload);
-               }
-       }
-       
-       /** validates client side that json does not include any complex objects
-        * relationship-list is omitted from this validation
-        */
-       protected boolean hasComplexObject(String json) {
-               if (json.isEmpty()) {
-                       return false;
-               }
-               String complex = "$.*.*";
-               String array = "$.*.*.*";
-               List<String> result = JsonPathUtil.getInstance().getPathList(json, complex);
-               List<String> result2 = JsonPathUtil.getInstance().getPathList(json, array);
-               
-               result.addAll(result2);
-               return result.stream().anyMatch(item -> LOCATE_COMPLEX_OBJECT.matcher(item).find());
+       protected String convertToPatchFormat(Object obj) {
+               return getPatchConverter().convertPatchFormat(obj);
        }
+
 }
diff --git a/common/src/main/java/org/onap/so/client/aai/AAISingleTransactionClient.java b/common/src/main/java/org/onap/so/client/aai/AAISingleTransactionClient.java
new file mode 100644 (file)
index 0000000..2ecdb7c
--- /dev/null
@@ -0,0 +1,267 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 - 2018 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.so.client.aai;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.core.GenericType;
+
+import org.onap.aai.domain.yang.Relationship;
+import org.onap.so.client.RestClient;
+import org.onap.so.client.aai.entities.AAIEdgeLabel;
+import org.onap.so.client.aai.entities.AAIError;
+import org.onap.so.client.aai.entities.bulkprocess.Transactions;
+import org.onap.so.client.aai.entities.singletransaction.OperationBodyRequest;
+import org.onap.so.client.aai.entities.singletransaction.OperationBodyResponse;
+import org.onap.so.client.aai.entities.singletransaction.SingleTransactionRequest;
+import org.onap.so.client.aai.entities.singletransaction.SingleTransactionResponse;
+import org.onap.so.client.aai.entities.uri.AAIResourceUri;
+import org.onap.so.client.aai.entities.uri.AAIUri;
+import org.onap.so.client.aai.entities.uri.AAIUriFactory;
+import org.onap.so.client.graphinventory.exceptions.BulkProcessFailed;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.base.Joiner;
+
+public class AAISingleTransactionClient extends AAIClient {
+
+       private final SingleTransactionRequest request;
+       private final AAIVersion version;
+       private int actionCount = 0;
+       
+       private final AAIPatchConverter patchConverter = new AAIPatchConverter();
+       
+       protected AAISingleTransactionClient(AAIVersion version) {
+               super();
+               this.version = version;
+               this.request = new SingleTransactionRequest();
+       }
+
+       /**
+        * creates a new object in A&AI
+        * 
+        * @param obj - can be any object which will marshal into a valid A&AI payload
+        * @param uri
+        * @return
+        */
+       public AAISingleTransactionClient create(AAIResourceUri uri, Object obj) {
+               request.getOperations().add(new OperationBodyRequest().withAction("put").withUri(uri.build().toString()).withBody(obj));
+               incrementActionAmount();
+               return this;
+       }
+
+       /**
+        * creates a new object in A&AI with no payload body
+        * 
+        * @param uri
+        * @return
+        */
+       public AAISingleTransactionClient createEmpty(AAIResourceUri uri) {
+               request.getOperations().add(new OperationBodyRequest().withAction("put").withUri(uri.build().toString()).withBody(new HashMap<String, String>()));
+               incrementActionAmount();
+               return this;
+       }
+
+       /**
+        * Adds a relationship between two objects in A&AI 
+        * @param uriA
+        * @param uriB
+        * @return
+        */
+       public AAISingleTransactionClient connect(AAIResourceUri uriA, AAIResourceUri uriB) {
+               AAIResourceUri uriAClone = uriA.clone();
+               request.getOperations().add(new OperationBodyRequest().withAction("put").withUri(uriAClone.relationshipAPI().build().toString()).withBody(this.buildRelationship(uriB)));
+               incrementActionAmount();
+               return this;
+       }
+
+       /**
+        * relationship between multiple objects in A&AI - connects A to all objects specified in list
+        * 
+        * @param uriA
+        * @param uris
+        * @return
+        */
+       public AAISingleTransactionClient connect(AAIResourceUri uriA, List<AAIResourceUri> uris) {
+               for (AAIResourceUri uri : uris) {
+                       this.connect(uriA, uri);
+               }
+               return this;
+       }
+       
+       public AAISingleTransactionClient connect(AAIResourceUri uriA, AAIResourceUri uriB, AAIEdgeLabel label) {
+               AAIResourceUri uriAClone = uriA.clone();
+               RestClient aaiRC = this.createClient(uriAClone.relationshipAPI());
+               aaiRC.put(this.buildRelationship(uriB, label));
+               return this;
+       }
+       
+       public AAISingleTransactionClient connect(AAIResourceUri uriA, List<AAIResourceUri> uris, AAIEdgeLabel label) {
+               for (AAIResourceUri uri : uris) {
+                       this.connect(uriA, uri, label);
+               }
+               return this;
+       }
+
+       /**
+        * Removes relationship from two objects in A&AI
+        * 
+        * @param uriA
+        * @param uriB
+        * @return
+        */
+       public AAISingleTransactionClient disconnect(AAIResourceUri uriA, AAIResourceUri uriB) {
+               AAIResourceUri uriAClone = uriA.clone();
+               request.getOperations().add(new OperationBodyRequest().withAction("delete").withUri(uriAClone.relationshipAPI().build().toString()).withBody(this.buildRelationship(uriB)));
+               incrementActionAmount();
+               return this;
+       }
+
+       /**
+        * Removes relationship from multiple objects - disconnects A from all objects specified in list
+        * @param uriA
+        * @param uris
+        * @return
+        */
+       public AAISingleTransactionClient disconnect(AAIResourceUri uriA, List<AAIResourceUri> uris) {
+               for (AAIResourceUri uri : uris) {
+                       this.disconnect(uriA, uri);
+               }
+               return this;
+       }
+       /**
+        * Deletes object from A&AI. Automatically handles resource-version.
+        * 
+        * @param uri
+        * @return
+        */
+       public AAISingleTransactionClient delete(AAIResourceUri uri) {
+               AAIResourcesClient client = new AAIResourcesClient();
+               AAIResourceUri clone = uri.clone();
+               Map<String, Object> result = client.get(new GenericType<Map<String, Object>>(){}, clone)
+                               .orElseThrow(() -> new NotFoundException(clone.build() + " does not exist in A&AI"));
+               String resourceVersion = (String) result.get("resource-version");
+               request.getOperations().add(new OperationBodyRequest().withAction("delete").withUri(clone.resourceVersion(resourceVersion).build().toString()).withBody(""));
+               incrementActionAmount();
+               return this;
+       }
+
+       /**
+        * @param obj - can be any object which will marshal into a valid A&AI payload
+        * @param uri
+        * @return
+        */
+       public AAISingleTransactionClient update(AAIResourceUri uri, Object obj) {
+               
+               final String payload = getPatchConverter().convertPatchFormat(obj);
+               request.getOperations().add(new OperationBodyRequest().withAction("patch").withUri(uri.build().toString()).withBody(payload));
+               incrementActionAmount();
+               return this;
+       }
+
+       private void incrementActionAmount() {
+               actionCount++;
+       }
+       /**
+        * Executes all created transactions in A&AI
+        * @throws BulkProcessFailed 
+        */
+       public void execute() throws BulkProcessFailed {
+               RestClient client = this.createClient(AAIUriFactory.createResourceUri(AAIObjectType.SINGLE_TRANSACTION));
+               try {
+                       SingleTransactionResponse response = client.post(this.request, SingleTransactionResponse.class);
+                       if (response != null) {
+                               final Optional<String> errorMessage = this.locateErrorMessages(response);
+                               if (errorMessage.isPresent()) {
+                                       throw new BulkProcessFailed("One or more transactions failed in A&AI. Check logs for payloads.\nMessages:\n" + errorMessage.get());
+                               }
+                       } else {
+                               throw new BulkProcessFailed("Transactions acccepted by A&AI, but there was no response. Unsure of result.");
+                       }
+               } finally {
+                       this.request.getOperations().clear();
+                       this.actionCount = 0;
+               }
+       }
+
+       protected Optional<String> locateErrorMessages(SingleTransactionResponse response) {
+               final List<String> errorMessages = new ArrayList<>();
+               final ObjectMapper mapper = new ObjectMapper();
+               
+               for (OperationBodyResponse body : response.getOperationResponses()) {
+                       if (Optional.ofNullable(body.getResponseStatusCode()).orElse(400) > 300) {
+                               AAIError error;
+                               try {
+                                       error = mapper.readValue(mapper.writeValueAsString(body.getResponseBody()), AAIError.class);
+                               } catch (IOException e) {
+                                       logger.error("could not parse error object from A&AI", e);
+                                       error = new AAIError();
+                               }
+                               AAIErrorFormatter formatter = new AAIErrorFormatter(error);
+                               String outputMessage = formatter.getMessage();
+                               errorMessages.add(outputMessage);
+                       }
+               }
+               
+               if (!errorMessages.isEmpty()) {
+                       return Optional.of(Joiner.on("\n").join(errorMessages));
+               } else {
+                       return Optional.empty();
+               }
+       }
+       
+       private Relationship buildRelationship(AAIResourceUri uri) {
+               return buildRelationship(uri, Optional.empty());
+       }
+       
+       private Relationship buildRelationship(AAIResourceUri uri, AAIEdgeLabel label) {
+               return buildRelationship(uri, Optional.of(label));
+       }
+       private Relationship buildRelationship(AAIResourceUri uri, Optional<AAIEdgeLabel> label) {
+               final Relationship result = new Relationship();
+               result.setRelatedLink(uri.build().toString());
+               if (label.isPresent()) {
+                       result.setRelationshipLabel(label.toString());
+               }
+               return result;
+       }
+
+       @Override
+       protected AAIVersion getVersion() {
+               return this.version;
+       }
+       
+       protected SingleTransactionRequest getRequest() {
+               return this.request;
+       }
+       
+       protected AAIPatchConverter getPatchConverter() {
+               return this.patchConverter;
+       }
+}
index 884d2aa..118a3ed 100644 (file)
@@ -20,6 +20,8 @@
 
 package org.onap.so.client.aai;
 
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -34,6 +36,7 @@ import javax.ws.rs.core.Response;
 
 import org.onap.aai.domain.yang.Relationship;
 import org.onap.so.client.RestClient;
+import org.onap.so.client.aai.entities.AAIEdgeLabel;
 import org.onap.so.client.aai.entities.AAIError;
 import org.onap.so.client.aai.entities.bulkprocess.OperationBody;
 import org.onap.so.client.aai.entities.bulkprocess.Transaction;
@@ -54,6 +57,9 @@ public class AAITransactionalClient extends AAIClient {
        private Transaction currentTransaction;
        private final AAIVersion version;
        private int actionCount = 0;
+       
+       private final AAIPatchConverter patchConverter = new AAIPatchConverter();
+       
        protected AAITransactionalClient(AAIVersion version) {
                super();
                this.version = version;
@@ -129,6 +135,20 @@ public class AAITransactionalClient extends AAIClient {
                return this;
        }
        
+       public AAITransactionalClient connect(AAIResourceUri uriA, AAIResourceUri uriB, AAIEdgeLabel label) {
+               AAIResourceUri uriAClone = uriA.clone();
+               RestClient aaiRC = this.createClient(uriAClone.relationshipAPI());
+               aaiRC.put(this.buildRelationship(uriB, label));
+               return this;
+       }
+       
+       public AAITransactionalClient connect(AAIResourceUri uriA, List<AAIResourceUri> uris, AAIEdgeLabel label) {
+               for (AAIResourceUri uri : uris) {
+                       this.connect(uriA, uri, label);
+               }
+               return this;
+       }
+       
        /**
         * Removes relationship from two objects in A&AI
         * 
@@ -178,7 +198,8 @@ public class AAITransactionalClient extends AAIClient {
         * @return
         */
        public AAITransactionalClient update(AAIResourceUri uri, Object obj) {
-               currentTransaction.getPatch().add(new OperationBody().withUri(uri.build().toString()).withBody(obj));
+               final String payload = getPatchConverter().convertPatchFormat(obj);
+               currentTransaction.getPatch().add(new OperationBody().withUri(uri.build().toString()).withBody(payload));
                incrementActionAmount();
                return this;
        }
@@ -247,9 +268,19 @@ public class AAITransactionalClient extends AAIClient {
                        return Optional.empty();
                }
        }
-       private Relationship buildRelationship(AAIUri uri) {
+       private Relationship buildRelationship(AAIResourceUri uri) {
+               return buildRelationship(uri, Optional.empty());
+       }
+       
+       private Relationship buildRelationship(AAIResourceUri uri, AAIEdgeLabel label) {
+               return buildRelationship(uri, Optional.of(label));
+       }
+       private Relationship buildRelationship(AAIResourceUri uri, Optional<AAIEdgeLabel> label) {
                final Relationship result = new Relationship();
                result.setRelatedLink(uri.build().toString());
+               if (label.isPresent()) {
+                       result.setRelationshipLabel(label.toString());
+               }
                return result;
        }
 
@@ -261,4 +292,8 @@ public class AAITransactionalClient extends AAIClient {
        protected Transactions getTransactions() {
                return this.transactions;
        }
+       
+       protected AAIPatchConverter getPatchConverter() {
+               return this.patchConverter;
+       }
 }
index 1803440..4b2aac1 100644 (file)
@@ -23,6 +23,8 @@ package org.onap.so.client.aai.entities.bulkprocess;
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import com.fasterxml.jackson.annotation.JsonRawValue;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 
 @JsonInclude(JsonInclude.Include.NON_NULL)
 @JsonPropertyOrder({
@@ -34,6 +36,7 @@ public class OperationBody {
 @JsonProperty("uri")
 private String uri;
 @JsonProperty("body")
+@JsonSerialize(using = OperationBodySerializer.class)
 private Object body;
 
 @JsonProperty("uri")
diff --git a/common/src/main/java/org/onap/so/client/aai/entities/bulkprocess/OperationBodySerializer.java b/common/src/main/java/org/onap/so/client/aai/entities/bulkprocess/OperationBodySerializer.java
new file mode 100644 (file)
index 0000000..2981e0d
--- /dev/null
@@ -0,0 +1,54 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 - 2018 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.so.client.aai.entities.bulkprocess;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+
+public class OperationBodySerializer extends StdSerializer<Object> {
+
+       private static final long serialVersionUID = 5367385969270400106L;
+
+       public OperationBodySerializer() {
+               this(null);
+       }
+       public OperationBodySerializer(Class<Object> t) {
+               super(t);
+       }
+
+       @Override
+       public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers)
+                       throws IOException, JsonProcessingException {
+               
+               if (value instanceof String) {
+                       gen.writeRawValue((String)value);
+               } else {
+                       gen.writeObject(value);
+               }
+               
+       }
+
+}
+
diff --git a/common/src/main/java/org/onap/so/client/aai/entities/singletransaction/OperationBodyRequest.java b/common/src/main/java/org/onap/so/client/aai/entities/singletransaction/OperationBodyRequest.java
new file mode 100644 (file)
index 0000000..f2626e9
--- /dev/null
@@ -0,0 +1,88 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 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.so.client.aai.entities.singletransaction;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import com.fasterxml.jackson.annotation.JsonRawValue;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonPropertyOrder({
+"action",
+"uri",
+"body"
+})
+public class OperationBodyRequest {
+
+@JsonProperty("action")
+private String action;
+@JsonProperty("uri")
+private String uri;
+@JsonProperty("body")
+@JsonSerialize(using = OperationBodyRequestSerializer.class)
+private Object body;
+
+
+public String getAction() {
+       return action;
+}
+
+public void setAction(String action) {
+       this.action = action;
+}
+
+public OperationBodyRequest withAction(String action) {
+       this.action = action;
+       return this;
+}
+@JsonProperty("uri")
+public String getUri() {
+return uri;
+}
+
+@JsonProperty("uri")
+public void setUri(String uri) {
+this.uri = uri;
+}
+
+public OperationBodyRequest withUri(String uri) {
+this.uri = uri;
+return this;
+}
+
+@JsonProperty("body")
+public Object getBody() {
+return body;
+}
+
+@JsonProperty("body")
+public void setBody(Object body) {
+this.body = body;
+}
+
+public OperationBodyRequest withBody(Object body) {
+this.body = body;
+return this;
+}
+
+}
diff --git a/common/src/main/java/org/onap/so/client/aai/entities/singletransaction/OperationBodyRequestSerializer.java b/common/src/main/java/org/onap/so/client/aai/entities/singletransaction/OperationBodyRequestSerializer.java
new file mode 100644 (file)
index 0000000..1707199
--- /dev/null
@@ -0,0 +1,54 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 - 2018 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.so.client.aai.entities.singletransaction;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+
+public class OperationBodyRequestSerializer extends StdSerializer<Object> {
+
+       private static final long serialVersionUID = 5367385969270400106L;
+
+       public OperationBodyRequestSerializer() {
+               this(null);
+       }
+       public OperationBodyRequestSerializer(Class<Object> t) {
+               super(t);
+       }
+
+       @Override
+       public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers)
+                       throws IOException, JsonProcessingException {
+               
+               if (value instanceof String) {
+                       gen.writeRawValue((String)value);
+               } else {
+                       gen.writeObject(value);
+               }
+               
+       }
+
+}
+
diff --git a/common/src/main/java/org/onap/so/client/aai/entities/singletransaction/OperationBodyResponse.java b/common/src/main/java/org/onap/so/client/aai/entities/singletransaction/OperationBodyResponse.java
new file mode 100644 (file)
index 0000000..71f65b5
--- /dev/null
@@ -0,0 +1,71 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 - 2018 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.so.client.aai.entities.singletransaction;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+
+@JsonPropertyOrder({
+"action",
+"uri",
+"response-status-code",
+"response-body"
+})
+public class OperationBodyResponse {
+
+       @JsonProperty("action")
+       public String action;
+       @JsonProperty("uri")
+       public String uri;
+       @JsonProperty("response-status-code")
+       public Integer responseStatusCode;
+       @JsonProperty("response-body")
+       public Object responseBody;
+       
+       public String getAction() {
+               return action;
+       }
+       public void setAction(String action) {
+               this.action = action;
+       }
+       public String getUri() {
+               return uri;
+       }
+       public void setUri(String uri) {
+               this.uri = uri;
+       }
+       @JsonProperty("response-status-code")
+       public Integer getResponseStatusCode() {
+               return responseStatusCode;
+       }
+       @JsonProperty("response-status-code")
+       public void setResponseStatusCode(Integer responseStatusCode) {
+               this.responseStatusCode = responseStatusCode;
+       }
+       @JsonProperty("response-body")
+       public Object getResponseBody() {
+               return responseBody;
+       }
+       @JsonProperty("response-body")
+       public void getResponseBody(Object responseBody) {
+               this.responseBody = responseBody;
+       }
+}
diff --git a/common/src/main/java/org/onap/so/client/aai/entities/singletransaction/SingleTransactionRequest.java b/common/src/main/java/org/onap/so/client/aai/entities/singletransaction/SingleTransactionRequest.java
new file mode 100644 (file)
index 0000000..0d392c4
--- /dev/null
@@ -0,0 +1,45 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 - 2018 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.so.client.aai.entities.singletransaction;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class SingleTransactionRequest {
+
+       @JsonProperty("operations")
+       public List<OperationBodyRequest> operations;
+       
+       public List<OperationBodyRequest> getOperations() {
+               
+               if (operations == null) {
+                       operations = new ArrayList<>();
+               }
+               
+               return operations;
+       }
+       
+       public void setOperations(List<OperationBodyRequest> operations) {
+               this.operations = operations;
+       }
+}
diff --git a/common/src/main/java/org/onap/so/client/aai/entities/singletransaction/SingleTransactionResponse.java b/common/src/main/java/org/onap/so/client/aai/entities/singletransaction/SingleTransactionResponse.java
new file mode 100644 (file)
index 0000000..db251b5
--- /dev/null
@@ -0,0 +1,47 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 - 2018 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.so.client.aai.entities.singletransaction;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class SingleTransactionResponse {
+
+       @JsonProperty("operation-responses")
+       public List<OperationBodyResponse> operationResponses;
+
+       @JsonProperty("operation-responses")
+       public List<OperationBodyResponse> getOperationResponses() {
+               if (operationResponses == null) {
+                       operationResponses = new ArrayList<>();
+               }
+               return operationResponses;
+       }
+
+       @JsonProperty("operation-responses")
+       public void setOperationResponses(List<OperationBodyResponse> operationResponses) {
+               this.operationResponses = operationResponses;
+       }
+       
+       
+}
diff --git a/common/src/test/java/org/onap/so/client/aai/AAIPatchConverterTest.java b/common/src/test/java/org/onap/so/client/aai/AAIPatchConverterTest.java
new file mode 100644 (file)
index 0000000..008b612
--- /dev/null
@@ -0,0 +1,102 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 - 2018 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.so.client.aai;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.HashMap;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.onap.aai.domain.yang.GenericVnf;
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+
+@RunWith(MockitoJUnitRunner.class)
+public class AAIPatchConverterTest {
+
+       private ObjectMapper mapper = new AAICommonObjectMapperProvider().getMapper();
+
+       @Test
+       public void convertObjectToPatchFormatTest() throws URISyntaxException, JsonParseException, JsonMappingException, IOException {
+               AAIPatchConverter validator = new AAIPatchConverter();
+               GenericVnf vnf = new GenericVnf();
+               vnf.setIpv4Loopback0Address("");
+               String result = validator.marshallObjectToPatchFormat(vnf);
+               GenericVnf resultObj = mapper.readValue(result.toString(), GenericVnf.class);
+               assertTrue("expect object to become a String to prevent double marshalling", result instanceof String);
+               assertNull("expect null because of custom mapper", resultObj.getIpv4Loopback0Address());
+               
+       }
+       
+       @Test
+       public void convertStringToPatchFormatTest() throws URISyntaxException, JsonParseException, JsonMappingException, IOException {
+               AAIPatchConverter validator = new AAIPatchConverter();
+               String payload = "{\"ipv4-loopback0-address\":\"\"}";
+               String result = validator.marshallObjectToPatchFormat(payload);
+               
+               assertEquals("expect no change", payload, result);
+       }
+       
+       @Test
+       public void convertMapToPatchFormatTest() throws URISyntaxException, JsonParseException, JsonMappingException, IOException {
+               AAIPatchConverter validator = new AAIPatchConverter();
+               HashMap<String, String> map = new HashMap<>();
+               map.put("ipv4-loopback0-address", "");
+               String result = validator.marshallObjectToPatchFormat(map);
+               
+               assertEquals("expect string", "{\"ipv4-loopback0-address\":\"\"}", result);
+       }
+       
+       @Test
+       public void hasComplexObjectTest() {
+               AAIPatchConverter validator = new AAIPatchConverter();
+               String hasNesting = "{ \"hello\" : \"world\", \"nested\" : { \"key\" : \"value\" } }";
+               String noNesting = "{ \"hello\" : \"world\" }";
+               String arrayCase = "{ \"hello\" : \"world\", \"nestedSimple\" : [\"value1\" , \"value2\"], \"nestedComplex\" : [{\"key\" : \"value\"}]}";
+               String empty = "{}";
+               String arrayCaseSimpleOnly = "{ \"hello\" : \"world\", \"nestedSimple\" : [\"value1\" , \"value2\"]}";
+               String relationshipListCaseNesting = "{ \"hello\" : \"world\", \"nestedSimple\" : [\"value1\" , \"value2\"], \"relationship-list\" : [{\"key\" : \"value\"}], \"nested\" : { \"key\" : \"value\" }}";
+               String relationshipListCase = "{ \"hello\" : \"world\", \"nestedSimple\" : [\"value1\" , \"value2\"], \"relationship-list\" : [{\"key\" : \"value\"}]}";
+               String nothing = "";
+               
+               assertTrue("expect has nesting", validator.hasComplexObject(hasNesting));
+               assertFalse("expect no nesting", validator.hasComplexObject(noNesting));
+               assertTrue("expect has nesting", validator.hasComplexObject(arrayCase));
+               assertFalse("expect no nesting", validator.hasComplexObject(empty));
+               assertFalse("expect no nesting", validator.hasComplexObject(arrayCaseSimpleOnly));
+               assertFalse("expect no nesting", validator.hasComplexObject(relationshipListCase));
+               assertTrue("expect has nesting", validator.hasComplexObject(relationshipListCaseNesting));
+               assertFalse("expect no nesting", validator.hasComplexObject(nothing));
+       }
+       
+}
index f2e371c..752c49e 100644 (file)
 package org.onap.so.client.aai;
 
 import static org.hamcrest.CoreMatchers.containsString;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
-import java.io.IOException;
 import java.net.URI;
 import java.net.URISyntaxException;
-import java.util.HashMap;
 
 import javax.ws.rs.core.Response;
 
@@ -42,11 +40,9 @@ import org.junit.rules.ExpectedException;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
-import org.onap.aai.domain.yang.GenericVnf;
+import org.onap.so.client.RestClientSSL;
 import org.onap.so.client.graphinventory.exceptions.GraphInventoryPatchDepthExceededException;
 
-import com.fasterxml.jackson.core.JsonParseException;
-import com.fasterxml.jackson.databind.JsonMappingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
 
 @RunWith(MockitoJUnitRunner.class)
@@ -60,65 +56,23 @@ public class AAIRestClientTest {
        @Rule
        public ExpectedException thrown = ExpectedException.none();
        
-       @Test
-       public void convertObjectToPatchFormatTest() throws URISyntaxException, JsonParseException, JsonMappingException, IOException {
-               AAIRestClient client = new AAIRestClient(props, new URI(""));
-               GenericVnf vnf = new GenericVnf();
-               vnf.setIpv4Loopback0Address("");
-               String result = client.convertObjectToPatchFormat(vnf);
-               GenericVnf resultObj = mapper.readValue(result.toString(), GenericVnf.class);
-               assertTrue("expect object to become a String to prevent double marshalling", result instanceof String);
-               assertNull("expect null because of custom mapper", resultObj.getIpv4Loopback0Address());
-               
-       }
-       
-       @Test
-       public void convertStringToPatchFormatTest() throws URISyntaxException, JsonParseException, JsonMappingException, IOException {
-               AAIRestClient client = new AAIRestClient(props, new URI(""));
-               String payload = "{\"ipv4-loopback0-address\":\"\"}";
-               String result = client.convertObjectToPatchFormat(payload);
-               
-               assertEquals("expect no change", payload, result);
-       }
-       
-       @Test
-       public void convertMapToPatchFormatTest() throws URISyntaxException, JsonParseException, JsonMappingException, IOException {
-               AAIRestClient client = new AAIRestClient(props, new URI(""));
-               HashMap<String, String> map = new HashMap<>();
-               map.put("ipv4-loopback0-address", "");
-               String result = client.convertObjectToPatchFormat(map);
-               
-               assertEquals("expect string", "{\"ipv4-loopback0-address\":\"\"}", result);
-       }
-       
        @Test
        public void failPatchOnComplexObject() throws URISyntaxException {
                AAIRestClient client = new AAIRestClient(props, new URI(""));
                this.thrown.expect(GraphInventoryPatchDepthExceededException.class); 
                this.thrown.expectMessage(containsString("Object exceeds allowed depth for update action"));
                client.patch("{ \"hello\" : \"world\", \"nestedSimple\" : [\"value1\" , \"value2\"], \"relationship-list\" : [{\"key\" : \"value\"}], \"nested\" : { \"key\" : \"value\" }}");
-
        }
        
        @Test
-       public void hasComplexObjectTest() throws URISyntaxException {
+       public void verifyPatchValidation() throws URISyntaxException {
                AAIRestClient client = new AAIRestClient(props, new URI(""));
-               String hasNesting = "{ \"hello\" : \"world\", \"nested\" : { \"key\" : \"value\" } }";
-               String noNesting = "{ \"hello\" : \"world\" }";
-               String arrayCase = "{ \"hello\" : \"world\", \"nestedSimple\" : [\"value1\" , \"value2\"], \"nestedComplex\" : [{\"key\" : \"value\"}]}";
-               String empty = "{}";
-               String arrayCaseSimpleOnly = "{ \"hello\" : \"world\", \"nestedSimple\" : [\"value1\" , \"value2\"]}";
-               String relationshipListCaseNesting = "{ \"hello\" : \"world\", \"nestedSimple\" : [\"value1\" , \"value2\"], \"relationship-list\" : [{\"key\" : \"value\"}], \"nested\" : { \"key\" : \"value\" }}";
-               String relationshipListCase = "{ \"hello\" : \"world\", \"nestedSimple\" : [\"value1\" , \"value2\"], \"relationship-list\" : [{\"key\" : \"value\"}]}";
-               String nothing = "";
-               
-               assertTrue("expect has nesting", client.hasComplexObject(hasNesting));
-               assertFalse("expect no nesting", client.hasComplexObject(noNesting));
-               assertTrue("expect has nesting", client.hasComplexObject(arrayCase));
-               assertFalse("expect no nesting", client.hasComplexObject(empty));
-               assertFalse("expect no nesting", client.hasComplexObject(arrayCaseSimpleOnly));
-               assertFalse("expect no nesting", client.hasComplexObject(relationshipListCase));
-               assertTrue("expect has nesting", client.hasComplexObject(relationshipListCaseNesting));
-               assertFalse("expect no nesting", client.hasComplexObject(nothing));
+               AAIRestClient spy = spy(client);
+               AAIPatchConverter patchValidatorMock = mock(AAIPatchConverter.class);
+               doReturn(patchValidatorMock).when(spy).getPatchConverter();
+               String payload = "{}";
+               doReturn(Response.ok().build()).when(spy).method(eq("PATCH"), any());
+               spy.patch(payload);
+               verify(patchValidatorMock, times(1)).convertPatchFormat(eq((Object)payload));
        }
 }
diff --git a/common/src/test/java/org/onap/so/client/aai/AAISingleTransactionClientTest.java b/common/src/test/java/org/onap/so/client/aai/AAISingleTransactionClientTest.java
new file mode 100644 (file)
index 0000000..8c42686
--- /dev/null
@@ -0,0 +1,133 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 - 2018 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.so.client.aai;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Optional;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.aai.domain.yang.Pserver;
+import org.onap.aai.domain.yang.v9.Complex;
+import org.onap.so.client.aai.entities.singletransaction.SingleTransactionRequest;
+import org.onap.so.client.aai.entities.singletransaction.SingleTransactionResponse;
+import org.onap.so.client.aai.entities.uri.AAIResourceUri;
+import org.onap.so.client.aai.entities.uri.AAIUriFactory;
+import org.onap.so.client.defaultproperties.DefaultAAIPropertiesImpl;
+import org.skyscreamer.jsonassert.JSONAssert;
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+
+public class AAISingleTransactionClientTest {
+
+       private final static String AAI_JSON_FILE_LOCATION = "src/test/resources/__files/aai/singletransaction/";
+       AAIResourceUri uriA = AAIUriFactory.createResourceUri(AAIObjectType.PSERVER, "pserver-hostname");
+       AAIResourceUri uriB = AAIUriFactory.createResourceUri(AAIObjectType.COMPLEX, "my-complex");
+       
+       ObjectMapper mapper;
+       
+       @Before
+       public void before() throws JsonParseException, JsonMappingException, IOException {
+               mapper = new AAICommonObjectMapperProvider().getMapper();
+               mapper.enable(SerializationFeature.INDENT_OUTPUT);
+       }
+       
+       @Test
+       public void testRequest() throws IOException {
+               AAIResourcesClient client = createClient();
+               Pserver pserver = new Pserver();
+               pserver.setHostname("pserver-hostname");
+               pserver.setFqdn("pserver-bulk-process-single-transactions-multiple-actions-1-fqdn");
+               Pserver pserver2 = new Pserver();
+               pserver2.setFqdn("patched-fqdn");
+               Complex complex = new Complex();
+               complex.setCity("my-city");
+               AAISingleTransactionClient singleTransaction = 
+               client.beginSingleTransaction()
+                       .create(uriA, pserver)
+                       .update(uriA, pserver2)
+                       .create(uriB, complex);
+               
+               
+               SingleTransactionRequest actual = singleTransaction.getRequest();
+               
+               SingleTransactionRequest expected = mapper.readValue(this.getJson("sample-request.json"), SingleTransactionRequest.class);
+               
+               JSONAssert.assertEquals(mapper.writeValueAsString(expected),mapper.writeValueAsString(actual), false);
+       }
+       
+       @Test
+       public void testFailure() throws IOException {
+               AAIResourcesClient client = createClient();
+               AAISingleTransactionClient singleTransaction = client.beginSingleTransaction();
+               SingleTransactionResponse expected = mapper.readValue(this.getJson("sample-response-failure.json"), SingleTransactionResponse.class);
+               Optional<String> errorMessage = singleTransaction.locateErrorMessages(expected);
+               
+               assertThat(expected.getOperationResponses().size(), greaterThan(0));
+               assertThat(errorMessage.isPresent(), equalTo(true));
+               
+       }
+       
+       @Test
+       public void testSuccessResponse() throws IOException {
+               AAIResourcesClient client = createClient();
+               AAISingleTransactionClient singleTransaction = client.beginSingleTransaction();
+               SingleTransactionResponse expected = mapper.readValue(this.getJson("sample-response.json"), SingleTransactionResponse.class);
+               Optional<String> errorMessage = singleTransaction.locateErrorMessages(expected);
+               
+               assertThat(expected.getOperationResponses().size(), greaterThan(0));
+               assertThat(errorMessage.isPresent(), equalTo(false));
+               
+       }
+       
+       @Test
+       public void confirmPatchFormat() {
+               AAISingleTransactionClient singleTransaction = spy(new AAISingleTransactionClient(AAIVersion.LATEST));
+               AAIPatchConverter mock = mock(AAIPatchConverter.class);
+               doReturn(mock).when(singleTransaction).getPatchConverter();
+               singleTransaction.update(uriA, "{}");
+               verify(mock, times(1)).convertPatchFormat(any());
+       }
+       private String getJson(String filename) throws IOException {
+                return new String(Files.readAllBytes(Paths.get(AAI_JSON_FILE_LOCATION + filename)));
+       }
+       
+       private AAIResourcesClient createClient() {
+               AAIResourcesClient client = spy(new AAIResourcesClient());
+               doReturn(new DefaultAAIPropertiesImpl()).when(client).getRestProperties();
+               return client;
+       }
+}
index f6ee826..cbf8d67 100644 (file)
 package org.onap.so.client.aai;
 
 import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
 import java.io.IOException;
 import java.nio.file.Files;
@@ -136,6 +140,15 @@ public class AAITransactionalClientTest {
                assertEquals(transactions.locateErrorMessages(getJson("response-failure.json")).get(), "another error message\nmy great error");
        }
        
+       @Test
+       public void confirmPatchFormat() {
+               AAITransactionalClient client = spy(new AAITransactionalClient(AAIVersion.LATEST));
+               AAIPatchConverter mock = mock(AAIPatchConverter.class);
+               doReturn(mock).when(client).getPatchConverter();
+               client.update(uriA, "{}");
+               verify(mock, times(1)).convertPatchFormat(any());
+       }
+       
        private String getJson(String filename) throws IOException {
                 return new String(Files.readAllBytes(Paths.get(AAI_JSON_FILE_LOCATION + filename)));
        }
diff --git a/common/src/test/resources/__files/aai/singletransaction/sample-request.json b/common/src/test/resources/__files/aai/singletransaction/sample-request.json
new file mode 100644 (file)
index 0000000..f0761a0
--- /dev/null
@@ -0,0 +1,26 @@
+{
+  "operations": [
+    {
+      "action": "put",
+      "uri": "/cloud-infrastructure/pservers/pserver/pserver-hostname",
+      "body": {
+        "hostname": "pserver-hostname",
+        "fqdn": "pserver-bulk-process-single-transactions-multiple-actions-1-fqdn"
+      }
+    },
+    {
+      "action": "patch",
+      "uri": "/cloud-infrastructure/pservers/pserver/pserver-hostname",
+      "body": {
+        "fqdn": "patched-fqdn"
+      }
+    },
+    {
+      "action": "put",
+      "uri": "/cloud-infrastructure/complexes/complex/my-complex",
+      "body": {
+        "city": "my-city"
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/common/src/test/resources/__files/aai/singletransaction/sample-response-failure.json b/common/src/test/resources/__files/aai/singletransaction/sample-response-failure.json
new file mode 100644 (file)
index 0000000..d0b0e39
--- /dev/null
@@ -0,0 +1,30 @@
+{
+       "operation-responses": [
+               {
+                       "action": "put",
+                       "uri": "/cloud-infrastructure/pservers/pserver/pserver-hostname",
+                       "response-status-code": 201,
+                       "response-body": null
+               },
+               {
+                       "action": "patch",
+                       "uri": "/cloud-infrastructure/pservers/pserver/pserver-hostname",
+                       "response-status-code": 200,
+                       "response-body": null
+               },
+               {
+                       "action": "put",
+                       "uri": "/cloud-infrastructure/complexes/complex/my-complex",
+                       "response-status-code": 400,
+                       "response-body": {
+                               "requestError": {
+                                       "serviceException": {
+                                               "messageId": "SVC3003",
+                                               "text": "another error message",
+                                               "variables": []
+                                       }
+                               }
+                       }
+               }
+       ]
+}
\ No newline at end of file
diff --git a/common/src/test/resources/__files/aai/singletransaction/sample-response.json b/common/src/test/resources/__files/aai/singletransaction/sample-response.json
new file mode 100644 (file)
index 0000000..a5b322e
--- /dev/null
@@ -0,0 +1,22 @@
+{
+  "operation-responses": [
+    {
+      "action": "put",
+      "uri": "/cloud-infrastructure/pservers/pserver/pserver-hostname",
+      "response-status-code": 201,
+      "response-body": null
+    },
+    {
+      "action": "patch",
+      "uri": "/cloud-infrastructure/pservers/pserver/pserver-hostname",
+      "response-status-code": 200,
+      "response-body": null
+    },
+    {
+      "action": "put",
+      "uri": "/cloud-infrastructure/complexes/complex/my-complex",
+      "response-status-code": 201,
+      "response-body": null
+    }
+  ]
+}
\ No newline at end of file