Performance Improvements for Gizmo bulk API 27/78327/1
authorSerban Popescu <serban.popescu@amdocs.com>
Tue, 12 Feb 2019 18:08:53 +0000 (13:08 -0500)
committerSerban Popescu <serban.popescu@amdocs.com>
Tue, 12 Feb 2019 18:43:46 +0000 (13:43 -0500)
Use bulk operations with Gizmo/Champ to improve performance.
Also allows for HA by allowing Champ to operate in stateless mode

Issue-ID: AAI-2147
Change-Id: If5bf6a053ea9540f54d0f79a95c8def3765b8e16
Signed-off-by: Serban Popescu <serban.popescu@amdocs.com>
champ-service/src/main/java/org/onap/champ/ChampRESTAPI.java
champ-service/src/main/java/org/onap/champ/entity/ChampBulkEdgeResponse.java [new file with mode: 0644]
champ-service/src/main/java/org/onap/champ/entity/ChampBulkOp.java [new file with mode: 0644]
champ-service/src/main/java/org/onap/champ/entity/ChampBulkPayload.java [new file with mode: 0644]
champ-service/src/main/java/org/onap/champ/entity/ChampBulkResponse.java [new file with mode: 0644]
champ-service/src/main/java/org/onap/champ/entity/ChampBulkVertexResponse.java [new file with mode: 0644]
champ-service/src/main/java/org/onap/champ/service/ChampDataService.java

index 1f9400f..3c71aa7 100644 (file)
@@ -59,6 +59,8 @@ import org.onap.aai.champcore.model.ChampRelationship;
 import org.onap.aai.cl.api.Logger;
 import org.onap.aai.cl.eelf.LoggerFactory;
 import org.onap.champ.async.ChampAsyncRequestProcessor;
+import org.onap.champ.entity.ChampBulkPayload;
+import org.onap.champ.entity.ChampBulkResponse;
 import org.onap.champ.entity.ChampObjectDeserializer;
 import org.onap.champ.entity.ChampObjectSerializer;
 import org.onap.champ.entity.ChampRelationshipDeserializer;
@@ -639,6 +641,38 @@ public class ChampRESTAPI {
     return response;
   }
 
+  @POST
+  @Path("bulk")
+  @Consumes(MediaType.APPLICATION_JSON)
+  @Produces(MediaType.APPLICATION_JSON)
+  public Response postBulk(String bulkPayload, @Context HttpHeaders headers,
+      @Context UriInfo uriInfo, @Context HttpServletRequest req) {
+    LoggingUtil.initMdcContext(req, headers);
+    long startTimeInMs = System.currentTimeMillis();
+    logger.info(ChampMsgs.INCOMING_REQUEST, "null", bulkPayload);
+    Response response = null;
+    try {
+      httpHeadersValidator.validateRequestHeaders(headers);
+      ChampBulkPayload bulkRequest = ChampBulkPayload.fromJson(bulkPayload);
+      ChampBulkResponse bulkResponse = champDataService.processBulkRequest(bulkRequest);
+      
+      response = Response.status(Status.OK).entity(bulkResponse.toJson()).build();
+    } catch (ChampServiceException ce) {
+      response = Response.status(ce.getHttpStatus()).entity(ce.getMessage()).build();
+    } catch (IllegalArgumentException e) {
+      response = Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+    } catch (Exception e) {
+      response = Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()).build();
+      LoggingUtil.logInternalError(logger, e);
+    } finally {
+      LoggingUtil.logRestRequest(logger, auditLogger, req, response);
+      metricsLogger.info(ChampMsgs.PROCESSED_REQUEST, "POST",
+          Long.toString(System.currentTimeMillis() - startTimeInMs));
+    }
+    
+    return response;
+  }
+  
   private boolean reservedKeyMatcher(Pattern p, String key) {
     Matcher m = p.matcher ( key );
     if (m.matches()) {
diff --git a/champ-service/src/main/java/org/onap/champ/entity/ChampBulkEdgeResponse.java b/champ-service/src/main/java/org/onap/champ/entity/ChampBulkEdgeResponse.java
new file mode 100644 (file)
index 0000000..23eaba0
--- /dev/null
@@ -0,0 +1,64 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
+ * Copyright © 2017-2018 Amdocs
+ * ================================================================================
+ * 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.champ.entity;
+
+
+import org.onap.aai.champcore.model.ChampRelationship;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+public class ChampBulkEdgeResponse {
+  private static final Gson gson = new GsonBuilder().create();
+
+  private String label;
+  private ChampRelationship edge;
+
+
+  public ChampBulkEdgeResponse(String label, ChampRelationship rel) {
+    this.label = label;
+    this.edge = rel;
+  }
+
+  public String toJson() {
+    return gson.toJson(this);
+  }
+
+  public static ChampBulkEdgeResponse fromJson(String jsonString) {
+    return gson.fromJson(jsonString, ChampBulkEdgeResponse.class);
+  }
+
+  public String getLabel() {
+    return label;
+  }
+
+  public void setLabel(String label) {
+    this.label = label;
+  }
+
+  public ChampRelationship getEdge() {
+    return edge;
+  }
+
+  public void setEdge(ChampRelationship edge) {
+    this.edge = edge;
+  }
+}
diff --git a/champ-service/src/main/java/org/onap/champ/entity/ChampBulkOp.java b/champ-service/src/main/java/org/onap/champ/entity/ChampBulkOp.java
new file mode 100644 (file)
index 0000000..192ed2d
--- /dev/null
@@ -0,0 +1,171 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
+ * Copyright © 2017-2018 Amdocs
+ * ================================================================================
+ * 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.champ.entity;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.core.Response.Status;
+
+import org.onap.aai.champcore.model.ChampObject;
+import org.onap.aai.champcore.model.ChampRelationship;
+import org.onap.champ.exception.ChampServiceException;
+import org.onap.champ.util.ChampProperties;
+import org.onap.champ.util.ChampServiceConstants;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+public class ChampBulkOp {
+  private static final Gson gson = new GsonBuilder().create();
+
+  private static final String CREATED_TS_NAME = ChampProperties.get(ChampServiceConstants.CHAMP_CREATED_TS_NAME);
+  private static final String LAST_MOD_TS_NAME = ChampProperties.get(ChampServiceConstants.CHAMP_LAST_MOD_TS_NAME);
+
+
+  private String operation;
+  private String id;
+  private String type;
+  private String label;
+  private String source;
+  private String target;
+  private Map<String, Object> properties;
+
+
+  public String toJson() {
+    return gson.toJson(this);
+  }
+
+  public static ChampBulkOp fromJson(String jsonString) {
+    return gson.fromJson(jsonString, ChampBulkOp.class);
+  }
+
+  public ChampObject toChampObject() throws ChampServiceException {
+    if (type == null) {
+      throw new ChampServiceException("Error constructing object from: " + toJson(), Status.INTERNAL_SERVER_ERROR);
+    }
+
+    ChampObject.Builder builder = new ChampObject.Builder(type);
+
+    if (id != null) {
+      builder = builder.key(id);
+    }
+    if (properties != null) {
+
+      //remove the create/updated timestamps as it cause mismatch issue while updating from graph
+      Map<String, Object> champProperties = properties.entrySet().stream()
+              .filter(x -> !(x.getKey().equals(CREATED_TS_NAME) || x.getKey().equals(LAST_MOD_TS_NAME)))
+              .collect(Collectors.toMap(x -> x.getKey(), x -> x.getValue()));
+
+      builder = builder.properties(champProperties);
+    }
+
+    return builder.build();
+  }
+
+  public ChampRelationship toChampRelationship() throws ChampServiceException {
+    if ( (type == null) || (source == null) || (target == null) ) {
+      throw new ChampServiceException("Error constructing relationship from: " + toJson(), Status.INTERNAL_SERVER_ERROR);
+    }
+
+    ChampObject srcObj = new ChampObject.Builder("").key(source).build();
+    ChampObject targetObj = new ChampObject.Builder("").key(target).build();
+    ChampRelationship.Builder builder = new ChampRelationship.Builder(srcObj, targetObj, type);
+
+    if (id != null) {
+      builder = builder.key(id);
+    }
+    if (properties != null) {
+      builder = builder.properties(properties);
+    }
+
+    return builder.build();
+  }
+
+  public String getId() {
+    return id;
+  }
+
+  public void setId(String id) {
+    this.id = id;
+  }
+
+  public String getType() {
+    return type;
+  }
+
+  public void setType(String type) {
+    this.type = type;
+  }
+
+  public Map<String, Object> getProperties() {
+    return properties;
+  }
+
+  public Object getProperty(String key) {
+    return properties.get(key);
+  }
+
+  public void setProperties(Map<String, Object> properties) {
+    this.properties = properties;
+  }
+
+  public void setProperty(String key, String value) {
+    if (properties == null) {
+      properties = new HashMap<String,Object>();
+    }
+
+    properties.put(key, value);
+  }
+
+  public String getOperation() {
+    return operation;
+  }
+
+  public void setOperation(String operation) {
+    this.operation = operation;
+  }
+
+  public String getLabel() {
+    return label;
+  }
+
+  public void setLabel(String label) {
+    this.label = label;
+  }
+
+  public String getSource() {
+    return source;
+  }
+
+  public void setSource(String source) {
+    this.source = source;
+  }
+
+  public String getTarget() {
+    return target;
+  }
+
+  public void setTarget(String target) {
+    this.target = target;
+  }
+}
diff --git a/champ-service/src/main/java/org/onap/champ/entity/ChampBulkPayload.java b/champ-service/src/main/java/org/onap/champ/entity/ChampBulkPayload.java
new file mode 100644 (file)
index 0000000..9ee9349
--- /dev/null
@@ -0,0 +1,84 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
+ * Copyright © 2017-2018 Amdocs
+ * ================================================================================
+ * 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.champ.entity;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+public class ChampBulkPayload {
+
+  public static String ADD_OP = "add";
+  public static String UPDATE_OP = "modify";
+  public static String DELETE_OP = "delete";
+  public static String PATCH_OP = "patch";
+
+  private List<ChampBulkOp> edgeDeleteOps = new ArrayList<ChampBulkOp>();
+  private List<ChampBulkOp> vertexDeleteOps = new ArrayList<ChampBulkOp>();
+  private List<ChampBulkOp> vertexAddModifyOps = new ArrayList<ChampBulkOp>();
+  private List<ChampBulkOp> edgeAddModifyOps = new ArrayList<ChampBulkOp>();
+
+  private static final Gson gson = new GsonBuilder().disableHtmlEscaping().create();
+
+  public String toJson() {
+    return gson.toJson(this);
+  }
+
+  public static ChampBulkPayload fromJson(String payload) {
+    return gson.fromJson(payload, ChampBulkPayload.class);
+  }
+
+  public List<ChampBulkOp> getEdgeDeleteOps() {
+    return edgeDeleteOps;
+  }
+
+  public void setEdgeDeleteOps(List<ChampBulkOp> ops) {
+    this.edgeDeleteOps = ops;
+  }
+
+  public List<ChampBulkOp> getVertexDeleteOps() {
+    return vertexDeleteOps;
+  }
+
+  public void setVertexDeleteOps(List<ChampBulkOp> ops) {
+    this.vertexDeleteOps = ops;
+  }
+
+  public List<ChampBulkOp> getVertexAddModifyOps() {
+    return vertexAddModifyOps;
+  }
+
+  public void setVertexAddModifyOps(List<ChampBulkOp> ops) {
+    this.vertexAddModifyOps = ops;
+  }
+
+  public List<ChampBulkOp> getEdgeAddModifyOps() {
+    return edgeAddModifyOps;
+  }
+
+  public void setEdgeAddModifyOps(List<ChampBulkOp> ops) {
+    this.edgeAddModifyOps = ops;
+  }
+}
diff --git a/champ-service/src/main/java/org/onap/champ/entity/ChampBulkResponse.java b/champ-service/src/main/java/org/onap/champ/entity/ChampBulkResponse.java
new file mode 100644 (file)
index 0000000..4114ec2
--- /dev/null
@@ -0,0 +1,62 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
+ * Copyright © 2017-2018 Amdocs
+ * ================================================================================
+ * 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.champ.entity;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+public class ChampBulkResponse {
+
+  private List<ChampBulkVertexResponse> objects = new ArrayList<ChampBulkVertexResponse>();
+  private List<ChampBulkEdgeResponse> relationships = new ArrayList<ChampBulkEdgeResponse>();
+
+  private static final Gson gson = new GsonBuilder().disableHtmlEscaping().create();
+
+  public String toJson() {
+    return gson.toJson(this);
+  }
+
+  public static ChampBulkResponse fromJson(String payload) {
+    return gson.fromJson(payload, ChampBulkResponse.class);
+  }
+
+  public List<ChampBulkVertexResponse> getObjects() {
+    return objects;
+  }
+
+  public void setObjects(List<ChampBulkVertexResponse> objects) {
+    this.objects = objects;
+  }
+
+  public List<ChampBulkEdgeResponse> getRelationships() {
+    return relationships;
+  }
+
+  public void setRelationships(List<ChampBulkEdgeResponse> relationships) {
+    this.relationships = relationships;
+  }
+
+}
diff --git a/champ-service/src/main/java/org/onap/champ/entity/ChampBulkVertexResponse.java b/champ-service/src/main/java/org/onap/champ/entity/ChampBulkVertexResponse.java
new file mode 100644 (file)
index 0000000..83f9ea8
--- /dev/null
@@ -0,0 +1,64 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
+ * Copyright © 2017-2018 Amdocs
+ * ================================================================================
+ * 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.champ.entity;
+
+
+import org.onap.aai.champcore.model.ChampObject;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+public class ChampBulkVertexResponse {
+  private static final Gson gson = new GsonBuilder().create();
+
+  private String label;
+  private ChampObject vertex;
+
+
+  public ChampBulkVertexResponse(String label, ChampObject obj) {
+    this.label = label;
+    this.vertex = obj;
+  }
+
+  public String toJson() {
+    return gson.toJson(this);
+  }
+
+  public static ChampBulkVertexResponse fromJson(String jsonString) {
+    return gson.fromJson(jsonString, ChampBulkVertexResponse.class);
+  }
+
+  public String getLabel() {
+    return label;
+  }
+
+  public void setLabel(String label) {
+    this.label = label;
+  }
+
+  public ChampObject getVertex() {
+    return vertex;
+  }
+
+  public void setVertex(ChampObject vertex) {
+    this.vertex = vertex;
+  }
+}
index 48c4183..762b948 100644 (file)
@@ -36,12 +36,18 @@ import org.onap.aai.champcore.model.ChampRelationship;
 import org.onap.aai.champcore.model.fluent.object.ObjectBuildOrPropertiesStep;
 import org.onap.aai.cl.api.Logger;
 import org.onap.aai.cl.eelf.LoggerFactory;
+import org.onap.champ.entity.ChampBulkEdgeResponse;
+import org.onap.champ.entity.ChampBulkOp;
+import org.onap.champ.entity.ChampBulkPayload;
+import org.onap.champ.entity.ChampBulkResponse;
+import org.onap.champ.entity.ChampBulkVertexResponse;
 import org.onap.champ.exception.ChampServiceException;
 import org.onap.champ.service.logging.ChampMsgs;
 import org.onap.champ.util.ChampProperties;
 import org.onap.champ.util.ChampServiceConstants;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -68,8 +74,8 @@ public class ChampDataService {
     this.graphImpl = graphImpl;
 
     ChampField field = new ChampField.Builder(ChampProperties.get("keyName"))
-        .type(ChampField.Type.STRING)
-        .build();
+            .type(ChampField.Type.STRING)
+            .build();
     ChampObjectIndex index = new ChampObjectIndex.Builder(ChampProperties.get("keyName"), "STRING", field).build();
 
     graphImpl.storeObjectIndex(index);
@@ -93,8 +99,8 @@ public class ChampDataService {
   }
 
   public ChampObject storeObject(ChampObject object, Optional<ChampTransaction> transaction)
-      throws ChampMarshallingException, ChampSchemaViolationException, ChampObjectNotExistsException,
-      ChampTransactionException, ChampServiceException {
+          throws ChampMarshallingException, ChampSchemaViolationException, ChampObjectNotExistsException,
+          ChampTransactionException, ChampServiceException {
 
     if (object.getProperty(KEY_NAME).isPresent() || object.getKey().isPresent()) {
       throw new ChampServiceException(KEY_NAME + " can't be updated", Status.BAD_REQUEST);
@@ -107,8 +113,8 @@ public class ChampDataService {
   }
 
   public ChampObject replaceObject(ChampObject object, String objectId, Optional<ChampTransaction> transaction)
-      throws ChampServiceException, ChampUnmarshallingException, ChampTransactionException, ChampMarshallingException,
-      ChampSchemaViolationException, ChampObjectNotExistsException {
+          throws ChampServiceException, ChampUnmarshallingException, ChampTransactionException, ChampMarshallingException,
+          ChampSchemaViolationException, ChampObjectNotExistsException {
     if (object.getKey().isPresent() && (!object.getKeyValue().equals(objectId))) {
       throw new ChampServiceException("Object Id in the URI doesn't match the body.", Status.BAD_REQUEST);
     }
@@ -122,7 +128,7 @@ public class ChampDataService {
       throw new ChampServiceException(objectId + " not found", Status.NOT_FOUND);
     }
     ObjectBuildOrPropertiesStep payloadBuilder = ChampObject.create().from(object).withKey(retrieved.get().getKey().get())
-        .withProperty(KEY_NAME, objectId);
+            .withProperty(KEY_NAME, objectId);
     if (retrieved.get().getProperty(SOT_NAME).isPresent()){
       payloadBuilder = payloadBuilder.withProperty(SOT_NAME, retrieved.get().getProperty(SOT_NAME).get());
     }
@@ -147,7 +153,7 @@ public class ChampDataService {
   }
 
   public void deleteObject(String objectId, Optional<ChampTransaction> transaction) throws ChampServiceException,
-      ChampObjectNotExistsException, ChampTransactionException, ChampUnmarshallingException {
+          ChampObjectNotExistsException, ChampTransactionException, ChampUnmarshallingException {
     Optional<ChampObject> retrieved = champUUIDService.getObjectbyUUID(objectId, transaction.orElse(null));
     if (!retrieved.isPresent()) {
       throw new ChampServiceException(objectId + " not found", Status.NOT_FOUND);
@@ -156,19 +162,19 @@ public class ChampDataService {
 
     if (relationships.count() > 0) {
       throw new ChampServiceException("Attempt to delete vertex with id " + objectId + " which has incident edges.",
-          Status.BAD_REQUEST);
+              Status.BAD_REQUEST);
     }
     graphImpl.deleteObject(retrieved.get().getKey().get(), transaction);
 
   }
 
   public ChampRelationship storeRelationship(ChampRelationship r, Optional<ChampTransaction> transaction)
-      throws ChampMarshallingException, ChampObjectNotExistsException, ChampSchemaViolationException,
-      ChampRelationshipNotExistsException, ChampUnmarshallingException, ChampTransactionException,
-      ChampServiceException {
+          throws ChampMarshallingException, ChampObjectNotExistsException, ChampSchemaViolationException,
+          ChampRelationshipNotExistsException, ChampUnmarshallingException, ChampTransactionException,
+          ChampServiceException {
 
     if (r.getSource() == null || !r.getSource().getKey().isPresent() || r.getTarget() == null
-        || !r.getTarget().getKey().isPresent()) {
+            || !r.getTarget().getKey().isPresent()) {
       logger.error(ChampMsgs.CHAMP_DATA_SERVICE_ERROR, "Source/Target Object key must be provided");
       throw new ChampServiceException("Source/Target Object key must be provided", Status.BAD_REQUEST);
     }
@@ -180,9 +186,9 @@ public class ChampDataService {
     }
 
     Optional<ChampObject> source = champUUIDService.getObjectbyUUID(r.getSource().getKey().get().toString(),
-        transaction.orElse(null));
+            transaction.orElse(null));
     Optional<ChampObject> target = champUUIDService.getObjectbyUUID(r.getTarget().getKey().get().toString(),
-        transaction.orElse(null));
+            transaction.orElse(null));
 
     if (!source.isPresent() || !target.isPresent()) {
       logger.error(ChampMsgs.CHAMP_DATA_SERVICE_ERROR, "Source/Target object not found");
@@ -192,19 +198,19 @@ public class ChampDataService {
     champUUIDService.populateUUIDProperty(r, java.util.UUID.randomUUID().toString());
 
     ChampRelationship payload = new ChampRelationship.Builder(source.get(), target.get(), r.getType())
-        .properties(r.getProperties()).build();
+            .properties(r.getProperties()).build();
     addTimestamps(payload, null);
     ChampRelationship created = graphImpl.storeRelationship(payload, transaction);
     return (ChampRelationship) champUUIDService.populateUUIDKey(created);
   }
 
   public ChampRelationship updateRelationship(ChampRelationship r, String rId, Optional<ChampTransaction> transaction)
-      throws ChampServiceException, ChampUnmarshallingException, ChampTransactionException, ChampMarshallingException,
-      ChampSchemaViolationException, ChampRelationshipNotExistsException {
+          throws ChampServiceException, ChampUnmarshallingException, ChampTransactionException, ChampMarshallingException,
+          ChampSchemaViolationException, ChampRelationshipNotExistsException {
     if (r.getKey().isPresent() && (!r.getKeyValue().equals(rId))) {
 
       throw new ChampServiceException("Relationship Id in the URI \"" + rId + "\" doesn't match the URI in the body"
-          + " \"" + r.getKeyValue() + "\"", Status.BAD_REQUEST);
+              + " \"" + r.getKeyValue() + "\"", Status.BAD_REQUEST);
 
     }
 
@@ -218,14 +224,14 @@ public class ChampDataService {
     }
     // check if key is present or if it equals the key that is in the URI
     if (r.getSource() == null || !r.getSource().getKey().isPresent() || r.getTarget() == null
-        || !r.getTarget().getKey().isPresent()) {
+            || !r.getTarget().getKey().isPresent()) {
       throw new ChampServiceException("Source/Target Object key must be provided", Status.BAD_REQUEST);
     }
     ChampObject source = retrieved.get().getSource();
     ChampObject target = retrieved.get().getTarget();
 
     if (!source.getProperty(KEY_NAME).get().toString().equals(r.getSource().getKey().get().toString())
-        || !target.getProperty(KEY_NAME).get().toString().equals(r.getTarget().getKey().get().toString())) {
+            || !target.getProperty(KEY_NAME).get().toString().equals(r.getTarget().getKey().get().toString())) {
       throw new ChampServiceException("Source/Target cannot be updated", Status.BAD_REQUEST);
     }
 
@@ -242,17 +248,17 @@ public class ChampDataService {
     }
 
     ChampRelationship payload = new ChampRelationship.Builder(source, target, r.getType())
-        .key(retrieved.get().getKey().get()).properties(r.getProperties()).property(KEY_NAME, rId).build();
+            .key(retrieved.get().getKey().get()).properties(r.getProperties()).property(KEY_NAME, rId).build();
     addTimestamps(payload, (Long)retrieved.get().getProperty(CREATED_TS_NAME).orElse(null));
     ChampRelationship updated = graphImpl.replaceRelationship(payload, transaction);
     return (ChampRelationship) champUUIDService.populateUUIDKey(updated);
   }
 
   public void deleteRelationship(String relationshipId, Optional<ChampTransaction> transaction)
-      throws ChampServiceException, ChampRelationshipNotExistsException, ChampTransactionException,
-      ChampUnmarshallingException {
+          throws ChampServiceException, ChampRelationshipNotExistsException, ChampTransactionException,
+          ChampUnmarshallingException {
     Optional<ChampRelationship> retrieved = champUUIDService.getRelationshipbyUUID(relationshipId,
-        transaction.orElse(null));
+            transaction.orElse(null));
     if (!retrieved.isPresent()) {
       throw new ChampServiceException(relationshipId + " not found", Status.NOT_FOUND);
     }
@@ -263,7 +269,7 @@ public class ChampDataService {
 
 
   public List<ChampRelationship> getRelationshipsByObject(String objectId, Optional<ChampTransaction> transaction)
-      throws ChampServiceException {
+          throws ChampServiceException {
     try {
       Optional<ChampObject> retrievedObject = champUUIDService.getObjectbyUUID(objectId, transaction.orElse(null));
       if (!retrievedObject.isPresent()) {
@@ -322,7 +328,7 @@ public class ChampDataService {
   }
 
   public ChampRelationship getRelationship(String id, Optional<ChampTransaction> transaction)
-      throws ChampServiceException {
+          throws ChampServiceException {
 
     Optional<ChampRelationship> retrieved = Optional.empty();
     try {
@@ -371,6 +377,87 @@ public class ChampDataService {
     return cache.get(id);
   }
 
+  public ChampBulkResponse processBulkRequest(ChampBulkPayload bulkPayload) throws ChampServiceException, ChampRelationshipNotExistsException, ChampTransactionException, ChampUnmarshallingException, ChampObjectNotExistsException, ChampMarshallingException, ChampSchemaViolationException {
+    // Open a transaction.  If any operations fail, we want to rollback
+    ChampTransaction transaction = graphImpl.openTransaction();
+    if (transaction == null) {
+      throw new ChampServiceException("Unable to open transaction", Status.INTERNAL_SERVER_ERROR);
+    }
+
+    ChampBulkResponse responsePayload = new ChampBulkResponse();
+    Map<String,ChampObject> addedObjects = new HashMap<String,ChampObject>();
+    List<ChampBulkVertexResponse> addedObjectsResp = new ArrayList<ChampBulkVertexResponse>();
+    List<ChampBulkEdgeResponse> addedEdgesResp = new ArrayList<ChampBulkEdgeResponse>();
+
+    try {
+      // 1. Process edge deletes
+      for (ChampBulkOp op : bulkPayload.getEdgeDeleteOps()) {
+        deleteRelationship(op.getId(), Optional.ofNullable(transaction));
+      }
+
+      // 2. Process vertex deletes
+      for (ChampBulkOp op : bulkPayload.getVertexDeleteOps()) {
+        deleteObject(op.getId(), Optional.ofNullable(transaction));
+      }
+
+      // 3. Add/modify vertexes
+      for (ChampBulkOp op : bulkPayload.getVertexAddModifyOps()) {
+        if (op.getOperation().equals(ChampBulkPayload.ADD_OP)) {
+          ChampObject addedObj = storeObject(op.toChampObject(), Optional.ofNullable(transaction));
+          addedObjects.put(op.getLabel(), addedObj);
+          addedObjectsResp.add(new ChampBulkVertexResponse(op.getLabel(), addedObj));
+        }
+        else {
+          ChampObject addedObj = replaceObject(op.toChampObject(), op.getId(), Optional.ofNullable(transaction));
+          addedObjects.put(op.getLabel(), addedObj);
+          addedObjectsResp.add(new ChampBulkVertexResponse(op.getLabel(), addedObj));
+        }
+      }
+
+      // 4. Add/modify edges
+      for (ChampBulkOp op : bulkPayload.getEdgeAddModifyOps()) {
+        // If the edge references a newly added vertex, we need to replace the reference with the real ID
+        op.setSource(resolveVertex(op.getSource(), addedObjects));
+        op.setTarget(resolveVertex(op.getTarget(), addedObjects));
+
+        if (op.getOperation().equals(ChampBulkPayload.ADD_OP)) {
+          ChampRelationship addedRel = storeRelationship(op.toChampRelationship(), Optional.ofNullable(transaction));
+          addedEdgesResp.add(new ChampBulkEdgeResponse(op.getLabel(), addedRel));
+        }
+        else {
+          ChampRelationship addedRel = updateRelationship(op.toChampRelationship(), op.getId(), Optional.ofNullable(transaction));
+          addedEdgesResp.add(new ChampBulkEdgeResponse(op.getLabel(), addedRel));
+        }
+      }
+    }
+    catch (Exception ex) {
+      // Rollback the transaction
+      graphImpl.rollbackTransaction(transaction);
+      throw ex;
+    }
+
+    // Commit transaction
+    graphImpl.commitTransaction(transaction);
+
+    responsePayload.setObjects(addedObjectsResp);
+    responsePayload.setRelationships(addedEdgesResp);
+    return responsePayload;
+  }
+
+
+  private String resolveVertex(String vertexId, Map<String, ChampObject> addedObjects) throws ChampServiceException {
+    if (vertexId.startsWith("$")) {
+      String key = vertexId.substring(1);
+      if (addedObjects.get(key) != null) {
+        return addedObjects.get(key).getKey().get().toString();
+      }
+
+      throw new ChampServiceException("Unable to resolve vertex " + key, Status.BAD_REQUEST);
+    }
+
+    return vertexId;
+  }
+
   private void addTimestamps(ChampElement e, Long oldCreated) {
     Long timestamp = System.currentTimeMillis();