cceb950bb6cc79a894054f92bf26bb5c9d8e38ce
[aai/aai-common.git] / aai-core / src / main / java / org / openecomp / aai / dbgraphgen / DbSearchWithTags.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * org.openecomp.aai
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
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
21 package org.openecomp.aai.dbgraphgen;
22
23 import java.util.HashMap;
24 import java.util.Iterator;
25 import java.util.LinkedHashSet;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Set;
29
30 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
31 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
32 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
33 import org.apache.tinkerpop.gremlin.process.traversal.step.util.Tree;
34 import org.apache.tinkerpop.gremlin.structure.Element;
35 import org.apache.tinkerpop.gremlin.structure.Vertex;
36
37 import org.openecomp.aai.db.props.AAIProperties;
38 import org.openecomp.aai.exceptions.AAIException;
39 import org.openecomp.aai.introspection.Loader;
40 import org.openecomp.aai.serialization.db.DBSerializer;
41 import org.openecomp.aai.serialization.engines.TransactionalGraphEngine;
42 import org.openecomp.aai.util.AAIConfig;
43 import com.att.eelf.configuration.EELFLogger;
44 import com.att.eelf.configuration.EELFManager;
45
46
47
48 /**
49  * Database-level Search-Utility class that uses edge-tags to help it navigate the graph.   
50  */
51 public class DbSearchWithTags{
52
53         private EELFLogger LOGGER = EELFManager.getInstance().getLogger(DbSearchWithTags.class);
54
55         private TransactionalGraphEngine engine;
56         
57         protected DbSearchWithTags() {
58                 
59         }
60         public DbSearchWithTags(Loader loader, TransactionalGraphEngine engine, DBSerializer serializer) {
61                 this.engine = engine;
62         }
63
64         /**
65          * Run edge tag query.
66          *
67          * @param transId the trans id
68          * @param fromAppId the from app id
69          * @param edgeTag the edge tag
70          * @param searchRootType - root collection point when doing the search
71          * @param displayRootType - if they want more data than would be included using the searchRootType, this
72          *                            lets them specify it.
73          * @param searchDirectionStr the search direction str
74          * @param initialFilterHash the initial filter hash
75          * @param secondaryFilterHash the secondary filter hash
76          * @param pruneLevel   either "none", "searchLevel" or "displayLevel".
77          * @param retNodeType    could be either "all" for the full map, or a single nodeType
78          * @param trimList     list of nodes to "stop" at when collecting data
79          * @return List<resultSet>
80          * @throws AAIException the AAI exception
81          */
82           public Tree<Vertex> runEdgeTagQuery( String transId, String fromAppId,
83                         String edgeTag, 
84                         String searchRootType, 
85                         Map<String,Object> initialFilterHash, 
86                         Map<String,Object> secondaryFilterHash) 
87                             throws AAIException{
88                 
89                 final String tag = edgeTag;
90                 final String reverseTag = edgeTag + "-REV";
91                 //max levels is not used at this time, but may be used again
92                 int maxLevels = 50; // default value
93                 String maxString = AAIConfig.get("aai.edgeTag.proc.max.levels");
94                 if( maxString != null &&  !maxString.equals("") ){
95                         try {
96                                 int maxVal = Integer.parseInt(maxString);
97                                 maxLevels = maxVal;
98                         }
99                         catch ( Exception nfe ){
100                                 // Don't worry, we will leave "maxLevels" set to the default value it was initialized with 
101                         }
102                 }
103           
104                 // First, we need to use the intialFilter plus the edgeTag to identify a set of search-root-nodes
105                 HashMap <String, Vertex> searchRootHash = identifyTopNodeSet( transId, fromAppId,
106                                 edgeTag, searchRootType, initialFilterHash, maxLevels );
107                 
108                 
109                 Set<String> keySet = searchRootHash.keySet();
110                 Iterator<String> itr = keySet.iterator();
111                 String[] arrayOfVertices = new String[keySet.size()];
112                 int i = 0;
113                 while (itr.hasNext()) {
114                         arrayOfVertices[i] = itr.next();
115                         i++;
116                 }
117                 //start from all vertices provided
118                 //repeat checking the out edge for the tag and the in edge for reverse tag
119                 //emit all vertices not already seen and start again
120                 //return a tree structure of the vertices and edges touched by this traversal
121                 Tree<Element> resultTree = this.engine.asAdmin().getReadOnlyTraversalSource().V(arrayOfVertices)
122                                 .emit().repeat(__.union(__.outE().has(tag, true), __.inE().has(reverseTag, true)).otherV()).tree().next();
123                 
124                 //the resulting tree includes the edges because of our query, we'll need to remove them
125                 Tree<Vertex> sanitizedTree = removeUnwantedItems(resultTree);
126
127                 //if we have secondary filters then check each tree returned for matches
128                 if (!secondaryFilterHash.isEmpty()) {
129                         filterOutResultTrees(sanitizedTree, secondaryFilterHash);
130                 }
131                 return sanitizedTree;
132                 
133           }// End of runEdgeTagQuery()
134         
135         /**
136          * Identify top node set.
137          *
138          * @param transId the trans id
139          * @param fromAppId the from app id
140          * @param edgeTag the edge tag
141          * @param topNodeType the top node type
142          * @param initialFilterHash the initial filter hash
143          * @param maxLevels the max levels
144          * @return List<titanVertex>
145          * @throws AAIException the AAI exception
146          */
147           public HashMap<String, Vertex> identifyTopNodeSet( String transId, String fromAppId,
148                           String edgeTag, String topNodeType, Map<String,Object> initialFilterHash, int maxLevels )   
149                                           throws AAIException {
150                   
151                   final String tag = edgeTag;
152                   final String reverseTag = edgeTag + "-REV";
153                   HashMap <String, Vertex> topVertHash = new HashMap <>();
154                   
155                   // Given the filter, we want to select all the nodes of the type the filter tells us that have the
156                   //    property they gave us.
157                   // Then looping through those start points, we will look "up" and then "down" to find the set of target/top nodes.
158                  
159                   if( initialFilterHash == null || initialFilterHash.isEmpty() ){
160                           throw new AAIException("AAI_6118", " initialFilterHash is required for identifyInitialNodeSet() call. \n"); 
161                   }
162
163                   // NOTE: we're expecting the filter to have a format like this: "nodeType.parameterName:parameterValue"
164                   Iterator <?> it = initialFilterHash.entrySet().iterator();
165                   // -- DEBUG -- for now we only deal with ONE initial filter parameter
166                   //     it would be easy enough to deal with multiple parameters if they all
167                   //     applied to the same nodeType.
168                   String propNodeTypeDotName = "";
169                   String initNodeType = "";
170                   String initPropName = "";
171          
172                   String extraChecks = "";
173                   
174                   String propVal = "";
175                   if( it.hasNext() ){
176                           Map.Entry<?,?> propEntry = (Map.Entry<?,?>) it.next();
177                           propNodeTypeDotName = (propEntry.getKey()).toString();
178                           propVal = (propEntry.getValue()).toString();
179                   }
180                   
181                   GraphTraversalSource source = this.engine.asAdmin().getReadOnlyTraversalSource();
182                   GraphTraversal<Vertex, Vertex> g;
183                                   
184                   int periodLoc = propNodeTypeDotName.indexOf(".");
185                   if( periodLoc <= 0 ){
186                           throw new AAIException("AAI_6120", "Bad filter param key passed in: [" + propNodeTypeDotName + "].  Expected format = [nodeName.paramName]\n"); 
187                   }
188                   else {
189                           initNodeType = propNodeTypeDotName.substring(0,periodLoc);
190                           initPropName = propNodeTypeDotName.substring(periodLoc + 1);
191                           
192                           //there used to be logic here that would do something special for generic-vnf.vnf-name and vserver.vserver-name
193                           //it would attempt a search as they sent it, if nothing came back, try as all upper, try as all lower, then fail
194                           //here it checks whether something comes back or not, if not change the case and try again
195                           if( (initNodeType.equals("generic-vnf") && initPropName.equals("vnf-name"))
196                                           || (initNodeType.equals("vserver") && initPropName.equals("vserver-name")) ){
197                                   if (!this.checkKludgeCase(initNodeType, initPropName, propVal)) {
198                                           if (this.checkKludgeCase(initNodeType, initPropName, propVal.toUpperCase())) {
199                                                   propVal = propVal.toUpperCase();
200                                           } else {
201                                                   if (this.checkKludgeCase(initNodeType, initPropName, propVal)) {
202                                                           propVal = propVal.toLowerCase();
203                                                   }
204                                           }
205                                   }
206                           }     
207                           g = source.V().has(AAIProperties.NODE_TYPE, initNodeType).has(initPropName, propVal);
208                           
209                           //search all around bounded by our edge tag for start nodes that match our topNodeType
210                           if (!topNodeType.equals(initNodeType)) {
211                                 
212                                 g.union(__.<Vertex>start().until(__.has(AAIProperties.NODE_TYPE, topNodeType))
213                                                  .repeat(__.union(__.inE().has(reverseTag, true), __.outE().has(tag, true)).otherV()),
214                                                  __.<Vertex>start().until(__.has(AAIProperties.NODE_TYPE, topNodeType))
215                                                  .repeat(__.union(__.inE().has(tag, true), __.outE().has(reverseTag, true)).otherV())).dedup();
216                           }
217                           
218                           List<Vertex> results = g.toList();
219                           
220                           results.forEach(v -> {
221                                   topVertHash.put(v.id().toString(), v);
222                           });
223                   }
224                   if( topVertHash.isEmpty() ){
225                                 // No Vertex was found  - throw a not-found exception
226                                 throw new AAIException("AAI_6114", "No Node of type " + topNodeType + " found for properties: " + initialFilterHash.toString() + extraChecks);
227                   }
228                   else {
229                           return topVertHash;
230                   }
231                   
232           }// End identifyInitialNodeSet()
233           
234           /**
235            * This is a carryover from the previous version.
236            * We may be able to remove this.
237            * 
238            * @param nodeType
239            * @param propName
240            * @param propValue
241            * @return
242            */
243           private boolean checkKludgeCase(String nodeType, String propName, String propValue) {
244                   return this.engine.getQueryBuilder().getVerticesByIndexedProperty(AAIProperties.NODE_TYPE, nodeType).getVerticesByProperty(propName, propValue).hasNext();
245           }
246           
247         /**
248          * This method starts from the top of the tree object and checks each nested tree.
249          * If that tree does not contain a node (or nodes) that match the filterHash, remove it
250          * 
251          * @param tree
252          * @param filterHash
253          * @throws AAIException
254          */
255         private void filterOutResultTrees(Tree<Vertex> tree, Map<String,Object> filterHash) throws AAIException {
256                 Set<Vertex> topLevelKeys = new LinkedHashSet<>(tree.keySet());
257                 for (Vertex topLevel : topLevelKeys) {
258                         if (!this.checkVertexWithFilters(topLevel, filterHash)) {
259                                 if (!filterOutResultTreesHelper(tree.get(topLevel), filterHash)) {
260                                         //if we never found anything to satisfy our filter, remove the entire result tree
261                                         tree.remove(topLevel);
262                                 }
263                         }
264                 }
265         }
266         
267         /**
268          * Checks all vertices of a tree with the provided filterHash
269          * 
270          * @param tree
271          * @param filterHash
272          * @return
273          * @throws AAIException
274          */
275         private boolean filterOutResultTreesHelper(Tree<Vertex> tree, Map<String,Object> filterHash) throws AAIException {
276                 
277                 Set<Vertex> keys = tree.keySet();
278
279                 for (Vertex v : keys) {
280                         if (checkVertexWithFilters(v, filterHash)) {
281                                 return true;
282                         } else {
283                                 if (filterOutResultTreesHelper(tree.get(v), filterHash)) {
284                                         return true;
285                                 }
286                         }
287                 }
288                 
289                 return false;
290         }
291         /**
292          * Checks whether a vertex matches the filterHash provided
293          * 
294          * @param v
295          * @param filterHash
296          * @return
297          * @throws AAIException
298          */
299         private boolean checkVertexWithFilters(Vertex v, Map<String,Object> filterHash) throws AAIException {
300                 Iterator <?> it = filterHash.entrySet().iterator();
301
302                 while( it.hasNext() ){
303                           Map.Entry<?,?> filtEntry = (Map.Entry<?,?>) it.next();
304                           String propNodeTypeDotName = (filtEntry.getKey()).toString();
305                           String value = (filtEntry.getValue()).toString();
306                           
307                           int periodLoc = propNodeTypeDotName.indexOf(".");
308                           if( periodLoc <= 0 ){
309                                   throw new AAIException("AAI_6120", "Bad filter param key passed in: [" + propNodeTypeDotName + "].  Expected format = [nodeName.paramName]\n"); 
310                           }
311                           else {
312                                   String nodeType = propNodeTypeDotName.substring(0,periodLoc);
313                                   String propertyName = propNodeTypeDotName.substring(periodLoc + 1);
314                                   String nt = v.<String>property("aai-node-type").orElse(null);
315                                   if( nt.equals( nodeType ) ){
316                                           if( propertyName.equals("vertex-id") ){
317                                                   // vertex-id can't be gotten the same way as other properties
318                                                   String thisVtxId = v.id().toString();
319                                                   if( thisVtxId.equals(value) ){
320                                                           return true;
321                                                   }
322                                           }
323                                           else {
324                                                   Object thisValObj = v.property(propertyName).orElse(null);
325                                                   if( thisValObj != null ){
326                                                           String thisVal = thisValObj.toString();
327                                                           if( thisVal.equals(value) ){
328                                                                   return true;
329                                                           }
330                                                   }
331                                           }
332                                   }                               
333                           }
334                   }
335                 
336                  return false;
337         }
338         
339         /**
340          * Removes every other tree from the originalTree provided.
341          * It is designed to specifically handle removing unwanted edges from the originalTree
342          * @param originalTree
343          * @return
344          */
345         private Tree<Vertex> removeUnwantedItems(Tree<Element> originalTree) {
346                 
347                 Tree<Vertex> newTree = new Tree<>();
348                 Set<Element> keys = originalTree.keySet();
349                 for (Element element : keys) {
350                         newTree.put((Vertex)element, removeUnwantedItemsHelper(originalTree.get(element).getTreesAtDepth(2)));
351                 }
352
353                 return newTree;
354                 
355                 
356         }
357         
358         private Tree<Vertex> removeUnwantedItemsHelper(List<Tree<Element>> originalTrees) {
359                 Tree<Vertex> newTree = new Tree<>();
360                 for (Tree<Element> tree : originalTrees) {
361                         newTree.addTree(removeUnwantedItems(tree));
362                 }
363                 
364                 return newTree;
365         }
366 }