1
2
3
4
5
6
7
8
9
10 package com.sri.emo.dbobj;
11
12 import com.jcorporate.expresso.core.controller.ControllerRequest;
13 import com.jcorporate.expresso.core.db.DBException;
14 import com.jcorporate.expresso.core.dbobj.DBObject;
15 import com.jcorporate.expresso.core.security.ReadOnlyUser;
16 import com.jcorporate.expresso.core.security.SuperUser;
17 import com.jcorporate.expresso.core.security.User;
18 import com.sri.common.controller.AbstractDBController;
19 import com.sri.emo.commandline.defaults.AbstractNodeExportSupport;
20 import org.dom4j.Element;
21
22 import java.io.IOException;
23 import java.util.*;
24
25
26 /***
27 * base class for custom handler for a matrix
28 *
29 * @author Larry Hamel
30 */
31 public abstract class AbstractMatrixHandler extends AbstractAttributeHandler {
32 /***
33 * XML tag names
34 */
35 public static final String ROW = "ROW";
36 public static final String NAME = "NAME";
37 public static final String SM_VAR_ID = "SM_VAR_ID";
38 public static final String SM_VAR_NAME = "SM_VAR_NAME";
39 public static final String CELL_ID = "CELL_ID";
40 public static final String PARAM_PREFIX = "Param";
41
42 public AbstractMatrixHandler(String attribName, Class editorClass,
43 String viewState, String promptEditState) {
44 super(attribName, editorClass, viewState, promptEditState);
45 }
46
47 protected abstract String getColumnID(Object columnObject) throws DBException;
48
49 protected abstract String getRowID(Object rowObject) throws DBException;
50
51 protected abstract Object[] getRowObjects(Node node) throws DBException;
52
53 protected abstract Object[] getColumnObjects(Node node) throws DBException;
54
55 /***
56 * get matrix, creating it from scratch if necessary
57 *
58 * @param matrixOwnerNode node which owns matrix attribute
59 */
60 public MatrixCell[][] getMatrix(Node matrixOwnerNode) throws DBException {
61 Attribute attrib = getAttribute(matrixOwnerNode);
62
63 if (attrib == null) {
64
65 ReadOnlyUser oldUser = matrixOwnerNode.getRequestingUser();
66
67 matrixOwnerNode.setRequestingUser(User.getUserFromId(matrixOwnerNode.ownerID()));
68 attrib = createAttrib(matrixOwnerNode, getAttribName());
69 matrixOwnerNode.setRequestingUser(oldUser);
70 getLogger().debug("creating new attribute of type: " +
71 getAttribName());
72 }
73
74 Object[] columnNodes = getColumnObjects(matrixOwnerNode);
75 Object[] rowObjects = getRowObjects(matrixOwnerNode);
76
77
78 MatrixCell searchCell = new MatrixCell(attrib.getAttribId());
79 List celllist = searchCell.searchAndRetrieveList();
80
81 if (celllist.size() == 0) {
82 return createMatrix(attrib, columnNodes, rowObjects);
83 }
84
85
86 HashMap columnHash = collateMatrix(celllist);
87 validateMatrix(columnHash, attrib, columnNodes, rowObjects);
88
89
90 MatrixCell[][] matrix = new MatrixCell[columnHash.size()][];
91 int i = 0;
92
93
94 for (int j = 0; j < columnNodes.length; j++) {
95 List list = (List) columnHash.get(getColumnID(columnNodes[j]));
96
97 if (list == null) {
98 continue;
99 }
100
101 matrix[i++] = (MatrixCell[]) list.toArray(new MatrixCell[list.size()]);
102 }
103
104 sortMatrixRows(columnHash, getRowComparator(matrixOwnerNode));
105
106 return matrix;
107 }
108
109 private MatrixCell[][] createMatrix(Attribute attrib, Object[] columnNodes,
110 Object[] rowObjects)
111 throws DBException {
112 ArrayList cells = new ArrayList();
113
114 for (int i = 0; i < columnNodes.length; i++) {
115 cells.add(createColumn(attrib, getColumnID(columnNodes[i]),
116 columnNodes, rowObjects));
117 }
118
119 return (MatrixCell[][]) cells.toArray(new MatrixCell[cells.size()][]);
120 }
121
122 protected abstract MatrixCell[] createColumn(Attribute attrib,
123 String colId,
124 Object[] columns,
125 Object[] rows)
126 throws DBException;
127
128 protected void sortMatrixRows(HashMap columnHash, Comparator rowCompare) {
129 if (rowCompare == null) {
130 return;
131 }
132
133
134 for (Iterator iterator = columnHash.values().iterator();
135 iterator.hasNext();) {
136 List list = (List) iterator.next();
137 Collections.sort(list, rowCompare);
138 }
139 }
140
141 /***
142 * find single attribute, warning if > 1 are found.
143 *
144 * @return attribute found, or null
145 */
146 public Attribute getAttribute(Node node) throws DBException {
147 Attribute[] attribs = node.getAttributes(getAttribName());
148 Attribute attrib = null;
149
150 if (attribs.length > 0) {
151 attrib = attribs[0];
152
153 if (attribs.length > 1) {
154 node.getLogger().error("More than one attrib of type '" +
155 getAttribName() + "' found for student model id/title: " +
156 node.getNodeId() + "/" + node.getNodeTitle() +
157 ". Using first one.");
158 }
159 }
160
161 return attrib;
162 }
163
164 /***
165 * note: this call is done during 'validate' (which any reader can do), so it uses superuser priv.
166 */
167 public static void clearMatrix(HashMap columnHash)
168 throws DBException {
169
170
171 ReadOnlyUser superUser = SuperUser.INSTANCE;
172
173 for (Iterator iter = columnHash.values().iterator(); iter.hasNext();) {
174 List list = (List) iter.next();
175
176 for (Iterator iterator = list.iterator(); iterator.hasNext();) {
177 MatrixCell cell = (MatrixCell) iterator.next();
178
179 if (superUser == SuperUser.INSTANCE) {
180 superUser = User.getAdmin(cell.getDataContext());
181 }
182
183 cell.setRequestingUser(superUser);
184
185 if (cell.find()) {
186 cell.delete();
187 }
188 }
189 }
190
191 columnHash.clear();
192 }
193
194 /***
195 * ASSUMES that rows are nodes. override if this isn't true!
196 */
197 protected Comparator getRowComparator(Node node)
198 throws DBException {
199 Node[] smvs = (Node[]) getColumnObjects(node);
200
201 return (Comparator) new NodeRowIdOrderingComparator(smvs);
202 }
203
204 /***
205 * note: this call is done as 'validate' (which any reader can do), so it uses superuser priv.
206 *
207 * @param colHashInNeedOfValidation hash of all columns in matrix now--may be incomplete, or have items that are obsolete
208 * @param columns canonical list of columns
209 * @param rowObjects canonical list of rows
210 */
211 protected void validateMatrix(HashMap colHashInNeedOfValidation,
212 Attribute attrib,
213 Object[] columns,
214 Object[] rowObjects) throws DBException {
215 if (columns.length == 0) {
216 clearMatrix(colHashInNeedOfValidation);
217 } else {
218
219 ArrayList confirmedCol_Ids = new ArrayList();
220 ArrayList addCol_IDs = new ArrayList();
221 ArrayList canonCol_IDs = new ArrayList();
222
223 for (int i = 0; i < columns.length; i++) {
224 String colID = getColumnID(columns[i]);
225 canonCol_IDs.add(colID);
226
227 if (colHashInNeedOfValidation.get(colID) == null) {
228
229 addCol_IDs.add(colID);
230 } else {
231
232 confirmedCol_Ids.add(colID);
233 }
234 }
235
236
237 ArrayList obsoleteList = new ArrayList(colHashInNeedOfValidation.keySet());
238 obsoleteList.removeAll(canonCol_IDs);
239
240 ReadOnlyUser superuser = User.getAdmin(attrib.getDataContext());
241
242 for (Iterator iterator = obsoleteList.iterator(); iterator.hasNext();) {
243 String colID = (String) iterator.next();
244
245 getLogger().debug("removing obsolete matrix column for id: " + colID);
246 for (Iterator iter = ((List) colHashInNeedOfValidation.get(colID)).iterator(); iter.hasNext();) {
247 MatrixCell cell = (MatrixCell) iter.next();
248
249
250 cell.setRequestingUser(superuser);
251
252 if (cell.find()) {
253 cell.delete();
254 }
255 }
256
257 colHashInNeedOfValidation.remove(colID);
258 }
259
260
261 for (Iterator iterator = addCol_IDs.iterator(); iterator.hasNext();) {
262 String colID = (String) iterator.next();
263
264
265 MatrixCell[] column = createColumn(attrib, colID, columns,
266 rowObjects);
267 colHashInNeedOfValidation.put(colID, Arrays.asList(column));
268 }
269
270
271
272 HashMap rowHash = new HashMap();
273 ArrayList canonRow_Ids = new ArrayList();
274
275 for (int i = 0; i < rowObjects.length; i++) {
276 Object row = rowObjects[i];
277 String rowID = getRowID(row);
278 rowHash.put(rowID, row);
279 canonRow_Ids.add(rowID);
280 }
281
282
283 for (Iterator iterator = confirmedCol_Ids.iterator(); iterator.hasNext();) {
284 String colID = (String) iterator.next();
285 List column = (List) colHashInNeedOfValidation.get(colID);
286
287 ArrayList confirmedRow_Ids = new ArrayList();
288 ArrayList removeCells = new ArrayList();
289
290 for (Iterator iter = column.iterator(); iter.hasNext();) {
291 MatrixCell cell = (MatrixCell) iter.next();
292
293 if (rowHash.get(cell.getRowId()) == null) {
294 removeCells.add(cell);
295 } else {
296 confirmedRow_Ids.add(cell.getRowId());
297 }
298 }
299
300
301 for (Iterator iter = removeCells.iterator(); iter.hasNext();) {
302 MatrixCell cell = (MatrixCell) iter.next();
303 column.remove(cell);
304
305
306
307
308 cell.setRequestingUser(superuser);
309 cell.delete();
310 }
311
312
313 List toAdd = (List) canonRow_Ids.clone();
314 toAdd.removeAll(confirmedRow_Ids);
315
316 for (Iterator iter = toAdd.iterator(); iter.hasNext();) {
317 int rowID = Integer.parseInt((String) iter.next());
318
319
320 String value = "0";
321
322 if (colID.equals("" + rowID)) {
323 value = "1";
324 }
325
326 column.add(new MatrixCell(attrib, colID, rowID, value));
327 }
328 }
329
330 sortMatrixRows(colHashInNeedOfValidation,
331 getRowComparator(attrib.getParentNode()));
332 }
333 }
334
335 /***
336 * from list of cells, determine lists of columns
337 */
338 public HashMap collateMatrix(List celllist) throws DBException {
339 HashMap columnHash = new HashMap();
340
341 for (Iterator iterator = celllist.iterator(); iterator.hasNext();) {
342 MatrixCell aCell = (MatrixCell) iterator.next();
343
344 ArrayList colList = (ArrayList) columnHash.get(aCell.getColumnId());
345
346 if (colList == null) {
347
348 colList = new ArrayList();
349 columnHash.put(aCell.getColumnId(), colList);
350 }
351
352 colList.add(aCell);
353 }
354
355 return columnHash;
356 }
357
358 /***
359 * clone data from existing to clone
360 */
361 public void clone(Attribute existing, Attribute clone)
362 throws DBException {
363
364 MatrixCell searchCell = new MatrixCell(User.getAdmin(existing.getDataContext()));
365 searchCell.setDataContext(existing.getDataContext());
366 searchCell.setAttribID(existing.getAttribId());
367
368 List celllist = searchCell.searchAndRetrieveList();
369
370 for (Iterator iterator = celllist.iterator(); iterator.hasNext();) {
371 MatrixCell cell = (MatrixCell) iterator.next();
372 cell.setRequestingUser(clone.getRequestingUser());
373
374
375 cell.setAttribID(clone.getAttribId());
376 cell.addIfNeeded();
377 }
378 }
379
380 public void delete(Attribute attribute) throws DBException {
381 MatrixCell searchCell = new MatrixCell(attribute.getRequestingUser());
382 searchCell.setDataContext(attribute.getDataContext());
383 searchCell.setAttribID(attribute.getAttribId());
384
385 List celllist = searchCell.searchAndRetrieveList();
386
387 for (Iterator iterator = celllist.iterator(); iterator.hasNext();) {
388 MatrixCell cell = (MatrixCell) iterator.next();
389 cell.delete();
390 }
391 }
392
393 /***
394 * @throws com.jcorporate.expresso.core.db.DBException
395 * if smv not found w/i map, or w/i system if external IDs are allowed
396 */
397 protected static Node getSMV_fromXML(String smvID, Map allNodesByXML_ID,
398 Element elem, ControllerRequest request) throws DBException {
399
400 boolean isExternalRefRequired = request.getParameter(
401 Node.RELATION_JOIN) == null;
402
403 Node colsmv = (Node) allNodesByXML_ID.get(smvID);
404
405 if (colsmv == null) {
406 if (isExternalRefRequired) {
407 colsmv = new Node(request, smvID);
408
409 if (!colsmv.find()) {
410 try {
411 throw new DBException("cannot find SMV with xml ID: " +
412 smvID +
413 " in this system (all SMVs searched) as described by reference within element: " +
414 AbstractDBController.getPrettyXML(elem));
415 } catch (IOException e) {
416 throw new DBException("cannot find SMV with xml ID: " +
417 smvID);
418 }
419 }
420 } else {
421 try {
422 throw new DBException("cannot find SMV with xml ID: " +
423 smvID +
424 " expected to be found within XML tree, as specified within element: " +
425 AbstractDBController.getPrettyXML(elem));
426 } catch (IOException e) {
427 throw new DBException("cannot find SMV with xml ID: " +
428 smvID);
429 }
430 }
431 }
432
433 return colsmv;
434 }
435
436 protected Node getOrigOV(Node[] ovs, Map allNodesByXML_ID)
437 throws DBException {
438 Node myOV = ovs[0];
439
440
441 for (Iterator iterator = allNodesByXML_ID.values().iterator();
442 iterator.hasNext();) {
443 Node anode = (Node) iterator.next();
444
445 if (anode.getNodeId().equals(myOV.getNodeId())) {
446 myOV = anode;
447
448 break;
449 }
450 }
451
452 return myOV;
453 }
454
455 /***
456 * Important: we use the ID of the owner of the node in order to add an
457 * attribute here. Attributes are created on-the-fly for the design matrix
458 * and scoring matrix, whenever someone views or edits for the first time
459 * before saving. Therefore, the viewer may not have the correct rights,
460 * but the owner of the node (the measurement model) does have these rights.
461 */
462 public static Attribute createAttrib(Node node, String type)
463 throws DBException {
464 Attribute attrib;
465 attrib = new Attribute(User.getUserFromId(node.getPermissions().owner()));
466 attrib.setParentNodeId(node.getNodeId());
467 attrib.setParentNodeType(node.getNodeType());
468 attrib.setAttributeType(type);
469 attrib.add();
470
471 return attrib;
472 }
473
474 public boolean isNeededInFullXML() {
475 return true;
476 }
477
478 /***
479 * @return an array of SQL Insert statements that are equivalent to the contents of this attribute; can be empty but never null
480 */
481 public String[] getInsertStatements(AbstractNodeExportSupport treeExporter, DBObject obj) throws DBException {
482 Attribute attrib = (Attribute) obj;
483 List result = new LinkedList();
484
485 MatrixCell searchCell = new MatrixCell(attrib.getAttribId());
486 List celllist = searchCell.searchAndRetrieveList();
487 for (Iterator iterator = celllist.iterator(); iterator.hasNext();) {
488 MatrixCell cell = (MatrixCell) iterator.next();
489 result.addAll(treeExporter.getSQL(cell));
490 }
491 return (String[]) result.toArray(new String[result.size()]);
492 }
493 }