Split out Titan specific dependencies from core
[aai/gizmo.git] / src / main / java / org / openecomp / crud / dao / champ / ChampDao.java
1 /**
2  * ============LICENSE_START=======================================================
3  * Gizmo
4  * ================================================================================
5  * Copyright © 2017 AT&T Intellectual Property.
6  * Copyright © 2017 Amdocs
7  * All rights reserved.
8  * ================================================================================
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  *
13  *    http://www.apache.org/licenses/LICENSE-2.0
14  *
15  * Unless required by applicable law or agreed to in writing, software
16  * distributed under the License is distributed on an "AS IS" BASIS,
17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  * See the License for the specific language governing permissions and
19  * limitations under the License.
20  * ============LICENSE_END=========================================================
21  *
22  * ECOMP is a trademark and service mark of AT&T Intellectual Property.
23  */
24 package org.openecomp.crud.dao.champ;
25
26 import org.openecomp.aai.champcore.ChampGraph;
27 import org.openecomp.aai.champcore.exceptions.ChampMarshallingException;
28 import org.openecomp.aai.champcore.exceptions.ChampObjectNotExistsException;
29 import org.openecomp.aai.champcore.exceptions.ChampRelationshipNotExistsException;
30 import org.openecomp.aai.champcore.exceptions.ChampSchemaViolationException;
31 import org.openecomp.aai.champcore.exceptions.ChampTransactionException;
32 import org.openecomp.aai.champcore.exceptions.ChampUnmarshallingException;
33 import org.openecomp.aai.champcore.model.ChampObject;
34 import org.openecomp.aai.champcore.model.ChampRelationship;
35 import org.openecomp.aai.champcore.model.fluent.object.ObjectBuildOrPropertiesStep;
36 import org.openecomp.cl.api.Logger;
37 import org.openecomp.cl.eelf.LoggerFactory;
38 import org.openecomp.crud.dao.GraphDao;
39 import org.openecomp.crud.entity.Edge;
40 import org.openecomp.crud.entity.Vertex;
41 import org.openecomp.crud.exception.CrudException;
42 import org.openecomp.crud.logging.CrudServiceMsgs;
43
44 import java.util.ArrayList;
45 import java.util.HashMap;
46 import java.util.List;
47 import java.util.Map;
48 import java.util.Optional;
49 import java.util.stream.Collectors;
50 import java.util.stream.Stream;
51
52
53 /**
54  * This is the integration layer between the CRUD API service and the low level Champ library
55  * for graph database interaction.
56  */
57 public class ChampDao implements GraphDao {
58
59   public static final String CONFIG_STORAGE_BACKEND = "storage.backend";
60   public static final String CONFIG_STORAGE_BACKEND_DB = "storage.backend.db";
61   public static final String STORAGE_HBASE_DB = "hbase";
62   public static final String STORAGE_CASSANDRA_DB = "cassandra";
63   public static final String CONFIG_STORAGE_HOSTNAMES = "storage.hostnames";
64   public static final String CONFIG_STORAGE_PORT = "storage.port";
65   public static final String CONFIG_HBASE_ZNODE_PARENT = "storage.hbase.ext.zookeeper.znode.parent";
66   public static final String CONFIG_GRAPH_NAME = "graph.name";
67   public static final String GRAPH_UNQ_INSTANCE_ID_SUFFIX = "graph.unique-instance-id-suffix";
68
69   public static final String CONFIG_EVENT_STREAM_PUBLISHER = "event.stream.publisher";
70   public static final String CONFIG_EVENT_STREAM_NUM_PUBLISHERS = "event.stream.num-publishers";
71
72
73   public static final String DEFAULT_GRAPH_NAME = "default_graph";
74
75   private enum GraphType {
76     IN_MEMORY,
77     TITAN,
78     DSE
79   }
80   
81   /**
82    * Instance of the API used for interacting with the Champ library.
83    */
84   private ChampGraph champApi = null;
85
86   private Logger logger = LoggerFactory.getInstance().getLogger(ChampDao.class.getName());
87
88
89   /**
90    * Creates a new instance of the ChampDao.
91    *
92    * @param champGraph - Concrete implementation of the graph dao layer
93    */
94   public ChampDao(ChampGraph champGraph) {
95         this.champApi = champGraph;
96   }
97
98   @Override
99   public Vertex getVertex(String id, String type) throws CrudException {
100
101     try {
102
103       if (logger.isDebugEnabled()) {
104         logger.debug("getVertex with id: " + id);
105       }
106
107       long idAsLong = Long.parseLong(id);
108
109       // Request the vertex from the graph db.
110       Optional<ChampObject> retrievedVertex = champApi.retrieveObject(idAsLong);
111
112       // Did we find it?
113       if (retrievedVertex.isPresent() && retrievedVertex.get().getProperties()
114           .get(org.openecomp.schema.OxmModelValidator.Metadata.NODE_TYPE.propertyName())!=null && retrievedVertex.get().getProperties()
115           .get(org.openecomp.schema.OxmModelValidator.Metadata.NODE_TYPE.propertyName()).toString()
116           .equalsIgnoreCase(type)) {  
117         
118         // Yup, convert it to a Vector object and return it.
119         return vertexFromChampObject(retrievedVertex.get(),type);
120
121       } else {
122
123         // We didn't find a vertex with the supplied id, so just throw an
124         // exception.
125         throw new CrudException("No vertex with id " + id + " found in graph",
126             javax.ws.rs.core.Response.Status.NOT_FOUND);
127       }
128
129     } catch (ChampUnmarshallingException | ChampTransactionException e) {
130
131       // Something went wrong - throw an exception.
132       throw new CrudException(e.getMessage(),
133           javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR);
134     }
135   }
136
137
138   @Override
139   public List<Edge> getVertexEdges(String id) throws CrudException {
140
141     if (logger.isDebugEnabled()) {
142       logger.debug("get Edges incident to vertex with id: " + id + " from graph");
143     }
144
145     try {
146       long idAsLong = Long.parseLong(id); // GDF - what to do about id???
147
148       // Request the vertex from the graph db.
149       Optional<ChampObject> retrievedVertex = champApi.retrieveObject(idAsLong);
150
151       // Did we find it?
152       if (retrievedVertex.isPresent()) {
153
154         // Query the Champ library for the edges which are incident to the specified
155         // vertex.
156         Stream<ChampRelationship> relationships =
157             champApi.retrieveRelationships(retrievedVertex.get());
158
159         // Build an edge list from the result stream.
160         List<Edge> edges = new ArrayList<Edge>();
161         relationships.forEach(r -> edges.add(edgeFromChampRelationship(r)));
162
163         return edges;
164
165       } else {
166
167         // We couldn't find the specified vertex, so throw an exception.
168         throw new CrudException("No vertex with id " + id + " found in graph",
169             javax.ws.rs.core.Response.Status.NOT_FOUND);
170       }
171
172     } catch (ChampUnmarshallingException e) {
173
174       // Something went wrong, so throw an exception.
175       throw new CrudException(e.getMessage(),
176           javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR);
177
178     } catch (ChampObjectNotExistsException e) {
179
180       // We couldn't find the specified vertex, so throw an exception.
181       throw new CrudException("No vertex with id " + id + " found in graph",
182           javax.ws.rs.core.Response.Status.NOT_FOUND);
183     } catch (ChampTransactionException e) {
184       throw new CrudException("Transaction error occured",
185           javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR);
186     }
187   }
188
189
190   @Override
191   public Vertex addVertex(String type, Map<String, Object> properties) throws CrudException {
192
193     if (logger.isDebugEnabled()) {
194       logger.debug("Add/update vertex: {label: " + type
195           + " properties:" + propertiesMapToString(properties));
196     }
197
198     //Add the aai_node_type so that AAI can read the data created by gizmo
199     properties.put(org.openecomp.schema.OxmModelValidator.Metadata.NODE_TYPE.propertyName(), type);
200
201     // Create an object to represent our vertex in the format expected by the Champ library.
202     ChampObject objectToCreate = buildChampObject(type, properties);
203
204     try {
205
206       // Ask the Champ library to store our vertex, placing the returned object into a
207       // list so that we can easily put that into our result object.
208       return vertexFromChampObject(champApi.storeObject(objectToCreate),type);
209
210     } catch (ChampMarshallingException
211         | ChampSchemaViolationException
212         | ChampObjectNotExistsException 
213         | ChampTransactionException e) {
214
215       // Something went wrong - throw an exception.
216       throw new CrudException(e.getMessage(),
217           javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR);
218     }
219   }
220
221
222   @Override
223   public Vertex updateVertex(String id, String type, Map<String, Object> properties)
224       throws CrudException {
225
226     if (logger.isDebugEnabled()) {
227       logger.debug("Update vertex with id: " + id + " with properties: "
228           + propertiesMapToString(properties));
229     }
230     //Add the aai_node_type so that AAI can read the data created by gizmo
231     properties.put(org.openecomp.schema.OxmModelValidator.Metadata.NODE_TYPE.propertyName(), type);
232
233     try {
234       // Now, build the updated version of the Champ Object...
235       ChampObject updateObject = buildChampObject(id, type, properties);
236       // ...and send it to the Champ library.
237       return vertexFromChampObject(champApi.replaceObject(updateObject),type);
238
239     } catch (ChampObjectNotExistsException e) {
240       throw new CrudException("Not Found", javax.ws.rs.core.Response.Status.NOT_FOUND);
241     } catch (NumberFormatException | ChampMarshallingException | ChampSchemaViolationException e) {
242       throw new CrudException(e.getMessage(),
243           javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR);
244     } catch (ChampTransactionException e) {
245       throw new CrudException("Transaction error occured",
246           javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR);
247     }
248
249   }
250
251
252   @Override
253   public List<Vertex> getVertices(String type, Map<String, Object> filter) throws CrudException {
254
255     if (logger.isDebugEnabled()) {
256       logger.debug("Retrieve vertices with type label: " + type + " which map query parameters: "
257           + propertiesMapToString(filter));
258     }
259
260
261     filter.put(org.openecomp.schema.OxmModelValidator.Metadata.NODE_TYPE.propertyName(), type);
262
263
264     Stream<ChampObject> retrievedVertices;
265     try {
266       retrievedVertices = champApi.queryObjects(filter);
267       
268     } catch (ChampTransactionException e) {
269       throw new CrudException("Transaction error occured",
270           javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR);
271     }
272
273     List<Vertex> vertices = retrievedVertices
274         .map(v -> vertexFromChampObject(v,type))
275         .collect(Collectors.toList());
276
277
278     if (logger.isDebugEnabled()) {
279       logger.debug("Resulting vertex list: " + retrievedVertices);
280     }
281
282     // ...and return it to the caller.
283     return vertices;
284   }
285
286   private Object getRelKey(String id) {
287     Object key = id;
288     // convert into Long if applicable . TODO : revisit in story NUC-304
289     try {
290       key = Long.parseLong(id);
291     } catch (NumberFormatException e) {
292       // The id isn't a Long, leave it as a string
293     }
294
295     return key;
296   }
297
298   @Override
299   public Edge getEdge(String id, String type) throws CrudException {
300
301     if (logger.isDebugEnabled()) {
302       logger.debug("Get edge with id: " + id);
303     }
304
305     try {
306
307       // Request the edge from the graph db.
308       Optional<ChampRelationship> relationship = champApi.retrieveRelationship(getRelKey(id));
309
310       // Did we find it?
311       if (relationship.isPresent() && relationship.get().getType().equals(type)) {
312
313         // Yup - return the result.
314         return edgeFromChampRelationship(relationship.get());
315
316       } else {
317
318         // We didn't find an edge with the supplied id, so throw an exception.
319         throw new CrudException("No edge with id " + id + " found in graph",
320             javax.ws.rs.core.Response.Status.NOT_FOUND);
321       }
322
323     } catch (ChampUnmarshallingException | ChampTransactionException e) {
324
325       // Something went wrong, so throw an exception.
326       throw new CrudException(e.getMessage(),
327           javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR);
328     }
329   }
330
331   @Override
332   public Edge addEdge(String type,
333                       Vertex source,
334                       Vertex target,
335                       Map<String, Object> properties) throws CrudException {
336
337     // For now, assume source and target are straight ids...
338     try {
339
340       Optional<ChampObject> sourceObject
341           = champApi.retrieveObject(Long.parseLong(source.getId().get()));
342       if (!sourceObject.isPresent() || !sourceObject.get().getType().equals(source.getType())) {
343         throw new CrudException("Error creating edge - source vertex with id " + source
344             + " does not exist in graph data base",
345             javax.ws.rs.core.Response.Status.BAD_REQUEST);
346       }
347
348       Optional<ChampObject> targetObject
349           = champApi.retrieveObject(Long.parseLong(target.getId().get()));
350       if (!targetObject.isPresent() || !targetObject.get().getType().equals(target.getType())) {
351         throw new CrudException("Error creating edge - target vertex with id " + target
352             + " does not exist in graph data base",
353             javax.ws.rs.core.Response.Status.BAD_REQUEST);
354       }
355
356       // Now, create the ChampRelationship object for our edge and store it in
357       // the graph database.
358       return edgeFromChampRelationship(
359           champApi.storeRelationship(
360               new ChampRelationship.Builder(sourceObject.get(), targetObject.get(), type)
361                   .properties(properties)
362                   .build()));
363
364     } catch (ChampMarshallingException
365         | ChampObjectNotExistsException
366         | ChampSchemaViolationException
367         | ChampRelationshipNotExistsException
368         | ChampUnmarshallingException | NumberFormatException | ChampTransactionException e) {
369
370       throw new CrudException("Error creating edge: " + e.getMessage(),
371           javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR);
372     }
373   }
374
375
376   @Override
377   public List<Edge> getEdges(String type, Map<String, Object> filter) throws CrudException {
378
379     filter.put(ChampRelationship.ReservedPropertyKeys.CHAMP_RELATIONSHIP_TYPE.toString(), type);
380
381     Stream<ChampRelationship> retrievedRelationships;
382     try {
383       retrievedRelationships = champApi.queryRelationships(filter);
384       
385     } catch (ChampTransactionException e) {
386       throw new CrudException("Transaction error occured",
387           javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR);
388     }
389     
390     // Process the result stream from the Champ library into an Edge list, keeping only
391     // edges of the specified type.
392     List<Edge> edges = retrievedRelationships
393         .map(r -> edgeFromChampRelationship(r))
394         .collect(Collectors.toList());
395
396     return edges;
397   }
398
399   @Override
400   public Edge updateEdge(Edge edge) throws CrudException {
401
402     if (logger.isDebugEnabled()) {
403       logger.debug("Update edge with id: " + edge.getId() + " with properties: "
404           + propertiesMapToString(edge.getProperties()));
405     }
406
407     try {
408       // Now, build the updated version of the Champ Relationship...
409       ChampRelationship updateRelationship = new ChampRelationship.Builder(
410           buildChampObject(edge.getSource().getId().get(), edge.getSource().getType(),
411               edge.getSource().getProperties()),
412           buildChampObject(edge.getTarget().getId().get(), edge.getTarget().getType(),
413               edge.getTarget().getProperties()),
414           edge.getType()).key(getRelKey(edge.getId().get()))
415           .properties(edge.getProperties()).build();
416       // ...and send it to the Champ library.
417       return edgeFromChampRelationship(champApi.replaceRelationship(updateRelationship));
418
419
420     } catch (ChampRelationshipNotExistsException ex) {
421       throw new CrudException("Not Found", javax.ws.rs.core.Response.Status.NOT_FOUND);
422     } catch (NumberFormatException | 
423              ChampUnmarshallingException | 
424              ChampMarshallingException | 
425              ChampSchemaViolationException |
426              ChampTransactionException ex) {
427
428       throw new CrudException(ex.getMessage(),
429           javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR);
430     } 
431   }
432
433   @Override
434   public void deleteVertex(String id, String type) throws CrudException {
435
436     try {
437
438       // First, retrieve the vertex that we intend to delete.
439       Optional<ChampObject> retrievedVertex = champApi.retrieveObject(Long.parseLong(id));
440
441       // Did we find it?
442       if (!retrievedVertex.isPresent() || !retrievedVertex.get().getType().equals(type)) {
443         throw new CrudException("Failed to delete vertex with id: "
444             + id + " - vertex does not exist.",
445             javax.ws.rs.core.Response.Status.NOT_FOUND);
446       }
447
448       // Now, verify that there are no edges incident to the vertex (they must be deleted
449       // first if so).
450       Stream<ChampRelationship> relationships =
451           champApi.retrieveRelationships(retrievedVertex.get());
452
453       if (relationships.count() > 0) {
454         throw new CrudException("Attempt to delete vertex with id "
455             + id + " which has incident edges.",
456             javax.ws.rs.core.Response.Status.BAD_REQUEST);
457       }
458
459       // Finally, we can attempt to delete our vertex.
460       champApi.deleteObject(Long.parseLong(id));
461
462
463     } catch (NumberFormatException
464            | ChampUnmarshallingException
465            | ChampObjectNotExistsException 
466            | ChampTransactionException e) {
467
468       throw new CrudException(e.getMessage(),
469           javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR);
470     }
471   }
472
473   @Override
474   public void deleteEdge(String id, String type) throws CrudException {
475
476     try {
477
478       // First, retrieve the edge that we want to delete.
479       Optional<ChampRelationship> relationshipToDelete
480           = champApi.retrieveRelationship(getRelKey(id));
481
482
483       // Did we find it?
484       if (!relationshipToDelete.isPresent() || !relationshipToDelete.get().getType().equals(type)) {
485         throw new CrudException("Failed to delete edge with id: " + id + " - edge does not exist",
486             javax.ws.rs.core.Response.Status.NOT_FOUND);
487       }
488
489       // Now we can delete the edge.
490       champApi.deleteRelationship(relationshipToDelete.get());
491
492     } catch (ChampRelationshipNotExistsException
493         | NumberFormatException
494         | ChampUnmarshallingException 
495         | ChampTransactionException e) {
496
497       throw new CrudException(e.getMessage(),
498           javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR);
499     }
500   }
501
502
503   /**
504    * This helper method generates a string representation of a properties map for
505    * logging purposes.
506    *
507    * @param properties - The properties map to be converted.
508    * @return - The log statement friendly conversion of the properties map.
509    */
510   private String propertiesMapToString(Map<String, Object> properties) {
511
512     StringBuilder sb = new StringBuilder();
513     sb.append("{");
514
515     for (String key : properties.keySet()) {
516       sb.append("(").append(key).append(" -> ").append(properties.get(key)).append(") ");
517     }
518
519     sb.append("}");
520
521     return sb.toString();
522   }
523
524
525   /**
526    * This helper method constructs a {@link ChampObject} suitable for passing to the Champ library.
527    *
528    * @param type       - The type to assign to our ChampObject
529    * @param properties - The set of properties to assign to our ChampObject
530    * @return - A populated ChampObject
531    */
532   private ChampObject buildChampObject(String type, Map<String, Object> properties) {
533
534     ObjectBuildOrPropertiesStep objectInProgress = ChampObject.create()
535         .ofType(type)
536         .withoutKey();
537
538     for (String key : properties.keySet()) {
539       objectInProgress.withProperty(key, properties.get(key));
540     }
541     return objectInProgress.build();
542   }
543
544
545   /**
546    * This helper method constructs a {@link ChampObject} suitable for passing to the Champ library.
547    *
548    * @param id         - Unique identifier for this object.
549    * @param type       - The type to assign to our ChampObject
550    * @param properties - The set of properties to assign to our ChampObject
551    * @return - A populated ChampObject
552    */
553   private ChampObject buildChampObject(String id, String type, Map<String, Object> properties) {
554
555     ObjectBuildOrPropertiesStep objectInProgress = ChampObject.create()
556         .ofType(type)
557         .withKey(Long.parseLong(id));
558
559     for (String key : properties.keySet()) {
560       objectInProgress.withProperty(key, properties.get(key));
561     }
562     return objectInProgress.build();
563   }
564
565
566   
567   
568   private Vertex vertexFromChampObject(ChampObject champObject, String type) {
569
570     // Get the identifier for this vertex from the Champ object.
571     Object id = champObject.getKey().orElse("");
572
573     // Start building our {@link Vertex} object.
574     Vertex.Builder vertexBuilder = new Vertex.Builder(type);
575     vertexBuilder.id(id.toString());
576
577     // Convert the properties associated with the Champ object into the form expected for
578     // a Vertex object.
579     for (String key : champObject.getProperties().keySet()) {
580       vertexBuilder.property(key, champObject.getProperties().get(key));
581     }
582
583     // ...and return it.
584     return vertexBuilder.build();
585   }
586
587
588   /**
589    * This helper method converts a {@link ChampRelationship} from the Champ library into an
590    * equivalent {@link Edge} object that is understood by the CRUD Service.
591    *
592    * @param relationship - The ChampRelationship object to be converted.
593    * @return - An Edge object corresponding to the supplied ChampRelationship
594    */
595   private Edge edgeFromChampRelationship(ChampRelationship relationship) {
596
597     // Populate the edge's id, if available.
598     Object relationshipId = relationship.getKey().orElse("");
599
600     Edge.Builder edgeBuilder = new Edge.Builder(relationship.getType())
601         .id(relationshipId.toString());
602     edgeBuilder.source(vertexFromChampObject(relationship.getSource(),
603         relationship.getSource().getProperties()
604             .get(org.openecomp.schema.OxmModelValidator.Metadata.NODE_TYPE.propertyName()) == null
605                 ? relationship.getSource().getType()
606                 : relationship.getSource().getProperties()
607                     .get(org.openecomp.schema.OxmModelValidator.Metadata.NODE_TYPE.propertyName()).toString()));
608     edgeBuilder.target(vertexFromChampObject(relationship.getTarget(),
609         relationship.getTarget().getProperties()
610             .get(org.openecomp.schema.OxmModelValidator.Metadata.NODE_TYPE.propertyName()) == null
611                 ? relationship.getTarget().getType()
612                 : relationship.getTarget().getProperties()
613                     .get(org.openecomp.schema.OxmModelValidator.Metadata.NODE_TYPE.propertyName()).toString()));
614
615     for (String key : relationship.getProperties().keySet()) {
616       edgeBuilder.property(key, relationship.getProperties().get(key).toString());
617     }
618
619     return edgeBuilder.build();
620   }
621
622   /**
623    * Performs any necessary shut down operations when the DAO is no longer needed.
624    */
625   public void close() {
626
627     if (champApi != null) {
628
629       logger.info(CrudServiceMsgs.STOPPING_CHAMP_DAO);
630
631       champApi.shutdown();
632     }
633   }
634 }