View Javadoc
1   /**
2    * Copyright 2018 Internet2
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *   http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package edu.internet2.middleware.grouper.app.graph;
18  
19  import edu.internet2.middleware.grouper.Composite;
20  import edu.internet2.middleware.grouper.Group;
21  import edu.internet2.middleware.grouper.GrouperSession;
22  import edu.internet2.middleware.grouper.Stem;
23  import edu.internet2.middleware.grouper.app.loader.GrouperLoader;
24  import edu.internet2.middleware.grouper.app.loader.ldap.LoaderLdapUtils;
25  import edu.internet2.middleware.grouper.app.visualization.StyleObjectType;
26  import edu.internet2.middleware.grouper.attr.assign.AttributeAssign;
27  import edu.internet2.middleware.grouper.exception.GrouperSessionException;
28  import edu.internet2.middleware.grouper.misc.CompositeType;
29  import edu.internet2.middleware.grouper.misc.GrouperObject;
30  import edu.internet2.middleware.grouper.misc.GrouperObjectSubjectWrapper;
31  import edu.internet2.middleware.grouper.misc.GrouperSessionHandler;
32  
33  import org.apache.commons.lang3.builder.EqualsBuilder;
34  import org.apache.commons.lang3.builder.HashCodeBuilder;
35  
36  import java.util.HashSet;
37  import java.util.LinkedList;
38  import java.util.List;
39  import java.util.Set;
40  
41  /**
42   * A GraphNode is a vertex of the directed graph. It holds a single {link GrouperObject}
43   * entity, and exposes that object's hashCode and equals methods, so that GraphNodes can
44   * be added to Sets without duplicate objects. This class has facilities for keeping track
45   * of ancillary data, such as parent nodes and child nodes, the node's distance from the
46   * start node, member count, and flags for whether the node's parents and children
47   * have been visited. Maintaining this data is optional, and is up to the caller to
48   * set them meaningfully.
49   */
50  public class GraphNode {
51    private GrouperObject grouperObject;
52    private long allMemberCount;
53    private long directMemberCount;
54    private List<String> objectTypeNames;
55  
56    private boolean stem;
57    private boolean group;
58    private boolean subject;
59    private boolean loaderGroup;
60    private boolean simpleLoaderGroup;
61    private boolean provisionerTarget;
62    private boolean intersectGroup;
63    private boolean complementGroup;
64    private boolean startNode;
65    private StyleObjectType styleObjectType;
66  
67    private boolean visitedParents;
68    private boolean visitedChildren;
69    private boolean startedProcessingParentPaths;
70    private boolean startedProcessingChildPaths;
71  
72    private long distanceFromStartNode;
73    private Set<GraphNode> parentNodes;
74    private Set<GraphNode> childNodes;
75  
76  
77    /**
78     * Constructor that also marks the node as the starting node for the graph
79     *
80     * @param grouperObject
81     * @param isStartNode
82     */
83    public GraphNode(GrouperObject grouperObject, boolean isStartNode) {
84      startNode = isStartNode;
85      this.grouperObject = grouperObject;
86  
87      parentNodes = new HashSet<GraphNode>();
88      childNodes = new HashSet<GraphNode>();
89  
90      determineObjectTypes();
91    }
92  
93    /**
94     * General constructor for a GraphNode containing a grouper Object.
95     *
96     * @param grouperObject
97     */
98    public GraphNode(GrouperObject grouperObject) {
99      this(grouperObject, false);
100   }
101 
102   /**
103    * Compares two nodes and considers them equal if they both hold the same GrouperObject.
104    *
105    * @param obj GraphNode to compare
106    * @return
107    */
108   @Override
109   public boolean equals(Object obj) {
110     if (this == obj)
111       return true;
112     if (obj == null)
113       return false;
114     if (getClass() != obj.getClass())
115       return false;
116     GraphNode/../../../../../edu/internet2/middleware/grouper/app/graph/GraphNode.html#GraphNode">GraphNode other = (GraphNode) obj;
117 
118     return new EqualsBuilder()
119       .append(this.grouperObject, other.grouperObject)
120       .isEquals();
121   }
122 
123   /**
124    * Computes the hashCode based on the contained GrouperObject
125    *
126    * @return
127    */
128   @Override
129   public int hashCode() {
130       return new HashCodeBuilder()
131               .append( this.grouperObject)
132               .toHashCode();
133   }
134 
135   private void determineObjectTypes() {
136 
137     // reset the statuses and recalculate
138     this.stem = false;
139     this.group = false;
140     this.subject = false;
141     this.loaderGroup = false;
142     this.simpleLoaderGroup = false;
143     this.intersectGroup = false;
144     this.complementGroup = false;
145     this.provisionerTarget = false;
146 
147     if (grouperObject instanceof Group) {
148       final Group./../../../../../edu/internet2/middleware/grouper/Group.html#Group">Group theGroup = (Group)grouperObject;
149       this.group = true;
150 
151       GrouperSession.internal_callbackRootGrouperSession(new GrouperSessionHandler() {
152         
153         @Override
154         public Object callback(GrouperSession grouperSession) throws GrouperSessionException {
155           // is it a sql loader job?
156           if (RelationGraph.getSqlLoaderAttributeDefName() != null
157               && theGroup.getAttributeDelegate().retrieveAssignment(null, RelationGraph.getSqlLoaderAttributeDefName(), false, false)!=null) {
158             GraphNode.this.loaderGroup = true;
159 
160             if ("SQL_SIMPLE".equals(theGroup.getAttribute(GrouperLoader.GROUPER_LOADER_TYPE))) {
161               GraphNode.this.simpleLoaderGroup = true;
162             }
163           }
164           // is an ldap loader job?
165           else {
166             AttributeAssign ldapAttributeAssign = theGroup.getAttributeDelegate().retrieveAssignment(null, LoaderLdapUtils.grouperLoaderLdapAttributeDefName(), false, false);
167             if (ldapAttributeAssign != null) {
168               GraphNode.this.loaderGroup = true;
169 
170               if ("LDAP_SIMPLE".equals(ldapAttributeAssign.getAttributeValueDelegate().retrieveValueString(LoaderLdapUtils.grouperLoaderLdapTypeName()))) {
171                 GraphNode.this.simpleLoaderGroup = true;
172               }
173 
174             }
175           }
176 
177           return null;
178         }
179       });
180 
181       // is it an intersect/complement group?
182       if (theGroup.hasComposite()) {
183         Composite composite = theGroup.getComposite(true);
184         if (composite.getType().equals(CompositeType.COMPLEMENT)) {
185           this.complementGroup = true;
186         } else if (composite.getType().equals(CompositeType.INTERSECTION)) {
187           this.intersectGroup = true;
188         }
189       }
190 
191     }
192 
193     if (grouperObject instanceof Stem) {
194       this.stem = true;
195     }
196 
197     if (grouperObject instanceof GrouperObjectSubjectWrapper) {
198       this.subject = true;
199     }
200 
201     if (grouperObject instanceof GrouperObjectProvisionerWrapper) {
202       this.provisionerTarget = true;
203     }
204 
205     // determine the VisualStyle object type this maps to
206     if (simpleLoaderGroup) {
207       styleObjectType = isStartNode() ? StyleObjectType.START_SIMPLE_LOADER_GROUP : StyleObjectType.SIMPLE_LOADER_GROUP;
208     } else if (loaderGroup) {
209       styleObjectType = isStartNode() ? StyleObjectType.START_LOADER_GROUP : StyleObjectType.LOADER_GROUP;
210     } else if (provisionerTarget) {
211       styleObjectType = StyleObjectType.PROVISIONER;
212     } else if (intersectGroup) {
213       styleObjectType = StyleObjectType.INTERSECT_GROUP;
214     } else if (complementGroup) {
215       styleObjectType = StyleObjectType.COMPLEMENT_GROUP;
216     } else if (group) {
217       styleObjectType = isStartNode() ? StyleObjectType.START_GROUP : StyleObjectType.GROUP;
218     } else if (stem) {
219       styleObjectType = isStartNode() ? StyleObjectType.START_STEM : StyleObjectType.STEM;
220     } else if (subject) {
221       styleObjectType = isStartNode() ? StyleObjectType.START_SUBJECT : StyleObjectType.SUBJECT;
222     } else if (provisionerTarget) {
223       styleObjectType = StyleObjectType.PROVISIONER;
224     } else {
225       styleObjectType = StyleObjectType.DEFAULT;
226     }
227 
228   }
229 
230   /**
231    * returns the underlying GrouperObject value
232    *
233    * @return the internal GrouperObject value
234    */
235   public GrouperObject getGrouperObject() {
236     return grouperObject;
237   }
238 
239   /**
240    * The total member count as set by the caller.
241    *
242    * @return
243    */
244   public long getAllMemberCount() {
245     return allMemberCount;
246   }
247 
248   /**
249    * the grouper object types for a group or stem, as set by the creator of this node
250    *
251    * @return the object type names
252    */
253   public List<String> getObjectTypeNames() {
254     return objectTypeNames;
255   }
256 
257   /**
258    * the direct member count as set by the caller
259    *
260    * @return
261    */
262   public long getDirectMemberCount() {
263     return directMemberCount;
264   }
265 
266   /**
267    * sets the total member count for this node
268    *
269    * @param allMemberCount member count
270    */
271   public void setAllMemberCount(long allMemberCount) {
272     this.allMemberCount = allMemberCount;
273   }
274 
275   /**
276    * sets the direct member count for this node.
277    *
278    * @param directMemberCount member count
279    */
280   public void setDirectMemberCount(long directMemberCount) {
281     this.directMemberCount = directMemberCount;
282   }
283 
284   /**
285    * sets the list of grouper object type names
286    *
287    * @param objectTypeNames
288    */
289   public void setObjectTypeNames(List<String> objectTypeNames) {
290     this.objectTypeNames = objectTypeNames;
291   }
292 
293   /**
294    * Adds one object type name to the node's current list.
295    *
296    * @param objectTypeName
297    */
298   public void addObjectTypeName(String objectTypeName) {
299     if (this.objectTypeNames == null) {
300       this.objectTypeNames = new LinkedList<String>();
301     }
302     this.objectTypeNames.add(objectTypeName);
303   }
304 
305   /**
306    * virtual method that returns true if both parents and children have been visited
307    *
308    * @return
309    */
310   public boolean isVisited() {
311     return visitedParents && visitedChildren;
312   }
313 
314   /**
315    * returns whether the parent nodes have been visited. Set by the caller
316    *
317    * @return true if parent nodes have been visited
318    */
319   public boolean isVisitedParents() {
320     return visitedParents;
321   }
322 
323   /**
324    * marks this node as having visited all its parent nodes
325    *
326    * @param visitedParents
327    */
328   public void setVisitedParents(boolean visitedParents) {
329     this.visitedParents = visitedParents;
330   }
331 
332   /**
333    * Returns whether the child nodes have been visited. Set by the caller
334    *
335    * @return true if child nodes have been visited
336    */
337   public boolean isVisitedChildren() {
338     return visitedChildren;
339   }
340 
341   /**
342    * marks this node as having visited all its child nodes
343    *
344    * @param visitedChildren
345    */
346   public void setVisitedChildren(boolean visitedChildren) {
347     this.visitedChildren = visitedChildren;
348   }
349 
350   /**
351    * Gets the parent nodes linked to this node. Set by the caller
352    *
353    * @return the set of all parent nodes reachable by this node
354    */
355   public Set<GraphNode> getParentNodes() {
356     return parentNodes;
357   }
358 
359   /**
360    * adds a parent node to the collection
361    *
362    * @param parentNode parent node to add
363    */
364   public void addParentNode(GraphNode parentNode) {
365     this.parentNodes.add(parentNode);
366   }
367 
368   /**
369    * Gets the child nodes linked to this node. Set by the caller
370    *
371    * @return the set of all child nodes reachable by this node
372    */
373   public Set<GraphNode> getChildNodes() {
374     return childNodes;
375   }
376 
377   /**
378    * Adds a child node to the collection
379    *
380    * @param childNode child node to add
381    */
382   public void addChildNode(GraphNode childNode) {
383     this.childNodes.add(childNode);
384   }
385 
386   /**
387    * Returns the number of hops from the starting node to this node. The value is set by
388    * the caller, but by convention parents of the start node will be negative numbers. The
389    * value should be zero only for the starting node.
390    *
391    * @return Distance from the starting node
392    */
393   public long getDistanceFromStartNode() {
394     return distanceFromStartNode;
395   }
396 
397   /**
398    * Sets the number of hops from the starting node to this node. If this is a
399    * parent of the start node, will be less than zero
400    *
401    * @param distanceFromStartNode
402    */
403   public void setDistanceFromStartNode(long distanceFromStartNode) {
404     this.distanceFromStartNode = distanceFromStartNode;
405   }
406 
407   /**
408    * True if the underlying Grouper object is a {@link Stem}
409    *
410    * @return whether the Grouper object is a stem
411    */
412   public boolean isStem() {
413     return stem;
414   }
415 
416   /**
417    * True if the underlying Grouper object is a {@link Group}
418    *
419    * @return whether the Grouper object is a group
420    */
421   public boolean isGroup() {
422     return group;
423   }
424 
425   /**
426    * True if the underlying Grouper object is a {@link GrouperObjectSubjectWrapper}
427    *
428    * @return whether the Grouper object is a subject wrapped as a GrouperObjectSubjectWrapper
429    */
430   public boolean isSubject() {
431     return subject;
432   }
433 
434   /**
435    * True if the underlying Grouper object is a group set up
436    * as a Grouper Loader job (either SQL or LDAP).
437    *
438    * @return whether the Grouper object is a group with loader settings
439    */
440   public boolean isLoaderGroup() {
441     return loaderGroup;
442   }
443 
444   /**
445    * True if the underlying Grouper object is a group set up
446    * as a SQL_SIMPLE or LDAP_SIMPLE Grouper Loader job
447    *
448    * @return whether the Grouper object is a group with loader settings
449    */
450   public boolean isSimpleLoaderGroup() {
451     return simpleLoaderGroup;
452   }
453 
454   /**
455    * True if the underlying Grouper object is a {@link GrouperObjectProvisionerWrapper}
456    *
457    * @return whether the Grouper object is a provisioner object wrapped as a GrouperObjectProvisionerWrapper
458    */
459   public boolean isProvisionerTarget() {
460     return provisionerTarget;
461   }
462 
463   /**
464    * True if the underlying Grouper object is group the has a composite type of INTERSECT
465    *
466    * @return whether the Grouper object has a composite INTERSECT type
467    */
468   public boolean isIntersectGroup() {
469     return intersectGroup;
470   }
471 
472   /**
473    * True if the underlying Grouper object is group the has a composite type of COMPLEMENT
474    *
475    * @return whether the Grouper object has a composite COMPLEMENT type
476    */
477   public boolean isComplementGroup() {
478     return complementGroup;
479   }
480 
481   /**
482    * True if this is the starting node. Set by the caller
483    *
484    * @return whether this is the starting node
485    */
486   public boolean isStartNode() {
487     return startNode;
488   }
489 
490   /**
491    * sets whether this is the start node
492    * @param startNode flag whether this is the starting node
493    */
494   public void setStartNode(boolean startNode) {
495     boolean oldStartNode = this.startNode;
496     this.startNode = startNode;
497 
498     // re-determine the object type since it may change
499     if (oldStartNode != startNode) {
500       determineObjectTypes();
501     };
502   }
503 
504   /**
505    * gets the style object type enum
506    *
507    * @return style object type
508    */
509   public StyleObjectType getStyleObjectType() {
510     return styleObjectType;
511   }
512 
513   /**
514    * helper method to get the id of the underlying GrouperObject
515    *
516    * @return the id of the underlying GrouperObject
517    */
518   public String getGrouperObjectId() {
519 //    if (isSubject()) {
520 //      // GrouperObjectSubjectWrapper constructs a weird source||||id string as the id; dig into the underlying subject to
521 //      // get just the id
522 //      return ((GrouperObjectSubjectWrapper)this.getGrouperObject()).getSubject().getId();
523 //    } else {
524       return this.getGrouperObject().getId();
525 //    }
526   }
527 
528   /**
529    * Helper method to get the id of the underlying GrouperObject
530    *
531    * @return the name of the underlying GrouperObject
532    */
533   public String getGrouperObjectName() {
534     return this.getGrouperObject().getName();
535   }
536 
537   public String toString() {
538     return this.getClass().getSimpleName() + ": {" + grouperObject + ", distanceFromStartNode=" + distanceFromStartNode + "}";
539   }
540 }