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  
11  package com.sri.emo.controller;
12  
13  import com.jcorporate.expresso.core.controller.ExpressoRequest;
14  import com.jcorporate.expresso.core.db.DBException;
15  import com.jcorporate.expresso.core.misc.StringUtil;
16  import com.sri.emo.dbobj.*;
17  import org.apache.log4j.Logger;
18  import org.dom4j.Element;
19  
20  import java.util.*;
21  
22  /***
23   * Reads Models from XML, nodes and all if desired.
24   *
25   * @author Michael Rimov
26   * @author Larry Hamel
27   */
28  public class ModelXMLReader {
29  
30      private static Logger slog = Logger.getLogger(ModelXMLReader.class);
31  
32      /***
33       * Default constructor.
34       */
35      public ModelXMLReader() {
36      }
37  
38  
39      /***
40       * Parses an entity and its associated parts.
41       *
42       * @param origroot Element the element in an XML document to parse.
43       * @return NodeType the instantiated NodeType that we just parsed. [It
44       *         is already added or updated to the database]
45       * @throws NumberFormatException upon integer parsing error.
46       * @throws DBException           upon Database exception.
47       */
48      public NodeType parseEntityAndParts(Element origroot) throws
49              NumberFormatException, DBException {
50          Element entityElm = (Element) origroot.selectSingleNode("//" + PartAction.ENTITY);
51          NodeType entity = new NodeType();
52          entity.setEntityName(entityElm.attributeValue(NodeType.NODE_TYPE_NAME));
53  
54          // do we already have this?
55          entity.find();
56          entity.setEntityDescription(entityElm.attributeValue(NodeType.NODE_TYPE_DESCRIP));
57          entity.setDisplayName(entityElm.attributeValue(NodeType.DISPLAY_TITLE));
58          entity.addOrUpdate();
59  
60          // get map of any existing parts before import
61          Part[] oldparts = entity.getParts();
62          HashMap map = new HashMap(oldparts.length);
63  
64          for (int i = 0; i < oldparts.length; i++) {
65              Part oldpart = oldparts[i];
66              map.put(oldpart.getPartType() + oldpart.getNodeRelation(),
67                      oldpart);
68          }
69  
70          boolean exists = false;
71  
72          // add or update parts
73          List allParts = origroot.selectNodes("//" + PartAction.PART);
74  
75          for (Iterator iterator = allParts.iterator(); iterator.hasNext();) {
76              Element partElm = (Element) iterator.next();
77              String parttype = partElm.attributeValue(Part.PART_TYPE);
78              String noderel = StringUtil.notNull(partElm.attributeValue(Part.NODE_PART_RELATION_TYPE));
79  
80              // remove so that any left can be identified
81              Part part = (Part) map.remove(parttype + noderel);
82  
83              if (part == null) {
84                  part = new Part();
85  
86                  // unique index: parent, parttype, noderel
87                  part.setPartType(parttype);
88  
89                  if ((noderel != null) && (noderel.length() > 0)) {
90                      part.setNodeRelation(noderel);
91                  }
92  
93                  part.setParentType(entity.getEntityName());
94                  exists = part.find();
95              } else {
96                  exists = true;
97              }
98  
99              part.setPartLabel(partElm.attributeValue(Part.PART_LABEL));
100             part.setPartDescrip(partElm.attributeValue(Part.PART_DESCRIP));
101 
102             int num = Integer.parseInt(partElm.attributeValue(Part.PART_NUM));
103             part.setPartNum(num);
104             part.setPartCardinality(partElm.attributeValue(Part.CARDINALITY));
105 
106             boolean isOwned = Boolean.valueOf(partElm.attributeValue(Part.PART_IS_ATTRIB)).booleanValue();
107             part.isOwnedAttribute(isOwned);
108 
109             String handler = partElm.attributeValue(Part.SPECIAL_HANDLER);
110 
111             if (handler != null) {
112                 part.setSpecialHandler(handler);
113                 part.getCustomHandler(); // will throw if cannot instantiate
114             }
115 
116             if (exists) {
117                 part.update();
118             } else {
119                 part.add();
120             }
121         }
122 
123         // we removed all current parts, so remove remainder
124         for (Iterator iterator = map.values().iterator();
125              iterator.hasNext();) {
126             Part delpart = (Part) iterator.next();
127             delpart.delete();
128         }
129 
130         // update factory
131         PartsFactory.refreshCache(entity.getEntityName());
132         return entity;
133     }
134 
135 
136     /***
137      * Parses and creates the appropriate nodes as received in the import.
138      *
139      * @param isFindAllIds          boolean
140      * @param isExternalRefRequired boolean
141      * @param root                  Element the root element we're parsing.
142      * @param request               ExpressoRequest the ControllerRequset.
143      * @return Map keyed by 'ident' attribute. values == the actual node.
144      * @throws DBException upon add error.
145      * @throws Exception   upon exception.
146      */
147     public Map parseImportXmlAndCreateNodes(boolean isFindAllIds,
148                                             boolean isExternalRefRequired,
149                                             Element root, ExpressoRequest request,
150                                             String xpathSelectionPrefix) throws DBException, Exception {
151         List list = root.selectNodes(xpathSelectionPrefix
152                 + Node.RELATED);
153         List referenceNodes = root.selectNodes(xpathSelectionPrefix
154                 + Node.REFERENCE);
155         Node rootnode = new Node(root);
156 
157         // parse out all picklists
158         List pickLists = new ArrayList();
159         org.dom4j.Node picklistRoot = root.selectSingleNode(xpathSelectionPrefix
160                 + AddNodeAction.PICKLISTS_TAGNAME);
161         if (picklistRoot != null) {
162             pickLists = ((Element) picklistRoot).elements();
163         }
164 
165         return createNodesFromXml(isFindAllIds,
166                 isExternalRefRequired, list, referenceNodes, rootnode, request, root, pickLists);
167     }
168 
169 
170     public Map createNodesFromXml(boolean isFindAllIds,
171                                   boolean isExternalRefRequired,
172                                   List relatedNodes,
173                                   List referenceNodes,
174                                   Node rootnode,
175                                   ExpressoRequest request,
176                                   Element root,
177                                   List pickLists) throws Exception {
178 
179         // map of all nodes, with key = 'ident' attribute
180         //(their ID in exporting system)
181         HashMap allNodesByXML_ID = new HashMap();
182 
183         // attribute set by Node constructor
184         allNodesByXML_ID.put(rootnode.getAttribute(Node.IDENT_TAG_NAME), rootnode);
185 
186         // first pass, find all related, 'inline' nodes anywhere in tree,
187         // and put basic-parsed node in hash, to ease
188         // relationship-building later
189         buildRelationships(allNodesByXML_ID, relatedNodes);
190 
191         // whether we ignore IDs that aren't in xml tree
192         // <REFERENCE REF_ID="25" NODE_TITLE="xx"
193         //               NODE_TYPE="TEMPLATE_TYPE"/>
194 
195         checkNodesIfFindById(isFindAllIds, allNodesByXML_ID, referenceNodes);
196 
197         parseAndAddNodes(isExternalRefRequired, request, allNodesByXML_ID, rootnode, pickLists);
198 
199         return allNodesByXML_ID;
200     }
201 
202     public void parseAndAddNodes(boolean isExternalRefRequired,
203                                  ExpressoRequest request,
204                                  HashMap allNodesByXML_ID,
205                                  Node rootnode,
206                                  List pickListElements) throws Exception {
207         try {
208             // ok, all is well: we have identified ALL nodes, though
209             // not fully parsed their relations nor attributes.
210             // save nodes since we need IDs to set up relations
211             for (Iterator iterator = allNodesByXML_ID.values().iterator(); iterator.hasNext();) {
212                 Node anode = (Node) iterator.next();
213                 anode.add();
214             }
215 
216             // avoid recursion by using list and iteration to walk down
217             //levels of nested items.
218             // we COULD just cycle through all nodes in allNode list,
219             // but then we wouldn't have info about level in xml tree
220             int level = 0;
221             List remaining = new ArrayList();
222             remaining.add(rootnode);
223 
224             // attach tree together with pass for relations
225             while (!remaining.isEmpty()) {
226                 List nextlevel = new ArrayList();
227 
228                 for (Iterator iterator = remaining.iterator();
229                      iterator.hasNext();) {
230                     Node anode = (Node) iterator.next();
231 
232                     // add formatting attribute for indentation
233                     anode.setAttribute(Node.INDENT, new Integer(level));
234 
235                     // here's the real meaty call
236                     List more = anode.parseXMLRelations(allNodesByXML_ID,
237                             isExternalRefRequired);
238                     nextlevel.addAll(more);
239                 }
240 
241                 remaining.clear();
242                 remaining = nextlevel;
243                 level++;
244             } // while any remaining nodes
245 
246             /*
247             convert elements
248             - <MATERIALS_AND_PRESENTATION_TYPE_x_MATERIALS_TYPE>
249   <ROW LIST_ID="5" VALUE_IN_PICKLIST="(not specified)" />
250   <ROW LIST_ID="94" VALUE_IN_PICKLIST="Diagram" />
251   <ROW LIST_ID="95" VALUE_IN_PICKLIST="Video clip" />
252   </MATERIALS_AND_PRESENTATION_TYPE_x_MATERIALS_TYPE>
253 - <MATERIALS_AND_PRESENTATION_TYPE_x_ROLE_OF_STIMULUS>
254 
255             */
256             Map translationMap = new HashMap();
257             for (Iterator iterator = pickListElements.iterator(); iterator.hasNext();) {
258                 Element element = (Element) iterator.next();
259                 Part part = Part.getPartFromXMLName(element.getName());
260                 if (part == null) {
261                     slog.error("Cannot find part for XML name: " + element.getName());
262                     continue;
263                 }
264 
265                 // get info about any existing entries for this picklist
266                 List existing = part.getPickList();
267                 Map existingMap = new HashMap(existing.size());
268                 for (Iterator iterator2 = existing.iterator(); iterator2.hasNext();) {
269                     PickList item = (PickList) iterator2.next();
270 
271                     // hack alert: we map display value; could hide duplicates
272                     existingMap.put(item.getDisplayString(), item);
273                 }
274 
275                 // compare new against existing, creating map
276                 List elementItems = element.elements();
277                 for (Iterator iterator1 = elementItems.iterator(); iterator1.hasNext();) {
278                     Element anElmItem = (Element) iterator1.next();
279                     String oldId = anElmItem.attributeValue(PickList.LIST_ID);
280                     String descrip = anElmItem.attributeValue(PickList.DISPLAY_IN_PICKLIST);
281                     String order = anElmItem.attributeValue(PickList.ORDER_NUM);
282 
283                     // is there a match already for descrip?
284                     Object existingItem = existingMap.get(descrip);
285                     if (existingItem != null) {
286                         translationMap.put(oldId, existingItem);
287                     } else {
288                         // create new picklist item to accommodate
289                         PickList item = new PickList(part.getParentType(), part.getPartType());
290                         item.setDisplayValue(descrip);
291                         item.setOrderNum(Integer.parseInt(order));
292                         item.add();
293                         translationMap.put(oldId, item);
294                     }
295                 }
296 
297             }
298 
299             // now another pass for attributes (which may depend on
300             // relations already being in place
301             for (Iterator iterator = allNodesByXML_ID.values().iterator();
302                  iterator.hasNext();) {
303                 Node anode = (Node) iterator.next();
304                 anode.parseXMLAttributes(allNodesByXML_ID, request, translationMap);
305             }
306         } catch (Exception e) {
307             // delete everything that has been created
308             for (Iterator iterator = allNodesByXML_ID.values().iterator();
309                  iterator.hasNext();) {
310                 Node anode = (Node) iterator.next();
311 
312                 // only nodes that have been saved will have
313                 //an (auto-inc) ID
314                 // but only trust a db query to see if this item was saved
315                 if (!anode.isFieldNull(Node.NODE_ID) && anode.find()) {
316                     anode.delete(); // will delete attributes
317                     //and relations (all details)
318                 }
319             }
320 
321             throw e;
322         }
323     }
324 
325     private void checkNodesIfFindById(boolean isFindAllIds,
326                                       HashMap allNodesByXML_ID, List list) throws DBException {
327         for (Iterator iterator = list.iterator(); iterator.hasNext();) {
328             Element referElem = (Element) iterator.next();
329             String refid = referElem.attributeValue(Node.REF_ID);
330 
331             if (allNodesByXML_ID.get(refid) == null && isFindAllIds) {
332                 // search for match
333                 Node searchnode = new Node(refid);
334 
335                 if (!searchnode.find()) {
336                     throw new DBException("Cannot find referenced node ID: " + refid +
337                             " neither inside XML nor in this system.");
338                 }
339             } // is external
340         }
341     }
342 
343     private void buildRelationships(HashMap allNodesByXML_ID, List list)
344             throws DBException {
345         Logger log = Logger.getLogger(ModelXMLReader.class);
346         for (Iterator iterator = list.iterator(); iterator.hasNext();) {
347             Element relElem = (Element) iterator.next();
348             List children = relElem.elements();
349 
350             for (Iterator childiter = children.iterator(); childiter.hasNext();) {
351                 Element childElem = (Element) childiter.next();
352 
353                 if (Node.REFERENCE.equals(childElem.getName())) {
354                     // skip for first pass; see next phase
355                     if (log.isDebugEnabled()) {
356                         log.debug("runDoImportNodeState:" +
357                                 "skip for first pass; see next phase");
358                     }
359                 } else {
360                     // inline node xml gets basic parsing; object
361                     //in RAM hash only so far
362                     Node relnode = new Node(childElem);
363                     allNodesByXML_ID.put(relnode.getAttribute(Node.
364                             IDENT_TAG_NAME), relnode);
365                 }
366             }
367         }
368     }
369 }