5b274ee24466b36d03d78f747504917b2193b789
[aai/aai-common.git] / aai-core / src / main / java / org / onap / aai / serialization / db / DBSerializer.java
1 /**
2  * ============LICENSE_START=======================================================
3  * org.onap.aai
4  * ================================================================================
5  * Copyright © 2017-2019 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *    http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20 package org.onap.aai.serialization.db;
21
22 import org.slf4j.Logger;
23 import org.slf4j.LoggerFactory;
24 import com.google.common.base.CaseFormat;
25 import org.apache.commons.lang.StringUtils;
26 import org.apache.tinkerpop.gremlin.process.traversal.Path;
27 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
28 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
29 import org.apache.tinkerpop.gremlin.structure.Direction;
30 import org.apache.tinkerpop.gremlin.structure.Edge;
31 import org.apache.tinkerpop.gremlin.structure.Vertex;
32 import org.apache.tinkerpop.gremlin.structure.VertexProperty;
33 import org.janusgraph.core.SchemaViolationException;
34 import org.javatuples.Pair;
35 import org.onap.aai.concurrent.AaiCallable;
36 import org.onap.aai.config.SpringContextAware;
37 import org.onap.aai.db.props.AAIProperties;
38 import org.onap.aai.edges.EdgeIngestor;
39 import org.onap.aai.edges.EdgeRule;
40 import org.onap.aai.edges.EdgeRuleQuery;
41 import org.onap.aai.edges.enums.AAIDirection;
42 import org.onap.aai.edges.enums.EdgeField;
43 import org.onap.aai.edges.enums.EdgeProperty;
44 import org.onap.aai.edges.enums.EdgeType;
45 import org.onap.aai.edges.exceptions.AmbiguousRuleChoiceException;
46 import org.onap.aai.edges.exceptions.EdgeRuleNotFoundException;
47 import org.onap.aai.exceptions.AAIException;
48 import org.onap.aai.introspection.*;
49 import org.onap.aai.introspection.exceptions.AAIUnknownObjectException;
50 import org.onap.aai.introspection.sideeffect.*;
51 import org.onap.aai.logging.ErrorLogHelper;
52 import org.onap.aai.logging.LogFormatTools;
53 import org.onap.aai.logging.StopWatch;
54 import org.onap.aai.parsers.query.QueryParser;
55 import org.onap.aai.parsers.relationship.RelationshipToURI;
56 import org.onap.aai.parsers.uri.URIParser;
57 import org.onap.aai.parsers.uri.URIToObject;
58 import org.onap.aai.parsers.uri.URIToRelationshipObject;
59 import org.onap.aai.query.builder.QueryBuilder;
60 import org.onap.aai.schema.enums.ObjectMetadata;
61 import org.onap.aai.schema.enums.PropertyMetadata;
62 import org.onap.aai.serialization.db.exceptions.MultipleEdgeRuleFoundException;
63 import org.onap.aai.serialization.db.exceptions.NoEdgeRuleFoundException;
64 import org.onap.aai.serialization.engines.TransactionalGraphEngine;
65 import org.onap.aai.serialization.engines.query.QueryEngine;
66 import org.onap.aai.setup.SchemaVersion;
67 import org.onap.aai.setup.SchemaVersions;
68 import org.onap.aai.util.AAIConfig;
69 import org.onap.aai.util.AAIConstants;
70 import org.onap.aai.util.delta.*;
71 import org.onap.aai.workarounds.NamingExceptions;
72 import org.springframework.context.ApplicationContext;
73
74 import javax.ws.rs.core.UriBuilder;
75 import java.io.UnsupportedEncodingException;
76 import java.lang.reflect.Array;
77 import java.lang.reflect.InvocationTargetException;
78 import java.net.MalformedURLException;
79 import java.net.URI;
80 import java.net.URISyntaxException;
81 import java.util.*;
82 import java.util.concurrent.ExecutionException;
83 import java.util.concurrent.ExecutorService;
84 import java.util.concurrent.Future;
85 import java.util.regex.Matcher;
86 import java.util.regex.Pattern;
87
88 public class DBSerializer {
89
90     private static final Logger LOGGER = LoggerFactory.getLogger(DBSerializer.class);
91     private static final String RELATIONSHIP_LABEL = "relationship-label";
92     private static final String RELATIONSHIP = "relationship";
93     public static final String FALSE = "false";
94     public static final String AAI_6145 = "AAI_6145";
95     public static final String AAI_6129 = "AAI_6129";
96
97     private final TransactionalGraphEngine engine;
98     private final String sourceOfTruth;
99     private final Set<String> groups;
100     private final ModelType introspectionType;
101     private final SchemaVersion version;
102     private final Loader latestLoader;
103     private EdgeSerializer edgeSer;
104     private EdgeIngestor edgeRules;
105     private final Loader loader;
106     private final String baseURL;
107     private double dbTimeMsecs = 0;
108     private long currentTimeMillis;
109
110     private SchemaVersions schemaVersions;
111     private Set<String> namedPropNodes;
112     private Map<String, ObjectDelta> objectDeltas = new LinkedHashMap<>();
113     private Map<Vertex, Boolean> updatedVertexes = new LinkedHashMap<>();
114     private Set<Vertex> edgeVertexes = new LinkedHashSet<>();
115     private Map<String, Pair<Introspector, LinkedHashMap<String, Introspector>>> impliedDeleteUriObjectPair = new LinkedHashMap<>();
116     private int notificationDepth;
117     private boolean isDeltaEventsEnabled;
118
119     /**
120      * Instantiates a new DB serializer.
121      *
122      * @param version the version
123      * @param engine the engine
124      * @param introspectionType the introspection type
125      * @param sourceOfTruth the source of truth
126      * @throws AAIException
127      */
128     public DBSerializer(SchemaVersion version, TransactionalGraphEngine engine, ModelType introspectionType,
129             String sourceOfTruth) throws AAIException {
130         this.engine = engine;
131         this.sourceOfTruth = sourceOfTruth;
132         this.groups = Collections.EMPTY_SET;
133         this.introspectionType = introspectionType;
134         this.schemaVersions = (SchemaVersions) SpringContextAware.getBean("schemaVersions");
135         SchemaVersion latestVersion = schemaVersions.getDefaultVersion();
136         this.latestLoader =
137                 SpringContextAware.getBean(LoaderFactory.class).createLoaderForVersion(introspectionType, latestVersion);
138         this.version = version;
139         this.loader =
140                 SpringContextAware.getBean(LoaderFactory.class).createLoaderForVersion(introspectionType, version);
141         this.namedPropNodes = this.latestLoader.getNamedPropNodes();
142         this.baseURL = AAIConfig.get(AAIConstants.AAI_SERVER_URL_BASE);
143         this.currentTimeMillis = System.currentTimeMillis();
144         // If creating the DBSerializer the old way then set the notification depth to maximum
145         this.notificationDepth = AAIProperties.MAXIMUM_DEPTH;
146         initBeans();
147     }
148
149     public DBSerializer(SchemaVersion version, TransactionalGraphEngine engine, ModelType introspectionType,
150         String sourceOfTruth, Set<String> groups) throws AAIException {
151         this.engine = engine;
152         this.sourceOfTruth = sourceOfTruth;
153         this.groups = groups;
154         this.introspectionType = introspectionType;
155         this.schemaVersions = (SchemaVersions) SpringContextAware.getBean("schemaVersions");
156         SchemaVersion latestVersion = schemaVersions.getDefaultVersion();
157         this.latestLoader =
158             SpringContextAware.getBean(LoaderFactory.class).createLoaderForVersion(introspectionType, latestVersion);
159         this.version = version;
160         this.loader =
161             SpringContextAware.getBean(LoaderFactory.class).createLoaderForVersion(introspectionType, version);
162         this.namedPropNodes = this.latestLoader.getNamedPropNodes();
163         this.baseURL = AAIConfig.get(AAIConstants.AAI_SERVER_URL_BASE);
164         this.currentTimeMillis = System.currentTimeMillis();
165         // If creating the DBSerializer the old way then set the notification depth to maximum
166         this.notificationDepth = AAIProperties.MAXIMUM_DEPTH;
167         initBeans();
168     }
169
170     public DBSerializer(SchemaVersion version,
171                         TransactionalGraphEngine engine,
172                         ModelType introspectionType,
173                         String sourceOfTruth,
174                         int notificationDepth) throws AAIException {
175         this.engine = engine;
176         this.sourceOfTruth = sourceOfTruth;
177         this.groups = Collections.EMPTY_SET;
178         this.introspectionType = introspectionType;
179         this.schemaVersions = (SchemaVersions) SpringContextAware.getBean("schemaVersions");
180         SchemaVersion latestVersion = schemaVersions.getDefaultVersion();
181         this.latestLoader =
182             SpringContextAware.getBean(LoaderFactory.class).createLoaderForVersion(introspectionType, latestVersion);
183         this.version = version;
184         this.loader =
185             SpringContextAware.getBean(LoaderFactory.class).createLoaderForVersion(introspectionType, version);
186         this.namedPropNodes = this.latestLoader.getNamedPropNodes();
187         this.baseURL = AAIConfig.get(AAIConstants.AAI_SERVER_URL_BASE);
188         this.currentTimeMillis = System.currentTimeMillis();
189         this.notificationDepth = notificationDepth;
190         initBeans();
191     }
192
193     public DBSerializer(SchemaVersion version,
194         TransactionalGraphEngine engine,
195         ModelType introspectionType,
196         String sourceOfTruth,
197         Set<String> groups,
198         int notificationDepth) throws AAIException {
199         this.engine = engine;
200         this.sourceOfTruth = sourceOfTruth;
201         this.groups = groups;
202         this.introspectionType = introspectionType;
203         this.schemaVersions = (SchemaVersions) SpringContextAware.getBean("schemaVersions");
204         SchemaVersion latestVersion = schemaVersions.getDefaultVersion();
205         this.latestLoader =
206             SpringContextAware.getBean(LoaderFactory.class).createLoaderForVersion(introspectionType, latestVersion);
207         this.version = version;
208         this.loader =
209             SpringContextAware.getBean(LoaderFactory.class).createLoaderForVersion(introspectionType, version);
210         this.namedPropNodes = this.latestLoader.getNamedPropNodes();
211         this.baseURL = AAIConfig.get(AAIConstants.AAI_SERVER_URL_BASE);
212         this.currentTimeMillis = System.currentTimeMillis();
213         this.notificationDepth = notificationDepth;
214         initBeans();
215     }
216
217     public DBSerializer(SchemaVersion version,
218                         TransactionalGraphEngine engine,
219                         ModelType introspectionType,
220                         String sourceOfTruth,
221                         int notificationDepth,
222                         String serverBase) throws AAIException {
223         this.engine = engine;
224         this.sourceOfTruth = sourceOfTruth;
225         this.groups = Collections.EMPTY_SET;
226         this.introspectionType = introspectionType;
227         this.schemaVersions = (SchemaVersions) SpringContextAware.getBean("schemaVersions");
228         SchemaVersion latestVersion = schemaVersions.getDefaultVersion();
229         this.latestLoader =
230             SpringContextAware.getBean(LoaderFactory.class).createLoaderForVersion(introspectionType, latestVersion);
231         this.version = version;
232         this.loader =
233             SpringContextAware.getBean(LoaderFactory.class).createLoaderForVersion(introspectionType, version);
234         this.namedPropNodes = this.latestLoader.getNamedPropNodes();
235         this.baseURL = serverBase;
236         this.currentTimeMillis = System.currentTimeMillis();
237         this.notificationDepth = notificationDepth;
238         initBeans();
239     }
240
241     public DBSerializer(SchemaVersion version,
242         TransactionalGraphEngine engine,
243         ModelType introspectionType,
244         String sourceOfTruth,
245         Set<String> groups,
246         int notificationDepth,
247         String serverBase) throws AAIException {
248         this.engine = engine;
249         this.sourceOfTruth = sourceOfTruth;
250         this.groups = groups;
251         this.introspectionType = introspectionType;
252         this.schemaVersions = (SchemaVersions) SpringContextAware.getBean("schemaVersions");
253         SchemaVersion latestVersion = schemaVersions.getDefaultVersion();
254         this.latestLoader =
255             SpringContextAware.getBean(LoaderFactory.class).createLoaderForVersion(introspectionType, latestVersion);
256         this.version = version;
257         this.loader =
258             SpringContextAware.getBean(LoaderFactory.class).createLoaderForVersion(introspectionType, version);
259         this.namedPropNodes = this.latestLoader.getNamedPropNodes();
260         this.baseURL = serverBase;
261         this.currentTimeMillis = System.currentTimeMillis();
262         this.notificationDepth = notificationDepth;
263         initBeans();
264     }
265
266     private void initBeans() {
267         // TODO proper spring wiring, but that requires a lot of refactoring so for now we have this
268         ApplicationContext ctx = SpringContextAware.getApplicationContext();
269         EdgeIngestor ei = ctx.getBean(EdgeIngestor.class);
270         setEdgeIngestor(ei);
271         EdgeSerializer es = ctx.getBean(EdgeSerializer.class);
272         setEdgeSerializer(es);
273         isDeltaEventsEnabled = Boolean.parseBoolean(SpringContextAware.getApplicationContext().getEnvironment().getProperty("delta.events.enabled", FALSE));
274     }
275
276     public void setEdgeSerializer(EdgeSerializer edgeSer) {
277         this.edgeSer = edgeSer;
278     }
279
280     public EdgeSerializer getEdgeSeriailizer() {
281         return this.edgeSer;
282     }
283
284     public void setEdgeIngestor(EdgeIngestor ei) {
285         this.edgeRules = ei;
286     }
287
288     public EdgeIngestor getEdgeIngestor() {
289         return this.edgeRules;
290     }
291
292     public Map<Vertex, Boolean> getUpdatedVertexes() {
293         return updatedVertexes;
294     }
295
296     public Map<String, Pair<Introspector, LinkedHashMap<String, Introspector>>> getImpliedDeleteUriObjectPair(){
297         return impliedDeleteUriObjectPair;
298     }
299
300     public Set<String> getGroups() {
301         return this.groups;
302     }
303
304     /**
305      * Touch standard vertex properties.
306      *  @param v the v
307      * @param isNewVertex the is new vertex
308      */
309     public void touchStandardVertexProperties(Vertex v, boolean isNewVertex) {
310         String timeNowInSec = Long.toString(currentTimeMillis);
311         if (isNewVertex) {
312             String uuid = UUID.randomUUID().toString();
313             v.property(AAIProperties.SOURCE_OF_TRUTH, this.sourceOfTruth);
314             v.property(AAIProperties.CREATED_TS, currentTimeMillis);
315             v.property(AAIProperties.AAI_UUID, uuid);
316             v.property(AAIProperties.RESOURCE_VERSION, timeNowInSec);
317             v.property(AAIProperties.LAST_MOD_TS, currentTimeMillis);
318             v.property(AAIProperties.LAST_MOD_SOURCE_OF_TRUTH, this.sourceOfTruth);
319         } else {
320             if(isDeltaEventsEnabled) {
321                 standardVertexPropsDeltas(v, timeNowInSec);
322             }
323             v.property(AAIProperties.RESOURCE_VERSION, timeNowInSec);
324             v.property(AAIProperties.LAST_MOD_TS, currentTimeMillis);
325             v.property(AAIProperties.LAST_MOD_SOURCE_OF_TRUTH, this.sourceOfTruth);
326         }
327     }
328
329     private void standardVertexPropsDeltas(Vertex v, String timeNowInSec) {
330         String uri = v.property(AAIProperties.AAI_URI).value().toString();
331         long createdTs = (Long) v.property(AAIProperties.CREATED_TS).value();
332         DeltaAction objDeltaAction = createdTs == currentTimeMillis ? DeltaAction.CREATE : DeltaAction.UPDATE;
333         if (getObjectDeltas().containsKey(uri)) {
334             getObjectDeltas().get(uri).setAction(objDeltaAction);
335         }
336
337         addPropDelta(uri, AAIProperties.AAI_UUID, PropertyDeltaFactory.getDelta(DeltaAction.STATIC, v.property(AAIProperties.AAI_UUID).value()), objDeltaAction);
338         addPropDelta(uri, AAIProperties.NODE_TYPE, PropertyDeltaFactory.getDelta(DeltaAction.STATIC, v.property(AAIProperties.NODE_TYPE).value()), objDeltaAction);
339         addPropDelta(uri, AAIProperties.SOURCE_OF_TRUTH, PropertyDeltaFactory.getDelta(DeltaAction.STATIC, v.property(AAIProperties.SOURCE_OF_TRUTH).value()), objDeltaAction);
340         addPropDelta(uri, AAIProperties.CREATED_TS, PropertyDeltaFactory.getDelta(DeltaAction.STATIC, v.property(AAIProperties.CREATED_TS).value()), objDeltaAction);
341
342         if (objDeltaAction.equals(DeltaAction.UPDATE)) {
343             addPropDelta(
344                 uri,
345                 AAIProperties.RESOURCE_VERSION,
346                 PropertyDeltaFactory.getDelta(objDeltaAction, timeNowInSec, v.property(AAIProperties.RESOURCE_VERSION).value()),
347                 objDeltaAction
348             );
349             addPropDelta(
350                 uri,
351                 AAIProperties.LAST_MOD_TS,
352                 PropertyDeltaFactory.getDelta(objDeltaAction, currentTimeMillis, v.property(AAIProperties.LAST_MOD_TS).value()),
353                 objDeltaAction
354             );
355             addPropDelta(
356                 uri,
357                 AAIProperties.LAST_MOD_SOURCE_OF_TRUTH,
358                 PropertyDeltaFactory.getDelta(objDeltaAction, this.sourceOfTruth, v.property(AAIProperties.LAST_MOD_SOURCE_OF_TRUTH).value()),
359                 objDeltaAction
360             );
361         } else {
362             addPropDelta(uri, AAIProperties.RESOURCE_VERSION, PropertyDeltaFactory.getDelta(objDeltaAction, v.property(AAIProperties.RESOURCE_VERSION).value()), objDeltaAction);
363             addPropDelta(uri, AAIProperties.LAST_MOD_TS, PropertyDeltaFactory.getDelta(objDeltaAction, v.property(AAIProperties.LAST_MOD_TS).value()), objDeltaAction);
364             addPropDelta(uri, AAIProperties.LAST_MOD_SOURCE_OF_TRUTH, PropertyDeltaFactory.getDelta(objDeltaAction, v.property(AAIProperties.LAST_MOD_SOURCE_OF_TRUTH).value()), objDeltaAction);
365         }
366     }
367
368     public Map<String, ObjectDelta> getObjectDeltas() {return objectDeltas;}
369
370     private void addPropDelta(String uri, String prop, PropertyDelta delta, DeltaAction objDeltaAction) {
371         ObjectDelta objectDelta = this.objectDeltas.getOrDefault(uri, new ObjectDelta(uri, objDeltaAction, this.sourceOfTruth, this.currentTimeMillis));
372         objectDelta.addPropertyDelta(prop, delta);
373         objectDeltas.put(uri, objectDelta);
374     }
375
376     private void addRelationshipDelta(String uri, RelationshipDelta delta, DeltaAction objDeltaAction) {
377         ObjectDelta objectDelta = this.objectDeltas.getOrDefault(uri, new ObjectDelta(uri, objDeltaAction, this.sourceOfTruth, this.currentTimeMillis));
378         objectDelta.addRelationshipDelta(delta);
379         objectDeltas.put(uri, objectDelta);
380     }
381
382     private void touchStandardVertexProperties(String nodeType, Vertex v, boolean isNewVertex) {
383         v.property(AAIProperties.NODE_TYPE, nodeType);
384         touchStandardVertexProperties(v, isNewVertex);
385     }
386
387     /**
388      * Creates the new vertex.
389      *
390      * @param wrappedObject the wrapped object
391      * @return the vertex
392      */
393     public Vertex createNewVertex(Introspector wrappedObject) {
394         Vertex v;
395         try {
396             StopWatch.conditionalStart();
397             v = this.engine.tx().addVertex(wrappedObject.getDbName());
398             touchStandardVertexProperties(wrappedObject.getDbName(), v, true);
399         } finally {
400             dbTimeMsecs += StopWatch.stopIfStarted();
401         }
402         return v;
403     }
404
405     /**
406      * Trim class name.
407      *
408      * @param className the class name
409      * @return the string
410      */
411     /*
412      * Removes the classpath from a class name
413      */
414     public String trimClassName(String className) {
415         String returnValue = "";
416
417         if (className.lastIndexOf('.') == -1) {
418             return className;
419         }
420         returnValue = className.substring(className.lastIndexOf('.') + 1);
421
422         return returnValue;
423     }
424
425     /**
426      * Serialize to db.
427      *
428      * @param obj the obj
429      * @param v the v
430      * @param uriQuery the uri query
431      * @param identifier the identifier
432      * @throws SecurityException the security exception
433      * @throws IllegalArgumentException the illegal argument exception
434      * @throws AAIException the AAI exception
435      * @throws UnsupportedEncodingException the unsupported encoding exception
436      */
437     public void serializeToDb(Introspector obj, Vertex v, QueryParser uriQuery, String identifier,
438             String requestContext) throws AAIException, UnsupportedEncodingException {
439         StopWatch.conditionalStart();
440         try {
441             if (uriQuery.isDependent()) {
442                 // try to find the parent
443                 List<Vertex> vertices = uriQuery.getQueryBuilder().getParentQuery().toList();
444                 if (!vertices.isEmpty()) {
445                     Vertex parent = vertices.get(0);
446                     this.reflectDependentVertex(parent, v, obj, requestContext);
447                 } else {
448                     dbTimeMsecs += StopWatch.stopIfStarted();
449                     throw new AAIException("AAI_6114",
450                             "No parent Node of type " + uriQuery.getParentResultType() + " for " + identifier);
451                 }
452             } else {
453                 serializeSingleVertex(v, obj, requestContext);
454             }
455
456         } catch (SchemaViolationException e) {
457             dbTimeMsecs += StopWatch.stopIfStarted();
458             throw new AAIException("AAI_6117", e);
459         }
460         dbTimeMsecs += StopWatch.stopIfStarted();
461     }
462
463     public void serializeSingleVertex(Vertex v, Introspector obj, String requestContext)
464             throws UnsupportedEncodingException, AAIException {
465         StopWatch.conditionalStart();
466         try {
467             boolean isTopLevel = obj.isTopLevel();
468             if (isTopLevel) {
469                 addUriIfNeeded(v, obj.getURI());
470             }
471             if (!isTopLevel) {
472                 URI uri = this.getURIForVertex(v);
473                 URIParser parser = new URIParser(this.loader, uri);
474                 if (parser.validate()) {
475                     addUriIfNeeded(v, uri.toString());
476                 }
477             }
478             processObject(obj, v, requestContext);
479         } catch (SchemaViolationException e) {
480             throw new AAIException("AAI_6117", e);
481         } finally {
482             dbTimeMsecs += StopWatch.stopIfStarted();
483         }
484     }
485
486     private void addUriIfNeeded(Vertex v, String uri) {
487         VertexProperty<String> uriProp = v.property(AAIProperties.AAI_URI);
488         if (!uriProp.isPresent() || !uriProp.value().equals(uri)) {
489             v.property(AAIProperties.AAI_URI, uri);
490         }
491     }
492
493     /**
494      * Process object.
495      *
496      * @param obj the obj
497      * @param v the v
498      * @return the list
499      * @throws IllegalArgumentException the illegal argument exception
500      * @throws SecurityException the security exception
501      * @throws AAIException the AAI exception
502      * @throws UnsupportedEncodingException the unsupported encoding exception
503      */
504     /*
505      * Helper method for reflectToDb
506      * Handles all the property setting
507      */
508     private List<Vertex> processObject(Introspector obj, Vertex v, String requestContext)
509             throws UnsupportedEncodingException, AAIException {
510         Set<String> properties = new LinkedHashSet<>(obj.getProperties());
511         properties.remove(AAIProperties.RESOURCE_VERSION);
512         List<Vertex> dependentVertexes = new ArrayList<>();
513         List<Vertex> processedVertexes = new ArrayList<>();
514
515         boolean isComplexType ;
516         boolean isListType;
517
518         // If the notification depth is set to maximum
519         // this is the behavior of the expected clients
520         if(notificationDepth == AAIProperties.MAXIMUM_DEPTH) {
521             if (!obj.isContainer()) {
522                 this.touchStandardVertexProperties(v, false);
523             }
524         }
525         this.executePreSideEffects(obj, v);
526         for (String property : properties) {
527             final String propertyType;
528             propertyType = obj.getType(property);
529             isComplexType = obj.isComplexType(property);
530             isListType = obj.isListType(property);
531             Object value = obj.getValue(property);
532
533             if (!(isComplexType || isListType)) {
534                 boolean canModify = this.canModify(obj, property, requestContext);
535
536                 if (canModify) {
537                     final Map<PropertyMetadata, String> metadata = obj.getPropertyMetadata(property);
538                     String dbProperty = property;
539                     if (metadata.containsKey(PropertyMetadata.DB_ALIAS)) {
540                         dbProperty = metadata.get(PropertyMetadata.DB_ALIAS);
541                     }
542                     if (metadata.containsKey(PropertyMetadata.DATA_LINK)) {
543                         // data linked properties are ephemeral
544                         // they are populated dynamically on GETs
545                         continue;
546                     }
547                     Object oldValue = v.property(dbProperty).orElse(null);
548                     String uri = getURIForVertex(v).toString();
549                     if (value != null) {
550                         if (!value.equals(oldValue)) {
551                             if (propertyType.toLowerCase().contains(".long")) {
552                                 v.property(dbProperty, new Integer(((Long) value).toString()));
553                             } else {
554                                 v.property(dbProperty, value);
555                             }
556                             if (isDeltaEventsEnabled) {
557                                 createDeltaProperty(uri, value, dbProperty, oldValue);
558                             }
559                             this.updatedVertexes.putIfAbsent(v, false);
560                         }
561                     } else {
562                         if (oldValue != null) {
563                             v.property(dbProperty).remove();
564                             if (isDeltaEventsEnabled) {
565                                 addPropDelta(uri, dbProperty, PropertyDeltaFactory.getDelta(DeltaAction.DELETE, oldValue), DeltaAction.UPDATE);
566                             }
567                             this.updatedVertexes.putIfAbsent(v, false);
568                         }
569                     }
570                 }
571             } else if (isListType) {
572                 List<Object> list = (List<Object>) value;
573                 if (obj.isComplexGenericType(property)) {
574                     if (list != null) {
575                         for (Object o : list) {
576                             Introspector child = IntrospectorFactory.newInstance(this.introspectionType, o);
577                             child.setURIChain(obj.getURI());
578                             processedVertexes.add(reflectDependentVertex(v, child, requestContext));
579                         }
580                     }
581                 } else {
582                     // simple list case
583                     if (isDeltaEventsEnabled) {
584                         String uri = getURIForVertex(v).toString();
585                         List<Object> oldVal = engine.getListProperty(v, property);
586                         engine.setListProperty(v, property, list);
587                         if (list == null || list.isEmpty()) { // property delete scenario, there is no new value
588                             if (oldVal != null && !oldVal.isEmpty()) { // and there is an old value
589                                 addPropDelta(uri, property, PropertyDeltaFactory.getDelta(DeltaAction.DELETE, oldVal), DeltaAction.UPDATE);
590                             }
591                         } else { // is either a create or update and is handled by the called method
592                             createDeltaProperty(uri, list, property, oldVal);
593                         }
594                     } else {
595                         engine.setListProperty(v, property, list);
596                     }
597                     this.updatedVertexes.putIfAbsent(v, false);
598                 }
599             } else {
600                 // method.getReturnType() is not 'simple' then create a vertex and edge recursively returning an edge
601                 // back to this method
602                 if (value != null) { // effectively ignore complex properties not included in the object we're
603                                      // processing
604                     if (value.getClass().isArray()) {
605
606                         int length = Array.getLength(value);
607                         for (int i = 0; i < length; i++) {
608                             Object arrayElement = Array.get(value, i);
609                             Introspector child = IntrospectorFactory.newInstance(this.introspectionType, arrayElement);
610                             child.setURIChain(obj.getURI());
611                             processedVertexes.add(reflectDependentVertex(v, child, requestContext));
612
613                         }
614                     } else if (!property.equals("relationship-list")) {
615                         // container case
616                         Introspector introspector = IntrospectorFactory.newInstance(this.introspectionType, value);
617                         if (introspector.isContainer()) {
618                             dependentVertexes.addAll(
619                                     this.engine.getQueryEngine().findChildrenOfType(v, introspector.getChildDBName()));
620                             introspector.setURIChain(obj.getURI());
621
622                             processedVertexes.addAll(processObject(introspector, v, requestContext));
623
624                         } else {
625                             dependentVertexes.addAll(
626                                     this.engine.getQueryEngine().findChildrenOfType(v, introspector.getDbName()));
627                             processedVertexes.add(reflectDependentVertex(v, introspector, requestContext));
628
629                         }
630                     } else if (property.equals("relationship-list")) {
631                         handleRelationships(obj, v);
632                     }
633                 }
634             }
635         }
636         this.writeThroughDefaults(v, obj);
637         /* handle those vertexes not touched */
638         for (Vertex toBeKept : processedVertexes) {
639             dependentVertexes.remove(toBeKept);
640         }
641
642         ImpliedDelete impliedDelete = new ImpliedDelete(engine, this);
643         List<Vertex> impliedDeleteVertices = impliedDelete.execute(v.id(), sourceOfTruth, obj.getName(), dependentVertexes);
644
645         if(notificationDepth == AAIProperties.MINIMUM_DEPTH){
646             for(Vertex curVertex : impliedDeleteVertices){
647                 if(!curVertex.property("aai-uri").isPresent()){
648                     LOGGER.debug("Encountered an vertex {} with missing aai-uri", curVertex.id());
649                     continue;
650                 }
651                 String curAaiUri = curVertex.<String>property(AAIProperties.AAI_URI).value();
652                 Introspector curObj = this.getLatestVersionView(curVertex, notificationDepth);
653
654                 LinkedHashMap<String, Introspector> curObjRelated = new LinkedHashMap<>();
655
656                 if(!curObj.isTopLevel()){
657                     curObjRelated.putAll(this.getRelatedObjects(engine.getQueryEngine(), curVertex, curObj, this.loader));
658                 }
659
660                 if(!impliedDeleteUriObjectPair.containsKey(curAaiUri)){
661                     impliedDeleteUriObjectPair.put(curAaiUri, new Pair<>(curObj, curObjRelated));
662                 }
663             }
664         }
665
666         impliedDelete.delete(impliedDeleteVertices);
667
668         // touch svp using vertex list for what changed
669         // if the notification depth is zero
670         if(notificationDepth == AAIProperties.MINIMUM_DEPTH){
671             this.updatedVertexes.entrySet().stream()
672                 .filter(e -> !e.getValue())
673                 .filter(e -> !edgeVertexes.contains(e.getKey()))
674                 .forEach(e -> {
675                     this.touchStandardVertexProperties(e.getKey(), false);
676                     e.setValue(true);
677                 });
678         }
679         this.executePostSideEffects(obj, v);
680         return processedVertexes;
681     }
682
683     private void createDeltaProperty(String uri, Object value, String dbProperty, Object oldValue) {
684         if (oldValue == null) {
685             addPropDelta(uri, dbProperty, PropertyDeltaFactory.getDelta(DeltaAction.CREATE, value), DeltaAction.UPDATE);
686         } else {
687             addPropDelta(uri, dbProperty, PropertyDeltaFactory.getDelta(DeltaAction.UPDATE, value, oldValue), DeltaAction.UPDATE);
688         }
689     }
690
691     public HashMap<String, Introspector> getRelatedObjects(QueryEngine queryEngine, Vertex v,
692                                                             Introspector obj, Loader loader) throws IllegalArgumentException, SecurityException, UnsupportedEncodingException, AAIException {
693
694         HashMap<String, Introspector> relatedVertices = new HashMap<>();
695         VertexProperty aaiUriProperty = v.property(AAIProperties.AAI_URI);
696
697         if (!aaiUriProperty.isPresent()) {
698             if (LOGGER.isDebugEnabled()) {
699                 LOGGER.debug("For the given vertex {}, it seems aai-uri is not present so not getting related objects",
700                     v.id().toString());
701             } else {
702                 LOGGER.info(
703                     "It seems aai-uri is not present in vertex, so not getting related objects, for more info enable debug log");
704             }
705             return relatedVertices;
706         }
707
708         String aaiUri = aaiUriProperty.value().toString();
709
710         if (!obj.isTopLevel()) {
711             String[] uriList = convertIntrospectorToUriList(aaiUri, obj, loader);
712             List<Vertex> vertexChain;
713             // If the uriList is null then there is something wrong with converting the uri
714             // into a list of aai-uris so falling back to the old mechanism for finding parents
715             if (uriList == null) {
716                 LOGGER.info(
717                     "Falling back to the old mechanism due to unable to convert aai-uri to list of uris but this is not optimal");
718                 vertexChain = queryEngine.findParents(v);
719             } else if (uriList.length == 1) {
720                 // If the uri list is size 1 the only uri in the list is the one represented by v thus no need to query
721                 vertexChain = Collections.singletonList(v);
722             } else {
723                 // the uriList at element 0 is the node in question and should not be included in the vertex chain lookup.
724                 vertexChain = queryEngine.findParents(Arrays.copyOfRange(uriList, 1, uriList.length));
725                 // inject v into start of vertexChain
726                 vertexChain.add(0, v);
727             }
728             for (Vertex vertex : vertexChain) {
729                 try {
730                     final Introspector vertexObj = this.getVertexProperties(vertex);
731                     relatedVertices.put(vertexObj.getObjectId(), vertexObj);
732                 } catch (AAIUnknownObjectException e) {
733                     LOGGER.warn("Unable to get vertex properties, partial list of related vertices returned");
734                 }
735             }
736         } else {
737             try {
738                 final Introspector vertexObj = this.getVertexProperties(v);
739                 relatedVertices.put(vertexObj.getObjectId(), vertexObj);
740             } catch (AAIUnknownObjectException e) {
741                 LOGGER.warn("Unable to get vertex properties, partial list of related vertices returned");
742             }
743         }
744
745         return relatedVertices;
746     }
747
748     /**
749      * Given an uri, introspector object and loader object
750      * it will check if the obj is top level object if it is,
751      * it will return immediately returning the uri passed in
752      * If it isn't, it will go through, get the uriTemplate
753      * from the introspector object and get the count of "/"s
754      * and remove that part of the uri using substring
755      * and keep doing that until the current object is top level
756      * Also added the max depth just so worst case scenario
757      * Then keep adding aai-uri to the list include the aai-uri passed in
758      * Convert that list into an array and return it
759      * <p>
760      *
761      * Example:
762      *
763      * <blockquote>
764      * aai-uri ->
765      * /cloud-infrastructure/cloud-regions/cloud-region/cloud-owner/cloud-region-id/tenants/tenant/tenant1/vservers/vserver/v1
766      *
767      * Given the uriTemplate vserver -> /vservers/vserver/{vserver-id}
768      * it converts to /vservers/vserver
769      *
770      * lastIndexOf /vservers/vserver in
771      * /cloud-infrastructure/cloud-regions/cloud-region/cloud-owner/cloud-region-id/tenants/tenant/tenant1/vservers/vserver/v1
772      * ^
773      * |
774      * |
775      * lastIndexOf
776      * Use substring to get the string from 0 to that lastIndexOf
777      * aai-uri -> /cloud-infrastructure/cloud-regions/cloud-region/cloud-owner/cloud-region-id/tenants/tenant/tenant1
778      *
779      * From this new aai-uri, generate a introspector from the URITOObject class
780      * and keep doing this until you
781      *
782      * </blockquote>
783      *
784      * @param aaiUri - aai-uri of the vertex representating the unique id of a given vertex
785      * @param obj - introspector object of the given starting vertex
786      * @param loader - Type of loader which will always be MoxyLoader to support model driven
787      * @return an array of strings which can be used to get the vertexes of parent and grand parents from a given vertex
788      * @throws UnsupportedEncodingException
789      * @throws AAIException
790      */
791     String[] convertIntrospectorToUriList(String aaiUri, Introspector obj, Loader loader)
792         throws UnsupportedEncodingException, AAIException {
793
794         List<String> uriList = new ArrayList<>();
795         String template;
796         String truncatedUri = aaiUri;
797         int depth = AAIProperties.MAXIMUM_DEPTH;
798         uriList.add(truncatedUri);
799
800         while (depth >= 0 && !obj.isTopLevel()) {
801             template = obj.getMetadata(ObjectMetadata.URI_TEMPLATE);
802
803             if (template == null) {
804                 LOGGER.warn("Unable to find the uriTemplate for the object {}", obj.getDbName());
805                 return null;
806             }
807
808             int templateCount = StringUtils.countMatches(template, "/");
809             int truncatedUriCount = StringUtils.countMatches(truncatedUri, "/");
810
811             if (templateCount > truncatedUriCount) {
812                 LOGGER.warn("Template uri {} contains more slashes than truncatedUri {}", template, truncatedUri);
813                 return null;
814             }
815
816             int cutIndex = StringUtils.ordinalIndexOf(truncatedUri, "/", truncatedUriCount - templateCount + 1);
817             truncatedUri = StringUtils.substring(truncatedUri, 0, cutIndex);
818             uriList.add(truncatedUri);
819             obj = new URIToObject(loader, UriBuilder.fromPath(truncatedUri).build()).getEntity();
820             depth--;
821         }
822
823         return uriList.toArray(new String[0]);
824     }
825
826     /**
827      * Handle relationships.
828      *
829      * @param obj the obj
830      * @param vertex the vertex
831      * @throws SecurityException the security exception
832      * @throws IllegalArgumentException the illegal argument exception
833      * @throws UnsupportedEncodingException the unsupported encoding exception
834      * @throws AAIException the AAI exception
835      */
836     /*
837      * Handles the explicit relationships defined for an obj
838      */
839     private void handleRelationships(Introspector obj, Vertex vertex)
840             throws UnsupportedEncodingException, AAIException {
841
842         Introspector wrappedRl = obj.getWrappedValue("relationship-list");
843         processRelationshipList(wrappedRl, vertex);
844
845     }
846
847     /**
848      * Process relationship list.
849      *
850      * @param wrapped the wrapped
851      * @param v the v
852      * @throws UnsupportedEncodingException the unsupported encoding exception
853      * @throws AAIException the AAI exception
854      */
855     private void processRelationshipList(Introspector wrapped, Vertex v)
856         throws UnsupportedEncodingException, AAIException {
857
858         List<Object> relationships = wrapped.getValue("relationship");
859         String mainUri = getURIForVertex(v).toString();
860         String aNodeType = v.property(AAIProperties.NODE_TYPE).value().toString();
861         EdgeRuleQuery.Builder cousinQueryBuilder = new EdgeRuleQuery.Builder(aNodeType)
862             .edgeType(EdgeType.COUSIN)
863             .version(wrapped.getVersion());
864         EdgeRuleQuery.Builder treeQueryBuilder = new EdgeRuleQuery.Builder(aNodeType)
865             .edgeType(EdgeType.TREE)
866             .version(wrapped.getVersion());
867
868         EdgeIngestor edgeIngestor = SpringContextAware.getBean(EdgeIngestor.class);
869
870         Set<Pair<String, String>> cousinUriAndLabels = new LinkedHashSet<>();
871         for (Object relationship : relationships) {
872
873             String label;
874             Introspector wrappedRel = IntrospectorFactory.newInstance(this.introspectionType, relationship);
875             String relUri = urlToUri(new RelationshipToURI(loader, wrappedRel).getUri().toString());
876
877             if (relUri.startsWith("/vnf/")) {
878                 QueryParser parser = engine.getQueryBuilder().createQueryFromRelationship(wrappedRel);
879                 List<Vertex> results = parser.getQueryBuilder().toList();
880                 if (results.isEmpty()) {
881                     final AAIException ex = new AAIException(AAI_6129,
882                         String.format("Node of type %s. Could not find object at: %s", parser.getResultType(), parser.getUri()));
883                     ex.getTemplateVars().add(parser.getResultType());
884                     ex.getTemplateVars().add(parser.getUri().toString());
885                     throw ex;
886                 } else {
887                     // still an issue if there's more than one
888                     if (results.get(0).property(AAIProperties.AAI_URI).isPresent()) {
889                         relUri = results.get(0).value(AAIProperties.AAI_URI);
890                     } else {
891                         LOGGER.warn("Not processing the vertex {} because its missing required property aai-uri", results.get(0).id());
892                         continue;
893                     }
894                 }
895             }
896
897             if (wrappedRel.getValue(RELATIONSHIP_LABEL) != null) {
898                 label = wrappedRel.getValue(RELATIONSHIP_LABEL);
899             } else {
900                 URIToObject uriToObject = new URIToObject(loader, URI.create(relUri));
901                 String bNodeType = uriToObject.getEntityName();
902                 EdgeRuleQuery ruleQuery = cousinQueryBuilder.to(bNodeType).build();
903                 if (!edgeIngestor.hasRule(ruleQuery)) {
904                     EdgeRuleQuery treeQuery = treeQueryBuilder.to(bNodeType).build();
905                     if (edgeIngestor.hasRule(treeQuery)) {
906                         throw new AAIException(AAI_6145); //attempted to create cousin edge for a parent-child edge rule
907                     }
908                     throw new AAIException("AAI_6120", String.format(
909                         "No EdgeRule found for passed nodeTypes: %s, %s.",
910                         aNodeType, bNodeType));
911                 } else {
912                     try {
913                         final List<EdgeRule> rules = new ArrayList<>(edgeIngestor.getRules(ruleQuery).values());
914                         if (rules.size() == 1) {
915                             label = rules.get(0).getLabel();
916                         } else {
917                             Optional<EdgeRule> defaultRule = rules.stream().filter(EdgeRule::isDefault).findFirst();
918                             if (defaultRule.isPresent()) {
919                                 label = defaultRule.get().getLabel();
920                             } else {
921                                 throw new AAIException(AAI_6145);
922                             }
923                         }
924                     } catch (EdgeRuleNotFoundException ea) {
925                         throw new AAIException(AAI_6145, ea);
926                     }
927                 }
928             }
929             cousinUriAndLabels.add(Pair.with(relUri, label));
930         }
931
932         List<Path> paths = this.engine.getQueryEngine().findCousinsAsPath(v);
933         Set<Path> toRemove = new HashSet<>();
934
935
936         //  for each path 3 things can happen:
937         //      1. The edge rule that created it is not in this version no action is to be taken on that edge
938         //      2. The edge rule exits in this version it's included in the request the edge is left alone
939         //      3. The edge rule exits in this version and is not included in the request it is marked for removal
940         for (Path path : paths) {
941             if (path.size() < 3) {
942                 continue;
943             }
944
945             // Path represents
946             //       v   ----related-to-->    otherV
947             // In the above case,
948             // path objects get(0) returns vertex v
949             // path objects.get(1) returns edge related-to
950             // path objects.get(2) returns vertex otherV
951             Vertex otherV = path.get(2);
952
953             String bUri;
954             if (otherV.property(AAIProperties.AAI_URI).isPresent()) {
955                 bUri = otherV.value(AAIProperties.AAI_URI);
956             } else {
957                 continue;
958             }
959             String edgeLabel = path.<Edge>get(1).label();
960
961             Pair<String, String> key = Pair.with(bUri, edgeLabel);
962             if (cousinUriAndLabels.contains(key)) {
963                 cousinUriAndLabels.remove(key);
964             } else {
965                 String bNodeType;
966                 if (otherV.property(AAIProperties.NODE_TYPE).isPresent()) {
967                     bNodeType = otherV.property(AAIProperties.NODE_TYPE).value().toString();
968                 } else {
969                     continue;
970                 }
971                 EdgeRuleQuery ruleQuery = cousinQueryBuilder.to(bNodeType).label(edgeLabel).build();
972                 if (edgeIngestor.hasRule(ruleQuery)) {
973                     toRemove.add(path);
974                 }
975             }
976
977         }
978
979         Set<Pair<Vertex, String>> toBeCreated = new HashSet<>();
980         for (Pair<String, String> cousinUriAndLabel : cousinUriAndLabels) {
981             Edge e;
982             Vertex cousinVertex;
983             String label = cousinUriAndLabel.getValue1();
984             String cousinUri = cousinUriAndLabel.getValue0();
985             QueryParser parser = engine.getQueryBuilder().createQueryFromURI(URI.create(cousinUri));
986
987             List<Vertex> results = parser.getQueryBuilder().toList();
988             if (results.isEmpty()) {
989                 final AAIException ex = new AAIException(AAI_6129,
990                     "Node of type " + parser.getResultType() + ". Could not find object at: " + parser.getUri());
991                 ex.getTemplateVars().add(parser.getResultType());
992                 ex.getTemplateVars().add(parser.getUri().toString());
993                 throw ex;
994             } else {
995                 // still an issue if there's more than one
996                 cousinVertex = results.get(0);
997             }
998
999             if (cousinVertex != null) {
1000                 String vType = (String) v.property(AAIProperties.NODE_TYPE).value();
1001                 String cousinType = (String) cousinVertex.property(AAIProperties.NODE_TYPE).value();
1002                 EdgeRuleQuery.Builder baseQ = new EdgeRuleQuery.Builder(vType, cousinType).label(label);
1003
1004                 if (!edgeRules.hasRule(baseQ.build())) {
1005                     throw new AAIException("AAI_6120", String.format(
1006                         "No EdgeRule found for passed nodeTypes: %s, %s%s.",
1007                         aNodeType, cousinType, label != null ? (" with label " + label) : ""));
1008                 } else if (edgeRules.hasRule(baseQ.edgeType(EdgeType.TREE).build())
1009                     && !edgeRules.hasRule(baseQ.edgeType(EdgeType.COUSIN).build())) {
1010                     throw new AAIException(AAI_6145);
1011                 }
1012
1013                 e = this.getEdgeBetween(EdgeType.COUSIN, v, cousinVertex, label);
1014
1015                 if (e == null) {
1016                     toBeCreated.add(Pair.with(cousinVertex, label));
1017                 }
1018             }
1019         }
1020
1021         for (Path path : toRemove) {
1022             if(isDeltaEventsEnabled) {
1023                 deltaForEdge(mainUri, path.get(1), DeltaAction.DELETE_REL, DeltaAction.UPDATE);
1024             }
1025             this.updatedVertexes.putIfAbsent(v, false);
1026             this.edgeVertexes.add(path.get(2));
1027             path.<Edge>get(1).remove();
1028         }
1029
1030         for (Pair<Vertex, String> create : toBeCreated) {
1031             try {
1032                 Edge e = edgeSer.addEdge(this.engine.asAdmin().getTraversalSource(), v, create.getValue0(), create.getValue1());
1033                 if (isDeltaEventsEnabled) {
1034                     deltaForEdge(mainUri, e, DeltaAction.CREATE_REL, DeltaAction.UPDATE);
1035                 }
1036                 this.updatedVertexes.putIfAbsent(v, false);
1037                 this.edgeVertexes.add(create.getValue0());
1038             } catch (NoEdgeRuleFoundException ex) {
1039                 throw new AAIException(AAI_6129, ex);
1040             }
1041         }
1042     }
1043
1044
1045     private void deltaForEdge(String mainUri, Edge edge, DeltaAction edgeAction, DeltaAction mainAction) {
1046         RelationshipDelta relationshipDelta = new RelationshipDelta(
1047             edgeAction,
1048             edge.inVertex().property(AAIProperties.AAI_UUID).value().toString(),
1049             edge.outVertex().property(AAIProperties.AAI_UUID).value().toString(),
1050             edge.inVertex().property(AAIProperties.AAI_URI).value().toString(),
1051             edge.outVertex().property(AAIProperties.AAI_URI).value().toString(),
1052             edge.label());
1053         edge.properties().forEachRemaining(p -> relationshipDelta.addProp(p.key(), p.value().toString()));
1054         addRelationshipDelta(mainUri, relationshipDelta, mainAction);
1055     }
1056
1057     /**
1058      * Write through defaults.
1059      *
1060      * @param v the v
1061      * @param obj the obj
1062      * @throws AAIUnknownObjectException
1063      */
1064     private void writeThroughDefaults(Vertex v, Introspector obj) throws AAIUnknownObjectException {
1065         Introspector latest = this.latestLoader.introspectorFromName(obj.getName());
1066         if (latest != null) {
1067             Set<String> required = latest.getRequiredProperties();
1068
1069             for (String field : required) {
1070                 String defaultValue = latest.getPropertyMetadata(field).get(PropertyMetadata.DEFAULT_VALUE);
1071                 if (defaultValue != null) {
1072                     Object vertexProp = v.property(field).orElse(null);
1073                     if (vertexProp == null) {
1074                         v.property(field, defaultValue);
1075                     }
1076                 }
1077             }
1078         }
1079
1080     }
1081
1082     /**
1083      * Reflect dependent vertex.
1084      *
1085      * @param v the v
1086      * @param dependentObj the dependent obj
1087      * @return the vertex
1088      * @throws IllegalArgumentException the illegal argument exception
1089      * @throws SecurityException the security exception
1090      * @throws AAIException the AAI exception
1091      * @throws UnsupportedEncodingException the unsupported encoding exception
1092      * @throws AAIUnknownObjectException
1093      */
1094     private Vertex reflectDependentVertex(Vertex v, Introspector dependentObj, String requestContext)
1095             throws AAIException, UnsupportedEncodingException {
1096
1097         QueryBuilder<Vertex> query = this.engine.getQueryBuilder(v);
1098         query.createEdgeTraversal(EdgeType.TREE, v, dependentObj);
1099         query.createKeyQuery(dependentObj);
1100
1101         List<Vertex> items = query.toList();
1102
1103         Vertex dependentVertex;
1104         if (items.size() == 1) {
1105             dependentVertex = items.get(0);
1106             this.verifyResourceVersion("update", dependentObj.getDbName(),
1107                     dependentVertex.<String>property(AAIProperties.RESOURCE_VERSION).orElse(null),
1108                     dependentObj.getValue(AAIProperties.RESOURCE_VERSION), dependentObj.getURI());
1109         } else {
1110             this.verifyResourceVersion("create", dependentObj.getDbName(), "",
1111                     dependentObj.getValue(AAIProperties.RESOURCE_VERSION), dependentObj.getURI());
1112             dependentVertex = createNewVertex(dependentObj);
1113         }
1114
1115         return reflectDependentVertex(v, dependentVertex, dependentObj, requestContext);
1116
1117     }
1118
1119     /**
1120      * Reflect dependent vertex.
1121      *
1122      * @param parent the parent
1123      * @param child the child
1124      * @param obj the obj
1125      * @return the vertex
1126      * @throws IllegalArgumentException the illegal argument exception
1127      * @throws SecurityException the security exception
1128      * @throws AAIException the AAI exception
1129      * @throws UnsupportedEncodingException the unsupported encoding exception
1130      * @throws AAIUnknownObjectException
1131      */
1132     private Vertex reflectDependentVertex(Vertex parent, Vertex child, Introspector obj, String requestContext)
1133             throws AAIException, UnsupportedEncodingException {
1134
1135         String parentUri = parent.<String>property(AAIProperties.AAI_URI).orElse(null);
1136         if (parentUri != null) {
1137             String uri;
1138             uri = obj.getURI();
1139             addUriIfNeeded(child, parentUri + uri);
1140         }
1141         processObject(obj, child, requestContext);
1142
1143         Edge e;
1144         e = this.getEdgeBetween(EdgeType.TREE, parent, child, null);
1145
1146         if (e == null) {
1147             String canBeLinked = obj.getMetadata(ObjectMetadata.CAN_BE_LINKED);
1148             if (canBeLinked != null && canBeLinked.equals("true")) {
1149                 Loader ldrForCntxt = SpringContextAware.getBean(LoaderFactory.class)
1150                         .createLoaderForVersion(introspectionType, getVerForContext(requestContext));
1151                 boolean isFirst = !this.engine.getQueryBuilder(ldrForCntxt, parent)
1152                         .createEdgeTraversal(EdgeType.TREE, parent, obj).hasNext();
1153                 if (isFirst) {
1154                     child.property(AAIProperties.LINKED, true);
1155                 }
1156             }
1157             e = edgeSer.addTreeEdge(this.engine.asAdmin().getTraversalSource(), parent, child);
1158             if(isDeltaEventsEnabled) {
1159                 deltaForEdge(child.property(AAIProperties.AAI_URI).value().toString(), e, DeltaAction.CREATE_REL, DeltaAction.CREATE);
1160             }
1161         }
1162         return child;
1163
1164     }
1165
1166     private SchemaVersion getVerForContext(String requestContext) {
1167         Pattern pattern = Pattern.compile("v[0-9]+");
1168         Matcher m = pattern.matcher(requestContext);
1169         if (!m.find()) {
1170             return this.version;
1171         } else {
1172             return new SchemaVersion(requestContext);
1173         }
1174     }
1175
1176     /**
1177      * Db to object.
1178      *
1179      * @param vertices the vertices
1180      * @param obj the obj
1181      * @param depth the depth
1182      * @param cleanUp the clean up
1183      * @return the introspector
1184      * @throws AAIException the AAI exception
1185      * @throws IllegalArgumentException the illegal argument exception
1186      * @throws SecurityException the security exception
1187      * @throws UnsupportedEncodingException the unsupported encoding exception
1188      * @throws AAIUnknownObjectException
1189      * @throws URISyntaxException
1190      */
1191     public Introspector dbToObject(List<Vertex> vertices, final Introspector obj, int depth, boolean nodeOnly,
1192                                    String cleanUp, boolean isSkipRelatedTo) throws UnsupportedEncodingException, AAIException {
1193         final int internalDepth;
1194         if (depth == Integer.MAX_VALUE) {
1195             internalDepth = depth--;
1196         } else {
1197             internalDepth = depth;
1198         }
1199         StopWatch.conditionalStart();
1200         if (vertices.size() > 1 && !obj.isContainer()) {
1201             dbTimeMsecs += StopWatch.stopIfStarted();
1202             throw new AAIException("AAI_6136",
1203                 "query object mismatch: this object cannot hold multiple items." + obj.getDbName());
1204         } else if (obj.isContainer()) {
1205             final List getList;
1206             String listProperty = null;
1207             for (String property : obj.getProperties()) {
1208                 if (obj.isListType(property) && obj.isComplexGenericType(property)) {
1209                     listProperty = property;
1210                     break;
1211                 }
1212             }
1213             final String propertyName = listProperty;
1214             getList = obj.getValue(listProperty);
1215
1216             /*
1217              * This is an experimental multithreading experiment
1218              * on get alls.
1219              */
1220             ExecutorService pool = GetAllPool.getInstance().getPool();
1221
1222             List<Future<Object>> futures = new ArrayList<>();
1223
1224             for (Vertex v : vertices) {
1225                 AaiCallable<Object> task = new AaiCallable<Object>() {
1226                     @Override
1227                     public Object process() throws UnsupportedEncodingException, AAIException {
1228                         Set<Vertex> seen = new HashSet<>();
1229                         Introspector childObject;
1230                         childObject = obj.newIntrospectorInstanceOfNestedProperty(propertyName);
1231                         dbToObject(childObject, v, seen, internalDepth, nodeOnly, cleanUp, isSkipRelatedTo);
1232                         return childObject.getUnderlyingObject();
1233                     }
1234                 };
1235                 futures.add(pool.submit(task));
1236             }
1237
1238             for (Future<Object> future : futures) {
1239                 try {
1240                     getList.add(future.get());
1241                 } catch (ExecutionException | InterruptedException e) {
1242                     dbTimeMsecs += StopWatch.stopIfStarted();
1243                     throw new AAIException("AAI_4000", e);
1244                 }
1245             }
1246         } else if (vertices.size() == 1) {
1247             Set<Vertex> seen = new HashSet<>();
1248             dbToObject(obj, vertices.get(0), seen, depth, nodeOnly, cleanUp, isSkipRelatedTo);
1249         } else {
1250             // obj = null;
1251         }
1252
1253         dbTimeMsecs += StopWatch.stopIfStarted();
1254         return obj;
1255     }
1256
1257     /**
1258      * Db to object.
1259      *
1260      * @param vertices the vertices
1261      * @param obj the obj
1262      * @param depth the depth
1263      * @param cleanUp the clean up
1264      * @return the introspector
1265      * @throws AAIException the AAI exception
1266      * @throws IllegalArgumentException the illegal argument exception
1267      * @throws SecurityException the security exception
1268      * @throws UnsupportedEncodingException the unsupported encoding exception
1269      * @throws AAIUnknownObjectException
1270      * @throws URISyntaxException
1271      */
1272     public Introspector dbToObject(List<Vertex> vertices, final Introspector obj, int depth, boolean nodeOnly,
1273             String cleanUp) throws UnsupportedEncodingException, AAIException {
1274         return dbToObject(vertices, obj, depth, nodeOnly, cleanUp, false);
1275     }
1276
1277     /**
1278      * Db to object.
1279      *
1280      * @param obj the obj
1281      * @param v the v
1282      * @param seen the seen
1283      * @param depth the depth
1284      * @param cleanUp the clean up
1285      * @return the introspector
1286      * @throws IllegalArgumentException the illegal argument exception
1287      * @throws SecurityException the security exception
1288      * @throws UnsupportedEncodingException the unsupported encoding exception
1289      * @throws AAIException the AAI exception
1290      * @throws AAIUnknownObjectException
1291      * @throws URISyntaxException
1292      */
1293     private Introspector dbToObject(Introspector obj, Vertex v, Set<Vertex> seen, int depth, boolean nodeOnly,
1294             String cleanUp) throws AAIException, UnsupportedEncodingException {
1295         return dbToObject(obj, v, seen, depth, nodeOnly, cleanUp, false);
1296     }
1297
1298     /**
1299      * Db to object.
1300      *
1301      * @param obj the obj
1302      * @param v the v
1303      * @param seen the seen
1304      * @param depth the depth
1305      * @param cleanUp the clean up
1306      * @return the introspector
1307      * @throws IllegalArgumentException the illegal argument exception
1308      * @throws SecurityException the security exception
1309      * @throws UnsupportedEncodingException the unsupported encoding exception
1310      * @throws AAIException the AAI exception
1311      * @throws AAIUnknownObjectException
1312      * @throws URISyntaxException
1313      */
1314     private Introspector dbToObject(Introspector obj, Vertex v, Set<Vertex> seen, int depth, boolean nodeOnly,
1315                                     String cleanUp, boolean isSkipRelatedTo) throws AAIException, UnsupportedEncodingException {
1316
1317         if (depth < 0) {
1318             return null;
1319         }
1320         depth--;
1321         seen.add(v);
1322
1323         boolean modified = false;
1324         for (String property : obj.getProperties(PropertyPredicates.isVisible())) {
1325             List<Object> getList = null;
1326
1327             if (!(obj.isComplexType(property) || obj.isListType(property))) {
1328                 this.copySimpleProperty(property, obj, v);
1329                 modified = true;
1330             } else {
1331                 if (obj.isComplexType(property)) {
1332                     /* container case */
1333
1334                     if (!property.equals("relationship-list") && depth >= 0) {
1335                         Introspector argumentObject = obj.newIntrospectorInstanceOfProperty(property);
1336                         Object result = dbToObject(argumentObject, v, seen, depth + 1, nodeOnly, cleanUp);
1337                         if (result != null) {
1338                             obj.setValue(property, argumentObject.getUnderlyingObject());
1339                             modified = true;
1340                         }
1341                     } else if (property.equals("relationship-list") && !nodeOnly) {
1342                         /* relationships need to be handled correctly */
1343                         Introspector relationshipList = obj.newIntrospectorInstanceOfProperty(property);
1344                         relationshipList = createRelationshipList(v, relationshipList, cleanUp, isSkipRelatedTo);
1345                         if (relationshipList != null) {
1346                             obj.setValue(property, relationshipList.getUnderlyingObject());
1347                             modified = true;
1348                         }
1349
1350                     }
1351                 } else if (obj.isListType(property)) {
1352
1353                     if (property.equals("any")) {
1354                         continue;
1355                     }
1356                     String genericType = obj.getGenericTypeClass(property).getSimpleName();
1357                     if (obj.isComplexGenericType(property) && depth >= 0) {
1358                         final String childDbName = convertFromCamelCase(genericType);
1359                         String vType = v.<String>property(AAIProperties.NODE_TYPE).orElse(null);
1360                         EdgeRule rule;
1361
1362                         try {
1363                             rule = edgeRules.getRule(
1364                                 new EdgeRuleQuery.Builder(vType, childDbName).edgeType(EdgeType.TREE).build());
1365                         } catch (EdgeRuleNotFoundException e) {
1366                             throw new NoEdgeRuleFoundException(e);
1367                         } catch (AmbiguousRuleChoiceException e) {
1368                             throw new MultipleEdgeRuleFoundException(e);
1369                         }
1370                         if (!rule.getContains().equals(AAIDirection.NONE.toString())) {
1371
1372                             Direction ruleDirection = rule.getDirection();
1373                             List<Vertex> verticesList = new ArrayList<>();
1374                             v.vertices(ruleDirection, rule.getLabel()).forEachRemaining(vertex -> {
1375                                 if (vertex.property(AAIProperties.NODE_TYPE).orElse("").equals(childDbName)) {
1376                                     verticesList.add(vertex);
1377                                 }
1378                             });
1379                             if (!verticesList.isEmpty()) {
1380                                 getList = obj.getValue(property);
1381                             }
1382                             int processed = 0;
1383                             for (Vertex childVertex : verticesList) {
1384                                 if (!seen.contains(childVertex)) {
1385                                     Introspector argumentObject = obj.newIntrospectorInstanceOfNestedProperty(property);
1386
1387                                     Object result =
1388                                         dbToObject(argumentObject, childVertex, seen, depth, nodeOnly, cleanUp, isSkipRelatedTo);
1389                                     if (result != null) {
1390                                         getList.add(argumentObject.getUnderlyingObject());
1391                                     }
1392
1393                                     processed++;
1394                                 } else {
1395                                     LOGGER.warn("Cycle found while serializing vertex id={}",
1396                                         childVertex.id().toString());
1397                                 }
1398                             }
1399                             if (processed == 0) {
1400                                 // vertices were all seen, reset the list
1401                                 getList = null;
1402                             }
1403                             if (processed > 0) {
1404                                 modified = true;
1405                             }
1406                         }
1407                     } else if (obj.isSimpleGenericType(property)) {
1408                         List<Object> temp = this.engine.getListProperty(v, property);
1409                         if (temp != null) {
1410                             getList = (List<Object>) obj.getValue(property);
1411                             getList.addAll(temp);
1412                             modified = true;
1413                         }
1414                     }
1415                 }
1416             }
1417         }
1418
1419         // no changes were made to this obj, discard the instance
1420         if (!modified) {
1421             return null;
1422         }
1423         this.enrichData(obj, v);
1424         return obj;
1425
1426     }
1427
1428     public Introspector getVertexProperties(Vertex v) throws AAIException, UnsupportedEncodingException {
1429         String nodeType = v.<String>property(AAIProperties.NODE_TYPE).orElse(null);
1430         if (nodeType == null) {
1431             throw new AAIException("AAI_6143");
1432         }
1433
1434         Introspector obj = this.latestLoader.introspectorFromName(nodeType);
1435         Set<Vertex> seen = new HashSet<>();
1436         int depth = 0;
1437         StopWatch.conditionalStart();
1438         this.dbToObject(obj, v, seen, depth, true, FALSE);
1439         dbTimeMsecs += StopWatch.stopIfStarted();
1440         return obj;
1441
1442     }
1443
1444     public Introspector getLatestVersionView(Vertex v) throws AAIException, UnsupportedEncodingException {
1445         return getLatestVersionView(v, AAIProperties.MAXIMUM_DEPTH);
1446     }
1447
1448     public Introspector getLatestVersionView(Vertex v, int depth) throws AAIException, UnsupportedEncodingException {
1449         String nodeType = v.<String>property(AAIProperties.NODE_TYPE).orElse(null);
1450         if (nodeType == null) {
1451             throw new AAIException("AAI_6143");
1452         }
1453         Introspector obj = this.latestLoader.introspectorFromName(nodeType);
1454         Set<Vertex> seen = new HashSet<>();
1455         StopWatch.conditionalStart();
1456         this.dbToObject(obj, v, seen, depth, false, FALSE);
1457         dbTimeMsecs += StopWatch.stopIfStarted();
1458         return obj;
1459     }
1460
1461     /**
1462      * Copy simple property.
1463      *
1464      * @param property the property
1465      * @param obj the obj
1466      * @param v the v
1467      * @throws IllegalArgumentException the illegal argument exception
1468      * @throws SecurityException the security exception
1469      */
1470     private void copySimpleProperty(String property, Introspector obj, Vertex v) {
1471         final Object temp = getProperty(obj, property, v);
1472         if (temp != null) {
1473             obj.setValue(property, temp);
1474         }
1475     }
1476
1477     public Map<String, Object> convertVertexToHashMap(Introspector obj, Vertex v) {
1478
1479         Set<String> simpleProperties = obj.getSimpleProperties(PropertyPredicates.isVisible());
1480         String[] simplePropsArray = new String[simpleProperties.size()];
1481         simplePropsArray = simpleProperties.toArray(simplePropsArray);
1482
1483         Map<String, Object> simplePropsHashMap = new HashMap<>(simplePropsArray.length * 2);
1484
1485         v.properties(simplePropsArray).forEachRemaining(vp -> simplePropsHashMap.put(vp.key(), vp.value()));
1486
1487         return simplePropsHashMap;
1488     }
1489
1490     public Introspector dbToRelationshipObject(Vertex v, boolean isSkipRelatedTo) throws UnsupportedEncodingException, AAIException {
1491         Introspector relationshipList = this.latestLoader.introspectorFromName("relationship-list");
1492         relationshipList = createRelationshipList(v, relationshipList, FALSE, isSkipRelatedTo);
1493         return relationshipList;
1494     }
1495
1496     /**
1497      * Creates the relationship list.
1498      *
1499      * @param v the v
1500      * @param obj the obj
1501      * @param cleanUp the clean up
1502      * @return the object
1503      * @throws IllegalArgumentException the illegal argument exception
1504      * @throws SecurityException the security exception
1505      * @throws UnsupportedEncodingException the unsupported encoding exception
1506      * @throws AAIException the AAI exception
1507      * @throws URISyntaxException
1508      */
1509     private Introspector createRelationshipList(Vertex v, Introspector obj, String cleanUp)
1510             throws UnsupportedEncodingException, AAIException {
1511         // default boolean value for isSkipRelatedTo is false
1512         return createRelationshipList(v, obj, cleanUp, false);
1513     }
1514
1515     /**
1516      * Creates the relationship list.
1517      *
1518      * @param v the v
1519      * @param obj the obj
1520      * @param cleanUp the clean up
1521      * @param isSkipRelatedTo to determine adding related-to-property in response
1522      * @return the object
1523      * @throws IllegalArgumentException the illegal argument exception
1524      * @throws SecurityException the security exception
1525      * @throws UnsupportedEncodingException the unsupported encoding exception
1526      * @throws AAIException the AAI exception
1527      * @throws URISyntaxException
1528      */
1529     private Introspector createRelationshipList(Vertex v, Introspector obj, String cleanUp, boolean isSkipRelatedTo)
1530             throws UnsupportedEncodingException, AAIException {
1531
1532         List<Object> relationshipObjList = obj.getValue(RELATIONSHIP);
1533         VertexProperty nodeTypeProperty = v.property(AAIProperties.NODE_TYPE);
1534
1535         if (!nodeTypeProperty.isPresent()) {
1536             LOGGER.warn("Not processing the vertex {} because its missing required property aai-node-type", v.id());
1537             return null;
1538         }
1539
1540         List<Path> paths = this.engine.getQueryEngine().findCousinsAsPath(v);
1541
1542         String aNodeType = v.property(AAIProperties.NODE_TYPE).value().toString();
1543
1544         EdgeIngestor edgeIngestor = SpringContextAware.getBean(EdgeIngestor.class);
1545
1546         EdgeRuleQuery.Builder queryBuilder = new EdgeRuleQuery.Builder(aNodeType)
1547             .edgeType(EdgeType.COUSIN)
1548             .version(obj.getVersion());
1549
1550         for (Path path : paths){
1551             if(path.size() < 3){
1552                 continue;
1553             }
1554
1555             // Path represents
1556             //       v   ----related-to-->    otherV
1557             // In the above case,
1558             // path objects get(0) returns vertex v
1559             // path objects.get(1) returns edge related-to
1560             // path objects.get(2) returns vertex otherV
1561             Edge edge = path.get(1);
1562             Vertex otherV= path.get(2);
1563
1564             // TODO: Come back and revisit this code
1565             // Create a query based on the a nodetype and b nodetype
1566             // which is also a cousin edge and ensure the version
1567             // is used properly so for example in order to be backwards
1568             // compatible if we had allowed a edge between a and b
1569             // in a previous release and we decided to remove it from
1570             // the edge rules in the future we can display the edge
1571             // only for the older apis and the new apis if the edge rule
1572             // is removed will not be seen in the newer version of the API
1573
1574             String bNodeType;
1575             if (otherV.property(AAIProperties.NODE_TYPE).isPresent()) {
1576                 bNodeType = otherV.value(AAIProperties.NODE_TYPE);
1577             } else {
1578                 continue;
1579             }
1580
1581             String edgeLabel = edge.label();
1582             EdgeRuleQuery ruleQuery = queryBuilder.to(bNodeType).label(edgeLabel).build();
1583
1584             if(!edgeIngestor.hasRule(ruleQuery)){
1585                 LOGGER.debug( "Caught an edge rule not found for query {}", ruleQuery);
1586                 continue;
1587             }
1588
1589             Introspector relationshipObj = obj.newIntrospectorInstanceOfNestedProperty(RELATIONSHIP);
1590             Object result = processEdgeRelationship(relationshipObj, otherV, cleanUp, edgeLabel, isSkipRelatedTo);
1591             if (result != null) {
1592                 relationshipObjList.add(result);
1593             }
1594
1595         }
1596
1597         if (relationshipObjList.isEmpty()) {
1598             return null;
1599         } else {
1600             return obj;
1601         }
1602     }
1603
1604     /**
1605      * Process edge relationship.
1606      *
1607      * @param relationshipObj the relationship obj
1608      * @param edgeLabel the edge's label
1609      * @param cleanUp the clean up
1610      * @return the object
1611      * @throws IllegalArgumentException the illegal argument exception
1612      * @throws SecurityException the security exception
1613      * @throws UnsupportedEncodingException the unsupported encoding exception
1614      * @throws AAIUnknownObjectException
1615      * @throws URISyntaxException
1616      */
1617     private Object processEdgeRelationship(Introspector relationshipObj, Vertex cousin, String cleanUp,
1618             String edgeLabel, boolean isSkipRelatedTo) throws UnsupportedEncodingException, AAIUnknownObjectException {
1619
1620         VertexProperty aaiUriProperty = cousin.property("aai-uri");
1621
1622         if (!aaiUriProperty.isPresent()) {
1623             return null;
1624         }
1625
1626         URI uri = UriBuilder.fromUri(aaiUriProperty.value().toString()).build();
1627
1628         URIToRelationshipObject uriParser;
1629         Introspector result;
1630         try {
1631             uriParser = new URIToRelationshipObject(relationshipObj.getLoader(), uri, this.baseURL);
1632             result = uriParser.getResult();
1633         } catch (AAIException | URISyntaxException e) {
1634             LOGGER.error("Error while processing edge relationship in version " + relationshipObj.getVersion()
1635                     + " (bad vertex ID=" + ": " + e.getMessage() + " " + LogFormatTools.getStackTop(e));
1636             return null;
1637         }
1638
1639         VertexProperty cousinVertexNodeType = cousin.property(AAIProperties.NODE_TYPE);
1640
1641         if (cousinVertexNodeType.isPresent()) {
1642             String cousinType = cousinVertexNodeType.value().toString();
1643             if (namedPropNodes.contains(cousinType) && !isSkipRelatedTo) {
1644                 this.addRelatedToProperty(result, cousin, cousinType);
1645             }
1646         }
1647
1648         if (edgeLabel != null && result.hasProperty(RELATIONSHIP_LABEL)) {
1649             result.setValue(RELATIONSHIP_LABEL, edgeLabel);
1650         }
1651
1652         return result.getUnderlyingObject();
1653     }
1654
1655     /**
1656      * Gets the URI for vertex.
1657      *
1658      * @param v the v
1659      * @return the URI for vertex
1660      * @throws IllegalArgumentException the illegal argument exception
1661      * @throws SecurityException the security exception
1662      * @throws UnsupportedEncodingException the unsupported encoding exception
1663      * @throws AAIUnknownObjectException
1664      */
1665     public URI getURIForVertex(Vertex v) throws UnsupportedEncodingException {
1666
1667         return getURIForVertex(v, false);
1668     }
1669
1670     public URI getURIForVertex(Vertex v, boolean overwrite) {
1671         URI uri = UriBuilder.fromPath("/unknown-uri").build();
1672
1673         String aaiUri = v.<String>property(AAIProperties.AAI_URI).orElse(null);
1674
1675         if (aaiUri != null && !overwrite) {
1676             uri = UriBuilder.fromPath(aaiUri).build();
1677         }
1678
1679         return uri;
1680     }
1681
1682
1683     public void addRelatedToProperty(Introspector relationship, Vertex cousinVertex, String cousinType)
1684             throws AAIUnknownObjectException {
1685
1686         Introspector obj;
1687
1688         try {
1689             obj = this.loader.introspectorFromName(cousinType);
1690         } catch (AAIUnknownObjectException ex) {
1691             if (LOGGER.isTraceEnabled()) {
1692                 LOGGER.trace("Encountered unknown object exception when trying to load nodetype of {} for vertex id {}",
1693                         cousinType, cousinVertex.id());
1694             }
1695             return;
1696         }
1697
1698         String nameProps = obj.getMetadata(ObjectMetadata.NAME_PROPS);
1699         List<Introspector> relatedToProperties = new ArrayList<>();
1700
1701         if (nameProps != null) {
1702             String[] props = nameProps.split(",");
1703             for (String prop : props) {
1704                 final Object temp = getProperty(obj, prop, cousinVertex);
1705                 Introspector relatedTo = relationship.newIntrospectorInstanceOfNestedProperty("related-to-property");
1706                 relatedTo.setValue("property-key", cousinType + "." + prop);
1707                 relatedTo.setValue("property-value", temp);
1708                 relatedToProperties.add(relatedTo);
1709             }
1710         }
1711
1712         if (!relatedToProperties.isEmpty()) {
1713             List<Object> relatedToList = relationship.getValue("related-to-property");
1714             for (Introspector introspector : relatedToProperties) {
1715                 relatedToList.add(introspector.getUnderlyingObject());
1716             }
1717         }
1718
1719     }
1720
1721     private Object getProperty(Introspector obj, String prop, Vertex vertex) {
1722
1723         final Map<PropertyMetadata, String> metadata = obj.getPropertyMetadata(prop);
1724         String dbPropertyName = prop;
1725
1726         if (metadata.containsKey(PropertyMetadata.DB_ALIAS)) {
1727             dbPropertyName = metadata.get(PropertyMetadata.DB_ALIAS);
1728         }
1729
1730         return vertex.<Object>property(dbPropertyName).orElse(null);
1731     }
1732
1733     /**
1734      * Creates the edge.
1735      *
1736      * @param relationship the relationship
1737      * @param inputVertex the input vertex
1738      * @return true, if successful
1739      * @throws UnsupportedEncodingException the unsupported encoding exception
1740      * @throws AAIException the AAI exception
1741      */
1742     public Vertex createEdge(Introspector relationship, Vertex inputVertex)
1743             throws UnsupportedEncodingException, AAIException {
1744
1745         Vertex relatedVertex;
1746         StopWatch.conditionalStart();
1747         QueryParser parser = engine.getQueryBuilder().createQueryFromRelationship(relationship);
1748
1749         String label = null;
1750         if (relationship.hasProperty(RELATIONSHIP_LABEL)) {
1751             label = relationship.getValue(RELATIONSHIP_LABEL);
1752         }
1753
1754         List<Vertex> results = parser.getQueryBuilder().toList();
1755         if (results.isEmpty()) {
1756             dbTimeMsecs += StopWatch.stopIfStarted();
1757             AAIException e = new AAIException(AAI_6129,
1758                     "Node of type " + parser.getResultType() + ". Could not find object at: " + parser.getUri());
1759             e.getTemplateVars().add(parser.getResultType());
1760             e.getTemplateVars().add(parser.getUri().toString());
1761             throw e;
1762         } else {
1763             // still an issue if there's more than one
1764             relatedVertex = results.get(0);
1765         }
1766
1767         if (relatedVertex != null) {
1768
1769             Edge e;
1770             try {
1771                 e = this.getEdgeBetween(EdgeType.COUSIN, inputVertex, relatedVertex, label);
1772                 if (e == null) {
1773                     e = edgeSer.addEdge(this.engine.asAdmin().getTraversalSource(), inputVertex, relatedVertex, label);
1774                     if(isDeltaEventsEnabled) {
1775                         deltaForEdge(inputVertex.property(AAIProperties.AAI_URI).value().toString(), e, DeltaAction.CREATE_REL, DeltaAction.UPDATE);
1776                     }
1777                 } else {
1778                     // attempted to link two vertexes already linked
1779                 }
1780             } finally {
1781                 dbTimeMsecs += StopWatch.stopIfStarted();
1782             }
1783         }
1784
1785         dbTimeMsecs += StopWatch.stopIfStarted();
1786         return relatedVertex;
1787     }
1788
1789     /**
1790      * Gets all the edges between of the type with the specified label.
1791      *
1792      * @param aVertex the out vertex
1793      * @param bVertex the in vertex
1794      * @return the edges between
1795      */
1796     private Edge getEdgeBetweenWithLabel(EdgeType type, Vertex aVertex, Vertex bVertex, EdgeRule edgeRule) {
1797
1798         Edge result = null;
1799
1800         if (bVertex != null) {
1801             GraphTraversal<Vertex, Edge> findEdgesBetween = null;
1802             if (EdgeType.TREE.equals(type)) {
1803                 GraphTraversal<Vertex, Vertex> findVertex = this.engine.asAdmin().getTraversalSource().V(bVertex);
1804                 if (edgeRule.getDirection().equals(Direction.IN)) {
1805                     findEdgesBetween = findVertex.outE(edgeRule.getLabel())
1806                             .has(EdgeProperty.CONTAINS.toString(), edgeRule.getContains())
1807                             .not(__.has(EdgeField.PRIVATE.toString(), true));
1808                 } else {
1809                     findEdgesBetween = findVertex.inE(edgeRule.getLabel())
1810                             .has(EdgeProperty.CONTAINS.toString(), edgeRule.getContains())
1811                             .not(__.has(EdgeField.PRIVATE.toString(), true));
1812                 }
1813                 findEdgesBetween = findEdgesBetween.filter(__.otherV().hasId(aVertex.id())).limit(1);
1814             } else {
1815                 findEdgesBetween = this.engine.asAdmin().getTraversalSource().V(aVertex).bothE(edgeRule.getLabel());
1816                 findEdgesBetween = findEdgesBetween.has(EdgeProperty.CONTAINS.toString(), "NONE")
1817                         .not(__.has(EdgeField.PRIVATE.toString(), true));
1818                 findEdgesBetween = findEdgesBetween.filter(__.otherV().hasId(bVertex.id())).limit(1);
1819             }
1820             List<Edge> list = findEdgesBetween.toList();
1821             if (!list.isEmpty()) {
1822                 result = list.get(0);
1823             }
1824         }
1825
1826         return result;
1827     }
1828
1829     /**
1830      * Gets all the edges string between of the type.
1831      *
1832      * @param aVertex the out vertex
1833      * @param bVertex the in vertex
1834      * @return the edges between
1835      * @throws NoEdgeRuleFoundException
1836      */
1837     private List<String> getEdgeLabelsBetween(EdgeType type, Vertex aVertex, Vertex bVertex) {
1838
1839         List<String> result = new ArrayList<>();
1840
1841         if (bVertex != null) {
1842             GraphTraversal<Vertex, Edge> findEdgesBetween = null;
1843             findEdgesBetween = this.engine.asAdmin().getTraversalSource().V(aVertex).bothE();
1844             if (EdgeType.TREE.equals(type)) {
1845                 findEdgesBetween = findEdgesBetween.not(__.or(__.has(EdgeProperty.CONTAINS.toString(), "NONE"),
1846                         __.has(EdgeField.PRIVATE.toString(), true)));
1847             } else {
1848                 findEdgesBetween = findEdgesBetween.has(EdgeProperty.CONTAINS.toString(), "NONE")
1849                         .not(__.has(EdgeField.PRIVATE.toString(), true));
1850             }
1851             findEdgesBetween = findEdgesBetween.filter(__.otherV().hasId(bVertex.id()));
1852             result = findEdgesBetween.label().toList();
1853         }
1854         return result;
1855     }
1856
1857     /**
1858      * Gets all the edges between the vertexes with the label and type.
1859      *
1860      * @param aVertex the out vertex
1861      * @param bVertex the in vertex
1862      * @param label
1863      * @return the edges between
1864      * @throws AAIException the AAI exception
1865      */
1866     private Edge getEdgesBetween(EdgeType type, Vertex aVertex, Vertex bVertex, String label) throws AAIException {
1867
1868         Edge edge = null;
1869
1870         if (bVertex != null) {
1871             String aType = aVertex.<String>property(AAIProperties.NODE_TYPE).value();
1872             String bType = bVertex.<String>property(AAIProperties.NODE_TYPE).value();
1873             EdgeRuleQuery q = new EdgeRuleQuery.Builder(aType, bType).edgeType(type).label(label).build();
1874             EdgeRule rule;
1875             try {
1876                 rule = edgeRules.getRule(q);
1877             } catch (EdgeRuleNotFoundException e) {
1878                 throw new NoEdgeRuleFoundException(e);
1879             } catch (AmbiguousRuleChoiceException e) {
1880                 throw new MultipleEdgeRuleFoundException(e);
1881             }
1882             edge = this.getEdgeBetweenWithLabel(type, aVertex, bVertex, rule);
1883         }
1884
1885         return edge;
1886     }
1887
1888     /**
1889      * Gets the edge between with the label and edge type.
1890      *
1891      * @param aVertex the out vertex
1892      * @param bVertex the in vertex
1893      * @param label
1894      * @return the edge between
1895      * @throws AAIException the AAI exception
1896      * @throws NoEdgeRuleFoundException
1897      */
1898     public Edge getEdgeBetween(EdgeType type, Vertex aVertex, Vertex bVertex, String label) throws AAIException {
1899
1900         StopWatch.conditionalStart();
1901         if (bVertex != null) {
1902
1903             Edge edge = this.getEdgesBetween(type, aVertex, bVertex, label);
1904             if (edge != null) {
1905                 dbTimeMsecs += StopWatch.stopIfStarted();
1906                 return edge;
1907             }
1908
1909         }
1910         dbTimeMsecs += StopWatch.stopIfStarted();
1911         return null;
1912     }
1913
1914     public Edge getEdgeBetween(EdgeType type, Vertex aVertex, Vertex bVertex) throws AAIException {
1915         return this.getEdgeBetween(type, aVertex, bVertex, null);
1916     }
1917
1918     /**
1919      * Delete edge.
1920      *
1921      * @param relationship the relationship
1922      * @param inputVertex the input vertex
1923      * @return true, if successful
1924      * @throws UnsupportedEncodingException the unsupported encoding exception
1925      * @throws AAIException the AAI exception
1926      */
1927     public Optional<Vertex> deleteEdge(Introspector relationship, Vertex inputVertex)
1928             throws UnsupportedEncodingException, AAIException {
1929
1930         Vertex relatedVertex;
1931         StopWatch.conditionalStart();
1932         QueryParser parser = engine.getQueryBuilder().createQueryFromRelationship(relationship);
1933
1934         List<Vertex> results = parser.getQueryBuilder().toList();
1935
1936         String label = null;
1937         if (relationship.hasProperty(RELATIONSHIP_LABEL)) {
1938             label = relationship.getValue(RELATIONSHIP_LABEL);
1939         }
1940
1941         if (results.isEmpty()) {
1942             dbTimeMsecs += StopWatch.stopIfStarted();
1943             return Optional.empty();
1944         }
1945
1946         relatedVertex = results.get(0);
1947         Edge edge;
1948         try {
1949             edge = this.getEdgeBetween(EdgeType.COUSIN, inputVertex, relatedVertex, label);
1950         } catch (NoEdgeRuleFoundException e) {
1951             dbTimeMsecs += StopWatch.stopIfStarted();
1952             throw new AAIException(AAI_6129, e);
1953         }
1954         if (edge != null) {
1955             if(isDeltaEventsEnabled) {
1956                 String mainUri = inputVertex.property(AAIProperties.AAI_URI).value().toString();
1957                 deltaForEdge(mainUri, edge, DeltaAction.DELETE_REL, DeltaAction.UPDATE);
1958             }
1959             edge.remove();
1960             dbTimeMsecs += StopWatch.stopIfStarted();
1961             return Optional.of(relatedVertex);
1962         } else {
1963             dbTimeMsecs += StopWatch.stopIfStarted();
1964             return Optional.empty();
1965         }
1966
1967     }
1968
1969     /**
1970      * Delete with traversal.
1971      *
1972      * @param startVertex the start vertex
1973      */
1974     public void deleteWithTraversal(Vertex startVertex) {
1975         StopWatch.conditionalStart();
1976         List<Vertex> results = this.engine.getQueryEngine().findDeletable(startVertex);
1977         this.delete(results);
1978     }
1979
1980     /**
1981      * Removes the list of vertexes from the graph
1982      * <p>
1983      * Current the vertex label will just be vertex but
1984      * in the future the aai-node-type property will be replaced
1985      * by using the vertex label as when retrieving an vertex
1986      * and retrieving an single property on an vertex will pre-fetch
1987      * all the properties of that vertex and this is due to the following property
1988      * <p>
1989      * <code>
1990      * query.fast-property=true
1991      * </code>
1992      * <p>
1993      * JanusGraph doesn't provide the capability to override that
1994      * at a transaction level and there is a plan to move to vertex label
1995      * so it is best to utilize this for now and when the change is applied
1996      *
1997      * @param vertices - list of vertices to delete from the graph
1998      */
1999     void delete(List<Vertex> vertices) {
2000         StopWatch.conditionalStart();
2001
2002         for (Vertex v : vertices) {
2003             LOGGER.debug("Removing vertex {} with label {}", v.id(), v.label());
2004             if(isDeltaEventsEnabled) {
2005                 deltaForVertexDelete(v);
2006             }
2007             //add the cousin vertexes of v to have their resource-version updated and notified on.
2008             v.edges(Direction.BOTH)
2009                 .forEachRemaining(e -> {
2010                     if (e.property(EdgeProperty.CONTAINS.toString()).isPresent()
2011                         && AAIDirection.NONE.toString().equals(e.<String>value(EdgeProperty.CONTAINS.toString()))) {
2012                         e.bothVertices().forEachRemaining(cousinV -> {
2013                             if (!v.equals(cousinV)) {
2014                                 edgeVertexes.add(cousinV);
2015                             }
2016                         });
2017                     }
2018                 });
2019
2020             //if somewhere along the way v was added to the sets tracking the what is to be updated/notified on
2021             // it should be removed from them as v is to be deleted
2022             edgeVertexes.remove(v);
2023             updatedVertexes.remove(v);
2024             v.remove();
2025         }
2026
2027         dbTimeMsecs += StopWatch.stopIfStarted();
2028     }
2029
2030     private void deltaForVertexDelete(Vertex v) {
2031         String aaiUri = v.property(AAIProperties.AAI_URI).value().toString();
2032         v.keys().forEach(k -> {
2033             List<Object> list = new ArrayList<>();
2034             v.properties(k).forEachRemaining(vp -> list.add(vp.value()));
2035             if (list.size() == 1) {
2036                 addPropDelta(aaiUri, k, PropertyDeltaFactory.getDelta(DeltaAction.DELETE, list.get(0)), DeltaAction.DELETE);
2037             } else {
2038                 addPropDelta(aaiUri, k, PropertyDeltaFactory.getDelta(DeltaAction.DELETE, list), DeltaAction.DELETE);
2039             }
2040
2041         });
2042         v.edges(Direction.BOTH).forEachRemaining(e -> deltaForEdge(aaiUri, e, DeltaAction.DELETE, DeltaAction.DELETE));
2043     }
2044
2045     /**
2046      * Delete.
2047      *
2048      * @param v the v
2049      * @param resourceVersion the resource version
2050      * @throws IllegalArgumentException the illegal argument exception
2051      * @throws AAIException the AAI exception
2052      * @throws InterruptedException the interrupted exception
2053      */
2054     public void delete(Vertex v, List<Vertex> deletableVertices, String resourceVersion, boolean enableResourceVersion)
2055             throws IllegalArgumentException, AAIException {
2056
2057         boolean result = verifyDeleteSemantics(v, resourceVersion, enableResourceVersion);
2058         /*
2059          * The reason why I want to call PreventDeleteSemantics second time is to catch the prevent-deletes in a chain
2060          * These are far-fewer than seeing a prevent-delete on the vertex to be deleted
2061          * So its better to make these in 2 steps
2062          */
2063         if (result && !deletableVertices.isEmpty()) {
2064             result = verifyPreventDeleteSemantics(deletableVertices);
2065         }
2066         if (result) {
2067
2068             try {
2069                 deleteWithTraversal(v);
2070             } catch (IllegalStateException e) {
2071                 throw new AAIException("AAI_6110", e);
2072             }
2073
2074         }
2075     }
2076
2077     /**
2078      * Delete.
2079      *
2080      * @param v the v
2081      * @param resourceVersion the resource version
2082      * @throws IllegalArgumentException the illegal argument exception
2083      * @throws AAIException the AAI exception
2084      * @throws InterruptedException the interrupted exception
2085      */
2086     public void delete(Vertex v, String resourceVersion, boolean enableResourceVersion)
2087             throws IllegalArgumentException, AAIException {
2088
2089         boolean result = verifyDeleteSemantics(v, resourceVersion, enableResourceVersion);
2090
2091         if (result) {
2092
2093             try {
2094                 deleteWithTraversal(v);
2095             } catch (IllegalStateException e) {
2096                 throw new AAIException("AAI_6110", e);
2097             }
2098
2099         }
2100     }
2101
2102     /**
2103      * Verify delete semantics.
2104      *
2105      * @param vertex the vertex
2106      * @param resourceVersion the resource version
2107      * @return true, if successful
2108      * @throws AAIException the AAI exception
2109      */
2110     private boolean verifyDeleteSemantics(Vertex vertex, String resourceVersion, boolean enableResourceVersion)
2111             throws AAIException {
2112         boolean result;
2113         String nodeType;
2114         nodeType = vertex.<String>property(AAIProperties.NODE_TYPE).orElse(null);
2115         if (enableResourceVersion) {
2116             this.verifyResourceVersion("delete", nodeType,
2117                 vertex.<String>property(AAIProperties.RESOURCE_VERSION).orElse(null), resourceVersion, nodeType);
2118         }
2119         List<Vertex> vertices = new ArrayList<>();
2120         vertices.add(vertex);
2121         result = verifyPreventDeleteSemantics(vertices);
2122
2123         return result;
2124     }
2125
2126     /**
2127      * Verify Prevent delete semantics.
2128      *
2129      * @param vertices the list of vertices
2130      * @return true, if successful
2131      * @throws AAIException the AAI exception
2132      */
2133     private boolean verifyPreventDeleteSemantics(List<Vertex> vertices) throws AAIException {
2134         boolean result = true;
2135         String errorDetail = " unknown delete semantic found";
2136         String aaiExceptionCode = "";
2137
2138         StopWatch.conditionalStart();
2139         /*
2140          * This takes in all the vertices in a cascade-delete-chain and checks if there is any edge with a
2141          * "prevent-delete" condition
2142          * If yes - that should prevent the deletion of the vertex
2143          * Dedup makes sure we dont capture the prevent-delete vertices twice
2144          * The prevent-delete vertices are stored so that the error message displays what prevents the delete
2145          */
2146
2147         List<Object> preventDeleteVertices = this.engine.asAdmin().getReadOnlyTraversalSource().V(vertices)
2148                 .union(__.inE().has(EdgeProperty.PREVENT_DELETE.toString(), AAIDirection.IN.toString()).outV()
2149                         .values(AAIProperties.NODE_TYPE),
2150                         __.outE().has(EdgeProperty.PREVENT_DELETE.toString(), AAIDirection.OUT.toString()).inV()
2151                                 .values(AAIProperties.NODE_TYPE))
2152                 .dedup().toList();
2153
2154         dbTimeMsecs += StopWatch.stopIfStarted();
2155         if (!preventDeleteVertices.isEmpty()) {
2156             aaiExceptionCode = "AAI_6110";
2157             errorDetail = String.format(
2158                     "Object is being reference by additional objects preventing it from being deleted. Please clean up references from the following types %s",
2159                     preventDeleteVertices);
2160             result = false;
2161         }
2162         if (!result) {
2163             throw new AAIException(aaiExceptionCode, errorDetail);
2164         }
2165         return result;
2166     }
2167
2168     /**
2169      * Verify resource version.
2170      *
2171      * @param action the action
2172      * @param nodeType the node type
2173      * @param currentResourceVersion the current resource version
2174      * @param resourceVersion the resource version
2175      * @param uri the uri
2176      * @return true, if successful
2177      * @throws AAIException the AAI exception
2178      */
2179     public boolean verifyResourceVersion(String action, String nodeType, String currentResourceVersion,
2180             String resourceVersion, String uri) throws AAIException {
2181         String enabled = "";
2182         String errorDetail = "";
2183         String aaiExceptionCode = "";
2184         boolean isDeleteResourceVersionOk = true;
2185         if (currentResourceVersion == null) {
2186             currentResourceVersion = "";
2187         }
2188
2189         if (resourceVersion == null) {
2190             resourceVersion = "";
2191         }
2192         try {
2193             enabled = AAIConfig.get(AAIConstants.AAI_RESVERSION_ENABLEFLAG);
2194
2195         } catch (AAIException e) {
2196             ErrorLogHelper.logException(e);
2197         }
2198         if (enabled.equals("true")) {
2199             if ("delete".equals(action)) {
2200                 isDeleteResourceVersionOk = verifyResourceVersionForDelete(currentResourceVersion, resourceVersion);
2201             }
2202             if ((!isDeleteResourceVersionOk)
2203                     || ((!"delete".equals(action)) && (!currentResourceVersion.equals(resourceVersion)))) {
2204                 if ("create".equals(action) && !resourceVersion.equals("")) {
2205                     errorDetail = "resource-version passed for " + action + " of " + uri;
2206                     aaiExceptionCode = "AAI_6135";
2207                 } else if (resourceVersion.equals("")) {
2208                     errorDetail = "resource-version not passed for " + action + " of " + uri;
2209                     aaiExceptionCode = "AAI_6130";
2210                 } else {
2211                     errorDetail = "resource-version MISMATCH for " + action + " of " + uri;
2212                     aaiExceptionCode = "AAI_6131";
2213                 }
2214
2215                 throw new AAIException(aaiExceptionCode, errorDetail);
2216
2217             }
2218         }
2219         return true;
2220     }
2221
2222     /**
2223      * Verify resource version for delete.
2224      *
2225      * @param currentResourceVersion the current resource version
2226      * @param resourceVersion the resource version
2227      * @return true, if successful or false if there is a mismatch
2228      */
2229     private boolean verifyResourceVersionForDelete(String currentResourceVersion, String resourceVersion) {
2230
2231         boolean isDeleteResourceVersionOk = true;
2232         String resourceVersionDisabledUuid = AAIConfig.get(AAIConstants.AAI_RESVERSION_DISABLED_UUID,
2233                 AAIConstants.AAI_RESVERSION_DISABLED_UUID_DEFAULT);
2234
2235         if ((!currentResourceVersion.equals(resourceVersion))
2236                 && (!resourceVersion.equals(resourceVersionDisabledUuid))) {
2237             isDeleteResourceVersionOk = false;
2238         }
2239         return isDeleteResourceVersionOk;
2240     }
2241
2242     /**
2243      * Convert from camel case.
2244      *
2245      * @param name the name
2246      * @return the string
2247      */
2248     private String convertFromCamelCase(String name) {
2249         String result = "";
2250         result = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_HYPHEN, name);
2251
2252         NamingExceptions exceptions = NamingExceptions.getInstance();
2253         result = exceptions.getDBName(result);
2254
2255         return result;
2256     }
2257
2258     private boolean canModify(Introspector obj, String propName, String requestContext) {
2259         final String readOnly = obj.getPropertyMetadata(propName).get(PropertyMetadata.READ_ONLY);
2260         if (readOnly != null) {
2261             final String[] items = readOnly.split(",");
2262             for (String item : items) {
2263                 if (requestContext.equals(item)) {
2264                     return false;
2265                 }
2266             }
2267         }
2268         return true;
2269     }
2270
2271     private void executePreSideEffects(Introspector obj, Vertex self) throws AAIException {
2272
2273         SideEffectRunner runner = new SideEffectRunner.Builder(this.engine, this).addSideEffect(DataCopy.class)
2274                 .addSideEffect(PrivateEdge.class).addSideEffect(OwnerCheck.class).build();
2275
2276         runner.execute(obj, self);
2277     }
2278
2279     private void executePostSideEffects(Introspector obj, Vertex self) throws AAIException {
2280
2281         SideEffectRunner runner =
2282                 new SideEffectRunner.Builder(this.engine, this).addSideEffect(DataLinkWriter.class).build();
2283
2284         runner.execute(obj, self);
2285     }
2286
2287     private void enrichData(Introspector obj, Vertex self) throws AAIException {
2288
2289         SideEffectRunner runner =
2290                 new SideEffectRunner.Builder(this.engine, this).addSideEffect(DataLinkReader.class).build();
2291
2292         runner.execute(obj, self);
2293     }
2294
2295     public double getDBTimeMsecs() {
2296         return (dbTimeMsecs);
2297     }
2298
2299     /**
2300      * Db to object With Filters
2301      * This is for a one-time run with Tenant Isloation to only filter relationships
2302      *
2303      * @param obj the obj
2304      * @param v the vertex from the graph
2305      * @param depth the depth
2306      * @param nodeOnly specify if to exclude relationships or not
2307      * @param filterCousinNodes
2308      * @return the introspector
2309      * @throws AAIException the AAI exception
2310      * @throws IllegalAccessException the illegal access exception
2311      * @throws IllegalArgumentException the illegal argument exception
2312      * @throws InvocationTargetException the invocation target exception
2313      * @throws SecurityException the security exception
2314      * @throws InstantiationException the instantiation exception
2315      * @throws NoSuchMethodException the no such method exception
2316      * @throws UnsupportedEncodingException the unsupported encoding exception
2317      * @throws MalformedURLException the malformed URL exception
2318      * @throws AAIUnknownObjectException
2319      * @throws URISyntaxException
2320      */
2321     public Introspector dbToObjectWithFilters(Introspector obj, Vertex v, Set<Vertex> seen, int depth, boolean nodeOnly,
2322                                               List<String> filterCousinNodes, List<String> filterParentNodes)
2323         throws AAIException, UnsupportedEncodingException {
2324         return dbToObjectWithFilters(obj, v, seen, depth, nodeOnly,
2325         filterCousinNodes, filterParentNodes, false);
2326     }
2327
2328     /**
2329      * Db to object With Filters
2330      * This is for a one-time run with Tenant Isloation to only filter relationships
2331      * TODO: Chnage the original dbToObject to take filter parent/cousins
2332      *
2333      * @param obj the obj
2334      * @param v the vertex from the graph
2335      * @param depth the depth
2336      * @param nodeOnly specify if to exclude relationships or not
2337      * @param filterCousinNodes
2338      * @param isSkipRelatedTo determine to incorporated related-to-property data
2339      * @return the introspector
2340      * @throws AAIException the AAI exception
2341      * @throws IllegalAccessException the illegal access exception
2342      * @throws IllegalArgumentException the illegal argument exception
2343      * @throws InvocationTargetException the invocation target exception
2344      * @throws SecurityException the security exception
2345      * @throws InstantiationException the instantiation exception
2346      * @throws NoSuchMethodException the no such method exception
2347      * @throws UnsupportedEncodingException the unsupported encoding exception
2348      * @throws MalformedURLException the malformed URL exception
2349      * @throws AAIUnknownObjectException
2350      * @throws URISyntaxException
2351      */
2352     // TODO - See if you can merge the 2 dbToObjectWithFilters
2353     public Introspector dbToObjectWithFilters(Introspector obj, Vertex v, Set<Vertex> seen, int depth, boolean nodeOnly,
2354             List<String> filterCousinNodes, List<String> filterParentNodes, boolean isSkipRelatedTo)
2355             throws AAIException, UnsupportedEncodingException {
2356         String cleanUp = FALSE;
2357         if (depth < 0) {
2358             return null;
2359         }
2360         depth--;
2361         seen.add(v);
2362         boolean modified = false;
2363         for (String property : obj.getProperties(PropertyPredicates.isVisible())) {
2364             List<Object> getList = null;
2365
2366             if (!(obj.isComplexType(property) || obj.isListType(property))) {
2367                 this.copySimpleProperty(property, obj, v);
2368                 modified = true;
2369             } else {
2370                 if (obj.isComplexType(property)) {
2371                     /* container case */
2372
2373                     if (!property.equals("relationship-list") && depth >= 0) {
2374                         Introspector argumentObject = obj.newIntrospectorInstanceOfProperty(property);
2375                         Object result = dbToObjectWithFilters(argumentObject, v, seen, depth + 1, nodeOnly,
2376                                 filterCousinNodes, filterParentNodes, isSkipRelatedTo);
2377                         if (result != null) {
2378                             obj.setValue(property, argumentObject.getUnderlyingObject());
2379                             modified = true;
2380                         }
2381                     } else if (property.equals("relationship-list") && !nodeOnly) {
2382                         /* relationships need to be handled correctly */
2383                         Introspector relationshipList = obj.newIntrospectorInstanceOfProperty(property);
2384                         relationshipList =
2385                                 createFilteredRelationshipList(v, relationshipList, cleanUp, filterCousinNodes, isSkipRelatedTo);
2386                         if (relationshipList != null) {
2387                             obj.setValue(property, relationshipList.getUnderlyingObject());
2388                             modified = true;
2389                         }
2390
2391                     }
2392                 } else if (obj.isListType(property)) {
2393
2394                     if (property.equals("any")) {
2395                         continue;
2396                     }
2397                     String genericType = obj.getGenericTypeClass(property).getSimpleName();
2398                     if (obj.isComplexGenericType(property) && depth >= 0) {
2399                         final String childDbName = convertFromCamelCase(genericType);
2400                         String vType = v.<String>property(AAIProperties.NODE_TYPE).orElse(null);
2401                         EdgeRule rule;
2402
2403                         boolean isThisParentRequired =
2404                                 filterParentNodes.parallelStream().anyMatch(childDbName::contains);
2405
2406                         EdgeRuleQuery q = new EdgeRuleQuery.Builder(vType, childDbName).edgeType(EdgeType.TREE).build();
2407
2408                         try {
2409                             rule = edgeRules.getRule(q);
2410                         } catch (EdgeRuleNotFoundException e) {
2411                             throw new NoEdgeRuleFoundException(e);
2412                         } catch (AmbiguousRuleChoiceException e) {
2413                             throw new MultipleEdgeRuleFoundException(e);
2414                         }
2415                         if (!rule.getContains().equals(AAIDirection.NONE.toString()) && isThisParentRequired) {
2416                             Direction ruleDirection = rule.getDirection();
2417                             List<Vertex> verticesList = new ArrayList<>();
2418                             v.vertices(ruleDirection, rule.getLabel()).forEachRemaining(vertex -> {
2419                                 if (vertex.property(AAIProperties.NODE_TYPE).orElse("").equals(childDbName)) {
2420                                     verticesList.add(vertex);
2421                                 }
2422                             });
2423                             if (!verticesList.isEmpty()) {
2424                                 getList = obj.getValue(property);
2425                             }
2426                             int processed = 0;
2427                             for (Vertex childVertex : verticesList) {
2428                                 if (!seen.contains(childVertex)) {
2429                                     Introspector argumentObject = obj.newIntrospectorInstanceOfNestedProperty(property);
2430
2431                                     Object result = dbToObjectWithFilters(argumentObject, childVertex, seen, depth,
2432                                             nodeOnly, filterCousinNodes, filterParentNodes, isSkipRelatedTo);
2433                                     if (result != null) {
2434                                         getList.add(argumentObject.getUnderlyingObject());
2435                                     }
2436
2437                                     processed++;
2438                                 } else {
2439                                     LOGGER.warn("Cycle found while serializing vertex id={}",
2440                                             childVertex.id().toString());
2441                                 }
2442                             }
2443                             if (processed == 0) {
2444                                 // vertices were all seen, reset the list
2445                                 getList = null;
2446                             }
2447                             if (processed > 0) {
2448                                 modified = true;
2449                             }
2450                         }
2451                     } else if (obj.isSimpleGenericType(property)) {
2452                         List<Object> temp = this.engine.getListProperty(v, property);
2453                         if (temp != null) {
2454                             getList = obj.getValue(property);
2455                             getList.addAll(temp);
2456                             modified = true;
2457                         }
2458
2459                     }
2460
2461                 }
2462
2463             }
2464         }
2465
2466         // no changes were made to this obj, discard the instance
2467         if (!modified) {
2468             return null;
2469         }
2470         this.enrichData(obj, v);
2471         return obj;
2472
2473     }
2474
2475     /**
2476      * Creates the relationship list with the filtered node types.
2477      *
2478      * @param v the v
2479      * @param obj the obj
2480      * @param cleanUp the clean up
2481      * @return the object
2482      * @throws IllegalArgumentException the illegal argument exception
2483      * @throws SecurityException the security exception
2484      * @throws UnsupportedEncodingException the unsupported encoding exception
2485      * @throws AAIException the AAI exception
2486      */
2487     private Introspector createFilteredRelationshipList(Vertex v, Introspector obj, String cleanUp,
2488             List<String> filterNodes, boolean isSkipRelatedTo) throws UnsupportedEncodingException, AAIException {
2489         List<Vertex> allCousins = this.engine.getQueryEngine().findCousinVertices(v);
2490
2491         Iterator<Vertex> cousinVertices = allCousins.stream().filter(item -> {
2492             String node = (String) item.property(AAIProperties.NODE_TYPE).orElse("");
2493             return filterNodes.parallelStream().anyMatch(node::contains);
2494         }).iterator();
2495
2496         List<Object> relationshipObjList = obj.getValue(RELATIONSHIP);
2497
2498         List<Vertex> cousins = new ArrayList<>();
2499         cousinVertices.forEachRemaining(cousins::add);
2500         for (Vertex cousin : cousins) {
2501
2502             Introspector relationshipObj = obj.newIntrospectorInstanceOfNestedProperty(RELATIONSHIP);
2503             Object result = processEdgeRelationship(relationshipObj, cousin, cleanUp, null, isSkipRelatedTo);
2504             if (result != null) {
2505                 relationshipObjList.add(result);
2506             }
2507
2508         }
2509
2510         if (relationshipObjList.isEmpty()) {
2511             return null;
2512         } else {
2513             return obj;
2514         }
2515     }
2516
2517     public Set<Vertex> touchStandardVertexPropertiesForEdges() {
2518         this.edgeVertexes.forEach(v -> this.touchStandardVertexProperties(v, false));
2519         return this.edgeVertexes;
2520     }
2521
2522     public void addVertexToEdgeVertexes(Vertex vertex){
2523         this.edgeVertexes.add(vertex);
2524     }
2525
2526     private String urlToUri(String url) {
2527         if (url.startsWith("/")) {
2528             url = url.substring(1);
2529         }
2530
2531         if (url.endsWith("/")) {
2532             url = url.substring(0, url.length() - 1);
2533         }
2534
2535         // TODO - Check if this makes to do for model driven for base uri path
2536         url = url.replaceFirst("[a-z][a-z]*/v\\d+/", "");
2537         if (url.charAt(0) != '/') {
2538             url = '/' + url;
2539         }
2540
2541         return url;
2542     }
2543
2544 }