View Javadoc

1   package com.sri.emo.dbobj.model_tree;
2   
3   import com.jcorporate.expresso.core.controller.ExpressoResponse;
4   import com.jcorporate.expresso.core.dataobjects.jdbc.JoinedDataObject;
5   import com.jcorporate.expresso.core.db.DBException;
6   import com.sri.emo.controller.AddNodeAction;
7   import com.sri.emo.dbobj.Attribute;
8   import com.sri.emo.dbobj.Node;
9   import com.sri.emo.dbobj.Part;
10  import com.sri.emo.dbobj.PartsFactory;
11  import org.apache.log4j.Logger;
12  
13  import java.util.ArrayList;
14  import java.util.Iterator;
15  import java.util.List;
16  
17  /***
18   * Factory that builds a tree hierarchy from the Nodes.  Included are parts, attributes,
19   * relations, etc.  If there is a problem with the Model hierarchy the bug is located
20   * here since this class is responsible for the construction.
21   *
22   * @author Michael Rimov
23   */
24  public class DefaultModelFactory {
25  
26      /***
27       * The single instance of the logger.
28       */
29      private static final Logger LOG = Logger.getLogger(DefaultModelFactory.class);
30  
31      /***
32       * Root node instance to the hierarchy.
33       */
34      private final Node root;
35  
36      /***
37       * Maximum levels to recurse.
38       */
39      private final int MAX_LEVELS;
40  
41      /***
42       * Constructs a Default Model Factory.
43       *
44       * @param rootNode         Node the base node that we're going create the tree from.
45       * @param response         ControllerResponse.
46       * @param maxNestingLevels int
47       */
48      public DefaultModelFactory(final Node rootNode, final ExpressoResponse response, int maxNestingLevels) {
49          assert maxNestingLevels > 0;
50          assert rootNode != null;
51          assert response != null;
52  
53          MAX_LEVELS = maxNestingLevels;
54          root = rootNode;
55      }
56  
57      /***
58       * Function that builds the actual model that has been defined in the
59       * constructor.
60       *
61       * @return Model the resulting model.
62       */
63      public Model buildModel() {
64  
65          long startTime = System.currentTimeMillis();
66  
67          //Start the building
68          ModelNode modelNode = buildModelNode(null, root, 0);
69  
70          //Grab the result
71          DefaultModel result = new DefaultModel(modelNode);
72          if (LOG.isInfoEnabled()) {
73              LOG.info("Model building complete.  Time taken: " + (System.currentTimeMillis() - startTime) + " ms.");
74          }
75          return result;
76      }
77  
78      /***
79       * Recursively constructs a model node given a dbobj.Node instance.
80       *
81       * @param parent         MutableModelNode parent.  May be null if root.
82       * @param dataNode       Node the current node to render.
83       * @param currentNesting int the current nesting level.
84       * @return ModelNode or null if we are past the current nesting level
85       */
86      private ModelNode buildModelNode(final MutableModelNode parent, final Node dataNode, int currentNesting) {
87  
88          DefaultModelNode currentModelNode;
89          if (parent != null) {
90              currentModelNode = new DefaultModelNode(dataNode, parent);
91          } else {
92              currentModelNode = new DefaultModelNode(dataNode);
93          }
94          
95          
96  
97          try {
98              Part[] parts = PartsFactory.getParts(dataNode.getNodeType());
99              addParts(currentModelNode, dataNode, currentNesting, parts);
100 
101         } catch (DBException ex) {
102             LOG.error("Error building model tree", ex);
103             throw new IllegalStateException(
104                     "Unable to build model tree. " + ex.getMessage() + " more details have been logged");
105         }
106 
107         return currentModelNode;
108     }
109 
110 
111     /***
112      * Checks if the current data node already exists further up the tree.  We
113      * don't use a standard hashmap because we only care about loops above us
114      * not in siblings.
115      *
116      * @param currentDataNode  Node the current data node that we're considering
117      *                         recursinv into.
118      * @param currentModelNode DefaultModelNode the current model node we've just
119      *                         added and we're concerned about checking his parents.
120      * @return boolean true if the node is recursive.
121      */
122     private boolean isNodeRecursive(final Node currentDataNode, final DefaultModelNode currentModelNode) {
123         if (getRecursiveNodeReference(currentDataNode, currentModelNode) == null) {
124             return false;
125         } else {
126             return true;
127         }
128     }
129 
130 
131     /***
132      * Retrieve the instance of the node further up the tree hierarchy
133      * that is a recursive reference.
134      *
135      * @param currentDataNode  Node the data node we're examining.
136      * @param currentModelNode DefaultModelNode the current node in the model
137      *                         tree that goes along with the data node.
138      * @return DefaultModelNode or null if there is no such node found.
139      */
140     private DefaultModelNode getRecursiveNodeReference(final Node currentDataNode,
141                                                        final DefaultModelNode currentModelNode) {
142         //Initialize the current examining node.
143         ModelNode currentExaminingNode = currentModelNode.getParent();
144 
145         //While we still have nodes to traverse up the tree.
146         while (currentExaminingNode != ModelNode.NO_PARENT) {
147 
148             //If we have a node instance.
149             //And it is equal node then we have a  recursive node.
150             if (currentExaminingNode.getVisitable() instanceof Node && currentDataNode.equals(currentExaminingNode.getVisitable())) {
151                     assert currentExaminingNode instanceof DefaultModelNode;
152                     return (DefaultModelNode) currentExaminingNode;
153             }
154 
155             //Otherwise we just continue up the tree
156             currentExaminingNode = currentExaminingNode.getParent();
157         }
158 
159         //We have not found a recursive node.
160         return null;
161 
162     }
163 
164     /***
165      * Add the parts to the tree.  May end up with a recursive call if
166      * a part contains a relation to a subnode.
167      *
168      * @param currentModelNode DefaultModelNode
169      * @param dataNode         Node
170      * @param currentNesting   int
171      * @param parts            The parts to add. (Prefetched in an array)
172      * @return True if we found at least one value to insert in the nested
173      *         nodes.  If we didn't then this current node fill status is going to
174      *         be empty.
175      * @throws DBException upon database query error.
176      */
177     private boolean addParts(final DefaultModelNode currentModelNode,
178                              final Node dataNode,
179                              final int currentNesting,
180                              Part[] parts) throws DBException {
181 
182         boolean foundAtLeastOneValueForNode = false;
183         if (parts == null) {
184             throw new DBException("No parts found for model type: " + dataNode.getNodeType());
185         }
186 
187         List allAttribs = dataNode.getAttributes();
188         List allRelations = dataNode.getRawRelatedUsingDataObjects();
189 
190         // ignore the reflexive relations formed when src node is part of
191         // another node
192         // @todo using this list iAmAPartOf, attach an artificial Part to bottom of Node, just like flat view has
193 //        List iAmAPartOf = AddNodeAction.filterIamPartOfJoinedRelations(allRelations);
194 
195         //Iterate through all the parts.
196         for (int i = 0; i < parts.length; i++) {
197             Part part = parts[i];
198             ArrayList attribsForThisPart = new ArrayList();
199             ArrayList relatedJoinedForThisPart = new ArrayList();
200 
201             //If the part is an owned attribute
202             if (part.isOwnedAttribute()) {
203                 AddNodeAction.filterAttribs(allAttribs, part, attribsForThisPart);
204                 foundAtLeastOneValueForNode |=
205                         addOwnedAttribute(currentModelNode, part, attribsForThisPart);
206             } else {
207                 AddNodeAction.filterJoinedDataObjectRelations(allRelations, part, relatedJoinedForThisPart);
208 //                AddNodeAction.filterRelations(allRelations, part, relatedMultiesForThisPart);
209                 foundAtLeastOneValueForNode |= addSharedAttribute(currentModelNode, currentNesting, part,
210                         relatedJoinedForThisPart);
211             }
212 
213             allAttribs.removeAll(attribsForThisPart);
214             allRelations.removeAll(relatedJoinedForThisPart);
215         } // for all parts
216 
217         //If somewhere we didn't find any filled attributes then
218         if (!foundAtLeastOneValueForNode) {
219 
220             //Set the current node fills status to empty.
221             currentModelNode.setModelFilledStatus(ModelFillStatus.EMPTY);
222         }
223 
224         // all relations and attribs should have been removed by now; any remaining are 'strays'
225         AddNodeAction.deleteJoinedRelations(allRelations, dataNode);
226         AddNodeAction.deleteAttributes(allAttribs, dataNode);
227 
228         return foundAtLeastOneValueForNode;
229     }
230 
231     /***
232      * Adds the shared attribute to the tree.
233      *
234      * @param currentModelNode DefaultModelNode the current data model node we're dealing with.
235      * @param currentNesting   int the current nesting level.  If we go over MAX_LEVELS then
236      *                         we end depth traversal of the model tree.
237      * @param part             Part
238      * @param relations        The prefetched list of relations to add.
239      * @return boolean true if there are children to descend into.
240      * @throws NullPointerException
241      * @throws DBException
242      */
243     private boolean addSharedAttribute(DefaultModelNode currentModelNode,
244                                        int currentNesting,
245                                        Part part,
246                                        List relations) throws NullPointerException, DBException {
247 
248         boolean returnValue = false;
249         //IF we got here then we are a shared attribute.
250         DefaultModelNode partToNest = new DefaultModelNode(part, currentModelNode);
251 
252         //Get the related nodes.
253 
254         //If there are no related nodes then set the fill status to empty
255         //and continue to the next node.
256         if (relations.size() == 0) {
257             partToNest.setModelFilledStatus(ModelFillStatus.EMPTY);
258             return false;
259         }
260 
261         //Iterate through the related nodes.
262         for (Iterator iterator = relations.iterator(); iterator.hasNext();) {
263             JoinedDataObject oneJoin = (JoinedDataObject) iterator.next();
264             Node aRelatedNode = (Node) oneJoin.getNestedFromFieldName("Node." + Node.NODE_ID);
265 
266             DefaultModelNode nested = new DefaultModelNode(aRelatedNode, partToNest);
267 
268             //If we are recursive
269             if (isNodeRecursive(aRelatedNode, nested)) {
270                 //Set the recursive node completion status.
271                 ModelNode recursedNode = getRecursiveNodeReference(aRelatedNode, nested);
272                 assert recursedNode != null;
273                 if (recursedNode == null) {
274                     throw new NullPointerException("getRecursiveNode() should not have returned Null if the node "
275                             + "is indeed recursive");
276                 }
277                 NodeCompletionStatus recursiveNodeStatus = new NodeCompletionStatus(recursedNode);
278                 nested.setNodeCompletionStatus(recursiveNodeStatus);
279                 nested.setModelFilledStatus(ModelFillStatus.PARTIALLY_FILLED);
280 
281                 //Else if we are under the maximum levels to prevent truncation
282             } else {
283                 if (currentNesting < MAX_LEVELS) {
284 
285                     //Set node status.
286                     nested.setNodeCompletionStatus(NodeCompletionStatus.COMPLETE_NODE);
287                     nested.setModelFilledStatus(ModelFillStatus.PARTIALLY_FILLED);
288 
289                     //Check to see if there are any parts defined for the
290                     //nested node
291                     Part[] nestedParts = PartsFactory.getParts(aRelatedNode.getNodeType());
292                     if (nestedParts.length > 0) {
293                         //If there are, then recurse
294                         returnValue |= addParts(nested, aRelatedNode, (currentNesting + 1), nestedParts);
295                     } else {
296                         //Otherwise we have an empty part -- continue afterwards
297                         nested.setModelFilledStatus(ModelFillStatus.EMPTY);
298                     }
299 
300                     //Otherwise truncate the node and continue
301                 } else {
302                     //Set truncated status on the node
303                     nested.setNodeCompletionStatus(NodeCompletionStatus.TRUNCATED_NODE);
304                     nested.setModelFilledStatus(ModelFillStatus.PARTIALLY_FILLED);
305                 }
306             }
307         } // for all related nodes.
308         return returnValue;
309     }
310     
311 
312     /***
313      * Adds an owned attribute (Part) to the current model node.
314      *
315      * @param currentModelNode DefaultModelNode the current model node
316      *                         to work with.
317      * @param part             Part the part we're examining.
318      * @param attribs          a prefetched list of attributes.
319      * @return boolean true if we have found a part for the node.
320      */
321     private boolean addOwnedAttribute(DefaultModelNode currentModelNode, Part part, List attribs) {
322         boolean foundAtLeastOneValueForNode = false;
323 
324         //Handle the attribute
325         if (attribs.size() == 1) {
326             Attribute attrib = (Attribute) attribs.get(0);
327             attrib.setExplicitPart(part);
328             DefaultModelNode parentForSingleAttribute = new DefaultModelNode(part, currentModelNode);
329             DefaultModelNode subNode = new DefaultModelNode(attrib, parentForSingleAttribute);
330             foundAtLeastOneValueForNode = true;
331             subNode.setModelFilledStatus(ModelFillStatus.PARTIALLY_FILLED);
332         } else if (attribs.size() == 0) {
333             DefaultModelNode newNode = new DefaultModelNode(part, currentModelNode);
334             foundAtLeastOneValueForNode |= false;
335             newNode.setModelFilledStatus(ModelFillStatus.EMPTY);
336         } else {
337             DefaultModelNode parentForMultiAttributes = new DefaultModelNode(part, currentModelNode);
338 
339             for (Iterator iterator = attribs.iterator(); iterator.hasNext();) {
340                 Attribute attrib = (Attribute) iterator.next();
341                 attrib.setExplicitPart(part);
342                 DefaultModelNode newNode = new DefaultModelNode(attrib, parentForMultiAttributes);
343                 foundAtLeastOneValueForNode = true;
344                 newNode.setModelFilledStatus(ModelFillStatus.PARTIALLY_FILLED);
345             }
346         }
347 
348         return foundAtLeastOneValueForNode;
349     }
350 }