2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2017 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
11 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
21 package org.openecomp.aai.serialization.db;
24 import java.io.UnsupportedEncodingException;
25 import java.lang.reflect.Array;
26 import java.lang.reflect.InvocationTargetException;
27 import java.net.MalformedURLException;
29 import java.net.URISyntaxException;
30 import java.util.ArrayList;
31 import java.util.HashSet;
32 import java.util.Iterator;
33 import java.util.LinkedHashSet;
34 import java.util.List;
36 import java.util.Optional;
38 import java.util.concurrent.Callable;
39 import java.util.concurrent.ExecutionException;
40 import java.util.concurrent.ExecutorService;
41 import java.util.concurrent.Future;
43 import javax.ws.rs.core.UriBuilder;
45 import org.apache.commons.collections.IteratorUtils;
46 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
47 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
48 import org.apache.tinkerpop.gremlin.process.traversal.step.util.Tree;
49 import org.apache.tinkerpop.gremlin.structure.Direction;
50 import org.apache.tinkerpop.gremlin.structure.Edge;
51 import org.apache.tinkerpop.gremlin.structure.Element;
52 import org.apache.tinkerpop.gremlin.structure.Vertex;
53 import org.apache.tinkerpop.gremlin.structure.VertexProperty;
54 import org.javatuples.Pair;
55 import org.openecomp.aai.db.props.AAIProperties;
56 import org.openecomp.aai.exceptions.AAIException;
57 import org.openecomp.aai.introspection.Introspector;
58 import org.openecomp.aai.introspection.IntrospectorFactory;
59 import org.openecomp.aai.introspection.Loader;
60 import org.openecomp.aai.introspection.LoaderFactory;
61 import org.openecomp.aai.introspection.ModelType;
62 import org.openecomp.aai.introspection.PropertyPredicates;
63 import org.openecomp.aai.introspection.Version;
64 import org.openecomp.aai.introspection.exceptions.AAIUnknownObjectException;
65 import org.openecomp.aai.introspection.sideeffect.DataCopy;
66 import org.openecomp.aai.introspection.sideeffect.DataLinkReader;
67 import org.openecomp.aai.introspection.sideeffect.DataLinkWriter;
68 import org.openecomp.aai.introspection.sideeffect.SideEffectRunner;
69 import org.openecomp.aai.logging.ErrorLogHelper;
70 import org.openecomp.aai.parsers.query.QueryParser;
71 import org.openecomp.aai.parsers.uri.URIParser;
72 import org.openecomp.aai.parsers.uri.URIToRelationshipObject;
73 import org.openecomp.aai.query.builder.QueryBuilder;
74 import org.openecomp.aai.schema.enums.ObjectMetadata;
75 import org.openecomp.aai.schema.enums.PropertyMetadata;
76 import org.openecomp.aai.serialization.db.exceptions.NoEdgeRuleFoundException;
77 import org.openecomp.aai.serialization.engines.TransactionalGraphEngine;
78 import org.openecomp.aai.serialization.tinkerpop.TreeBackedVertex;
79 import org.openecomp.aai.util.AAIApiServerURLBase;
80 import org.openecomp.aai.util.AAIConfig;
81 import org.openecomp.aai.util.AAIConstants;
82 import org.openecomp.aai.workarounds.NamingExceptions;
84 import com.att.eelf.configuration.EELFLogger;
85 import com.att.eelf.configuration.EELFManager;
86 import com.google.common.base.CaseFormat;
87 import com.thinkaurelius.titan.core.SchemaViolationException;
89 public class DBSerializer {
91 private static final EELFLogger LOGGER = EELFManager.getInstance().getLogger(DBSerializer.class);
93 private final TransactionalGraphEngine engine;
94 private final String sourceOfTruth;
95 private final ModelType introspectionType;
96 private final Version version;
97 private final Loader latestLoader;
98 private final EdgeRules edgeRules = EdgeRules.getInstance();
99 private final Loader loader;
100 private final String baseURL;
102 * Instantiates a new DB serializer.
104 * @param version the version
105 * @param engine the engine
107 * @param introspectionType the introspection type
108 * @param sourceOfTruth the source of truth
109 * @param llBuilder the ll builder
110 * @throws AAIException
112 public DBSerializer(Version version, TransactionalGraphEngine engine, ModelType introspectionType, String sourceOfTruth) throws AAIException {
113 this.engine = engine;
114 this.sourceOfTruth = sourceOfTruth;
115 this.introspectionType = introspectionType;
116 this.latestLoader = LoaderFactory.createLoaderForVersion(introspectionType, AAIProperties.LATEST);
117 this.version = version;
118 this.loader = LoaderFactory.createLoaderForVersion(introspectionType, version);
119 this.baseURL = AAIApiServerURLBase.get(version);
123 * Touch standard vertex properties.
126 * @param isNewVertex the is new vertex
129 * to be defined and expanded later
131 public void touchStandardVertexProperties(Vertex v, boolean isNewVertex) {
133 long unixTimeNow = System.currentTimeMillis();
134 String timeNowInSec = "" + unixTimeNow;
136 v.property(AAIProperties.SOURCE_OF_TRUTH, this.sourceOfTruth);
137 v.property(AAIProperties.CREATED_TS, timeNowInSec);
139 v.property(AAIProperties.RESOURCE_VERSION, timeNowInSec );
140 v.property(AAIProperties.LAST_MOD_TS, timeNowInSec);
141 v.property(AAIProperties.LAST_MOD_SOURCE_OF_TRUTH, this.sourceOfTruth);
145 private void touchStandardVertexProperties(String nodeType, Vertex v, boolean isNewVertex) {
147 v.property(AAIProperties.NODE_TYPE, nodeType);
148 touchStandardVertexProperties(v, isNewVertex);
155 * Creates the new vertex.
157 * @param wrappedObject the wrapped object
159 * @throws UnsupportedEncodingException the unsupported encoding exception
160 * @throws AAIException the AAI exception
162 public Vertex createNewVertex(Introspector wrappedObject) {
165 Vertex v = this.engine.tx().addVertex();
166 touchStandardVertexProperties(wrappedObject.getDbName(), v, true);
174 * @param className the class name
178 * Removes the classpath from a class name
180 public String trimClassName (String className) {
181 String returnValue = "";
183 if (className.lastIndexOf('.') == -1) {
186 returnValue = className.substring(className.lastIndexOf('.') + 1, className.length());
196 * @param uriQuery the uri query
197 * @param identifier the identifier
198 * @throws SecurityException the security exception
199 * @throws IllegalAccessException the illegal access exception
200 * @throws IllegalArgumentException the illegal argument exception
201 * @throws InvocationTargetException the invocation target exception
202 * @throws InstantiationException the instantiation exception
203 * @throws InterruptedException the interrupted exception
204 * @throws NoSuchMethodException the no such method exception
205 * @throws AAIException the AAI exception
206 * @throws UnsupportedEncodingException the unsupported encoding exception
207 * @throws AAIUnknownObjectException
209 public void serializeToDb(Introspector obj, Vertex v, QueryParser uriQuery, String identifier, String requestContext) throws AAIException, UnsupportedEncodingException {
212 if (uriQuery.isDependent()) {
213 //try to find the parent
214 List<Vertex> vertices = uriQuery.getQueryBuilder().getParentQuery().toList();
215 if (!vertices.isEmpty()) {
216 Vertex parent = vertices.get(0);
217 this.reflectDependentVertex(parent, v, obj, requestContext);
219 throw new AAIException("AAI_6114", "No parent Node of type " + uriQuery.getParentResultType() + " for " + identifier);
222 serializeSingleVertex(v, obj, requestContext);
225 } catch (SchemaViolationException e) {
226 throw new AAIException("AAI_6117", e);
231 public void serializeSingleVertex(Vertex v, Introspector obj, String requestContext) throws UnsupportedEncodingException, AAIException {
233 boolean isTopLevel = obj.isTopLevel();
235 v.property(AAIProperties.AAI_URI, obj.getURI());
237 processObject(obj, v, requestContext);
239 URI uri = this.getURIForVertex(v);
240 URIParser parser = new URIParser(this.loader, uri);
241 if (parser.validate()) {
242 VertexProperty<String> uriProp = v.property(AAIProperties.AAI_URI);
243 if (!uriProp.isPresent() || uriProp.isPresent() && !uriProp.value().equals(uri.toString())) {
244 v.property(AAIProperties.AAI_URI, uri.toString());
248 } catch (SchemaViolationException e) {
249 throw new AAIException("AAI_6117", e);
256 * @param <T> the generic type
260 * @throws IllegalAccessException the illegal access exception
261 * @throws IllegalArgumentException the illegal argument exception
262 * @throws InvocationTargetException the invocation target exception
263 * @throws InstantiationException the instantiation exception
264 * @throws NoSuchMethodException the no such method exception
265 * @throws SecurityException the security exception
266 * @throws AAIException the AAI exception
267 * @throws UnsupportedEncodingException the unsupported encoding exception
268 * @throws AAIUnknownObjectException
271 * Helper method for reflectToDb
272 * Handles all the property setting
274 private <T> List<Vertex> processObject (Introspector obj, Vertex v, String requestContext) throws UnsupportedEncodingException, AAIException {
275 Set<String> properties = new LinkedHashSet<>(obj.getProperties());
276 properties.remove(AAIProperties.RESOURCE_VERSION);
277 List<Vertex> dependentVertexes = new ArrayList<>();
278 List<Vertex> processedVertexes = new ArrayList<>();
279 boolean isComplexType = false;
280 boolean isListType = false;
281 if (!obj.isContainer()) {
282 this.touchStandardVertexProperties(obj.getDbName(), v, false);
284 this.executePreSideEffects(obj, v);
285 for (String property : properties) {
287 final String propertyType;
288 propertyType = obj.getType(property);
289 isComplexType = obj.isComplexType(property);
290 isListType = obj.isListType(property);
291 value = obj.getValue(property);
293 if (!(isComplexType || isListType)) {
294 boolean canModify = this.canModify(obj, property, requestContext);
297 final Map<PropertyMetadata, String> metadata = obj.getPropertyMetadata(property);
298 String dbProperty = property;
299 if (metadata.containsKey(PropertyMetadata.DB_ALIAS)) {
300 dbProperty = metadata.get(PropertyMetadata.DB_ALIAS);
302 if (metadata.containsKey(PropertyMetadata.DATA_LINK)) {
303 //data linked properties are ephemeral
304 //they are populated dynamically on GETs
308 if (!value.equals(v.property(dbProperty).orElse(null))) {
309 if (propertyType.toLowerCase().contains(".long")) {
310 v.property(dbProperty, new Integer(((Long)value).toString()));
312 v.property(dbProperty, value);
316 v.property(dbProperty).remove();
319 } else if (isListType) {
320 List<Object> list = (List<Object>)value;
321 if (obj.isComplexGenericType(property)) {
323 for (Object o : list) {
324 Introspector child = IntrospectorFactory.newInstance(this.introspectionType, o);
325 child.setURIChain(obj.getURI());
326 processedVertexes.add(reflectDependentVertex(v, child, requestContext));
331 engine.setListProperty(v, property, list);
334 //method.getReturnType() is not 'simple' then create a vertex and edge recursively returning an edge back to this method
335 if (value != null) { //effectively ignore complex properties not included in the object we're processing
336 if (value.getClass().isArray()) {
338 int length = Array.getLength(value);
339 for (int i = 0; i < length; i ++) {
340 Object arrayElement = Array.get(value, i);
341 Introspector child = IntrospectorFactory.newInstance(this.introspectionType, arrayElement);
342 child.setURIChain(obj.getURI());
343 processedVertexes.add(reflectDependentVertex(v, child, requestContext));
346 } else if (!property.equals("relationship-list")) {
348 Introspector introspector = IntrospectorFactory.newInstance(this.introspectionType, value);
349 if (introspector.isContainer()) {
350 dependentVertexes.addAll(this.engine.getQueryEngine().findChildrenOfType(v, introspector.getChildDBName()));
351 introspector.setURIChain(obj.getURI());
353 processedVertexes.addAll(processObject(introspector, v, requestContext));
356 dependentVertexes.addAll(this.engine.getQueryEngine().findChildrenOfType(v, introspector.getDbName()));
357 processedVertexes.add(reflectDependentVertex(v, introspector, requestContext));
360 } else if (property.equals("relationship-list")) {
361 handleRelationships(obj, v);
366 this.writeThroughDefaults(v, obj);
367 /* handle those vertexes not touched */
368 for (Vertex toBeRemoved : processedVertexes) {
369 dependentVertexes.remove(toBeRemoved);
371 this.deleteItemsWithTraversal(dependentVertexes);
373 this.executePostSideEffects(obj, v);
374 return processedVertexes;
378 * Handle relationships.
381 * @param vertex the vertex
382 * @throws SecurityException the security exception
383 * @throws IllegalAccessException the illegal access exception
384 * @throws IllegalArgumentException the illegal argument exception
385 * @throws InvocationTargetException the invocation target exception
386 * @throws UnsupportedEncodingException the unsupported encoding exception
387 * @throws AAIException the AAI exception
390 * Handles the explicit relationships defined for an obj
392 private void handleRelationships(Introspector obj, Vertex vertex) throws UnsupportedEncodingException, AAIException {
396 Introspector wrappedRl = obj.getWrappedValue("relationship-list");
397 processRelationshipList(wrappedRl, vertex);
404 * Process relationship list.
406 * @param wrapped the wrapped
408 * @throws UnsupportedEncodingException the unsupported encoding exception
409 * @throws AAIException the AAI exception
411 private void processRelationshipList(Introspector wrapped, Vertex v) throws UnsupportedEncodingException, AAIException {
413 List<Object> relationships = (List<Object>)wrapped.getValue("relationship");
415 List<Pair<Vertex, Vertex>> addEdges = new ArrayList<>();
416 List<Edge> existingEdges = this.engine.getQueryEngine().findEdgesForVersion(v, wrapped.getLoader());
418 for (Object relationship : relationships) {
420 Vertex cousinVertex = null;
421 Introspector wrappedRel = IntrospectorFactory.newInstance(this.introspectionType, relationship);
422 QueryParser parser = engine.getQueryBuilder().createQueryFromRelationship(wrappedRel);
424 List<Vertex> results = parser.getQueryBuilder().toList();
425 if (results.isEmpty()) {
426 final AAIException ex = new AAIException("AAI_6129", "Node of type " + parser.getResultType() + ". Could not find object at: " + parser.getUri());
427 ex.getTemplateVars().add(parser.getResultType());
428 ex.getTemplateVars().add(parser.getUri().toString());
431 //still an issue if there's more than one
432 cousinVertex = results.get(0);
435 if (cousinVertex != null) {
437 e = this.getEdgeBetween(EdgeType.COUSIN, v, cousinVertex);
440 addEdges.add(new Pair<>(v, cousinVertex));
442 existingEdges.remove(e);
444 } catch (NoEdgeRuleFoundException e1) {
445 throw new AAIException("AAI_6145");
450 for (Edge edge : existingEdges) {
453 for (Pair<Vertex, Vertex> pair : addEdges) {
455 edgeRules.addEdge(this.engine.asAdmin().getTraversalSource(), pair.getValue0(), pair.getValue1());
456 } catch (NoEdgeRuleFoundException e) {
457 throw new AAIException("AAI_6129", e);
464 * Write through defaults.
468 * @throws AAIUnknownObjectException
470 private void writeThroughDefaults(Vertex v, Introspector obj) throws AAIUnknownObjectException {
471 Introspector latest = this.latestLoader.introspectorFromName(obj.getName());
472 if (latest != null) {
473 Set<String> required = latest.getRequiredProperties();
475 for (String field : required) {
476 String defaultValue = null;
477 Object vertexProp = null;
478 defaultValue = latest.getPropertyMetadata(field).get(PropertyMetadata.DEFAULT_VALUE);
479 if (defaultValue != null) {
480 vertexProp = v.<Object>property(field).orElse(null);
481 if (vertexProp == null) {
482 v.property(field, defaultValue);
492 * Reflect dependent vertex.
495 * @param dependentObj the dependent obj
497 * @throws IllegalAccessException the illegal access exception
498 * @throws IllegalArgumentException the illegal argument exception
499 * @throws InvocationTargetException the invocation target exception
500 * @throws InstantiationException the instantiation exception
501 * @throws NoSuchMethodException the no such method exception
502 * @throws SecurityException the security exception
503 * @throws AAIException the AAI exception
504 * @throws UnsupportedEncodingException the unsupported encoding exception
505 * @throws AAIUnknownObjectException
507 private Vertex reflectDependentVertex(Vertex v, Introspector dependentObj, String requestContext) throws AAIException, UnsupportedEncodingException {
509 //QueryParser p = this.engine.getQueryBuilder().createQueryFromURI(obj.getURI());
510 //List<Vertex> items = p.getQuery().toList();
511 QueryBuilder query = this.engine.getQueryBuilder(v);
512 query.createEdgeTraversal(EdgeType.TREE, v, dependentObj);
513 query.createKeyQuery(dependentObj);
515 List<Vertex> items = query.toList();
517 Vertex dependentVertex = null;
518 if (items.size() == 1) {
519 dependentVertex = items.get(0);
520 this.verifyResourceVersion("update", dependentObj.getDbName(), dependentVertex.<String>property(AAIProperties.RESOURCE_VERSION).orElse(null), (String)dependentObj.getValue(AAIProperties.RESOURCE_VERSION), (String)dependentObj.getURI());
522 this.verifyResourceVersion("create", dependentObj.getDbName(), "", (String)dependentObj.getValue(AAIProperties.RESOURCE_VERSION), (String)dependentObj.getURI());
523 dependentVertex = createNewVertex(dependentObj);
526 return reflectDependentVertex(v, dependentVertex, dependentObj, requestContext);
531 * Reflect dependent vertex.
533 * @param parent the parent
534 * @param child the child
537 * @throws IllegalAccessException the illegal access exception
538 * @throws IllegalArgumentException the illegal argument exception
539 * @throws InvocationTargetException the invocation target exception
540 * @throws InstantiationException the instantiation exception
541 * @throws NoSuchMethodException the no such method exception
542 * @throws SecurityException the security exception
543 * @throws AAIException the AAI exception
544 * @throws UnsupportedEncodingException the unsupported encoding exception
545 * @throws AAIUnknownObjectException
547 private Vertex reflectDependentVertex(Vertex parent, Vertex child, Introspector obj, String requestContext) throws AAIException, UnsupportedEncodingException {
549 String parentUri = parent.<String>property(AAIProperties.AAI_URI).orElse(null);
550 if (parentUri != null) {
553 child.property(AAIProperties.AAI_URI, parentUri + uri);
555 processObject(obj, child, requestContext);
558 e = this.getEdgeBetween(EdgeType.TREE, parent, child);
560 String canBeLinked = obj.getMetadata(ObjectMetadata.CAN_BE_LINKED);
561 if (canBeLinked != null && canBeLinked.equals("true")) {
562 boolean isFirst = !this.engine.getQueryBuilder(parent).createEdgeTraversal(EdgeType.TREE, parent, obj).hasNext();
564 child.property(AAIProperties.LINKED, true);
567 edgeRules.addTreeEdge(this.engine.asAdmin().getTraversalSource(), parent, child);
576 * @param vertices the vertices
578 * @param depth the depth
579 * @param cleanUp the clean up
580 * @return the introspector
581 * @throws AAIException the AAI exception
582 * @throws IllegalAccessException the illegal access exception
583 * @throws IllegalArgumentException the illegal argument exception
584 * @throws InvocationTargetException the invocation target exception
585 * @throws SecurityException the security exception
586 * @throws InstantiationException the instantiation exception
587 * @throws NoSuchMethodException the no such method exception
588 * @throws UnsupportedEncodingException the unsupported encoding exception
589 * @throws MalformedURLException the malformed URL exception
590 * @throws AAIUnknownObjectException
591 * @throws URISyntaxException
593 public Introspector dbToObject(List<Vertex> vertices, final Introspector obj, int depth, boolean nodeOnly, String cleanUp) throws UnsupportedEncodingException, AAIException {
595 final int internalDepth;
596 if (depth == Integer.MAX_VALUE) {
597 internalDepth = depth--;
599 internalDepth = depth;
601 if (vertices.size() > 1 && !obj.isContainer()) {
602 throw new AAIException("AAI_6136", "query object mismatch: this object cannot hold multiple items." + obj.getDbName());
603 } else if (obj.isContainer()) {
605 String listProperty = null;
606 for (String property : obj.getProperties()) {
607 if (obj.isListType(property) && obj.isComplexGenericType(property)) {
608 listProperty = property;
612 final String propertyName = listProperty;
613 getList = (List)obj.getValue(listProperty);
615 /* This is an experimental multithreading experiment
618 ExecutorService pool = GetAllPool.getInstance().getPool();
620 List<Future<Object>> futures = new ArrayList<>();
621 for (Vertex v : vertices) {
622 Callable<Object> task = () -> {
623 Set<Vertex> seen = new HashSet<>();
624 Introspector childObject = obj.newIntrospectorInstanceOfNestedProperty(propertyName);
625 Tree<Element> tree = this.engine.getQueryEngine().findSubGraph(v, internalDepth, nodeOnly);
626 TreeBackedVertex treeVertex = new TreeBackedVertex(v, tree);
627 dbToObject(childObject, treeVertex, seen, internalDepth, nodeOnly, cleanUp);
628 return childObject.getUnderlyingObject();
629 //getList.add(childObject.getUnderlyingObject());
631 futures.add(pool.submit(task));
634 for (Future<Object> future : futures) {
636 getList.add(future.get());
637 } catch (ExecutionException e) {
638 throw new AAIException("AAI_4000", e);
639 } catch (InterruptedException e) {
640 throw new AAIException("AAI_4000", e);
643 } else if (vertices.size() == 1) {
644 Set<Vertex> seen = new HashSet<>();
645 Tree<Element> tree = this.engine.getQueryEngine().findSubGraph(vertices.get(0), depth, nodeOnly);
646 TreeBackedVertex treeVertex = new TreeBackedVertex(vertices.get(0), tree);
647 dbToObject(obj, treeVertex, seen, depth, nodeOnly, cleanUp);
661 * @param seen the seen
662 * @param depth the depth
663 * @param cleanUp the clean up
664 * @return the introspector
665 * @throws IllegalAccessException the illegal access exception
666 * @throws IllegalArgumentException the illegal argument exception
667 * @throws InvocationTargetException the invocation target exception
668 * @throws SecurityException the security exception
669 * @throws InstantiationException the instantiation exception
670 * @throws NoSuchMethodException the no such method exception
671 * @throws UnsupportedEncodingException the unsupported encoding exception
672 * @throws AAIException the AAI exception
673 * @throws MalformedURLException the malformed URL exception
674 * @throws AAIUnknownObjectException
675 * @throws URISyntaxException
677 private Introspector dbToObject(Introspector obj, Vertex v, Set<Vertex> seen, int depth, boolean nodeOnly, String cleanUp) throws AAIException, UnsupportedEncodingException {
685 boolean modified = false;
686 for (String property : obj.getProperties(PropertyPredicates.isVisible())) {
687 List<Object> getList = null;
688 Vertex[] vertices = null;
690 if (!(obj.isComplexType(property) || obj.isListType(property))) {
691 this.copySimpleProperty(property, obj, v);
694 if (obj.isComplexType(property)) {
697 if (!property.equals("relationship-list") && depth >= 0) {
698 Introspector argumentObject = obj.newIntrospectorInstanceOfProperty(property);
699 Object result = dbToObject(argumentObject, v, seen, depth+1, nodeOnly, cleanUp);
700 if (result != null) {
701 obj.setValue(property, argumentObject.getUnderlyingObject());
704 } else if (property.equals("relationship-list") && !nodeOnly){
705 /* relationships need to be handled correctly */
706 Introspector relationshipList = obj.newIntrospectorInstanceOfProperty(property);
707 relationshipList = createRelationshipList(v, relationshipList, cleanUp);
708 if (relationshipList != null) {
710 obj.setValue(property, relationshipList.getUnderlyingObject());
715 } else if (obj.isListType(property)) {
717 if (property.equals("any")) {
720 String genericType = obj.getGenericTypeClass(property).getSimpleName();
721 if (obj.isComplexGenericType(property) && depth >= 0) {
722 final String childDbName = convertFromCamelCase(genericType);
723 String vType = v.<String>property(AAIProperties.NODE_TYPE).orElse(null);
726 rule = edgeRules.getEdgeRule(EdgeType.TREE, vType, childDbName);
727 if (rule.getIsParent().equals("true") || rule.getIsParent().equals("reverse")) {
728 //vertices = this.queryEngine.findRelatedVertices(v, Direction.OUT, rule.getLabel(), childDbName);
729 Direction ruleDirection = rule.getDirection();
730 Iterator<Vertex> itr = v.vertices(ruleDirection, rule.getLabel());
731 List<Vertex> verticesList = (List<Vertex>)IteratorUtils.toList(itr);
732 itr = verticesList.stream().filter(item -> {
733 return item.property(AAIProperties.NODE_TYPE).orElse("").equals(childDbName);
736 getList = (List<Object>)obj.getValue(property);
740 while (itr.hasNext()) {
741 Vertex childVertex = itr.next();
742 if (!seen.contains(childVertex)) {
743 Introspector argumentObject = obj.newIntrospectorInstanceOfNestedProperty(property);
745 Object result = dbToObject(argumentObject, childVertex, seen, depth, nodeOnly, cleanUp);
746 if (result != null) {
747 getList.add(argumentObject.getUnderlyingObject());
753 LOGGER.warn("Cycle found while serializing vertex id={}", childVertex.id().toString());
756 if (processed == 0) {
757 //vertices were all seen, reset the list
764 } else if (obj.isSimpleGenericType(property)) {
765 List<Object> temp = this.engine.getListProperty(v, property);
767 getList = (List<Object>)obj.getValue(property);
768 getList.addAll(temp);
779 //no changes were made to this obj, discard the instance
783 this.enrichData(obj, v);
789 public Introspector getVertexProperties(Vertex v) throws AAIException, UnsupportedEncodingException {
790 String nodeType = v.<String>property(AAIProperties.NODE_TYPE).orElse(null);
791 if (nodeType == null) {
792 throw new AAIException("AAI_6143");
794 Introspector obj = this.latestLoader.introspectorFromName(nodeType);
795 Set<Vertex> seen = new HashSet<>();
797 String cleanUp = "false";
798 boolean nodeOnly = true;
799 this.dbToObject(obj, v, seen, depth, nodeOnly, cleanUp);
804 public Introspector getLatestVersionView(Vertex v) throws AAIException, UnsupportedEncodingException {
805 String nodeType = v.<String>property(AAIProperties.NODE_TYPE).orElse(null);
806 if (nodeType == null) {
807 throw new AAIException("AAI_6143");
809 Introspector obj = this.latestLoader.introspectorFromName(nodeType);
810 Set<Vertex> seen = new HashSet<>();
811 int depth = AAIProperties.MAXIMUM_DEPTH;
812 String cleanUp = "false";
813 boolean nodeOnly = false;
814 Tree<Element> tree = this.engine.getQueryEngine().findSubGraph(v, depth, nodeOnly);
815 TreeBackedVertex treeVertex = new TreeBackedVertex(v, tree);
816 this.dbToObject(obj, treeVertex, seen, depth, nodeOnly, cleanUp);
821 * Copy simple property.
823 * @param property the property
826 * @throws InstantiationException the instantiation exception
827 * @throws IllegalAccessException the illegal access exception
828 * @throws IllegalArgumentException the illegal argument exception
829 * @throws InvocationTargetException the invocation target exception
830 * @throws NoSuchMethodException the no such method exception
831 * @throws SecurityException the security exception
833 private void copySimpleProperty(String property, Introspector obj, Vertex v) {
834 final Map<PropertyMetadata, String> metadata = obj.getPropertyMetadata(property);
835 String dbPropertyName = property;
836 if (metadata.containsKey(PropertyMetadata.DB_ALIAS)) {
837 dbPropertyName = metadata.get(PropertyMetadata.DB_ALIAS);
839 final Object temp = v.<Object>property(dbPropertyName).orElse(null);
842 obj.setValue(property, temp);
847 * Simple db to object.
851 * @throws InstantiationException the instantiation exception
852 * @throws IllegalAccessException the illegal access exception
853 * @throws IllegalArgumentException the illegal argument exception
854 * @throws InvocationTargetException the invocation target exception
855 * @throws NoSuchMethodException the no such method exception
856 * @throws SecurityException the security exception
858 private void simpleDbToObject (Introspector obj, Vertex v) {
859 for (String property : obj.getProperties()) {
862 if (!(obj.isComplexType(property) || obj.isListType(property))) {
863 this.copySimpleProperty(property, obj, v);
869 * Creates the relationship list.
873 * @param cleanUp the clean up
875 * @throws InstantiationException the instantiation exception
876 * @throws IllegalAccessException the illegal access exception
877 * @throws IllegalArgumentException the illegal argument exception
878 * @throws InvocationTargetException the invocation target exception
879 * @throws NoSuchMethodException the no such method exception
880 * @throws SecurityException the security exception
881 * @throws UnsupportedEncodingException the unsupported encoding exception
882 * @throws AAIException the AAI exception
883 * @throws MalformedURLException the malformed URL exception
884 * @throws URISyntaxException
886 private Introspector createRelationshipList(Vertex v, Introspector obj, String cleanUp) throws UnsupportedEncodingException, AAIException {
889 List<Vertex> cousins = this.engine.getQueryEngine().findCousinVertices(v);
891 List<Object> relationshipObjList = obj.getValue("relationship");
893 for (Vertex cousin : cousins) {
895 Introspector relationshipObj = obj.newIntrospectorInstanceOfNestedProperty("relationship");
896 Object result = processEdgeRelationship(relationshipObj, cousin, cleanUp);
897 if (result != null) {
898 relationshipObjList.add(result);
903 if (relationshipObjList.isEmpty()) {
911 * Process edge relationship.
913 * @param relationshipObj the relationship obj
914 * @param edge the edge
915 * @param direction the direction
916 * @param cleanUp the clean up
918 * @throws InstantiationException the instantiation exception
919 * @throws IllegalAccessException the illegal access exception
920 * @throws IllegalArgumentException the illegal argument exception
921 * @throws InvocationTargetException the invocation target exception
922 * @throws NoSuchMethodException the no such method exception
923 * @throws SecurityException the security exception
924 * @throws UnsupportedEncodingException the unsupported encoding exception
925 * @throws AAIException the AAI exception
926 * @throws MalformedURLException the malformed URL exception
927 * @throws AAIUnknownObjectException
928 * @throws URISyntaxException
930 private Object processEdgeRelationship(Introspector relationshipObj, Vertex cousin, String cleanUp) throws UnsupportedEncodingException, AAIUnknownObjectException {
933 //we must look up all parents in this case because we need to compute name-properties
934 //we cannot used the cached aaiUri to perform this action currently
935 Optional<Pair<Vertex, List<Introspector>>> tuple = this.getParents(relationshipObj.getLoader(), cousin, "true".equals(cleanUp));
936 //damaged vertex found, ignore
937 if (!tuple.isPresent()) {
940 List<Introspector> list = tuple.get().getValue1();
941 URI uri = this.getURIFromList(list);
943 URIToRelationshipObject uriParser = null;
944 Introspector result = null;
946 uriParser = new URIToRelationshipObject(relationshipObj.getLoader(), uri, this.baseURL);
947 result = uriParser.getResult();
948 } catch (AAIException | URISyntaxException e) {
949 LOGGER.error("Error while processing edge relationship in version " + relationshipObj.getVersion() + " (bad vertex ID=" + tuple.get().getValue0().id().toString() + ": " + e.getMessage(), e);
950 if ("true".equals(cleanUp)) {
951 this.deleteWithTraversal(tuple.get().getValue0());
956 if(list.size() > 0 && this.version.compareTo(Version.v8) >= 0){
957 this.addRelatedToProperty(result, list.get(0));
960 return result.getUnderlyingObject();
964 * Gets the URI for vertex.
967 * @return the URI for vertex
968 * @throws InstantiationException the instantiation exception
969 * @throws IllegalAccessException the illegal access exception
970 * @throws IllegalArgumentException the illegal argument exception
971 * @throws InvocationTargetException the invocation target exception
972 * @throws NoSuchMethodException the no such method exception
973 * @throws SecurityException the security exception
974 * @throws UnsupportedEncodingException the unsupported encoding exception
975 * @throws AAIUnknownObjectException
977 public URI getURIForVertex(Vertex v) throws UnsupportedEncodingException {
979 return getURIForVertex(v, false);
982 public URI getURIForVertex(Vertex v, boolean overwrite) throws UnsupportedEncodingException {
983 URI uri = UriBuilder.fromPath("/unknown-uri").build();
985 String aaiUri = v.<String>property(AAIProperties.AAI_URI).orElse(null);
987 if (aaiUri != null && !overwrite) {
988 uri = UriBuilder.fromPath(aaiUri).build();
990 Optional<Pair<Vertex, List<Introspector>>> tuple = this.getParents(this.loader, v, false);
991 if (tuple.isPresent()) {
992 List<Introspector> list = tuple.get().getValue1();
993 uri = this.getURIFromList(list);
999 * Gets the URI from list.
1001 * @param list the list
1002 * @return the URI from list
1003 * @throws UnsupportedEncodingException the unsupported encoding exception
1005 private URI getURIFromList(List<Introspector> list) throws UnsupportedEncodingException {
1007 StringBuilder sb = new StringBuilder();
1008 for (Introspector i : list) {
1009 sb.insert(0, i.getURI());
1012 uri = sb.toString();
1013 URI result = UriBuilder.fromPath(uri).build();
1020 * @param start the start
1021 * @param removeDamaged the remove damaged
1022 * @return the parents
1023 * @throws InstantiationException the instantiation exception
1024 * @throws IllegalAccessException the illegal access exception
1025 * @throws IllegalArgumentException the illegal argument exception
1026 * @throws InvocationTargetException the invocation target exception
1027 * @throws NoSuchMethodException the no such method exception
1028 * @throws SecurityException the security exception
1029 * @throws AAIUnknownObjectException
1031 private Optional<Pair<Vertex, List<Introspector>>> getParents(Loader loader, Vertex start, boolean removeDamaged) {
1033 List<Vertex> results = this.engine.getQueryEngine().findParents(start);
1034 List<Introspector> objs = new ArrayList<>();
1035 boolean shortCircuit = false;
1036 for (Vertex v : results) {
1037 String nodeType = v.<String>property(AAIProperties.NODE_TYPE).orElse(null);
1038 Introspector obj = null;
1039 //vertex on the other end of this edge is bad
1040 if (nodeType == null) {
1041 //log something here about what was found and that it was removed
1042 ErrorLogHelper.logError("AAI-6143", "Found a damaged parent vertex " + v.id().toString());
1043 if (removeDamaged) {
1044 this.deleteWithTraversal(v);
1046 shortCircuit = true;
1049 obj = loader.introspectorFromName(nodeType);
1050 } catch (AAIUnknownObjectException e) {
1051 LOGGER.info("attempted to create node type " + nodeType + " but we do not understand it for version: " + loader.getVersion());
1057 //can't make a valid path because we don't understand this object
1060 this.simpleDbToObject(obj, v);
1065 //stop processing and don't return anything for this bad vertex
1067 return Optional.empty();
1070 return Optional.of(new Pair<>(results.get(results.size()-1), objs));
1073 * Takes a list of vertices and a list of objs and assumes they are in
1074 * the order you want the URIs to be nested.
1075 * [A,B,C] creates uris [A, AB, ABC]
1078 * @throws UnsupportedEncodingException
1079 * @throws URISyntaxException
1081 public void setCachedURIs(List<Vertex> vertices, List<Introspector> objs) throws UnsupportedEncodingException, URISyntaxException {
1083 String uriChain = "";
1084 for (int i = 0; i < vertices.size(); i++) {
1087 v = vertices.get(i);
1088 aaiUri = v.<String>property(AAIProperties.AAI_URI).orElse(null);
1089 if (aaiUri != null) {
1092 URI uri = UriBuilder.fromPath(objs.get(i).getURI()).build();
1093 aaiUri = uri.toString();
1095 v.property(AAIProperties.AAI_URI, uriChain);
1106 * @throws AAIUnknownObjectException
1107 * @throws IllegalArgumentException elated to property.
1109 * @param relationship the relationship
1110 * @param child the throws IllegalArgumentException, AAIUnknownObjectException child
1112 public void addRelatedToProperty(Introspector relationship, Introspector child) throws AAIUnknownObjectException {
1113 String nameProps = child.getMetadata(ObjectMetadata.NAME_PROPS);
1114 List<Introspector> relatedToProperties = new ArrayList<>();
1116 if (nameProps != null) {
1117 String[] props = nameProps.split(",");
1118 for (String prop : props) {
1119 Introspector relatedTo = relationship.newIntrospectorInstanceOfNestedProperty("related-to-property");
1120 relatedTo.setValue("property-key", child.getDbName() + "." + prop);
1121 relatedTo.setValue("property-value", child.getValue(prop));
1122 relatedToProperties.add(relatedTo);
1126 if (relatedToProperties.size() > 0) {
1127 List relatedToList = (List)relationship.getValue("related-to-property");
1128 for (Introspector obj : relatedToProperties) {
1129 relatedToList.add(obj.getUnderlyingObject());
1138 * @param relationship the relationship
1139 * @param inputVertex the input vertex
1140 * @return true, if successful
1141 * @throws UnsupportedEncodingException the unsupported encoding exception
1142 * @throws AAIException the AAI exception
1144 public boolean createEdge(Introspector relationship, Vertex inputVertex) throws UnsupportedEncodingException, AAIException {
1146 Vertex relatedVertex = null;
1148 QueryParser parser = engine.getQueryBuilder().createQueryFromRelationship(relationship);
1150 List<Vertex> results = parser.getQueryBuilder().toList();
1151 if (results.size() == 0) {
1152 AAIException e = new AAIException("AAI_6129", "Node of type " + parser.getResultType() + ". Could not find object at: " + parser.getUri());
1153 e.getTemplateVars().add(parser.getResultType());
1154 e.getTemplateVars().add(parser.getUri().toString());
1157 //still an issue if there's more than one
1158 relatedVertex = results.get(0);
1161 if (relatedVertex != null) {
1165 e = this.getEdgeBetween(EdgeType.COUSIN, inputVertex, relatedVertex);
1167 edgeRules.addEdge(this.engine.asAdmin().getTraversalSource(), inputVertex, relatedVertex);
1170 //attempted to link two vertexes already linked
1172 } catch (NoEdgeRuleFoundException e1) {
1173 throw new AAIException("AAI_6129", e1);
1185 * Gets the edges between.
1187 * @param aVertex the out vertex
1188 * @param bVertex the in vertex
1189 * @return the edges between
1190 * @throws AAIException the AAI exception
1191 * @throws NoEdgeRuleFoundException
1193 private List<Edge> getEdgesBetween(EdgeType type, Vertex aVertex, Vertex bVertex) throws AAIException, NoEdgeRuleFoundException {
1195 List<Edge> result = new ArrayList<>();
1197 if (bVertex != null) {
1198 EdgeRule rule = edgeRules.getEdgeRule(type, aVertex, bVertex);
1199 GraphTraversal<Vertex, Edge> findEdgesBetween = null;
1200 findEdgesBetween = this.engine.asAdmin().getTraversalSource().V(aVertex).bothE(rule.getLabel()).filter(__.otherV().hasId(bVertex.id()));
1201 List<Edge> edges = findEdgesBetween.toList();
1202 for (Edge edge : edges) {
1203 if (edge.label().equals(rule.getLabel())) {
1214 * Gets the edge between.
1216 * @param aVertex the out vertex
1217 * @param bVertex the in vertex
1218 * @return the edge between
1219 * @throws AAIException the AAI exception
1220 * @throws NoEdgeRuleFoundException
1222 public Edge getEdgeBetween(EdgeType type, Vertex aVertex, Vertex bVertex) throws AAIException {
1226 if (bVertex != null) {
1228 List<Edge> edges = this.getEdgesBetween(type, aVertex, bVertex);
1230 if (edges.size() > 0) {
1231 return edges.get(0);
1243 * @param relationship the relationship
1244 * @param inputVertex the input vertex
1245 * @return true, if successful
1246 * @throws UnsupportedEncodingException the unsupported encoding exception
1247 * @throws AAIException the AAI exception
1249 public boolean deleteEdge(Introspector relationship, Vertex inputVertex) throws UnsupportedEncodingException, AAIException {
1251 Vertex relatedVertex = null;
1253 QueryParser parser = engine.getQueryBuilder().createQueryFromRelationship(relationship);
1255 List<Vertex> results = parser.getQueryBuilder().toList();
1257 if (results.size() == 0) {
1261 relatedVertex = results.get(0);
1264 edge = this.getEdgeBetween(EdgeType.COUSIN, inputVertex, relatedVertex);
1265 } catch (NoEdgeRuleFoundException e) {
1266 throw new AAIException("AAI_6129", e);
1278 * Delete items with traversal.
1280 * @param vertexes the vertexes
1281 * @throws IllegalStateException the illegal state exception
1283 public void deleteItemsWithTraversal(List<Vertex> vertexes) throws IllegalStateException {
1284 for (Vertex v : vertexes) {
1285 LOGGER.debug("About to delete the vertex with id: " + v.id());
1286 deleteWithTraversal(v);
1291 * Delete with traversal.
1293 * @param startVertex the start vertex
1295 public void deleteWithTraversal(Vertex startVertex) {
1297 List<Vertex> results = this.engine.getQueryEngine().findDeletable(startVertex);
1299 for (Vertex v : results) {
1300 LOGGER.warn("Removing vertex " + v.id().toString());
1311 * @param resourceVersion the resource version
1312 * @throws IllegalArgumentException the illegal argument exception
1313 * @throws AAIException the AAI exception
1314 * @throws InterruptedException the interrupted exception
1316 public void delete(Vertex v, String resourceVersion, boolean enableResourceVersion) throws IllegalArgumentException, AAIException {
1318 boolean result = verifyDeleteSemantics(v, resourceVersion, enableResourceVersion);
1321 deleteWithTraversal(v);
1322 } catch (IllegalStateException e) {
1323 throw new AAIException("AAI_6110", e);
1332 * Verify delete semantics.
1334 * @param vertex the vertex
1335 * @param resourceVersion the resource version
1336 * @return true, if successful
1337 * @throws AAIException the AAI exception
1339 private boolean verifyDeleteSemantics(Vertex vertex, String resourceVersion, boolean enableResourceVersion) throws AAIException {
1340 boolean result = false;
1341 String nodeType = "";
1342 DeleteSemantic semantic = null;
1343 List<Edge> inEdges = null;
1344 List<Edge> outEdges = null;
1345 String errorDetail = " unknown delete semantic found";
1346 String aaiExceptionCode = "";
1347 nodeType = vertex.<String>property(AAIProperties.NODE_TYPE).orElse(null);
1348 if (enableResourceVersion && !this.verifyResourceVersion("delete", nodeType, vertex.<String>property(AAIProperties.RESOURCE_VERSION).orElse(null), resourceVersion, nodeType)) {
1350 semantic = edgeRules.getDeleteSemantic(nodeType);
1351 inEdges = (List<Edge>)IteratorUtils.toList(vertex.edges(Direction.IN));
1352 outEdges = (List<Edge>)IteratorUtils.toList(vertex.edges(Direction.OUT));
1353 if (semantic.equals(DeleteSemantic.CASCADE_TO_CHILDREN)) {
1355 } else if (semantic.equals(DeleteSemantic.ERROR_IF_ANY_EDGES)) {
1356 if (inEdges.isEmpty() && outEdges.isEmpty()) {
1359 errorDetail = " Node cannot be deleted because it still has Edges and the " + semantic + " scope was used.\n";
1360 aaiExceptionCode = "AAI_6110";
1362 } else if (semantic.equals(DeleteSemantic.ERROR_IF_ANY_IN_EDGES) || semantic.equals(DeleteSemantic.ERROR_4_IN_EDGES_OR_CASCADE)) {
1364 if (inEdges.isEmpty()) {
1367 //are there any cousin edges?
1369 for (Edge e : inEdges) {
1370 if (e.<Boolean>property(EdgeProperties.out(EdgeProperty.IS_PARENT)).orElse(false)) {
1374 for (Edge e : outEdges) {
1375 if (e.<Boolean>property(EdgeProperties.in(EdgeProperty.IS_PARENT)).orElse(false)) {
1380 result = children == inEdges.size();
1384 errorDetail = " Node cannot be deleted because it still has Edges and the " + semantic + " scope was used.\n";
1385 aaiExceptionCode = "AAI_6110";
1387 } else if (semantic.equals(DeleteSemantic.THIS_NODE_ONLY)) {
1388 if (outEdges.isEmpty() && inEdges.isEmpty()) {
1392 for (Edge edge : outEdges) {
1393 Object property = edge.<Boolean>property(EdgeProperties.out(EdgeProperty.IS_PARENT)).orElse(null);
1394 if (property != null && property.equals(Boolean.TRUE)) {
1395 Vertex v = edge.inVertex();
1396 String vType = v.<String>property(AAIProperties.NODE_TYPE).orElse(null);
1397 errorDetail = " Node cannot be deleted using scope = " + semantic +
1398 " another node (type = " + vType + ") depends on it for uniqueness.";
1399 aaiExceptionCode = "AAI_6110";
1405 for (Edge edge : inEdges) {
1406 Object property = edge.<Boolean>property(EdgeProperties.in(EdgeProperty.IS_PARENT)).orElse(null);
1407 if (property != null && property.equals(Boolean.TRUE)) {
1408 Vertex v = edge.outVertex();
1409 String vType = v.<String>property(AAIProperties.NODE_TYPE).orElse(null);
1410 errorDetail = " Node cannot be deleted using scope = " + semantic +
1411 " another node (type = " + vType + ") depends on it for uniqueness.";
1412 aaiExceptionCode = "AAI_6110";
1422 throw new AAIException(aaiExceptionCode, errorDetail);
1428 * Verify resource version.
1430 * @param action the action
1431 * @param nodeType the node type
1432 * @param currentResourceVersion the current resource version
1433 * @param resourceVersion the resource version
1434 * @param uri the uri
1435 * @return true, if successful
1436 * @throws AAIException the AAI exception
1438 public boolean verifyResourceVersion(String action, String nodeType, String currentResourceVersion, String resourceVersion, String uri) throws AAIException {
1439 String enabled = "";
1440 String errorDetail = "";
1441 String aaiExceptionCode = "";
1442 if (currentResourceVersion == null) {
1443 currentResourceVersion = "";
1446 if (resourceVersion == null) {
1447 resourceVersion = "";
1450 enabled = AAIConfig.get(AAIConstants.AAI_RESVERSION_ENABLEFLAG);
1451 } catch (AAIException e) {
1452 ErrorLogHelper.logException(e);
1454 // We're only doing the resource version checks for v5 and later
1455 if (enabled.equals("true") && this.version.compareTo(Version.v8) > 0) {
1456 if (!currentResourceVersion.equals(resourceVersion)) {
1457 if (action.equals("create") && !resourceVersion.equals("")) {
1458 errorDetail = "resource-version passed for " + action + " of " + uri;
1459 aaiExceptionCode = "AAI_6135";
1460 } else if (resourceVersion.equals("")) {
1461 errorDetail = "resource-version not passed for " + action + " of " + uri;
1462 aaiExceptionCode = "AAI_6130";
1464 errorDetail = "resource-version MISMATCH for " + action + " of " + uri;
1465 aaiExceptionCode = "AAI_6131";
1468 throw new AAIException(aaiExceptionCode, errorDetail);
1476 * Convert from camel case.
1478 * @param name the name
1479 * @return the string
1481 private String convertFromCamelCase (String name) {
1483 result = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_HYPHEN, name);
1485 NamingExceptions exceptions = NamingExceptions.getInstance();
1486 result = exceptions.getDBName(result);
1491 private boolean canModify(Introspector obj, String propName, String requestContext) {
1492 final String readOnly = obj.getPropertyMetadata(propName).get(PropertyMetadata.READ_ONLY);
1493 if (readOnly != null) {
1494 final String[] items = readOnly.split(",");
1495 for (String item : items) {
1496 if (requestContext.equals(item)) {
1504 private void executePreSideEffects(Introspector obj, Vertex self) throws AAIException {
1506 SideEffectRunner runner = new SideEffectRunner
1507 .Builder(this.engine, this).addSideEffect(DataCopy.class).build();
1509 runner.execute(obj, self);
1512 private void executePostSideEffects(Introspector obj, Vertex self) throws AAIException {
1514 SideEffectRunner runner = new SideEffectRunner
1515 .Builder(this.engine, this).addSideEffect(DataLinkWriter.class).build();
1517 runner.execute(obj, self);
1520 private void enrichData(Introspector obj, Vertex self) throws AAIException {
1522 SideEffectRunner runner = new SideEffectRunner
1523 .Builder(this.engine, this).addSideEffect(DataLinkReader.class).build();
1525 runner.execute(obj, self);