From 88a81c3ffef8fcefc4ebefb3b37b2250908799e6 Mon Sep 17 00:00:00 2001 From: Serban Popescu Date: Tue, 12 Feb 2019 13:08:53 -0500 Subject: [PATCH] Performance Improvements for Gizmo bulk API 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 --- .../src/main/java/org/onap/champ/ChampRESTAPI.java | 34 ++++ .../onap/champ/entity/ChampBulkEdgeResponse.java | 64 ++++++++ .../java/org/onap/champ/entity/ChampBulkOp.java | 171 +++++++++++++++++++++ .../org/onap/champ/entity/ChampBulkPayload.java | 84 ++++++++++ .../org/onap/champ/entity/ChampBulkResponse.java | 62 ++++++++ .../onap/champ/entity/ChampBulkVertexResponse.java | 64 ++++++++ .../org/onap/champ/service/ChampDataService.java | 141 +++++++++++++---- 7 files changed, 593 insertions(+), 27 deletions(-) create mode 100644 champ-service/src/main/java/org/onap/champ/entity/ChampBulkEdgeResponse.java create mode 100644 champ-service/src/main/java/org/onap/champ/entity/ChampBulkOp.java create mode 100644 champ-service/src/main/java/org/onap/champ/entity/ChampBulkPayload.java create mode 100644 champ-service/src/main/java/org/onap/champ/entity/ChampBulkResponse.java create mode 100644 champ-service/src/main/java/org/onap/champ/entity/ChampBulkVertexResponse.java diff --git a/champ-service/src/main/java/org/onap/champ/ChampRESTAPI.java b/champ-service/src/main/java/org/onap/champ/ChampRESTAPI.java index 1f9400f..3c71aa7 100644 --- a/champ-service/src/main/java/org/onap/champ/ChampRESTAPI.java +++ b/champ-service/src/main/java/org/onap/champ/ChampRESTAPI.java @@ -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 index 0000000..23eaba0 --- /dev/null +++ b/champ-service/src/main/java/org/onap/champ/entity/ChampBulkEdgeResponse.java @@ -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 index 0000000..192ed2d --- /dev/null +++ b/champ-service/src/main/java/org/onap/champ/entity/ChampBulkOp.java @@ -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 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 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 getProperties() { + return properties; + } + + public Object getProperty(String key) { + return properties.get(key); + } + + public void setProperties(Map properties) { + this.properties = properties; + } + + public void setProperty(String key, String value) { + if (properties == null) { + properties = new HashMap(); + } + + 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 index 0000000..9ee9349 --- /dev/null +++ b/champ-service/src/main/java/org/onap/champ/entity/ChampBulkPayload.java @@ -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 edgeDeleteOps = new ArrayList(); + private List vertexDeleteOps = new ArrayList(); + private List vertexAddModifyOps = new ArrayList(); + private List edgeAddModifyOps = new ArrayList(); + + 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 getEdgeDeleteOps() { + return edgeDeleteOps; + } + + public void setEdgeDeleteOps(List ops) { + this.edgeDeleteOps = ops; + } + + public List getVertexDeleteOps() { + return vertexDeleteOps; + } + + public void setVertexDeleteOps(List ops) { + this.vertexDeleteOps = ops; + } + + public List getVertexAddModifyOps() { + return vertexAddModifyOps; + } + + public void setVertexAddModifyOps(List ops) { + this.vertexAddModifyOps = ops; + } + + public List getEdgeAddModifyOps() { + return edgeAddModifyOps; + } + + public void setEdgeAddModifyOps(List 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 index 0000000..4114ec2 --- /dev/null +++ b/champ-service/src/main/java/org/onap/champ/entity/ChampBulkResponse.java @@ -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 objects = new ArrayList(); + private List relationships = new ArrayList(); + + 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 getObjects() { + return objects; + } + + public void setObjects(List objects) { + this.objects = objects; + } + + public List getRelationships() { + return relationships; + } + + public void setRelationships(List 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 index 0000000..83f9ea8 --- /dev/null +++ b/champ-service/src/main/java/org/onap/champ/entity/ChampBulkVertexResponse.java @@ -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; + } +} diff --git a/champ-service/src/main/java/org/onap/champ/service/ChampDataService.java b/champ-service/src/main/java/org/onap/champ/service/ChampDataService.java index 48c4183..762b948 100644 --- a/champ-service/src/main/java/org/onap/champ/service/ChampDataService.java +++ b/champ-service/src/main/java/org/onap/champ/service/ChampDataService.java @@ -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 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 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 transaction) throws ChampServiceException, - ChampObjectNotExistsException, ChampTransactionException, ChampUnmarshallingException { + ChampObjectNotExistsException, ChampTransactionException, ChampUnmarshallingException { Optional 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 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 source = champUUIDService.getObjectbyUUID(r.getSource().getKey().get().toString(), - transaction.orElse(null)); + transaction.orElse(null)); Optional 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 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 transaction) - throws ChampServiceException, ChampRelationshipNotExistsException, ChampTransactionException, - ChampUnmarshallingException { + throws ChampServiceException, ChampRelationshipNotExistsException, ChampTransactionException, + ChampUnmarshallingException { Optional 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 getRelationshipsByObject(String objectId, Optional transaction) - throws ChampServiceException { + throws ChampServiceException { try { Optional retrievedObject = champUUIDService.getObjectbyUUID(objectId, transaction.orElse(null)); if (!retrievedObject.isPresent()) { @@ -322,7 +328,7 @@ public class ChampDataService { } public ChampRelationship getRelationship(String id, Optional transaction) - throws ChampServiceException { + throws ChampServiceException { Optional 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 addedObjects = new HashMap(); + List addedObjectsResp = new ArrayList(); + List addedEdgesResp = new ArrayList(); + + 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 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(); -- 2.16.6