From be2eab9395a787ca4b13447f9d2c382d12f5dcd5 Mon Sep 17 00:00:00 2001 From: "Bansal, Nitin (nb121v)" Date: Thu, 16 Nov 2017 14:56:59 -0500 Subject: Add bulk API to gizmo Add bulk API to gizmo IssueID: AAI-481 Change-Id: Iff9df1a8fdc73c87d726da7294c2eb9f471080f1 Signed-off-by: Bansal, Nitin (nb121v) --- src/main/java/org/openecomp/crud/dao/GraphDao.java | 24 +- .../org/openecomp/crud/dao/champ/ChampDao.java | 1343 +++++++++++--------- .../openecomp/crud/dao/champion/ChampionDao.java | 585 +++++++++ .../crud/dao/champion/ChampionEdgeSerializer.java | 49 + .../dao/champion/ChampionVertexSerializer.java | 47 + src/main/java/org/openecomp/crud/entity/Edge.java | 16 +- .../java/org/openecomp/crud/entity/Vertex.java | 16 +- .../openecomp/crud/logging/CrudServiceMsgs.java | 1 + .../org/openecomp/crud/logging/LoggingUtil.java | 2 +- .../openecomp/crud/parser/CrudResponseBuilder.java | 55 +- .../org/openecomp/crud/service/BulkPayload.java | 128 ++ .../crud/service/CrudGraphDataService.java | 143 ++- .../openecomp/crud/service/CrudRestService.java | 190 ++- .../openecomp/crud/util/CrudServiceConstants.java | 1 + .../org/openecomp/schema/OxmModelValidator.java | 49 +- .../openecomp/schema/RelationshipSchemaLoader.java | 39 +- .../resources/logging/CrudServiceMsgs.properties | 3 + 17 files changed, 2006 insertions(+), 685 deletions(-) create mode 100644 src/main/java/org/openecomp/crud/dao/champion/ChampionDao.java create mode 100644 src/main/java/org/openecomp/crud/dao/champion/ChampionEdgeSerializer.java create mode 100644 src/main/java/org/openecomp/crud/dao/champion/ChampionVertexSerializer.java create mode 100644 src/main/java/org/openecomp/crud/service/BulkPayload.java (limited to 'src/main') diff --git a/src/main/java/org/openecomp/crud/dao/GraphDao.java b/src/main/java/org/openecomp/crud/dao/GraphDao.java index c714249..67c1ff3 100644 --- a/src/main/java/org/openecomp/crud/dao/GraphDao.java +++ b/src/main/java/org/openecomp/crud/dao/GraphDao.java @@ -23,13 +23,14 @@ */ package org.openecomp.crud.dao; +import java.util.List; +import java.util.Map; + import org.openecomp.crud.entity.Edge; + import org.openecomp.crud.entity.Vertex; import org.openecomp.crud.exception.CrudException; -import java.util.List; -import java.util.Map; - public interface GraphDao { public Vertex getVertex(String id) throws CrudException; @@ -137,4 +138,21 @@ public interface GraphDao { * @throws CrudException */ public void deleteEdge(String id, String type) throws CrudException; + + + public String openTransaction(); + public void commitTransaction(String id) throws CrudException; + public void rollbackTransaction(String id) throws CrudException; + public boolean transactionExists(String id) throws CrudException; + + public Vertex addVertex(String type, Map properties, String txId) throws CrudException; + public Edge addEdge(String type, Vertex source, Vertex target, Map properties, String txId) + throws CrudException; + public Vertex updateVertex(String id, String type, Map properties, String txId) + throws CrudException; + + public Edge updateEdge(Edge edge, String txId) throws CrudException; + public void deleteVertex(String id, String type, String txId) throws CrudException; + public void deleteEdge(String id, String type, String txId) throws CrudException; + public Edge getEdge(String id, String type, String txId) throws CrudException; } diff --git a/src/main/java/org/openecomp/crud/dao/champ/ChampDao.java b/src/main/java/org/openecomp/crud/dao/champ/ChampDao.java index 31bc9ab..ed01038 100644 --- a/src/main/java/org/openecomp/crud/dao/champ/ChampDao.java +++ b/src/main/java/org/openecomp/crud/dao/champ/ChampDao.java @@ -23,7 +23,16 @@ */ package org.openecomp.crud.dao.champ; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; +import java.util.stream.Stream; + import org.openecomp.aai.champcore.ChampGraph; +import org.openecomp.aai.champcore.ChampTransaction; import org.openecomp.aai.champcore.exceptions.ChampMarshallingException; import org.openecomp.aai.champcore.exceptions.ChampObjectNotExistsException; import org.openecomp.aai.champcore.exceptions.ChampRelationshipNotExistsException; @@ -41,628 +50,770 @@ import org.openecomp.crud.entity.Vertex; import org.openecomp.crud.exception.CrudException; import org.openecomp.crud.logging.CrudServiceMsgs; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.stream.Collectors; -import java.util.stream.Stream; - - /** - * This is the integration layer between the CRUD API service and the low level Champ library - * for graph database interaction. + * This is the integration layer between the CRUD API service and the low level Champ library for graph database + * interaction. */ public class ChampDao implements GraphDao { - public static final String CONFIG_STORAGE_BACKEND = "storage.backend"; - public static final String CONFIG_STORAGE_BACKEND_DB = "storage.backend.db"; - public static final String STORAGE_HBASE_DB = "hbase"; - public static final String STORAGE_CASSANDRA_DB = "cassandra"; - public static final String CONFIG_STORAGE_HOSTNAMES = "storage.hostnames"; - public static final String CONFIG_STORAGE_PORT = "storage.port"; - public static final String CONFIG_HBASE_ZNODE_PARENT = "storage.hbase.ext.zookeeper.znode.parent"; - public static final String CONFIG_GRAPH_NAME = "graph.name"; - public static final String GRAPH_UNQ_INSTANCE_ID_SUFFIX = "graph.unique-instance-id-suffix"; - - public static final String CONFIG_EVENT_STREAM_PUBLISHER = "event.stream.publisher"; - public static final String CONFIG_EVENT_STREAM_NUM_PUBLISHERS = "event.stream.num-publishers"; - - - public static final String DEFAULT_GRAPH_NAME = "default_graph"; - - private enum GraphType { - IN_MEMORY, - TITAN, - DSE - } - - /** - * Instance of the API used for interacting with the Champ library. - */ - private ChampGraph champApi = null; - - private Logger logger = LoggerFactory.getInstance().getLogger(ChampDao.class.getName()); - - - /** - * Creates a new instance of the ChampDao. - * - * @param champGraph - Concrete implementation of the graph dao layer - */ - public ChampDao(ChampGraph champGraph) { - this.champApi = champGraph; - } - - @Override - public Vertex getVertex(String id) throws CrudException { - - try { - - if (logger.isDebugEnabled()) { - logger.debug("getVertex with id: " + id); - } - - long idAsLong = Long.parseLong(id); - - Optional retrievedVertex = champApi.retrieveObject(idAsLong); - - String nodeType = org.openecomp.schema.OxmModelValidator.Metadata.NODE_TYPE.propertyName(); - if(retrievedVertex.isPresent() && - retrievedVertex.get().getProperties().get(nodeType)!=null) { - return vertexFromChampObject(retrievedVertex.get(), - retrievedVertex.get().getProperties().get(nodeType).toString()); - } else { - - // We didn't find a vertex with the supplied id, so just throw an - // exception. - throw new CrudException("No vertex with id " + id + " found in graph", - javax.ws.rs.core.Response.Status.NOT_FOUND); - } - - } catch (ChampUnmarshallingException | ChampTransactionException e) { - - // Something went wrong - throw an exception. - throw new CrudException(e.getMessage(), - javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR); - } - } - - @Override - public Vertex getVertex(String id, String type) throws CrudException { - - try { - - if (logger.isDebugEnabled()) { - logger.debug("getVertex with id: " + id); - } - - long idAsLong = Long.parseLong(id); - - // Request the vertex from the graph db. - Optional retrievedVertex = champApi.retrieveObject(idAsLong); - - // Did we find it? - if (retrievedVertex.isPresent() && retrievedVertex.get().getProperties() - .get(org.openecomp.schema.OxmModelValidator.Metadata.NODE_TYPE.propertyName())!=null && retrievedVertex.get().getProperties() - .get(org.openecomp.schema.OxmModelValidator.Metadata.NODE_TYPE.propertyName()).toString() - .equalsIgnoreCase(type)) { - - // Yup, convert it to a Vector object and return it. - return vertexFromChampObject(retrievedVertex.get(),type); - - } else { - - // We didn't find a vertex with the supplied id, so just throw an - // exception. - throw new CrudException("No vertex with id " + id + " found in graph", - javax.ws.rs.core.Response.Status.NOT_FOUND); - } - - } catch (ChampUnmarshallingException | ChampTransactionException e) { - - // Something went wrong - throw an exception. - throw new CrudException(e.getMessage(), - javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR); - } - } - - - @Override - public List getVertexEdges(String id) throws CrudException { - - if (logger.isDebugEnabled()) { - logger.debug("get Edges incident to vertex with id: " + id + " from graph"); - } - - try { - long idAsLong = Long.parseLong(id); // GDF - what to do about id??? - - // Request the vertex from the graph db. - Optional retrievedVertex = champApi.retrieveObject(idAsLong); - - // Did we find it? - if (retrievedVertex.isPresent()) { - - // Query the Champ library for the edges which are incident to the specified - // vertex. - Stream relationships = - champApi.retrieveRelationships(retrievedVertex.get()); - - // Build an edge list from the result stream. - List edges = new ArrayList(); - relationships.forEach(r -> edges.add(edgeFromChampRelationship(r))); - - return edges; - - } else { - - // We couldn't find the specified vertex, so throw an exception. - throw new CrudException("No vertex with id " + id + " found in graph", - javax.ws.rs.core.Response.Status.NOT_FOUND); - } - - } catch (ChampUnmarshallingException e) { - - // Something went wrong, so throw an exception. - throw new CrudException(e.getMessage(), - javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR); + public static final String CONFIG_STORAGE_BACKEND = "storage.backend"; + public static final String CONFIG_STORAGE_BACKEND_DB = "storage.backend.db"; + public static final String STORAGE_HBASE_DB = "hbase"; + public static final String STORAGE_CASSANDRA_DB = "cassandra"; + public static final String CONFIG_STORAGE_HOSTNAMES = "storage.hostnames"; + public static final String CONFIG_STORAGE_PORT = "storage.port"; + public static final String CONFIG_HBASE_ZNODE_PARENT = "storage.hbase.ext.zookeeper.znode.parent"; + public static final String CONFIG_GRAPH_NAME = "graph.name"; + public static final String GRAPH_UNQ_INSTANCE_ID_SUFFIX = "graph.unique-instance-id-suffix"; + + public static final String CONFIG_EVENT_STREAM_PUBLISHER = "event.stream.publisher"; + public static final String CONFIG_EVENT_STREAM_NUM_PUBLISHERS = "event.stream.num-publishers"; + + private static Map transactions = new ConcurrentHashMap(); + public static final String DEFAULT_GRAPH_NAME = "default_graph"; + + private enum GraphType { + IN_MEMORY, TITAN, DSE + } + + /** + * Instance of the API used for interacting with the Champ library. + */ + private ChampGraph champApi = null; + + private Logger logger = LoggerFactory.getInstance().getLogger(ChampDao.class.getName()); + + /** + * Creates a new instance of the ChampDao. + * + * @param champGraph + * - Concrete implementation of the graph dao layer + */ + public ChampDao(ChampGraph champGraph) { + this.champApi = champGraph; + } + + @Override + public Vertex getVertex(String id) throws CrudException { + + try { + + if (logger.isDebugEnabled()) { + logger.debug("getVertex with id: " + id); + } + + long idAsLong = Long.parseLong(id); + + Optional retrievedVertex = champApi.retrieveObject(idAsLong); + + String nodeType = org.openecomp.schema.OxmModelValidator.Metadata.NODE_TYPE.propertyName(); + if (retrievedVertex.isPresent() && retrievedVertex.get().getProperties().get(nodeType) != null) { + return vertexFromChampObject(retrievedVertex.get(), retrievedVertex.get().getProperties().get(nodeType).toString()); + } else { + + // We didn't find a vertex with the supplied id, so just throw an + // exception. + throw new CrudException("No vertex with id " + id + " found in graph", javax.ws.rs.core.Response.Status.NOT_FOUND); + } + + } catch (ChampUnmarshallingException | ChampTransactionException e) { + + // Something went wrong - throw an exception. + throw new CrudException(e.getMessage(), javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR); + } + } + + @Override + public Vertex getVertex(String id, String type) throws CrudException { + + try { + + if (logger.isDebugEnabled()) { + logger.debug("getVertex with id: " + id); + } + + long idAsLong = Long.parseLong(id); + + // Request the vertex from the graph db. + Optional retrievedVertex = champApi.retrieveObject(idAsLong); + + // Did we find it? + if (retrievedVertex.isPresent() + && retrievedVertex.get().getProperties().get(org.openecomp.schema.OxmModelValidator.Metadata.NODE_TYPE.propertyName()) != null + && retrievedVertex.get().getProperties().get(org.openecomp.schema.OxmModelValidator.Metadata.NODE_TYPE.propertyName()).toString() + .equalsIgnoreCase(type)) { + + // Yup, convert it to a Vector object and return it. + return vertexFromChampObject(retrievedVertex.get(), type); + + } else { + + // We didn't find a vertex with the supplied id, so just throw an + // exception. + throw new CrudException("No vertex with id " + id + " found in graph", javax.ws.rs.core.Response.Status.NOT_FOUND); + } + + } catch (ChampUnmarshallingException | ChampTransactionException e) { + + // Something went wrong - throw an exception. + throw new CrudException(e.getMessage(), javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR); + } + } + + @Override + public List getVertexEdges(String id) throws CrudException { + + if (logger.isDebugEnabled()) { + logger.debug("get Edges incident to vertex with id: " + id + " from graph"); + } + + try { + long idAsLong = Long.parseLong(id); // GDF - what to do about id??? + + // Request the vertex from the graph db. + Optional retrievedVertex = champApi.retrieveObject(idAsLong); + + // Did we find it? + if (retrievedVertex.isPresent()) { + + // Query the Champ library for the edges which are incident to the specified + // vertex. + Stream relationships = champApi.retrieveRelationships(retrievedVertex.get()); + + // Build an edge list from the result stream. + List edges = new ArrayList(); + relationships.forEach(r -> edges.add(edgeFromChampRelationship(r))); + + return edges; + + } else { + + // We couldn't find the specified vertex, so throw an exception. + throw new CrudException("No vertex with id " + id + " found in graph", javax.ws.rs.core.Response.Status.NOT_FOUND); + } + + } catch (ChampUnmarshallingException e) { + + // Something went wrong, so throw an exception. + throw new CrudException(e.getMessage(), javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR); + + } catch (ChampObjectNotExistsException e) { + + // We couldn't find the specified vertex, so throw an exception. + throw new CrudException("No vertex with id " + id + " found in graph", javax.ws.rs.core.Response.Status.NOT_FOUND); + } catch (ChampTransactionException e) { + throw new CrudException("Transaction error occured", javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR); + } + } + + @Override + public Vertex addVertex(String type, Map properties) throws CrudException { + + if (logger.isDebugEnabled()) { + logger.debug("Add/update vertex: {label: " + type + " properties:" + propertiesMapToString(properties)); + } + + // Add the aai_node_type so that AAI can read the data created by gizmo + properties.put(org.openecomp.schema.OxmModelValidator.Metadata.NODE_TYPE.propertyName(), type); + + // Create an object to represent our vertex in the format expected by the Champ library. + ChampObject objectToCreate = buildChampObject(type, properties); + + try { + + // Ask the Champ library to store our vertex, placing the returned object into a + // list so that we can easily put that into our result object. + return vertexFromChampObject(champApi.storeObject(objectToCreate), type); + + } catch (ChampMarshallingException | ChampSchemaViolationException | ChampObjectNotExistsException | ChampTransactionException e) { + + // Something went wrong - throw an exception. + throw new CrudException(e.getMessage(), javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR); + } + } + + @Override + public Vertex updateVertex(String id, String type, Map properties) throws CrudException { + + if (logger.isDebugEnabled()) { + logger.debug("Update vertex with id: " + id + " with properties: " + propertiesMapToString(properties)); + } + // Add the aai_node_type so that AAI can read the data created by gizmo + properties.put(org.openecomp.schema.OxmModelValidator.Metadata.NODE_TYPE.propertyName(), type); + + try { + // Now, build the updated version of the Champ Object... + ChampObject updateObject = buildChampObject(id, type, properties); + // ...and send it to the Champ library. + return vertexFromChampObject(champApi.replaceObject(updateObject), type); + + } catch (ChampObjectNotExistsException e) { + throw new CrudException("Not Found", javax.ws.rs.core.Response.Status.NOT_FOUND); + } catch (NumberFormatException | ChampMarshallingException | ChampSchemaViolationException e) { + throw new CrudException(e.getMessage(), javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR); + } catch (ChampTransactionException e) { + throw new CrudException("Transaction error occured", javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR); + } + + } + + @Override + public List getVertices(String type, Map filter) throws CrudException { + + if (logger.isDebugEnabled()) { + logger.debug("Retrieve vertices with type label: " + type + " which map query parameters: " + propertiesMapToString(filter)); + } + + filter.put(org.openecomp.schema.OxmModelValidator.Metadata.NODE_TYPE.propertyName(), type); + + Stream retrievedVertices; + try { + retrievedVertices = champApi.queryObjects(filter); + + } catch (ChampTransactionException e) { + throw new CrudException("Transaction error occured", javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR); + } + + List vertices = retrievedVertices.map(v -> vertexFromChampObject(v, type)).collect(Collectors.toList()); + + if (logger.isDebugEnabled()) { + logger.debug("Resulting vertex list: " + retrievedVertices); + } + + // ...and return it to the caller. + return vertices; + } + + private Object getRelKey(String id) { + Object key = id; + // convert into Long if applicable . TODO : revisit in story NUC-304 + try { + key = Long.parseLong(id); + } catch (NumberFormatException e) { + // The id isn't a Long, leave it as a string + } + + return key; + } + + @Override + public Edge getEdge(String id, String type) throws CrudException { + + if (logger.isDebugEnabled()) { + logger.debug("Get edge with id: " + id); + } + + try { + + // Request the edge from the graph db. + Optional relationship = champApi.retrieveRelationship(getRelKey(id)); + + // Did we find it? + if (relationship.isPresent() && relationship.get().getType().equals(type)) { + + // Yup - return the result. + return edgeFromChampRelationship(relationship.get()); + + } else { + + // We didn't find an edge with the supplied id, so throw an exception. + throw new CrudException("No edge with id " + id + " found in graph", javax.ws.rs.core.Response.Status.NOT_FOUND); + } + + } catch (ChampUnmarshallingException | ChampTransactionException e) { + + // Something went wrong, so throw an exception. + throw new CrudException(e.getMessage(), javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR); + } + } + + @Override + public Edge addEdge(String type, Vertex source, Vertex target, Map properties) throws CrudException { + + // For now, assume source and target are straight ids... + try { + + Optional sourceObject = champApi.retrieveObject(Long.parseLong(source.getId().get())); + if (!sourceObject.isPresent() || !sourceObject.get().getType().equals(source.getType())) { + throw new CrudException("Error creating edge - source vertex with id " + source + " does not exist in graph data base", + javax.ws.rs.core.Response.Status.BAD_REQUEST); + } + + Optional targetObject = champApi.retrieveObject(Long.parseLong(target.getId().get())); + if (!targetObject.isPresent() || !targetObject.get().getType().equals(target.getType())) { + throw new CrudException("Error creating edge - target vertex with id " + target + " does not exist in graph data base", + javax.ws.rs.core.Response.Status.BAD_REQUEST); + } + + // Now, create the ChampRelationship object for our edge and store it in + // the graph database. + return edgeFromChampRelationship( + champApi.storeRelationship(new ChampRelationship.Builder(sourceObject.get(), targetObject.get(), type).properties(properties).build())); + + } catch (ChampMarshallingException | ChampObjectNotExistsException | ChampSchemaViolationException | ChampRelationshipNotExistsException + | ChampUnmarshallingException | NumberFormatException | ChampTransactionException e) { + + throw new CrudException("Error creating edge: " + e.getMessage(), javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR); + } + } + + @Override + public List getEdges(String type, Map filter) throws CrudException { + + filter.put(ChampRelationship.ReservedPropertyKeys.CHAMP_RELATIONSHIP_TYPE.toString(), type); + + Stream retrievedRelationships; + try { + retrievedRelationships = champApi.queryRelationships(filter); + + } catch (ChampTransactionException e) { + throw new CrudException("Transaction error occured", javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR); + } + + // Process the result stream from the Champ library into an Edge list, keeping only + // edges of the specified type. + List edges = retrievedRelationships.map(r -> edgeFromChampRelationship(r)).collect(Collectors.toList()); + + return edges; + } + + @Override + public Edge updateEdge(Edge edge) throws CrudException { + + if (logger.isDebugEnabled()) { + logger.debug("Update edge with id: " + edge.getId() + " with properties: " + propertiesMapToString(edge.getProperties())); + } + + try { + // Now, build the updated version of the Champ Relationship... + ChampRelationship updateRelationship = new ChampRelationship.Builder( + buildChampObject(edge.getSource().getId().get(), edge.getSource().getType(), edge.getSource().getProperties()), + buildChampObject(edge.getTarget().getId().get(), edge.getTarget().getType(), edge.getTarget().getProperties()), edge.getType()) + .key(getRelKey(edge.getId().get())).properties(edge.getProperties()).build(); + // ...and send it to the Champ library. + return edgeFromChampRelationship(champApi.replaceRelationship(updateRelationship)); + + } catch (ChampRelationshipNotExistsException ex) { + throw new CrudException("Not Found", javax.ws.rs.core.Response.Status.NOT_FOUND); + } catch (NumberFormatException | ChampUnmarshallingException | ChampMarshallingException | ChampSchemaViolationException + | ChampTransactionException ex) { + + throw new CrudException(ex.getMessage(), javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR); + } + } + + @Override + public void deleteVertex(String id, String type) throws CrudException { + + try { + + // First, retrieve the vertex that we intend to delete. + Optional retrievedVertex = champApi.retrieveObject(Long.parseLong(id)); + + // Did we find it? + if (!retrievedVertex.isPresent() || !retrievedVertex.get().getType().equals(type)) { + throw new CrudException("Failed to delete vertex with id: " + id + " - vertex does not exist.", javax.ws.rs.core.Response.Status.NOT_FOUND); + } + + // Now, verify that there are no edges incident to the vertex (they must be deleted + // first if so). + Stream relationships = champApi.retrieveRelationships(retrievedVertex.get()); + + if (relationships.count() > 0) { + throw new CrudException("Attempt to delete vertex with id " + id + " which has incident edges.", javax.ws.rs.core.Response.Status.BAD_REQUEST); + } + + // Finally, we can attempt to delete our vertex. + champApi.deleteObject(Long.parseLong(id)); + + } catch (NumberFormatException | ChampUnmarshallingException | ChampObjectNotExistsException | ChampTransactionException e) { + + throw new CrudException(e.getMessage(), javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR); + } + } + + @Override + public void deleteEdge(String id, String type) throws CrudException { + + try { + + // First, retrieve the edge that we want to delete. + Optional relationshipToDelete = champApi.retrieveRelationship(getRelKey(id)); + + // Did we find it? + if (!relationshipToDelete.isPresent() || !relationshipToDelete.get().getType().equals(type)) { + throw new CrudException("Failed to delete edge with id: " + id + " - edge does not exist", javax.ws.rs.core.Response.Status.NOT_FOUND); + } + + // Now we can delete the edge. + champApi.deleteRelationship(relationshipToDelete.get()); + + } catch (ChampRelationshipNotExistsException | NumberFormatException | ChampUnmarshallingException | ChampTransactionException e) { + + throw new CrudException(e.getMessage(), javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR); + } + } + + /** + * This helper method generates a string representation of a properties map for logging purposes. + * + * @param properties + * - The properties map to be converted. + * @return - The log statement friendly conversion of the properties map. + */ + private String propertiesMapToString(Map properties) { + + StringBuilder sb = new StringBuilder(); + sb.append("{"); + + for (String key : properties.keySet()) { + sb.append("(").append(key).append(" -> ").append(properties.get(key)).append(") "); + } + + sb.append("}"); + + return sb.toString(); + } + + /** + * This helper method constructs a {@link ChampObject} suitable for passing to the Champ library. + * + * @param type + * - The type to assign to our ChampObject + * @param properties + * - The set of properties to assign to our ChampObject + * @return - A populated ChampObject + */ + private ChampObject buildChampObject(String type, Map properties) { + + ObjectBuildOrPropertiesStep objectInProgress = ChampObject.create().ofType(type).withoutKey(); + + for (String key : properties.keySet()) { + objectInProgress.withProperty(key, properties.get(key)); + } + return objectInProgress.build(); + } + + /** + * This helper method constructs a {@link ChampObject} suitable for passing to the Champ library. + * + * @param id + * - Unique identifier for this object. + * @param type + * - The type to assign to our ChampObject + * @param properties + * - The set of properties to assign to our ChampObject + * @return - A populated ChampObject + */ + private ChampObject buildChampObject(String id, String type, Map properties) { + + ObjectBuildOrPropertiesStep objectInProgress = ChampObject.create().ofType(type).withKey(Long.parseLong(id)); + + for (String key : properties.keySet()) { + objectInProgress.withProperty(key, properties.get(key)); + } + return objectInProgress.build(); + } + + private Vertex vertexFromChampObject(ChampObject champObject, String type) { + + // Get the identifier for this vertex from the Champ object. + Object id = champObject.getKey().orElse(""); + + // Start building our {@link Vertex} object. + Vertex.Builder vertexBuilder = new Vertex.Builder(type); + vertexBuilder.id(id.toString()); + + // Convert the properties associated with the Champ object into the form expected for + // a Vertex object. + for (String key : champObject.getProperties().keySet()) { + vertexBuilder.property(key, champObject.getProperties().get(key)); + } + + // ...and return it. + return vertexBuilder.build(); + } + + /** + * This helper method converts a {@link ChampRelationship} from the Champ library into an equivalent {@link Edge} + * object that is understood by the CRUD Service. + * + * @param relationship + * - The ChampRelationship object to be converted. + * @return - An Edge object corresponding to the supplied ChampRelationship + */ + private Edge edgeFromChampRelationship(ChampRelationship relationship) { + + // Populate the edge's id, if available. + Object relationshipId = relationship.getKey().orElse(""); + + Edge.Builder edgeBuilder = new Edge.Builder(relationship.getType()).id(relationshipId.toString()); + edgeBuilder.source(vertexFromChampObject(relationship.getSource(), + relationship.getSource().getProperties().get(org.openecomp.schema.OxmModelValidator.Metadata.NODE_TYPE.propertyName()) == null + ? relationship.getSource().getType() + : relationship.getSource().getProperties().get(org.openecomp.schema.OxmModelValidator.Metadata.NODE_TYPE.propertyName()).toString())); + edgeBuilder.target(vertexFromChampObject(relationship.getTarget(), + relationship.getTarget().getProperties().get(org.openecomp.schema.OxmModelValidator.Metadata.NODE_TYPE.propertyName()) == null + ? relationship.getTarget().getType() + : relationship.getTarget().getProperties().get(org.openecomp.schema.OxmModelValidator.Metadata.NODE_TYPE.propertyName()).toString())); + + for (String key : relationship.getProperties().keySet()) { + edgeBuilder.property(key, relationship.getProperties().get(key).toString()); + } + + return edgeBuilder.build(); + } + + /** + * Performs any necessary shut down operations when the DAO is no longer needed. + */ + public void close() { + + if (champApi != null) { + + logger.info(CrudServiceMsgs.STOPPING_CHAMP_DAO); + + champApi.shutdown(); + } + } + + @Override + public String openTransaction() { + + ChampTransaction transaction = champApi.openTransaction(); + + transactions.put(transaction.id(), transaction); + logger.info(CrudServiceMsgs.TRANSACTION, "Stored transaction " + transaction.id() + " in hashmap"); + logger.info(CrudServiceMsgs.TRANSACTION, "Hash map contents:"); + for (String key : transactions.keySet()) { + logger.info(CrudServiceMsgs.TRANSACTION, key); + } + return transaction.id(); + } + + @Override + public void commitTransaction(String id) throws CrudException { + + try { + champApi.commitTransaction(getTransaction(id)); + } catch (ChampTransactionException e) { + throw new CrudException("Error while commiting transaction " + id, javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR); + } + transactions.remove(id); + } + + @Override + public void rollbackTransaction(String id) throws CrudException { + + try { + champApi.rollbackTransaction(getTransaction(id)); + } catch (ChampTransactionException e) { + throw new CrudException("Error while transaction rollback " + id, javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR); + } + transactions.remove(id); + } + + private ChampTransaction getTransaction(String id) throws CrudException { + + logger.info(CrudServiceMsgs.TRANSACTION, "Looking up transaction " + id); + if (transactions.containsKey(id)) { + logger.info(CrudServiceMsgs.TRANSACTION, "Found it!"); + return (transactions.get(id)); + } else { + logger.info(CrudServiceMsgs.TRANSACTION, "Didn't find transaction id " + id + ". Hash map contains: "); + for (String key : transactions.keySet()) { + logger.info(CrudServiceMsgs.TRANSACTION, key); + } + throw new CrudException("No open transaction with id: " + id, javax.ws.rs.core.Response.Status.NOT_FOUND); + } + } + + @Override + public Vertex addVertex(String type, Map properties, String txId) throws CrudException { + if (logger.isDebugEnabled()) { + logger.debug("Add/update vertex: {label: " + type + " properties:" + propertiesMapToString(properties)); + } + + // Add the aai_node_type so that AAI can read the data created by gizmo + properties.put(org.openecomp.schema.OxmModelValidator.Metadata.NODE_TYPE.propertyName(), type); + + // Create an object to represent our vertex in the format expected by the Champ library. + ChampObject objectToCreate = buildChampObject(type, properties); + + try { + + // Ask the Champ library to store our vertex, placing the returned object into a + // list so that we can easily put that into our result object. + return vertexFromChampObject(champApi.storeObject(objectToCreate, Optional.of(getTransaction(txId))), type); + + } catch (ChampMarshallingException | ChampSchemaViolationException | ChampObjectNotExistsException | ChampTransactionException e) { + + // Something went wrong - throw an exception. + throw new CrudException(e.getMessage(), javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR); + } + } + + @Override + public Edge addEdge(String type, Vertex source, Vertex target, Map properties, String txId) throws CrudException { + // For now, assume source and target are straight ids... + try { + + Optional sourceObject = champApi.retrieveObject(Long.parseLong(source.getId().get()), Optional.of(getTransaction(txId))); + if (!sourceObject.isPresent() || !sourceObject.get().getType().equals(source.getType())) { + throw new CrudException("Error creating edge - source vertex with id " + source + " does not exist in graph data base", + javax.ws.rs.core.Response.Status.BAD_REQUEST); + } + + Optional targetObject = champApi.retrieveObject(Long.parseLong(target.getId().get()), Optional.of(getTransaction(txId))); + if (!targetObject.isPresent() || !targetObject.get().getType().equals(target.getType())) { + throw new CrudException("Error creating edge - target vertex with id " + target + " does not exist in graph data base", + javax.ws.rs.core.Response.Status.BAD_REQUEST); + } + + // Now, create the ChampRelationship object for our edge and store it in + // the graph database. + return edgeFromChampRelationship( + champApi.storeRelationship(new ChampRelationship.Builder(sourceObject.get(), targetObject.get(), type).properties(properties).build(), + Optional.of(getTransaction(txId)))); + + } catch (ChampMarshallingException | ChampObjectNotExistsException | ChampSchemaViolationException | ChampTransactionException + | ChampRelationshipNotExistsException | ChampUnmarshallingException e) { + + throw new CrudException("Error creating edge: " + e.getMessage(), javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR); + } + + } + + @Override + public Vertex updateVertex(String id, String type, Map properties, String txId) throws CrudException { + if (logger.isDebugEnabled()) { + logger.debug("Update vertex with id: " + id + " with properties: " + propertiesMapToString(properties)); + } + // Add the aai_node_type so that AAI can read the data created by gizmo + properties.put(org.openecomp.schema.OxmModelValidator.Metadata.NODE_TYPE.propertyName(), type); + + try { + // Now, build the updated version of the Champ Object... + ChampObject updateObject = buildChampObject(id, type, properties); + // ...and send it to the Champ library. + return vertexFromChampObject(champApi.replaceObject(updateObject, Optional.of(getTransaction(txId))), type); + + } catch (ChampObjectNotExistsException e) { + throw new CrudException("Not Found", javax.ws.rs.core.Response.Status.NOT_FOUND); + } catch (NumberFormatException | ChampMarshallingException | ChampTransactionException | ChampSchemaViolationException e) { + throw new CrudException(e.getMessage(), javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR); + } + } + + @Override + public boolean transactionExists(String id) throws CrudException { + return transactions.containsKey(id); + } + + @Override + public void deleteVertex(String id, String type, String txId) throws CrudException { + try { + + // First, retrieve the vertex that we intend to delete. + Optional retrievedVertex = champApi.retrieveObject(Long.parseLong(id), Optional.of(getTransaction(txId))); + + // Did we find it? + if (!retrievedVertex.isPresent() || !retrievedVertex.get().getType().equals(type)) { + throw new CrudException("Failed to delete vertex with id: " + id + " - vertex does not exist.", javax.ws.rs.core.Response.Status.NOT_FOUND); + } + + // Now, verify that there are no edges incident to the vertex (they must be deleted + // first if so). + Stream relationships = champApi.retrieveRelationships(retrievedVertex.get(), Optional.of(getTransaction(txId))); + + if (relationships.count() > 0) { + throw new CrudException("Attempt to delete vertex with id " + id + " which has incident edges.", javax.ws.rs.core.Response.Status.BAD_REQUEST); + } + + // Finally, we can attempt to delete our vertex. + champApi.deleteObject(Long.parseLong(id), Optional.of(getTransaction(txId))); - } catch (ChampObjectNotExistsException e) { + } catch (NumberFormatException | ChampUnmarshallingException | ChampObjectNotExistsException | ChampTransactionException e) { - // We couldn't find the specified vertex, so throw an exception. - throw new CrudException("No vertex with id " + id + " found in graph", - javax.ws.rs.core.Response.Status.NOT_FOUND); - } catch (ChampTransactionException e) { - throw new CrudException("Transaction error occured", - javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR); - } - } + throw new CrudException(e.getMessage(), javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR); + } + } - @Override - public Vertex addVertex(String type, Map properties) throws CrudException { + @Override + public Edge updateEdge(Edge edge, String txId) throws CrudException { + if (logger.isDebugEnabled()) { + logger.debug("Update edge with id: " + edge.getId() + " with properties: " + propertiesMapToString(edge.getProperties())); + } - if (logger.isDebugEnabled()) { - logger.debug("Add/update vertex: {label: " + type - + " properties:" + propertiesMapToString(properties)); - } + try { + // Now, build the updated version of the Champ Relationship... + ChampRelationship updateRelationship = new ChampRelationship.Builder( + buildChampObject(edge.getSource().getId().get(), edge.getSource().getType(), edge.getSource().getProperties()), + buildChampObject(edge.getTarget().getId().get(), edge.getTarget().getType(), edge.getTarget().getProperties()), edge.getType()) + .key(getRelKey(edge.getId().get())).properties(edge.getProperties()).build(); + // ...and send it to the Champ library. + return edgeFromChampRelationship(champApi.replaceRelationship(updateRelationship, Optional.of(getTransaction(txId)))); - //Add the aai_node_type so that AAI can read the data created by gizmo - properties.put(org.openecomp.schema.OxmModelValidator.Metadata.NODE_TYPE.propertyName(), type); + } catch (ChampRelationshipNotExistsException ex) { + throw new CrudException("Not Found", javax.ws.rs.core.Response.Status.NOT_FOUND); + } catch (NumberFormatException | ChampUnmarshallingException | ChampMarshallingException | ChampSchemaViolationException + | ChampTransactionException ex) { - // Create an object to represent our vertex in the format expected by the Champ library. - ChampObject objectToCreate = buildChampObject(type, properties); + throw new CrudException(ex.getMessage(), javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR); + } + } - try { + @Override + public void deleteEdge(String id, String type, String txId) throws CrudException { + try { - // Ask the Champ library to store our vertex, placing the returned object into a - // list so that we can easily put that into our result object. - return vertexFromChampObject(champApi.storeObject(objectToCreate),type); + // First, retrieve the edge that we want to delete. + Optional relationshipToDelete = champApi.retrieveRelationship(getRelKey(id), Optional.of(getTransaction(txId))); - } catch (ChampMarshallingException - | ChampSchemaViolationException - | ChampObjectNotExistsException - | ChampTransactionException e) { + // Did we find it? + if (!relationshipToDelete.isPresent() || !relationshipToDelete.get().getType().equals(type)) { + throw new CrudException("Failed to delete edge with id: " + id + " - edge does not exist", javax.ws.rs.core.Response.Status.NOT_FOUND); + } - // Something went wrong - throw an exception. - throw new CrudException(e.getMessage(), - javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR); - } - } + // Now we can delete the edge. + champApi.deleteRelationship(relationshipToDelete.get(), Optional.of(getTransaction(txId))); + } catch (ChampRelationshipNotExistsException | NumberFormatException | ChampUnmarshallingException | ChampTransactionException e) { - @Override - public Vertex updateVertex(String id, String type, Map properties) - throws CrudException { + throw new CrudException(e.getMessage(), javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR); + } - if (logger.isDebugEnabled()) { - logger.debug("Update vertex with id: " + id + " with properties: " - + propertiesMapToString(properties)); - } - //Add the aai_node_type so that AAI can read the data created by gizmo - properties.put(org.openecomp.schema.OxmModelValidator.Metadata.NODE_TYPE.propertyName(), type); + } - try { - // Now, build the updated version of the Champ Object... - ChampObject updateObject = buildChampObject(id, type, properties); - // ...and send it to the Champ library. - return vertexFromChampObject(champApi.replaceObject(updateObject),type); + @Override + public Edge getEdge(String id, String type, String txId) throws CrudException { + if (logger.isDebugEnabled()) { + logger.debug("Get edge with id: " + id); + } - } catch (ChampObjectNotExistsException e) { - throw new CrudException("Not Found", javax.ws.rs.core.Response.Status.NOT_FOUND); - } catch (NumberFormatException | ChampMarshallingException | ChampSchemaViolationException e) { - throw new CrudException(e.getMessage(), - javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR); - } catch (ChampTransactionException e) { - throw new CrudException("Transaction error occured", - javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR); - } + try { - } + // Request the edge from the graph db. + Optional relationship = champApi.retrieveRelationship(getRelKey(id), Optional.of(getTransaction(txId))); + // Did we find it? + if (relationship.isPresent() && relationship.get().getType().equals(type)) { - @Override - public List getVertices(String type, Map filter) throws CrudException { + // Yup - return the result. + return edgeFromChampRelationship(relationship.get()); - if (logger.isDebugEnabled()) { - logger.debug("Retrieve vertices with type label: " + type + " which map query parameters: " - + propertiesMapToString(filter)); - } + } else { + // We didn't find an edge with the supplied id, so throw an exception. + throw new CrudException("No edge with id " + id + " found in graph", javax.ws.rs.core.Response.Status.NOT_FOUND); + } - filter.put(org.openecomp.schema.OxmModelValidator.Metadata.NODE_TYPE.propertyName(), type); + } catch (ChampUnmarshallingException | ChampTransactionException e) { + // Something went wrong, so throw an exception. + throw new CrudException(e.getMessage(), javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR); + } + } - Stream retrievedVertices; - try { - retrievedVertices = champApi.queryObjects(filter); - - } catch (ChampTransactionException e) { - throw new CrudException("Transaction error occured", - javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR); - } - - List vertices = retrievedVertices - .map(v -> vertexFromChampObject(v,type)) - .collect(Collectors.toList()); - - - if (logger.isDebugEnabled()) { - logger.debug("Resulting vertex list: " + retrievedVertices); - } - - // ...and return it to the caller. - return vertices; - } - - private Object getRelKey(String id) { - Object key = id; - // convert into Long if applicable . TODO : revisit in story NUC-304 - try { - key = Long.parseLong(id); - } catch (NumberFormatException e) { - // The id isn't a Long, leave it as a string - } - - return key; - } - - @Override - public Edge getEdge(String id, String type) throws CrudException { - - if (logger.isDebugEnabled()) { - logger.debug("Get edge with id: " + id); - } - - try { - - // Request the edge from the graph db. - Optional relationship = champApi.retrieveRelationship(getRelKey(id)); - - // Did we find it? - if (relationship.isPresent() && relationship.get().getType().equals(type)) { - - // Yup - return the result. - return edgeFromChampRelationship(relationship.get()); - - } else { - - // We didn't find an edge with the supplied id, so throw an exception. - throw new CrudException("No edge with id " + id + " found in graph", - javax.ws.rs.core.Response.Status.NOT_FOUND); - } - - } catch (ChampUnmarshallingException | ChampTransactionException e) { - - // Something went wrong, so throw an exception. - throw new CrudException(e.getMessage(), - javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR); - } - } - - @Override - public Edge addEdge(String type, - Vertex source, - Vertex target, - Map properties) throws CrudException { - - // For now, assume source and target are straight ids... - try { - - Optional sourceObject - = champApi.retrieveObject(Long.parseLong(source.getId().get())); - if (!sourceObject.isPresent() || !sourceObject.get().getType().equals(source.getType())) { - throw new CrudException("Error creating edge - source vertex with id " + source - + " does not exist in graph data base", - javax.ws.rs.core.Response.Status.BAD_REQUEST); - } - - Optional targetObject - = champApi.retrieveObject(Long.parseLong(target.getId().get())); - if (!targetObject.isPresent() || !targetObject.get().getType().equals(target.getType())) { - throw new CrudException("Error creating edge - target vertex with id " + target - + " does not exist in graph data base", - javax.ws.rs.core.Response.Status.BAD_REQUEST); - } - - // Now, create the ChampRelationship object for our edge and store it in - // the graph database. - return edgeFromChampRelationship( - champApi.storeRelationship( - new ChampRelationship.Builder(sourceObject.get(), targetObject.get(), type) - .properties(properties) - .build())); - - } catch (ChampMarshallingException - | ChampObjectNotExistsException - | ChampSchemaViolationException - | ChampRelationshipNotExistsException - | ChampUnmarshallingException | NumberFormatException | ChampTransactionException e) { - - throw new CrudException("Error creating edge: " + e.getMessage(), - javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR); - } - } - - - @Override - public List getEdges(String type, Map filter) throws CrudException { - - filter.put(ChampRelationship.ReservedPropertyKeys.CHAMP_RELATIONSHIP_TYPE.toString(), type); - - Stream retrievedRelationships; - try { - retrievedRelationships = champApi.queryRelationships(filter); - - } catch (ChampTransactionException e) { - throw new CrudException("Transaction error occured", - javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR); - } - - // Process the result stream from the Champ library into an Edge list, keeping only - // edges of the specified type. - List edges = retrievedRelationships - .map(r -> edgeFromChampRelationship(r)) - .collect(Collectors.toList()); - - return edges; - } - - @Override - public Edge updateEdge(Edge edge) throws CrudException { - - if (logger.isDebugEnabled()) { - logger.debug("Update edge with id: " + edge.getId() + " with properties: " - + propertiesMapToString(edge.getProperties())); - } - - try { - // Now, build the updated version of the Champ Relationship... - ChampRelationship updateRelationship = new ChampRelationship.Builder( - buildChampObject(edge.getSource().getId().get(), edge.getSource().getType(), - edge.getSource().getProperties()), - buildChampObject(edge.getTarget().getId().get(), edge.getTarget().getType(), - edge.getTarget().getProperties()), - edge.getType()).key(getRelKey(edge.getId().get())) - .properties(edge.getProperties()).build(); - // ...and send it to the Champ library. - return edgeFromChampRelationship(champApi.replaceRelationship(updateRelationship)); - - - } catch (ChampRelationshipNotExistsException ex) { - throw new CrudException("Not Found", javax.ws.rs.core.Response.Status.NOT_FOUND); - } catch (NumberFormatException | - ChampUnmarshallingException | - ChampMarshallingException | - ChampSchemaViolationException | - ChampTransactionException ex) { - - throw new CrudException(ex.getMessage(), - javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR); - } - } - - @Override - public void deleteVertex(String id, String type) throws CrudException { - - try { - - // First, retrieve the vertex that we intend to delete. - Optional retrievedVertex = champApi.retrieveObject(Long.parseLong(id)); - - // Did we find it? - if (!retrievedVertex.isPresent() || !retrievedVertex.get().getType().equals(type)) { - throw new CrudException("Failed to delete vertex with id: " - + id + " - vertex does not exist.", - javax.ws.rs.core.Response.Status.NOT_FOUND); - } - - // Now, verify that there are no edges incident to the vertex (they must be deleted - // first if so). - Stream relationships = - champApi.retrieveRelationships(retrievedVertex.get()); - - if (relationships.count() > 0) { - throw new CrudException("Attempt to delete vertex with id " - + id + " which has incident edges.", - javax.ws.rs.core.Response.Status.BAD_REQUEST); - } - - // Finally, we can attempt to delete our vertex. - champApi.deleteObject(Long.parseLong(id)); - - - } catch (NumberFormatException - | ChampUnmarshallingException - | ChampObjectNotExistsException - | ChampTransactionException e) { - - throw new CrudException(e.getMessage(), - javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR); - } - } - - @Override - public void deleteEdge(String id, String type) throws CrudException { - - try { - - // First, retrieve the edge that we want to delete. - Optional relationshipToDelete - = champApi.retrieveRelationship(getRelKey(id)); - - - // Did we find it? - if (!relationshipToDelete.isPresent() || !relationshipToDelete.get().getType().equals(type)) { - throw new CrudException("Failed to delete edge with id: " + id + " - edge does not exist", - javax.ws.rs.core.Response.Status.NOT_FOUND); - } - - // Now we can delete the edge. - champApi.deleteRelationship(relationshipToDelete.get()); - - } catch (ChampRelationshipNotExistsException - | NumberFormatException - | ChampUnmarshallingException - | ChampTransactionException e) { - - throw new CrudException(e.getMessage(), - javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR); - } - } - - - /** - * This helper method generates a string representation of a properties map for - * logging purposes. - * - * @param properties - The properties map to be converted. - * @return - The log statement friendly conversion of the properties map. - */ - private String propertiesMapToString(Map properties) { - - StringBuilder sb = new StringBuilder(); - sb.append("{"); - - for (String key : properties.keySet()) { - sb.append("(").append(key).append(" -> ").append(properties.get(key)).append(") "); - } - - sb.append("}"); - - return sb.toString(); - } - - - /** - * This helper method constructs a {@link ChampObject} suitable for passing to the Champ library. - * - * @param type - The type to assign to our ChampObject - * @param properties - The set of properties to assign to our ChampObject - * @return - A populated ChampObject - */ - private ChampObject buildChampObject(String type, Map properties) { - - ObjectBuildOrPropertiesStep objectInProgress = ChampObject.create() - .ofType(type) - .withoutKey(); - - for (String key : properties.keySet()) { - objectInProgress.withProperty(key, properties.get(key)); - } - return objectInProgress.build(); - } - - - /** - * This helper method constructs a {@link ChampObject} suitable for passing to the Champ library. - * - * @param id - Unique identifier for this object. - * @param type - The type to assign to our ChampObject - * @param properties - The set of properties to assign to our ChampObject - * @return - A populated ChampObject - */ - private ChampObject buildChampObject(String id, String type, Map properties) { - - ObjectBuildOrPropertiesStep objectInProgress = ChampObject.create() - .ofType(type) - .withKey(Long.parseLong(id)); - - for (String key : properties.keySet()) { - objectInProgress.withProperty(key, properties.get(key)); - } - return objectInProgress.build(); - } - - - - - private Vertex vertexFromChampObject(ChampObject champObject, String type) { - - // Get the identifier for this vertex from the Champ object. - Object id = champObject.getKey().orElse(""); - - // Start building our {@link Vertex} object. - Vertex.Builder vertexBuilder = new Vertex.Builder(type); - vertexBuilder.id(id.toString()); - - // Convert the properties associated with the Champ object into the form expected for - // a Vertex object. - for (String key : champObject.getProperties().keySet()) { - vertexBuilder.property(key, champObject.getProperties().get(key)); - } - - // ...and return it. - return vertexBuilder.build(); - } - - - /** - * This helper method converts a {@link ChampRelationship} from the Champ library into an - * equivalent {@link Edge} object that is understood by the CRUD Service. - * - * @param relationship - The ChampRelationship object to be converted. - * @return - An Edge object corresponding to the supplied ChampRelationship - */ - private Edge edgeFromChampRelationship(ChampRelationship relationship) { - - // Populate the edge's id, if available. - Object relationshipId = relationship.getKey().orElse(""); - - Edge.Builder edgeBuilder = new Edge.Builder(relationship.getType()) - .id(relationshipId.toString()); - edgeBuilder.source(vertexFromChampObject(relationship.getSource(), - relationship.getSource().getProperties() - .get(org.openecomp.schema.OxmModelValidator.Metadata.NODE_TYPE.propertyName()) == null - ? relationship.getSource().getType() - : relationship.getSource().getProperties() - .get(org.openecomp.schema.OxmModelValidator.Metadata.NODE_TYPE.propertyName()).toString())); - edgeBuilder.target(vertexFromChampObject(relationship.getTarget(), - relationship.getTarget().getProperties() - .get(org.openecomp.schema.OxmModelValidator.Metadata.NODE_TYPE.propertyName()) == null - ? relationship.getTarget().getType() - : relationship.getTarget().getProperties() - .get(org.openecomp.schema.OxmModelValidator.Metadata.NODE_TYPE.propertyName()).toString())); - - for (String key : relationship.getProperties().keySet()) { - edgeBuilder.property(key, relationship.getProperties().get(key).toString()); - } - - return edgeBuilder.build(); - } - - /** - * Performs any necessary shut down operations when the DAO is no longer needed. - */ - public void close() { - - if (champApi != null) { - - logger.info(CrudServiceMsgs.STOPPING_CHAMP_DAO); - - champApi.shutdown(); - } - } } diff --git a/src/main/java/org/openecomp/crud/dao/champion/ChampionDao.java b/src/main/java/org/openecomp/crud/dao/champion/ChampionDao.java new file mode 100644 index 0000000..e1bda7a --- /dev/null +++ b/src/main/java/org/openecomp/crud/dao/champion/ChampionDao.java @@ -0,0 +1,585 @@ +/** + * ============LICENSE_START======================================================= + * Gizmo + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * 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========================================================= + * + * ECOMP is a trademark and service mark of AT&T Intellectual Property. + */ +package org.openecomp.crud.dao.champion; + +import net.dongliu.gson.GsonJava8TypeAdapterFactory; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; + +import org.apache.http.NameValuePair; +import org.apache.http.client.utils.URLEncodedUtils; +import org.apache.http.message.BasicNameValuePair; +import org.eclipse.jetty.util.security.Password; +import org.openecomp.aai.logging.LoggingContext; +import org.openecomp.cl.api.Logger; +import org.openecomp.cl.eelf.LoggerFactory; +import org.openecomp.crud.dao.GraphDao; +import org.openecomp.crud.entity.Edge; +import org.openecomp.crud.entity.Vertex; +import org.openecomp.crud.exception.CrudException; +import org.openecomp.crud.util.CrudServiceConstants; +import org.openecomp.restclient.client.OperationResult; +import org.openecomp.restclient.client.RestClient; +import org.openecomp.restclient.enums.RestAuthenticationMode; +import org.slf4j.MDC; + +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +public class ChampionDao implements GraphDao { + private RestClient client; + private String baseUrl; + + private static final String HEADER_FROM_APP = "X-FromAppId"; + private static final String HEADER_TRANS_ID = "X-TransactionId"; + + private Logger logger = LoggerFactory.getInstance().getLogger(ChampionDao.class.getName()); + + // We use a custom vertex serializer for Champion because it expects "key" instead of "id" + private static final Gson championGson = new GsonBuilder() + .registerTypeAdapterFactory(new GsonJava8TypeAdapterFactory()) + .registerTypeAdapter(Vertex.class, new ChampionVertexSerializer()) + .registerTypeAdapter(Edge.class, new ChampionEdgeSerializer()) + .create(); + + public ChampionDao(String championUrl, String certPassword) { + try { + client = new RestClient().authenticationMode(RestAuthenticationMode.SSL_CERT) + .validateServerHostname(false) + .validateServerCertChain(false) + .clientCertFile(CrudServiceConstants.CRD_CHAMPION_AUTH_FILE) + .clientCertPassword(Password.deobfuscate(certPassword)); + + baseUrl = championUrl; + } catch (Exception e) { + System.out.println("Error setting up Champion configuration"); + e.printStackTrace(); + System.exit(1); + } + } + + @Override + public Vertex getVertex(String id) throws CrudException { + String url = baseUrl + "objects/" + id; + Map> headers = new HashMap<>(); + headers.put(HEADER_FROM_APP, Arrays.asList("Gizmo")); + headers.put(HEADER_TRANS_ID, Arrays.asList(MDC.get(LoggingContext.LoggingField.REQUEST_ID.toString()))); + + OperationResult getResult = client.get(url, headers, MediaType.APPLICATION_JSON_TYPE); + + if (getResult.getResultCode() == 200) { + return Vertex.fromJson(getResult.getResult()); + } else { + // We didn't find a vertex with the supplied id, so just throw an exception. + throw new CrudException("No vertex with id " + id + " found in graph", javax.ws.rs.core.Response.Status.NOT_FOUND); + } + } + + @Override + public Vertex getVertex(String id, String type) throws CrudException { + String url = baseUrl + "objects/" + id; + Map> headers = new HashMap<>(); + headers.put(HEADER_FROM_APP, Arrays.asList("Gizmo")); + headers.put(HEADER_TRANS_ID, Arrays.asList(MDC.get(LoggingContext.LoggingField.REQUEST_ID.toString()))); + + OperationResult getResult = client.get(url, headers, MediaType.APPLICATION_JSON_TYPE); + + if (getResult.getResultCode() == 200) { + Vertex vert = Vertex.fromJson(getResult.getResult()); + + if (!vert.getType().equalsIgnoreCase(type)) { + // We didn't find a vertex with the supplied type, so just throw an exception. + throw new CrudException("No vertex with id " + id + "and type " + type + " found in graph", javax.ws.rs.core.Response.Status.NOT_FOUND); + } + return vert; + } else { + // We didn't find a vertex with the supplied id, so just throw an exception. + throw new CrudException("No vertex with id " + id + " found in graph", javax.ws.rs.core.Response.Status.NOT_FOUND); + } + } + + @Override + public List getVertexEdges(String id) throws CrudException { + String url = baseUrl + "objects/relationships/" + id; + Map> headers = new HashMap<>(); + headers.put(HEADER_FROM_APP, Arrays.asList("Gizmo")); + headers.put(HEADER_TRANS_ID, Arrays.asList(MDC.get(LoggingContext.LoggingField.REQUEST_ID.toString()))); + + OperationResult getResult = client.get(url, headers, MediaType.APPLICATION_JSON_TYPE); + + if (getResult.getResultCode() == 200) { + return championGson.fromJson(getResult.getResult(), new TypeToken>(){}.getType()); + } else { + // We didn't find a vertex with the supplied id, so just throw an exception. + throw new CrudException("No vertex with id " + id + " found in graph", javax.ws.rs.core.Response.Status.NOT_FOUND); + } + } + + @Override + public List getVertices(String type, Map filter) throws CrudException { + filter.put(org.openecomp.schema.OxmModelValidator.Metadata.NODE_TYPE.propertyName(), type); + + String url = baseUrl + "objects/filter" + "?" + + URLEncodedUtils.format(convertToNameValuePair(filter), Charset.defaultCharset()); + + Map> headers = new HashMap<>(); + headers.put(HEADER_FROM_APP, Arrays.asList("Gizmo")); + headers.put(HEADER_TRANS_ID, Arrays.asList(MDC.get(LoggingContext.LoggingField.REQUEST_ID.toString()))); + + OperationResult getResult = client.get(url, headers, MediaType.APPLICATION_JSON_TYPE); + + if (getResult.getResultCode() == 200) { + return championGson.fromJson(getResult.getResult(), new TypeToken>(){}.getType()); + } else { + // We didn't find a vertex with the supplied id, so just throw an exception. + throw new CrudException("No vertices found in graph for given filters", javax.ws.rs.core.Response.Status.NOT_FOUND); + } + } + + @Override + public Edge getEdge(String id, String type) throws CrudException { + String url = baseUrl + "relationships/" + id; + Map> headers = new HashMap<>(); + headers.put(HEADER_FROM_APP, Arrays.asList("Gizmo")); + headers.put(HEADER_TRANS_ID, Arrays.asList(MDC.get(LoggingContext.LoggingField.REQUEST_ID.toString()))); + + OperationResult getResult = client.get(url, headers, MediaType.APPLICATION_JSON_TYPE); + + if (getResult.getResultCode() == 200) { + Edge edge = Edge.fromJson(getResult.getResult()); + + if (!edge.getType().equalsIgnoreCase(type)) { + // We didn't find an edge with the supplied type, so just throw an exception. + throw new CrudException("No edge with id " + id + "and type " + type + " found in graph", javax.ws.rs.core.Response.Status.NOT_FOUND); + } + return edge; + } else { + // We didn't find a edge with the supplied type, so just throw an exception. + throw new CrudException("No edge with id " + id + " found in graph", javax.ws.rs.core.Response.Status.NOT_FOUND); + } + } + + @Override + public List getEdges(String type, Map filter) throws CrudException { + String url = baseUrl + "relationships/filter" + "?" + + URLEncodedUtils.format(convertToNameValuePair(filter), Charset.defaultCharset()); + + Map> headers = new HashMap<>(); + headers.put(HEADER_FROM_APP, Arrays.asList("Gizmo")); + headers.put(HEADER_TRANS_ID, Arrays.asList(MDC.get(LoggingContext.LoggingField.REQUEST_ID.toString()))); + + OperationResult getResult = client.get(url, headers, MediaType.APPLICATION_JSON_TYPE); + + if (getResult.getResultCode() == 200) { + return championGson.fromJson(getResult.getResult(), new TypeToken>(){}.getType()); + } else { + // We didn't find a vertex with the supplied id, so just throw an exception. + throw new CrudException("No edges found in graph for given filters", javax.ws.rs.core.Response.Status.NOT_FOUND); + } + } + + @Override + public Vertex addVertex(String type, Map properties) throws CrudException { + String url = baseUrl + "objects"; + Map> headers = new HashMap<>(); + headers.put(HEADER_FROM_APP, Arrays.asList("Gizmo")); + headers.put(HEADER_TRANS_ID, Arrays.asList(MDC.get(LoggingContext.LoggingField.REQUEST_ID.toString()))); + + // Add the aai_node_type so that AAI can read the data created by gizmo + // TODO: This probably shouldn't be here + properties.put(org.openecomp.schema.OxmModelValidator.Metadata.NODE_TYPE.propertyName(), type); + + Vertex.Builder insertVertexBuilder = new Vertex.Builder(type); + properties.forEach(insertVertexBuilder::property); + Vertex insertVertex = insertVertexBuilder.build(); + + OperationResult getResult = client.post(url, insertVertex.toJson(), headers, + MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_JSON_TYPE); + + if (getResult.getResultCode() == Response.Status.CREATED.getStatusCode()) { + return Vertex.fromJson(getResult.getResult()); + } else { + // We didn't create a vertex with the supplied type, so just throw an exception. + throw new CrudException("Failed to create vertex", Response.Status.fromStatusCode(getResult.getResultCode())); + } + } + + @Override + public Vertex updateVertex(String id, String type, Map properties) throws CrudException { + String url = baseUrl + "objects/" + id; + Map> headers = new HashMap<>(); + headers.put(HEADER_FROM_APP, Arrays.asList("Gizmo")); + headers.put(HEADER_TRANS_ID, Arrays.asList(MDC.get(LoggingContext.LoggingField.REQUEST_ID.toString()))); + + // Add the aai_node_type so that AAI can read the data created by gizmo + // TODO: This probably shouldn't be here + properties.put(org.openecomp.schema.OxmModelValidator.Metadata.NODE_TYPE.propertyName(), type); + + Vertex.Builder insertVertexBuilder = new Vertex.Builder(type); + insertVertexBuilder.id(id); + properties.forEach(insertVertexBuilder::property); + Vertex insertVertex = insertVertexBuilder.build(); + + String payload = insertVertex.toJson(championGson); + OperationResult getResult = client.put(url, payload, headers, + MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_JSON_TYPE); + + if (getResult.getResultCode() == Response.Status.OK.getStatusCode()) { + return Vertex.fromJson(getResult.getResult()); + } else { + // We didn't create a vertex with the supplied type, so just throw an exception. + throw new CrudException("Failed to update vertex", Response.Status.fromStatusCode(getResult.getResultCode())); + } + } + + @Override + public void deleteVertex(String id, String type) throws CrudException { + String url = baseUrl + "objects/" + id; + Map> headers = new HashMap<>(); + headers.put(HEADER_FROM_APP, Arrays.asList("Gizmo")); + headers.put(HEADER_TRANS_ID, Arrays.asList(MDC.get(LoggingContext.LoggingField.REQUEST_ID.toString()))); + + OperationResult getResult = client.delete(url, headers, MediaType.APPLICATION_JSON_TYPE); + + if (getResult.getResultCode() != Response.Status.OK.getStatusCode()) { + // We didn't delete a vertex with the supplied id, so just throw an exception. + throw new CrudException("Failed to delete vertex", Response.Status.fromStatusCode(getResult.getResultCode())); + } + } + + @Override + public Edge addEdge(String type, Vertex source, Vertex target, Map properties) throws CrudException { + String url = baseUrl + "relationships"; + Map> headers = new HashMap<>(); + headers.put(HEADER_FROM_APP, Arrays.asList("Gizmo")); + headers.put(HEADER_TRANS_ID, Arrays.asList(MDC.get(LoggingContext.LoggingField.REQUEST_ID.toString()))); + + // Try requests to ensure source and target exist in Champion + Vertex dbSource = getVertex(source.getId().get(), source.getType()); + Vertex dbTarget = getVertex(target.getId().get(), target.getType()); + + Edge.Builder insertEdgeBuilder = new Edge.Builder(type).source(dbSource).target(dbTarget); + properties.forEach(insertEdgeBuilder::property); + Edge insertEdge = insertEdgeBuilder.build(); + + String edgeJson = insertEdge.toJson(championGson); + OperationResult getResult = client.post(url, edgeJson, headers, + MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_JSON_TYPE); + + if (getResult.getResultCode() == Response.Status.CREATED.getStatusCode()) { + return Edge.fromJson(getResult.getResult()); + } else { + // We didn't create an edge with the supplied type, so just throw an exception. + throw new CrudException("Failed to create edge", Response.Status.fromStatusCode(getResult.getResultCode())); + } + } + + @Override + public Edge updateEdge(Edge edge) throws CrudException { + if (!edge.getId().isPresent()) + { + throw new CrudException("Unable to identify edge: " + edge.toString(), Response.Status.BAD_REQUEST); + } + String url = baseUrl + "relationships/" + edge.getId().get(); + Map> headers = new HashMap<>(); + headers.put(HEADER_FROM_APP, Arrays.asList("Gizmo")); + headers.put(HEADER_TRANS_ID, Arrays.asList(MDC.get(LoggingContext.LoggingField.REQUEST_ID.toString()))); + + String edgeJson = edge.toJson(championGson); + OperationResult getResult = client.put(url, edgeJson, headers, + MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_JSON_TYPE); + + if (getResult.getResultCode() == Response.Status.OK.getStatusCode()) { + return Edge.fromJson(getResult.getResult()); + } else { + // We didn't create an edge with the supplied type, so just throw an exception. + throw new CrudException("Failed to update edge", Response.Status.fromStatusCode(getResult.getResultCode())); + } + } + + @Override + public void deleteEdge(String id, String type) throws CrudException { + String url = baseUrl + "relationships/" + id; + Map> headers = new HashMap<>(); + headers.put(HEADER_FROM_APP, Arrays.asList("Gizmo")); + headers.put(HEADER_TRANS_ID, Arrays.asList(MDC.get(LoggingContext.LoggingField.REQUEST_ID.toString()))); + + OperationResult getResult = client.delete(url, headers, MediaType.APPLICATION_JSON_TYPE); + + if (getResult.getResultCode() != 200) { + // We didn't find an edge with the supplied type, so just throw an exception. + throw new CrudException("No edge with id " + id + " found in graph", javax.ws.rs.core.Response.Status.NOT_FOUND); + } + } + + @Override + public String openTransaction() { + String url = baseUrl + "transaction"; + Map> headers = new HashMap<>(); + headers.put(HEADER_FROM_APP, Arrays.asList("Gizmo")); + headers.put(HEADER_TRANS_ID, Arrays.asList(MDC.get(LoggingContext.LoggingField.REQUEST_ID.toString()))); + + OperationResult getResult = client.post(url, "", headers, MediaType.TEXT_PLAIN_TYPE, MediaType.TEXT_PLAIN_TYPE); + + if (getResult.getResultCode() == 200) { + return getResult.getResult(); + } else { + return null; + } + } + + @Override + public void commitTransaction(String id) throws CrudException { + String url = baseUrl + "transaction/" + id; + Map> headers = new HashMap<>(); + headers.put(HEADER_FROM_APP, Arrays.asList("Gizmo")); + headers.put(HEADER_TRANS_ID, Arrays.asList(MDC.get(LoggingContext.LoggingField.REQUEST_ID.toString()))); + + OperationResult getResult = client.put(url, "{\"method\": \"commit\"}", headers, MediaType.APPLICATION_JSON_TYPE, MediaType.TEXT_PLAIN_TYPE); + + if (getResult.getResultCode() != 200) { + throw new CrudException("Unable to commit transaction", Response.Status.fromStatusCode(getResult.getResultCode())); + } + } + + @Override + public void rollbackTransaction(String id) throws CrudException { + String url = baseUrl + "transaction/" + id; + Map> headers = new HashMap<>(); + headers.put(HEADER_FROM_APP, Arrays.asList("Gizmo")); + headers.put(HEADER_TRANS_ID, Arrays.asList(MDC.get(LoggingContext.LoggingField.REQUEST_ID.toString()))); + + OperationResult getResult = client.put(url, "{\"method\": \"rollback\"}", headers, MediaType.APPLICATION_JSON_TYPE, MediaType.TEXT_PLAIN_TYPE); + + if (getResult.getResultCode() != 200) { + throw new CrudException("Unable to rollback transaction", Response.Status.fromStatusCode(getResult.getResultCode())); + } + } + + @Override + public boolean transactionExists(String id) throws CrudException { + String url = baseUrl + "transaction/" + id; + Map> headers = new HashMap<>(); + headers.put(HEADER_FROM_APP, Arrays.asList("Gizmo")); + headers.put(HEADER_TRANS_ID, Arrays.asList(MDC.get(LoggingContext.LoggingField.REQUEST_ID.toString()))); + + OperationResult getResult = client.get(url, headers, MediaType.APPLICATION_JSON_TYPE); + + return getResult.getResultCode() == 200; + } + + @Override + public Vertex addVertex(String type, Map properties, String txId) throws CrudException { + String url = baseUrl + "objects?transactionId=" + txId; + Map> headers = new HashMap<>(); + headers.put(HEADER_FROM_APP, Arrays.asList("Gizmo")); + headers.put(HEADER_TRANS_ID, Arrays.asList(MDC.get(LoggingContext.LoggingField.REQUEST_ID.toString()))); + + // Add the aai_node_type so that AAI can read the data created by gizmo + // TODO: This probably shouldn't be here + properties.put(org.openecomp.schema.OxmModelValidator.Metadata.NODE_TYPE.propertyName(), type); + + Vertex.Builder insertVertexBuilder = new Vertex.Builder(type); + properties.forEach(insertVertexBuilder::property); + Vertex insertVertex = insertVertexBuilder.build(); + + OperationResult getResult = client.post(url, insertVertex.toJson(), headers, + MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_JSON_TYPE); + + if (getResult.getResultCode() == Response.Status.CREATED.getStatusCode()) { + return Vertex.fromJson(getResult.getResult()); + } else { + // We didn't create a vertex with the supplied type, so just throw an exception. + throw new CrudException("Failed to create vertex", Response.Status.fromStatusCode(getResult.getResultCode())); + } + } + + @Override + public Edge addEdge(String type, Vertex source, Vertex target, Map properties, String txId) throws CrudException { + String url = baseUrl + "relationships?transactionId=" + txId; + Map> headers = new HashMap<>(); + headers.put(HEADER_FROM_APP, Arrays.asList("Gizmo")); + headers.put(HEADER_TRANS_ID, Arrays.asList(MDC.get(LoggingContext.LoggingField.REQUEST_ID.toString()))); + + // Try requests to ensure source and target exist in Champion + Vertex dbSource = getVertex(source.getId().get(), source.getType(), txId); + Vertex dbTarget = getVertex(target.getId().get(), target.getType(), txId); + + Edge.Builder insertEdgeBuilder = new Edge.Builder(type).source(dbSource).target(dbTarget); + properties.forEach(insertEdgeBuilder::property); + Edge insertEdge = insertEdgeBuilder.build(); + + OperationResult getResult = client.post(url, insertEdge.toJson(championGson), headers, + MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_JSON_TYPE); + + if (getResult.getResultCode() == Response.Status.CREATED.getStatusCode()) { + return Edge.fromJson(getResult.getResult()); + } else { + // We didn't create an edge with the supplied type, so just throw an exception. + throw new CrudException("Failed to create edge", Response.Status.fromStatusCode(getResult.getResultCode())); + } + } + + @Override + public Vertex updateVertex(String id, String type, Map properties, String txId) throws CrudException { + String url = baseUrl + "objects/" + id + "?transactionId=" + txId; + Map> headers = new HashMap<>(); + headers.put(HEADER_FROM_APP, Arrays.asList("Gizmo")); + headers.put(HEADER_TRANS_ID, Arrays.asList(MDC.get(LoggingContext.LoggingField.REQUEST_ID.toString()))); + + // Add the aai_node_type so that AAI can read the data created by gizmo + // TODO: This probably shouldn't be here + properties.put(org.openecomp.schema.OxmModelValidator.Metadata.NODE_TYPE.propertyName(), type); + + Vertex.Builder insertVertexBuilder = new Vertex.Builder(type); + insertVertexBuilder.id(id); + properties.forEach(insertVertexBuilder::property); + Vertex insertVertex = insertVertexBuilder.build(); + + String payload = insertVertex.toJson(championGson); + OperationResult getResult = client.put(url, payload, headers, + MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_JSON_TYPE); + + if (getResult.getResultCode() == Response.Status.OK.getStatusCode()) { + return Vertex.fromJson(getResult.getResult()); + } else { + // We didn't create a vertex with the supplied type, so just throw an exception. + throw new CrudException("Failed to update vertex", Response.Status.fromStatusCode(getResult.getResultCode())); + } + } + + @Override + public void deleteVertex(String id, String type, String txId) throws CrudException { + String url = baseUrl + "objects/" + id + "?transactionId=" + txId; + Map> headers = new HashMap<>(); + headers.put(HEADER_FROM_APP, Arrays.asList("Gizmo")); + headers.put(HEADER_TRANS_ID, Arrays.asList(MDC.get(LoggingContext.LoggingField.REQUEST_ID.toString()))); + + OperationResult getResult = client.delete(url, headers, MediaType.APPLICATION_JSON_TYPE); + + if (getResult.getResultCode() != Response.Status.OK.getStatusCode()) { + // We didn't delete a vertex with the supplied id, so just throw an exception. + throw new CrudException("Failed to delete vertex", Response.Status.fromStatusCode(getResult.getResultCode())); + } + } + + @Override + public Edge updateEdge(Edge edge, String txId) throws CrudException { + if (!edge.getId().isPresent()) + { + throw new CrudException("Unable to identify edge: " + edge.toString(), Response.Status.BAD_REQUEST); + } + String url = baseUrl + "relationships/" + edge.getId().get() + "?transactionId=" + txId; + Map> headers = new HashMap<>(); + headers.put(HEADER_FROM_APP, Arrays.asList("Gizmo")); + headers.put(HEADER_TRANS_ID, Arrays.asList(MDC.get(LoggingContext.LoggingField.REQUEST_ID.toString()))); + + OperationResult getResult = client.put(url, edge.toJson(championGson), headers, + MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_JSON_TYPE); + + if (getResult.getResultCode() == Response.Status.OK.getStatusCode()) { + return Edge.fromJson(getResult.getResult()); + } else { + // We didn't create an edge with the supplied type, so just throw an exception. + throw new CrudException("Failed to update edge: " + getResult.getFailureCause(), Response.Status.fromStatusCode(getResult.getResultCode())); + } + } + + @Override + public void deleteEdge(String id, String type, String txId) throws CrudException { + String url = baseUrl + "relationships/" + id + "?transactionId=" + txId; + Map> headers = new HashMap<>(); + headers.put(HEADER_FROM_APP, Arrays.asList("Gizmo")); + headers.put(HEADER_TRANS_ID, Arrays.asList(MDC.get(LoggingContext.LoggingField.REQUEST_ID.toString()))); + + OperationResult getResult = client.delete(url, headers, MediaType.APPLICATION_JSON_TYPE); + + if (getResult.getResultCode() != 200) { + // We didn't find an edge with the supplied type, so just throw an exception. + throw new CrudException("No edge with id " + id + " found in graph", javax.ws.rs.core.Response.Status.NOT_FOUND); + } + } + + @Override + public Edge getEdge(String id, String type, String txId) throws CrudException { + String url = baseUrl + "relationships/" + id + "?transactionId=" + txId; + Map> headers = new HashMap<>(); + headers.put(HEADER_FROM_APP, Arrays.asList("Gizmo")); + headers.put(HEADER_TRANS_ID, Arrays.asList(MDC.get(LoggingContext.LoggingField.REQUEST_ID.toString()))); + + OperationResult getResult = client.get(url, headers, MediaType.APPLICATION_JSON_TYPE); + + if (getResult.getResultCode() == 200) { + Edge edge = Edge.fromJson(getResult.getResult()); + + if (!edge.getType().equalsIgnoreCase(type)) { + // We didn't find an edge with the supplied type, so just throw an exception. + throw new CrudException("No edge with id " + id + "and type " + type + " found in graph", javax.ws.rs.core.Response.Status.NOT_FOUND); + } + return edge; + } else { + // We didn't find an edge with the supplied id, so just throw an exception. + throw new CrudException("No edge with id " + id + " found in graph", javax.ws.rs.core.Response.Status.NOT_FOUND); + } + } + + public Vertex getVertex(String id, String type, String txId) throws CrudException { + String url = baseUrl + "objects/" + id + "?transactionId=" + txId; + Map> headers = new HashMap<>(); + headers.put(HEADER_FROM_APP, Arrays.asList("Gizmo")); + headers.put(HEADER_TRANS_ID, Arrays.asList(MDC.get(LoggingContext.LoggingField.REQUEST_ID.toString()))); + + OperationResult getResult = client.get(url, headers, MediaType.APPLICATION_JSON_TYPE); + + if (getResult.getResultCode() == 200) { + Vertex vert = Vertex.fromJson(getResult.getResult()); + + if (!vert.getType().equalsIgnoreCase(type)) { + // We didn't find a vertex with the supplied type, so just throw an exception. + throw new CrudException("No vertex with id " + id + "and type " + type + " found in graph", javax.ws.rs.core.Response.Status.NOT_FOUND); + } + return vert; + } else { + // We didn't find a vertex with the supplied id, so just throw an exception. + throw new CrudException("No vertex with id " + id + " found in graph", javax.ws.rs.core.Response.Status.NOT_FOUND); + } + } + + // https://stackoverflow.com/questions/26942330/convert-mapstring-string-to-listnamevaluepair-is-this-the-most-efficient + private List convertToNameValuePair(Map pairs) { + List nvpList = new ArrayList<>(pairs.size()); + + pairs.forEach((key, value) -> nvpList.add(new BasicNameValuePair(key, value.toString()))); + + return nvpList; + } +} diff --git a/src/main/java/org/openecomp/crud/dao/champion/ChampionEdgeSerializer.java b/src/main/java/org/openecomp/crud/dao/champion/ChampionEdgeSerializer.java new file mode 100644 index 0000000..5ca6c2c --- /dev/null +++ b/src/main/java/org/openecomp/crud/dao/champion/ChampionEdgeSerializer.java @@ -0,0 +1,49 @@ +/** + * ============LICENSE_START======================================================= + * Gizmo + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * 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========================================================= + * + * ECOMP is a trademark and service mark of AT&T Intellectual Property. + */ +package org.openecomp.crud.dao.champion; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +import org.openecomp.crud.entity.Edge; +import org.openecomp.crud.entity.Vertex; + +import java.lang.reflect.Type; + +public class ChampionEdgeSerializer implements JsonSerializer { + @Override + public JsonElement serialize(Edge edge, Type type, JsonSerializationContext jsonSerializationContext) { + final JsonObject edgeObj = new JsonObject(); + if (edge.getId().isPresent()) { + edgeObj.add("key", jsonSerializationContext.serialize(edge.getId().get())); + } + edgeObj.add("type", jsonSerializationContext.serialize(edge.getType())); + edgeObj.add("properties", jsonSerializationContext.serialize(edge.getProperties())); + edgeObj.add("source", jsonSerializationContext.serialize(edge.getSource())); + edgeObj.add("target", jsonSerializationContext.serialize(edge.getTarget())); + return edgeObj; + } +} diff --git a/src/main/java/org/openecomp/crud/dao/champion/ChampionVertexSerializer.java b/src/main/java/org/openecomp/crud/dao/champion/ChampionVertexSerializer.java new file mode 100644 index 0000000..ab743d7 --- /dev/null +++ b/src/main/java/org/openecomp/crud/dao/champion/ChampionVertexSerializer.java @@ -0,0 +1,47 @@ +/** + * ============LICENSE_START======================================================= + * Gizmo + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * 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========================================================= + * + * ECOMP is a trademark and service mark of AT&T Intellectual Property. + */ +package org.openecomp.crud.dao.champion; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +import org.openecomp.crud.entity.Vertex; + +import java.lang.reflect.Type; + +public class ChampionVertexSerializer implements JsonSerializer { + @Override + public JsonElement serialize(Vertex vertex, Type type, JsonSerializationContext jsonSerializationContext) { + final JsonObject vertexObj = new JsonObject(); + if (vertex.getId().isPresent()) { + vertexObj.add("key", jsonSerializationContext.serialize(vertex.getId().get())); + } + vertexObj.add("type", jsonSerializationContext.serialize(vertex.getType())); + vertexObj.add("properties", jsonSerializationContext.serialize(vertex.getProperties())); + + return vertexObj; + } +} diff --git a/src/main/java/org/openecomp/crud/entity/Edge.java b/src/main/java/org/openecomp/crud/entity/Edge.java index 803511f..ceae494 100644 --- a/src/main/java/org/openecomp/crud/entity/Edge.java +++ b/src/main/java/org/openecomp/crud/entity/Edge.java @@ -23,16 +23,22 @@ */ package org.openecomp.crud.entity; +import net.dongliu.gson.GsonJava8TypeAdapterFactory; + import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import com.google.gson.annotations.SerializedName; import java.util.HashMap; import java.util.Map; import java.util.Optional; public class Edge { - private static final Gson gson = new GsonBuilder().create(); + private static final Gson gson = new GsonBuilder() + .registerTypeAdapterFactory(new GsonJava8TypeAdapterFactory()) + .create(); + @SerializedName(value="id", alternate={"key"}) private final Optional id; private final String type; private final Map properties; @@ -106,6 +112,14 @@ public class Edge { return gson.toJson(this); } + public String toJson(Gson customGson) { + return customGson.toJson(this); + } + + public static Edge fromJson(String jsonString) { + return gson.fromJson(jsonString, Edge.class); + } + public Optional getId() { return id; } diff --git a/src/main/java/org/openecomp/crud/entity/Vertex.java b/src/main/java/org/openecomp/crud/entity/Vertex.java index 1b0b97d..d2f2b08 100644 --- a/src/main/java/org/openecomp/crud/entity/Vertex.java +++ b/src/main/java/org/openecomp/crud/entity/Vertex.java @@ -23,17 +23,23 @@ */ package org.openecomp.crud.entity; +import net.dongliu.gson.GsonJava8TypeAdapterFactory; + import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import com.google.gson.annotations.SerializedName; import java.util.HashMap; import java.util.Map; import java.util.Optional; public class Vertex { - private static final Gson gson = new GsonBuilder().create(); + private static final Gson gson = new GsonBuilder() + .registerTypeAdapterFactory(new GsonJava8TypeAdapterFactory()).create(); + @SerializedName(value="id", alternate={"key"}) private final Optional id; + private final String type; private final Map properties; @@ -82,6 +88,14 @@ public class Vertex { return gson.toJson(this); } + public String toJson(Gson customGson) { + return customGson.toJson(this); + } + + public static Vertex fromJson(String jsonString) { + return gson.fromJson(jsonString, Vertex.class); + } + @Override public String toString() { return "Vertex [id=" + id + ", type=" + type + ", properties=" + properties + "]"; diff --git a/src/main/java/org/openecomp/crud/logging/CrudServiceMsgs.java b/src/main/java/org/openecomp/crud/logging/CrudServiceMsgs.java index 265f327..98cc4fa 100644 --- a/src/main/java/org/openecomp/crud/logging/CrudServiceMsgs.java +++ b/src/main/java/org/openecomp/crud/logging/CrudServiceMsgs.java @@ -43,6 +43,7 @@ public enum CrudServiceMsgs implements LogMessageEnum { INVALID_OXM_FILE, INVALID_OXM_DIR, OXM_FILE_CHANGED, + TRANSACTION, /** * Successfully loaded schema: {0} diff --git a/src/main/java/org/openecomp/crud/logging/LoggingUtil.java b/src/main/java/org/openecomp/crud/logging/LoggingUtil.java index aaa250a..2ef2772 100644 --- a/src/main/java/org/openecomp/crud/logging/LoggingUtil.java +++ b/src/main/java/org/openecomp/crud/logging/LoggingUtil.java @@ -81,7 +81,7 @@ public class LoggingUtil { (req != null) ? req.getMethod() : "Unknown", (req != null) ? req.getRequestURL().toString() : "Unknown", (req != null) ? req.getRemoteHost() : "Unknown", - Integer.toString(response.getStatus()) + " error: " + (response.getEntity() == null ? "" + Integer.toString(response.getStatus()) + " payload: " + (response.getEntity() == null ? "" : response.getEntity().toString())); MDC.clear(); } diff --git a/src/main/java/org/openecomp/crud/parser/CrudResponseBuilder.java b/src/main/java/org/openecomp/crud/parser/CrudResponseBuilder.java index 1a84322..308583d 100644 --- a/src/main/java/org/openecomp/crud/parser/CrudResponseBuilder.java +++ b/src/main/java/org/openecomp/crud/parser/CrudResponseBuilder.java @@ -23,20 +23,27 @@ */ package org.openecomp.crud.parser; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.ws.rs.core.Response.Status; import org.openecomp.crud.entity.Edge; import org.openecomp.crud.entity.Vertex; import org.openecomp.crud.exception.CrudException; +import org.openecomp.crud.service.BulkPayload; import org.openecomp.crud.service.EdgePayload; import org.openecomp.crud.service.VertexPayload; import org.openecomp.schema.RelationshipSchemaLoader; -import java.util.ArrayList; -import java.util.List; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; public class CrudResponseBuilder { @@ -45,6 +52,42 @@ public class CrudResponseBuilder { public static final String SOURCE = "source"; public static final String TARGET = "target"; public static final String URL_BASE = "services/inventory/"; + + public static String buildUpsertBulkResponse(HashMap vertices, HashMap edges , String version,BulkPayload incomingPayload) + throws CrudException { + + for (JsonElement e : incomingPayload.getObjects()) { + List> entries = new ArrayList>(e.getAsJsonObject().entrySet()); + + Map.Entry item = entries.get(1); + + Vertex responseVertex = vertices.get(item.getKey()); + if(responseVertex != null){ + JsonObject v = gson.fromJson(buildUpsertVertexResponse(responseVertex,version), JsonObject.class); + item.setValue(v); + }else{ + item.setValue(gson.fromJson("{}", JsonObject.class)); + } + + } + for (JsonElement e : incomingPayload.getRelationships()) { + List> entries = new ArrayList>(e.getAsJsonObject().entrySet()); + + Map.Entry item = entries.get(1); + + Edge responseEdge = edges.get(item.getKey()); + if(responseEdge != null){ + JsonObject v = gson.fromJson(buildUpsertEdgeResponse(responseEdge,version), JsonObject.class); + item.setValue(v); + }else{ + item.setValue(gson.fromJson("{}", JsonObject.class)); + } + + } + return incomingPayload.toJson(); + } + + public static String buildUpsertVertexResponse(Vertex vertex, String version) throws CrudException { diff --git a/src/main/java/org/openecomp/crud/service/BulkPayload.java b/src/main/java/org/openecomp/crud/service/BulkPayload.java new file mode 100644 index 0000000..28cf420 --- /dev/null +++ b/src/main/java/org/openecomp/crud/service/BulkPayload.java @@ -0,0 +1,128 @@ +/** + * ============LICENSE_START======================================================= + * Gizmo + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * 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========================================================= + * + * ECOMP is a trademark and service mark of AT&T Intellectual Property. + */ +package org.openecomp.crud.service; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import org.openecomp.crud.exception.CrudException; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.ws.rs.core.Response.Status; + +public class BulkPayload { + public enum OperationType { + CREATE, UPDATE, DELETE + } + + + + 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 BulkPayload fromJson(String payload) throws CrudException { + try { + if (payload == null || payload.isEmpty()) { + throw new CrudException("Invalid Json Payload", Status.BAD_REQUEST); + } + return gson.fromJson(payload, BulkPayload.class); + } catch (Exception ex) { + throw new CrudException("Invalid Json Payload", Status.BAD_REQUEST); + } + } + + 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; + } + + @Override + public String toString() { + return "BulkPayload [objects=" + objects + ", relationships=" + relationships + "]"; + } + + public static void main(String[] args) throws Exception { + BulkPayload p = new BulkPayload(); + JsonObject root = new JsonObject(); + JsonArray vertices = new JsonArray(); + JsonObject v1 = new JsonObject(); + JsonObject v2 = new JsonObject(); + JsonObject prop = new JsonObject(); + + prop.addProperty("p1","value1"); + prop.addProperty("p2","value2"); + v1.add("v1", prop); + v2.add("v2",prop); + + vertices.add(v1); + vertices.add(v2); + + + root.add("objects", vertices); + + String s = "{\"objects\":[{\"v1\":{\"p1\":\"value1\",\"p2\":\"value2\"}},{\"v2\":{\"p1\":\"value1\",\"p2\":\"value2\"}}]}"; + + p = BulkPayload.fromJson(s); + + List po = p.getObjects(); + List ids = new ArrayList(); + for (JsonElement e : po){ + Set> entries = e.getAsJsonObject().entrySet(); + + for (Map.Entry entry : entries) { + ids.add(entry.getKey()); + } + } + + + System.out.println("root: " + root.toString()); + System.out.println("payload ids: " + ids.toString()); + + } + +} \ No newline at end of file diff --git a/src/main/java/org/openecomp/crud/service/CrudGraphDataService.java b/src/main/java/org/openecomp/crud/service/CrudGraphDataService.java index f38fe0f..c6b6a48 100644 --- a/src/main/java/org/openecomp/crud/service/CrudGraphDataService.java +++ b/src/main/java/org/openecomp/crud/service/CrudGraphDataService.java @@ -23,11 +23,19 @@ */ package org.openecomp.crud.service; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.ws.rs.core.Response.Status; + import org.onap.aaiutils.oxm.OxmModelLoader; import org.openecomp.aai.champcore.ChampGraph; import org.openecomp.crud.dao.GraphDao; import org.openecomp.crud.dao.champ.ChampDao; import org.openecomp.crud.entity.Edge; + import org.openecomp.crud.entity.Vertex; import org.openecomp.crud.exception.CrudException; import org.openecomp.crud.parser.CrudResponseBuilder; @@ -35,32 +43,149 @@ import org.openecomp.schema.OxmModelValidator; import org.openecomp.schema.RelationshipSchemaLoader; import org.openecomp.schema.RelationshipSchemaValidator; -import java.util.List; -import java.util.Map; +import com.google.gson.JsonElement; public class CrudGraphDataService { private GraphDao dao; public CrudGraphDataService(ChampGraph graphImpl) throws CrudException { + this.dao = new ChampDao(graphImpl); + + loadModels(); + } - this.dao = new ChampDao(graphImpl); - //load the schemas - try { + public CrudGraphDataService(GraphDao dao) throws CrudException { + this.dao = dao; + + loadModels(); + } + + private void loadModels() throws CrudException { + //load the schemas + try { OxmModelLoader.loadModels(); } catch (Exception e) { throw new CrudException(e); } - RelationshipSchemaLoader.loadModels(); - } - + RelationshipSchemaLoader.loadModels(); + } public String addVertex(String version, String type, VertexPayload payload) throws CrudException { Vertex vertex = OxmModelValidator.validateIncomingUpsertPayload(null, version, type, payload.getProperties()); return addVertex(version, vertex); } + + public String addBulk(String version, BulkPayload payload) throws CrudException { + HashMap vertices = new HashMap(); + HashMap edges = new HashMap(); + String txId = dao.openTransaction(); + try { + // Handle vertices + for (JsonElement v : payload.getObjects()) { + List> entries = new ArrayList>(v.getAsJsonObject().entrySet()); + + if (entries.size() != 2) { + throw new CrudException("", Status.BAD_REQUEST); + } + Map.Entry opr = entries.get(0); + Map.Entry item = entries.get(1); + + VertexPayload vertexPayload = VertexPayload.fromJson(item.getValue().getAsJsonObject().toString()); + + if (opr.getValue().getAsString().equalsIgnoreCase("add") || opr.getValue().getAsString().equalsIgnoreCase("modify")) { + Vertex validatedVertex; + Vertex persistedVertex; + if (opr.getValue().getAsString().equalsIgnoreCase("add")) { + validatedVertex = OxmModelValidator.validateIncomingUpsertPayload(null, version, vertexPayload.getType(), + vertexPayload.getProperties()); + // Call champDAO to add the vertex + persistedVertex = dao.addVertex(validatedVertex.getType(), validatedVertex.getProperties(), txId); + } else { + validatedVertex = OxmModelValidator.validateIncomingUpsertPayload(vertexPayload.getId(), version, vertexPayload.getType(), + vertexPayload.getProperties()); + // Call champDAO to update the vertex + persistedVertex = dao.updateVertex(vertexPayload.getId(), validatedVertex.getType(), validatedVertex.getProperties(), txId); + } + + Vertex outgoingVertex = OxmModelValidator.validateOutgoingPayload(version, persistedVertex); + + vertices.put(item.getKey(), outgoingVertex); + + } else if (opr.getValue().getAsString().equalsIgnoreCase("delete")) { + dao.deleteVertex(vertexPayload.getId(), OxmModelValidator.resolveCollectionType(version, vertexPayload.getType()), txId); + } + + } + // Handle Edges + for (JsonElement v : payload.getRelationships()) { + List> entries = new ArrayList>(v.getAsJsonObject().entrySet()); + + if (entries.size() != 2) { + throw new CrudException("", Status.BAD_REQUEST); + } + Map.Entry opr = entries.get(0); + Map.Entry item = entries.get(1); + + EdgePayload edgePayload = EdgePayload.fromJson(item.getValue().getAsJsonObject().toString()); + + if (opr.getValue().getAsString().equalsIgnoreCase("add") || opr.getValue().getAsString().equalsIgnoreCase("modify")) { + Edge validatedEdge; + Edge persistedEdge; + if (opr.getValue().getAsString().equalsIgnoreCase("add")) { + // Fix the source/detination + if (edgePayload.getSource().startsWith("$")) { + Vertex source = vertices.get(edgePayload.getSource().substring(1)); + if (source == null) { + throw new CrudException("Not able to find vertex: " + edgePayload.getSource().substring(1), Status.INTERNAL_SERVER_ERROR); + } + edgePayload.setSource("services/inventory/" + version + "/" + source.getType() + "/" + source.getId().get()); + } + if (edgePayload.getTarget().startsWith("$")) { + Vertex target = vertices.get(edgePayload.getTarget().substring(1)); + if (target == null) { + throw new CrudException("Not able to find vertex: " + edgePayload.getTarget().substring(1), Status.INTERNAL_SERVER_ERROR); + } + edgePayload.setTarget("services/inventory/" + version + "/" + target.getType() + "/" + target.getId().get()); + } + validatedEdge = RelationshipSchemaValidator.validateIncomingAddPayload(version, edgePayload.getType(), edgePayload); + persistedEdge = dao.addEdge(validatedEdge.getType(), validatedEdge.getSource(), validatedEdge.getTarget(), + validatedEdge.getProperties(), txId); + } else { + Edge edge = dao.getEdge(edgePayload.getId(), edgePayload.getType(), txId); + validatedEdge = RelationshipSchemaValidator.validateIncomingUpdatePayload(edge, version, edgePayload); + persistedEdge = dao.updateEdge(edge, txId); + } + + Edge outgoingEdge = RelationshipSchemaValidator.validateOutgoingPayload(version, persistedEdge); + + edges.put(item.getKey(), outgoingEdge); + + } else if (opr.getValue().getAsString().equalsIgnoreCase("delete")) { + RelationshipSchemaValidator.validateType(version, edgePayload.getType()); + dao.deleteEdge(edgePayload.getId(), edgePayload.getType(), txId); + } + + } + // close champ TX + dao.commitTransaction(txId); + } catch (CrudException ex) { + dao.rollbackTransaction(txId); + throw ex; + } catch (Exception ex) { + dao.rollbackTransaction(txId); + throw ex; + } finally { + if (dao.transactionExists(txId)) { + dao.rollbackTransaction(txId); + } + } + + + return CrudResponseBuilder.buildUpsertBulkResponse(vertices, edges, version, payload); + } private String addVertex(String version, Vertex vertex) throws CrudException { Vertex addedVertex = dao.addVertex(vertex.getType(), vertex.getProperties()); @@ -182,5 +307,7 @@ public class CrudGraphDataService { type, filter)); return CrudResponseBuilder.buildGetVerticesResponse(items, version); } + + } diff --git a/src/main/java/org/openecomp/crud/service/CrudRestService.java b/src/main/java/org/openecomp/crud/service/CrudRestService.java index bef7b04..8a9f1c0 100644 --- a/src/main/java/org/openecomp/crud/service/CrudRestService.java +++ b/src/main/java/org/openecomp/crud/service/CrudRestService.java @@ -23,20 +23,13 @@ */ package org.openecomp.crud.service; -import org.apache.cxf.jaxrs.ext.PATCH; -import org.openecomp.auth.Auth; -import org.openecomp.cl.api.Logger; -import org.openecomp.cl.eelf.LoggerFactory; -import org.openecomp.crud.exception.CrudException; -import org.openecomp.crud.logging.CrudServiceMsgs; -import org.openecomp.crud.logging.LoggingUtil; -import org.openecomp.crud.util.CrudServiceConstants; -import org.slf4j.MDC; - import java.security.cert.X509Certificate; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; + import javax.security.auth.x500.X500Principal; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.Consumes; @@ -55,6 +48,18 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.UriInfo; +import org.apache.cxf.jaxrs.ext.PATCH; +import org.openecomp.auth.Auth; +import org.openecomp.cl.api.Logger; +import org.openecomp.cl.eelf.LoggerFactory; +import org.openecomp.crud.exception.CrudException; +import org.openecomp.crud.logging.CrudServiceMsgs; +import org.openecomp.crud.logging.LoggingUtil; +import org.openecomp.crud.util.CrudServiceConstants; +import org.slf4j.MDC; + +import com.google.gson.JsonElement; + public class CrudRestService { private CrudGraphDataService crudGraphDataService; @@ -221,21 +226,6 @@ public class CrudRestService { return response; } - @PUT - @Path("/relationships/{version}/{type}/") - @Consumes({MediaType.APPLICATION_JSON}) - @Produces({MediaType.APPLICATION_JSON}) - public Response updateEdgeWithoutId(String content, @Context HttpHeaders headers, @Context HttpServletRequest req) { - - LoggingUtil.initMdcContext(req, headers); - - logger.debug("Incoming request..." + content); - Response response = Response.status (Status.BAD_REQUEST).entity ("Cannot Update Edge Without Specifying Id in URL").build(); - - LoggingUtil.logRestRequest(logger, auditLogger, req, response); - return response; - } - @PUT @Path("/relationships/{version}/{type}/{id}") @Consumes({MediaType.APPLICATION_JSON}) @@ -460,6 +450,142 @@ public class CrudRestService { .type(MediaType.APPLICATION_JSON).build(); } + LoggingUtil.logRestRequest(logger, auditLogger, req, response); + return response; + } + + private void validateBulkPayload(BulkPayload payload) throws CrudException { + List vertices = new ArrayList(); + List edges = new ArrayList(); + + for (JsonElement v : payload.getObjects()) { + List> entries = new ArrayList>(v.getAsJsonObject().entrySet()); + + if (entries.size() != 2) { + throw new CrudException("", Status.BAD_REQUEST); + } + Map.Entry opr = entries.get(0); + Map.Entry item = entries.get(1); + + if (vertices.contains(item.getKey())) { + throw new CrudException("duplicate vertex in payload: " + item.getKey(), Status.BAD_REQUEST); + } + VertexPayload vertexPayload = VertexPayload.fromJson(item.getValue().getAsJsonObject().toString()); + if (vertexPayload.getType() == null) { + throw new CrudException("Vertex Type cannot be null for: " + item.getKey(), Status.BAD_REQUEST); + } + + if (!opr.getKey().equalsIgnoreCase("operation")) { + throw new CrudException("operation missing in item: " + item.getKey(), Status.BAD_REQUEST); + } + + if (!opr.getValue().getAsString().equalsIgnoreCase("add") && !opr.getValue().getAsString().equalsIgnoreCase("modify") + && !opr.getValue().getAsString().equalsIgnoreCase("delete")) { + throw new CrudException("Invalid operation at item: " + item.getKey(), Status.BAD_REQUEST); + } + // check if ID is populate for modify/delete operation + if ((opr.getValue().getAsString().equalsIgnoreCase("modify") || opr.getValue().getAsString().equalsIgnoreCase("delete")) + && (vertexPayload.getId() == null)) { + + throw new CrudException("Mising ID at item: " + item.getKey(), Status.BAD_REQUEST); + + } + + vertices.add(item.getKey()); + } + + for (JsonElement v : payload.getRelationships()) { + List> entries = new ArrayList>(v.getAsJsonObject().entrySet()); + + if (entries.size() != 2) { + throw new CrudException("", Status.BAD_REQUEST); + } + Map.Entry opr = entries.get(0); + Map.Entry item = entries.get(1); + + if (edges.contains(item.getKey())) { + throw new CrudException("duplicate Edge in payload: " + item.getKey(), Status.BAD_REQUEST); + } + + EdgePayload edgePayload = EdgePayload.fromJson(item.getValue().getAsJsonObject().toString()); + + if (edgePayload.getType() == null) { + throw new CrudException("Edge Type cannot be null for: " + item.getKey(), Status.BAD_REQUEST); + } + + if (!opr.getKey().equalsIgnoreCase("operation")) { + throw new CrudException("operation missing in item: " + item.getKey(), Status.BAD_REQUEST); + } + + if (!opr.getValue().getAsString().equalsIgnoreCase("add") && !opr.getValue().getAsString().equalsIgnoreCase("modify") + && !opr.getValue().getAsString().equalsIgnoreCase("delete")) { + throw new CrudException("Invalid operation at item: " + item.getKey(), Status.BAD_REQUEST); + } + // check if ID is populate for modify/delete operation + if ((edgePayload.getId() == null) && (opr.getValue().getAsString().equalsIgnoreCase("modify") + || opr.getValue().getAsString().equalsIgnoreCase("delete"))) { + + throw new CrudException("Mising ID at item: " + item.getKey(), Status.BAD_REQUEST); + + } + if (opr.getValue().getAsString().equalsIgnoreCase("add")) { + if(edgePayload.getSource()==null || edgePayload.getTarget()==null){ + throw new CrudException("Source/Target cannot be null for edge: " + item.getKey(), + Status.BAD_REQUEST); + } + if (edgePayload.getSource().startsWith("$") && !vertices.contains(edgePayload.getSource().substring(1))) { + throw new CrudException("Source Vertex " + edgePayload.getSource().substring(1) + " not found for Edge: " + item.getKey(), + Status.BAD_REQUEST); + } + + if (edgePayload.getTarget().startsWith("$") && !vertices.contains(edgePayload.getTarget().substring(1))) { + throw new CrudException("Target Vertex " + edgePayload.getSource().substring(1) + " not found for Edge: " + item.getKey(), + Status.BAD_REQUEST); + } + } + edges.add(item.getKey()); + + } + + } + + @POST + @Path("/{version}/bulk/") + @Consumes({MediaType.APPLICATION_JSON}) + @Produces({MediaType.APPLICATION_JSON}) + public Response addBulk(String content, @PathParam("version") String version, + @PathParam("type") String type, @PathParam("uri") @Encoded String uri, + @Context HttpHeaders headers, @Context UriInfo uriInfo, + @Context HttpServletRequest req) { + + LoggingUtil.initMdcContext(req, headers); + + logger.debug("Incoming request..." + content); + Response response = null; + + if (validateRequest(req, uri, content, Action.POST, + CrudServiceConstants.CRD_AUTH_POLICY_NAME)) { + + try { + BulkPayload payload = BulkPayload.fromJson(content); + if ((payload.getObjects() == null && payload.getRelationships() == null) || (payload.getObjects() != null && payload.getObjects().isEmpty() + && payload.getRelationships() != null && payload.getRelationships().isEmpty())) { + throw new CrudException("Invalid request Payload", Status.BAD_REQUEST); + } + + validateBulkPayload(payload); + String result = crudGraphDataService.addBulk(version, payload); + response = Response.status(Status.OK).entity(result).type(mediaType).build(); + } catch (CrudException ce) { + response = Response.status(ce.getHttpStatus()).entity(ce.getMessage()).build(); + } catch (Exception e) { + response = Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()).build(); + } + } else { + response = Response.status(Status.FORBIDDEN).entity(content) + .type(MediaType.APPLICATION_JSON).build(); + } + LoggingUtil.logRestRequest(logger, auditLogger, req, response); return response; } @@ -636,20 +762,6 @@ public class CrudRestService { return response; } - @DELETE - @Path("/relationships/{version}/{type}/") - @Consumes({MediaType.APPLICATION_JSON}) - @Produces({MediaType.APPLICATION_JSON}) - public Response deleteEdgeWithouId(String content, @Context HttpHeaders headers, @Context HttpServletRequest req) { - - LoggingUtil.initMdcContext(req, headers); - - logger.debug("Incoming request..." + content); - Response response = Response.status ( Status.BAD_REQUEST ).entity ( "Cannot Delete Edge Without Specifying Id in URL" ).build (); - LoggingUtil.logRestRequest(logger, auditLogger, req, response); - return response; - } - @DELETE @Path("/relationships/{version}/{type}/{id}") @Consumes({MediaType.APPLICATION_JSON}) diff --git a/src/main/java/org/openecomp/crud/util/CrudServiceConstants.java b/src/main/java/org/openecomp/crud/util/CrudServiceConstants.java index 9543e2d..1a4858f 100644 --- a/src/main/java/org/openecomp/crud/util/CrudServiceConstants.java +++ b/src/main/java/org/openecomp/crud/util/CrudServiceConstants.java @@ -43,6 +43,7 @@ public class CrudServiceConstants { public static final String CRD_CONFIG_FILE = CRD_SPECIFIC_CONFIG + "crud-api.properties"; public static final String CRD_AUTH_FILE = CRD_HOME_AUTH + "crud_policy.json"; + public static final String CRD_CHAMPION_AUTH_FILE = CRD_HOME_AUTH + "champion-cert.p12"; public static final String CRD_AUTH_POLICY_NAME = "crud"; diff --git a/src/main/java/org/openecomp/schema/OxmModelValidator.java b/src/main/java/org/openecomp/schema/OxmModelValidator.java index 0ae3e13..e51b23f 100644 --- a/src/main/java/org/openecomp/schema/OxmModelValidator.java +++ b/src/main/java/org/openecomp/schema/OxmModelValidator.java @@ -163,6 +163,7 @@ public class OxmModelValidator { CaseFormat.LOWER_HYPHEN.to(CaseFormat.UPPER_CAMEL, type)); final DynamicType modelObjectType = jaxbContext.getDynamicType(modelObjectClass); + final DynamicType reservedType = jaxbContext.getDynamicType("ReservedPropNames"); Set> payloadEntriesSet = properties.getAsJsonObject() .entrySet(); @@ -173,7 +174,9 @@ public class OxmModelValidator { // check for valid field if (modelObjectType.getDescriptor().getMappingForAttributeName(keyJavaName) == null) { - throw new CrudException("Invalid field: " + entry.getKey(), Status.BAD_REQUEST); + if(reservedType.getDescriptor().getMappingForAttributeName(keyJavaName) == null){ + throw new CrudException("Invalid field: " + entry.getKey(), Status.BAD_REQUEST); + } } } @@ -219,6 +222,20 @@ public class OxmModelValidator { } } } + + // Handle reserved properties + for (DatabaseMapping mapping : reservedType.getDescriptor().getMappings()) { + if (mapping.isAbstractDirectMapping()) { + DatabaseField field = mapping.getField(); + String keyName = field.getName().substring(0, field.getName().indexOf("/")); + + if (entriesMap.containsKey(keyName)) { + Object value = CrudServiceUtil.validateFieldType(entriesMap.get(keyName) + .getAsString(), field.getType()); + modelVertexBuilder.property(keyName, value); + } + } + } return modelVertexBuilder.build(); } catch (Exception e) { @@ -237,6 +254,7 @@ public class OxmModelValidator { CaseFormat.LOWER_HYPHEN.to(CaseFormat.UPPER_CAMEL, type)); final DynamicType modelObjectType = jaxbContext.getDynamicType(modelObjectClass); + final DynamicType reservedType = jaxbContext.getDynamicType("ReservedPropNames"); Set> payloadEntriesSet = properties.getAsJsonObject() .entrySet(); @@ -247,19 +265,26 @@ public class OxmModelValidator { String keyJavaName = CaseFormat.LOWER_HYPHEN.to(CaseFormat.LOWER_CAMEL, entry.getKey()); - // check for valid field - if (modelObjectType.getDescriptor().getMappingForAttributeName(keyJavaName) == null) { + DatabaseField field = null; + String defaultValue = null; + + if (modelObjectType.getDescriptor().getMappingForAttributeName(keyJavaName) != null) { + field = modelObjectType.getDescriptor().getMappingForAttributeName(keyJavaName).getField(); + defaultValue = modelObjectType.getDescriptor() + .getMappingForAttributeName(keyJavaName) + .getProperties().get("defaultValue") == null ? "" + : modelObjectType.getDescriptor().getMappingForAttributeName(keyJavaName) + .getProperties().get("defaultValue").toString(); + } + else if (reservedType.getDescriptor().getMappingForAttributeName(keyJavaName) != null) { + field = reservedType.getDescriptor().getMappingForAttributeName(keyJavaName).getField(); + defaultValue = ""; + } + + if (field == null) { throw new CrudException("Invalid field: " + entry.getKey(), Status.BAD_REQUEST); } - DatabaseField field = modelObjectType.getDescriptor() - .getMappingForAttributeName(keyJavaName).getField(); - String defaultValue = modelObjectType.getDescriptor() - .getMappingForAttributeName(keyJavaName) - .getProperties().get("defaultValue") == null ? "" - : modelObjectType.getDescriptor().getMappingForAttributeName(keyJavaName) - .getProperties().get("defaultValue").toString(); - // check if mandatory field is not set to null if (((XMLField) field).isRequired() && entry.getValue() instanceof JsonNull && !defaultValue.isEmpty()) { @@ -288,7 +313,7 @@ public class OxmModelValidator { } } - + private static DatabaseField getDatabaseField(String fieldName, DynamicType modelObjectType) { for (DatabaseField field : modelObjectType.getDescriptor().getAllFields()) { int ix = field.getName().indexOf("/"); diff --git a/src/main/java/org/openecomp/schema/RelationshipSchemaLoader.java b/src/main/java/org/openecomp/schema/RelationshipSchemaLoader.java index d4ae2b9..89a9e58 100644 --- a/src/main/java/org/openecomp/schema/RelationshipSchemaLoader.java +++ b/src/main/java/org/openecomp/schema/RelationshipSchemaLoader.java @@ -23,34 +23,38 @@ */ package org.openecomp.schema; -import org.apache.commons.io.IOUtils; -import org.openecomp.aai.dbmodel.DbEdgeRules; -import org.openecomp.cl.eelf.LoggerFactory; -import org.openecomp.crud.exception.CrudException; -import org.openecomp.crud.logging.CrudServiceMsgs; -import org.openecomp.crud.util.CrudServiceConstants; -import org.openecomp.crud.util.FileWatcher; -import org.springframework.core.io.UrlResource; -import org.springframework.core.io.support.PathMatchingResourcePatternResolver; -import org.springframework.core.io.support.ResourcePatternResolver; - -import java.io.*; -import java.util.Arrays; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; +import java.util.Arrays; import java.util.Comparator; -import java.util.concurrent.ConcurrentHashMap; import java.util.Date; import java.util.List; import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import java.util.SortedSet; -import java.util.stream.Collectors; import java.util.Timer; import java.util.TimerTask; import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + import javax.ws.rs.core.Response.Status; +import org.apache.commons.io.IOUtils; +import org.openecomp.cl.eelf.LoggerFactory; +import org.openecomp.crud.exception.CrudException; +import org.openecomp.crud.logging.CrudServiceMsgs; +import org.openecomp.crud.util.CrudServiceConstants; +import org.openecomp.crud.util.FileWatcher; +import org.springframework.core.io.UrlResource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; + public class RelationshipSchemaLoader { private static Map versionContextMap = new ConcurrentHashMap<>(); @@ -153,7 +157,6 @@ public class RelationshipSchemaLoader { String errorMsg = "Expecting a rules and a edge_properties files for " + version + ". Found: " + filenames; logger.warn(CrudServiceMsgs.INVALID_OXM_FILE, errorMsg); }}); - logger.info(CrudServiceMsgs.LOADED_OXM_FILE, "Relationship Schema and Properties files: " + rulesFiles.stream().map(f -> filename(f)).collect(Collectors.toList())); } catch (IOException e) { logger.error(CrudServiceMsgs.INVALID_OXM_DIR, rulesDir); diff --git a/src/main/resources/logging/CrudServiceMsgs.properties b/src/main/resources/logging/CrudServiceMsgs.properties index 13554e5..a4c2991 100644 --- a/src/main/resources/logging/CrudServiceMsgs.properties +++ b/src/main/resources/logging/CrudServiceMsgs.properties @@ -55,3 +55,6 @@ EXCEPTION_DURING_METHOD_CALL=\ OXM_LOAD_ERROR=\ CRD0503E|\ Unable to load OXM schema: {0} +TRANSACTION=\ + CRD0008I|\ + TRANSACTION: {0} -- cgit 1.2.3-korg