View Javadoc

1   /* ===================================================================
2    * Copyright 2002-04 SRI International.
3    * Released under the MOZILLA PUBLIC LICENSE Version 1.1
4    * which can be obtained at http://www.mozilla.org/MPL/MPL-1.1.html
5    * This software is distributed on an "AS IS"
6    * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.
7    * See the License for the specific language governing rights and
8    * limitations under the License.
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              // need to create matrix attrib with node-owners perm.
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          // find existing
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          // validate, sort existing cells, adding/deleting as nec.
86          HashMap columnHash = collateMatrix(celllist);
87          validateMatrix(columnHash, attrib, columnNodes, rowObjects);
88  
89          // one less column than
90          MatrixCell[][] matrix = new MatrixCell[columnHash.size()][];
91          int i = 0;
92  
93          // ovCats are provided in proper order, by value
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; // one column is not used
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         // order cells in each column
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         // remove all matrix items
170         //        getLogger().debug("deleting all matrix cells.");
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             // check column list for additional & obsolete columns
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                     // need to add
229                     addCol_IDs.add(colID);
230                 } else {
231                     // ok
232                     confirmedCol_Ids.add(colID);
233                 }
234             }
235 
236             // are there any obsolete columns that should be removed?
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                     // use superuser priv's, since this could be cleanup happening by a read-only person
250                     cell.setRequestingUser(superuser);
251 
252                     if (cell.find()) {
253                         cell.delete();
254                     }
255                 }
256 
257                 colHashInNeedOfValidation.remove(colID);
258             }
259 
260             // were there columns to add?
261             for (Iterator iterator = addCol_IDs.iterator(); iterator.hasNext();) {
262                 String colID = (String) iterator.next();
263 
264                 //                getLogger().debug("adding new matrix column for SMV id: " + smvId);
265                 MatrixCell[] column = createColumn(attrib, colID, columns,
266                         rowObjects);
267                 colHashInNeedOfValidation.put(colID, Arrays.asList(column));
268             }
269 
270             // were the confirmed columns complete with all rows?
271             // set up hash tables and tools
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             // go through all old columns, looking for problems
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                 // any to remove?
301                 for (Iterator iter = removeCells.iterator(); iter.hasNext();) {
302                     MatrixCell cell = (MatrixCell) iter.next();
303                     column.remove(cell);
304 
305                     // note that a read-only guest could be the UID, and
306                     // this deletion is a system validation function, so
307                     // use system ID
308                     cell.setRequestingUser(superuser);
309                     cell.delete();
310                 }
311 
312                 // any OV rows missing in list?
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                     // special for identity matrix:
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                 // first item
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         // search with admin priv. since we are cloning
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             // clone
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         // find smv now corresponding to this xml id
400         boolean isExternalRefRequired = request.getParameter(
401                 Node.RELATION_JOIN) == null; // whether we must look for IDs missing in xml tree externally
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         // need original object which has xml attached to it as attribute, so look in orignal map, but by new id
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 }