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
68 ModelNode modelNode = buildModelNode(null, root, 0);
69
70
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
143 ModelNode currentExaminingNode = currentModelNode.getParent();
144
145
146 while (currentExaminingNode != ModelNode.NO_PARENT) {
147
148
149
150 if (currentExaminingNode.getVisitable() instanceof Node && currentDataNode.equals(currentExaminingNode.getVisitable())) {
151 assert currentExaminingNode instanceof DefaultModelNode;
152 return (DefaultModelNode) currentExaminingNode;
153 }
154
155
156 currentExaminingNode = currentExaminingNode.getParent();
157 }
158
159
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
191
192
193
194
195
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
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
209 foundAtLeastOneValueForNode |= addSharedAttribute(currentModelNode, currentNesting, part,
210 relatedJoinedForThisPart);
211 }
212
213 allAttribs.removeAll(attribsForThisPart);
214 allRelations.removeAll(relatedJoinedForThisPart);
215 }
216
217
218 if (!foundAtLeastOneValueForNode) {
219
220
221 currentModelNode.setModelFilledStatus(ModelFillStatus.EMPTY);
222 }
223
224
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
250 DefaultModelNode partToNest = new DefaultModelNode(part, currentModelNode);
251
252
253
254
255
256 if (relations.size() == 0) {
257 partToNest.setModelFilledStatus(ModelFillStatus.EMPTY);
258 return false;
259 }
260
261
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
269 if (isNodeRecursive(aRelatedNode, nested)) {
270
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
282 } else {
283 if (currentNesting < MAX_LEVELS) {
284
285
286 nested.setNodeCompletionStatus(NodeCompletionStatus.COMPLETE_NODE);
287 nested.setModelFilledStatus(ModelFillStatus.PARTIALLY_FILLED);
288
289
290
291 Part[] nestedParts = PartsFactory.getParts(aRelatedNode.getNodeType());
292 if (nestedParts.length > 0) {
293
294 returnValue |= addParts(nested, aRelatedNode, (currentNesting + 1), nestedParts);
295 } else {
296
297 nested.setModelFilledStatus(ModelFillStatus.EMPTY);
298 }
299
300
301 } else {
302
303 nested.setNodeCompletionStatus(NodeCompletionStatus.TRUNCATED_NODE);
304 nested.setModelFilledStatus(ModelFillStatus.PARTIALLY_FILLED);
305 }
306 }
307 }
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
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 }