package com.openMap1.mapper.converters; /** * This class implements an XOReader for V3 XML which is equivalent * to an MDLXOreader driven by V3-V3 mappings - but it can be done simply in Java * because the mappings are so regular in form */ import java.util.Enumeration; import java.util.Hashtable; import java.util.Iterator; import java.util.StringTokenizer; import java.util.Vector; import org.eclipse.emf.ecore.EAttribute; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EClassifier; import org.eclipse.emf.ecore.EPackage; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.EStructuralFeature; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.Text; import com.openMap1.mapper.core.ClassSet; import com.openMap1.mapper.core.MDLWriteException; import com.openMap1.mapper.core.MapperException; import com.openMap1.mapper.core.NamespaceSet; import com.openMap1.mapper.core.RunIssue; import com.openMap1.mapper.core.namespace; import com.openMap1.mapper.impl.ElementDefImpl; import com.openMap1.mapper.reader.AbstractReaderWriter; import com.openMap1.mapper.reader.XOReader; import com.openMap1.mapper.reader.objectRep; import com.openMap1.mapper.reader.objectToken; import com.openMap1.mapper.util.ModelUtil; import com.openMap1.mapper.util.XMLOutputFile; import com.openMap1.mapper.util.XMLUtil; import com.openMap1.mapper.util.messageChannel; import com.openMap1.mapper.writer.MappedXMLWriter; import com.openMap1.mapper.writer.XMLWriter; import com.openMap1.mapper.writer.objectGetter; import com.openMap1.mapper.MappedStructure; /** * Java mapping class for the HL7 V3 XML ITS - equivalent to * a set of mappings between the V3 XML ITS and the RIM-based V3 class model * * @author robert * */ public class V3_XML_ITS extends AbstractReaderWriter implements XOReader, objectGetter, XMLWriter{ /** * all objectReps in the XML instance * First String key = qualified class name * Second String key = XPath to node = subset of the objectRep * Vector = all objectReps with that path */ private Hashtable<String, Hashtable<String,Vector<objectRep>>> allObjectReps = new Hashtable<String, Hashtable<String,Vector<objectRep>>>(); private static String V3NAMESPACEURI = "urn:hl7-org:v3"; private static String V3NAMESPACEPREFIX = ""; //private static String VOCABULARYNAMESPACEURI = "urn:hl7-org:v3/voc"; //private boolean hasV3Namespace; //--------------------------------------------------------------------------------- // Constructors //--------------------------------------------------------------------------------- /** * constructor for XOReader and objectGetter uses */ public V3_XML_ITS(Element XMLFileRoot, MappedStructure ms, EPackage classModel, messageChannel mChan) throws MapperException { super(XMLFileRoot, ms, classModel,mChan); findAllObjectReps(); } /** * constructor for XMLWriter uses * @param oGet * @param ms * @param classModel * @param mChan * @param doRunTracing * @throws MapperException */ public V3_XML_ITS(objectGetter oGet, MappedStructure ms, EPackage classModel, messageChannel mChan, Boolean doRunTracing) throws MapperException { super(oGet, ms, classModel,mChan,doRunTracing); } /** * When the root element is reset, all the objectReps must be reset */ public void setRoot(Node el) throws MapperException { super.setRoot(el); findAllObjectReps(); } /** * set the root of the XML instance being read * @param el * @throws MapperException */ public void setInputRoot(Node el) throws MapperException { oGet.setRoot(el); } //--------------------------------------------------------------------------------- // Handling namespaces //--------------------------------------------------------------------------------- /** set the namespaces in the output XML file to be * the same as those in the output structure definition, in both prefix and URI * - except we do not want the XML Schema namespace in the output namespaces. */ public void setOutputNamespaces() throws MapperException { xout.setNSSet(new NamespaceSet()); for (int i = 0; i < ms().getNamespaceSet().size(); i++) { namespace ns = ms().getNamespaceSet().getByIndex(i); if (!(ns.URI().equals(XMLUtil.SCHEMAURI))) xout.NSSet().addNamespace(ns); } } //--------------------------------------------------------------------------------- // Finding all objectReps in the XML instance //--------------------------------------------------------------------------------- private void findAllObjectReps() throws MapperException { allObjectReps = new Hashtable<String, Hashtable<String,Vector<objectRep>>>(); EClass theClass = getEntryClass(); String path = "/" + theClass.getName(); // check that the tag name of the top element is the entry class name if (theClass.getName().equals(XMLFileRoot.getLocalName())) addObjectReps(theClass,XMLFileRoot,path); } /** * recursive descent of the whole XML Instance, recording * an objecRep for every node reached through associations in the * Ecore model * @param theClass * @param el * @param path * @throws MapperException */ private void addObjectReps(EClass theClass,Element el,String path) throws MapperException { addObjectRep(theClass,el,path); for (Iterator<Element>ie = XMLUtil.childElements(el).iterator();ie.hasNext();) { Element childEl = ie.next(); String tagName = XMLUtil.getLocalName(childEl); String newPath = path + "/" + tagName; EStructuralFeature feature = theClass.getEStructuralFeature(tagName); // if the tag name is not an association role name in the model, ignore if ((feature != null) && (feature instanceof EReference)) { EReference ref = (EReference)feature; EClass childClass = (EClass)ref.getEType(); addObjectReps(childClass,childEl,newPath); } } } /** * record a single objectRep for a class on an Element * @param theClass * @param el * @param path * @throws MapperException */ private void addObjectRep(EClass theClass,Element el,String path) throws MapperException { String className = ModelUtil.getQualifiedClassName(theClass); Hashtable<String,Vector<objectRep>> objectReps = allObjectReps.get(className); if (objectReps == null) objectReps = new Hashtable<String,Vector<objectRep>>(); Vector<objectRep> repsForPath = objectReps.get(path); if (repsForPath == null) repsForPath = new Vector<objectRep>(); // use the path as a subset for the objectRep repsForPath.add(new objectRep(el,className,path,this)); objectReps.put(path, repsForPath); allObjectReps.put(className, objectReps); } /** * @return the single entry class to the RMIM * @throws MapperException if there are none, or more than one */ private EClass getEntryClass() throws MapperException { EClass entry = null; /* find the one EClass marked as the entry class, and set it as the root of the tree; * iterate over all packages to find it*/ for (Iterator<EPackage> ip = classModel.getESubpackages().iterator();ip.hasNext();) { EPackage rmimPackage = ip.next(); // iterate over classes in the package for (Iterator<EClassifier> ic = rmimPackage.getEClassifiers().iterator(); ic.hasNext();) { EClassifier ec = ic.next(); if ((ec instanceof EClass) && (ModelUtil.getEAnnotationDetail(ec, "entry") != null)) { if (entry != null) throw new MapperException("More than one entry class in V3 RMIM"); entry = (EClass)ec; } } } if (entry == null) throw new MapperException("no entry classese in V3 RMIM"); return entry; } //-------------------------------------------------------------------------------------------- // Data retrieval methods //-------------------------------------------------------------------------------------------- /** * Vector of objectTokens for all nodes representing objects * in any subclasses of a given class, in all subsets of those subclasses. * @param className - the name of the class * Note this implementation does not address subclasses - not relevant for V3? */ public Vector<objectToken> getAllObjectTokens(String className) throws MapperException { Vector<objectToken> result = new Vector<objectToken>(); Hashtable<String,Vector<objectRep>> oReps = allObjectReps.get(className); if (oReps != null) for (Enumeration<Vector<objectRep>> en = oReps.elements();en.hasMoreElements();) { Vector<objectRep> reps = en.nextElement(); for (Iterator<objectRep> ir = reps.iterator();ir.hasNext();) result.add(ir.next()); } return result; } /** * check that any incoming objectRep is of the form you expect * @param oRep * @throws MapperException */ private void checkObjectRep(objectRep oRep) throws MapperException { String description = " class " + oRep.className() + "; subset: " + oRep.subset(); // only accept objectReps from this reader if (oRep.reader() != this) throw new MapperException("objectRep from another XOReader " + oRep.reader().ms().getMappingSetName() + ": " + description); // the node representing an object must be an Element if (!(oRep.objNode() instanceof Element)) throw new MapperException("Mapped mode is not an element, but is a " + oRep.objNode().getClass().getName() + ": "+ description); // the subset must be a path beginning with ""; if (!(oRep.subset().startsWith("/"))) throw new MapperException("Mapped mode path is not valid: " + description); } /** * String value of a property of some represented object * * @param oRep - the objectToken for the object * @param propertyName - the name of the property */ public String getPropertyValue(objectToken oTok, String propertyName) throws MapperException { if (oTok instanceof objectRep) { objectRep oRep = (objectRep)oTok; checkObjectRep(oRep); Element el = (Element)oRep.objNode(); // special attribute 'textContent' if (propertyName.equals("textContent")) return XMLUtil.getText(el); // special attribute 'element_position' else if (propertyName.equals("element_position")) return XMLUtil.ordinalPosition(el); // all other attributes else return el.getAttribute(propertyName); } else throw new MapperException("Object token is not an objectRep when getting property " + propertyName + " of class " + oTok.className()); } /** * Main method for following associations through mappings; the other two methods in the XOReader * interface can be got by simple calls to this method, as shown in MDLXOReader. * * @param oTok object token for the start object * @param assocName association name, usually composed from the two end role names * @param otherClass qualified class name at the target end of the association * @param thisEnd end of the start object, if the role name is not specified; or -1 if it is * @param otherRole role name leading to the target end; or "" if not specified * @return object tokens for objects reached by the association * @throws MapperException * * For V3 RMIMs, the navigable end */ public Vector<objectToken> getTheAssociatedObjectReps(objectToken oTok, String assocName, String otherClass, int thisEnd, String otherRole) throws MapperException { Vector<objectToken> result = new Vector<objectToken>(); if (!(oTok instanceof objectRep)) throw new MapperException("Object token is not an objectRep when following association " + assocName + " of class " + oTok.className()); objectRep oRep = (objectRep)oTok; checkObjectRep(oRep); Element el = (Element)oRep.objNode(); EClass parent = ModelUtil.getNamedClass(classModel, oTok.className()); if (parent == null) throw new MapperException("Cannot find parent class " + oTok.className()); String roleName = ""; /* a call with role not specified (as used e.g. by writing procedures) * so you need to work out the role name*/ if (otherRole.equals("")) { // usually expect to navigate from end 1 to end 2 if (thisEnd == 1) {roleName = assocName;} // because the other end role name is always "" /* when testing inclusion filters in an XOWriter, the reader is asked * to navigate the association in the opposite direction. Return the one object, * if it has the correct association to this object */ else if (thisEnd == 2) { Node parentNode = oRep.objNode().getParentNode(); String pathToThisObject = oRep.subset(); String pathToParent = removeLastStep(pathToThisObject); // the subset of parent objectRep String step = lastStep(pathToThisObject); // check that the parent class has the named association to the start class EClass parentClass = ModelUtil.getNamedClass(classModel, otherClass); if ((parentClass != null) && (parentClass.getEStructuralFeature(step) != null)) { EReference ref = (EReference)parentClass.getEStructuralFeature(step); EClass child = (EClass)ref.getEType(); if (ModelUtil.getQualifiedClassName(child).equals(oRep.className())) { result.add(new objectRep(parentNode,otherClass,pathToParent,this)); } } // if the step does not match, leave the result Vector empty return result; } } /* call with role name specified */ else { // expect the end to be set (conventionally) to -1 if (thisEnd != -1) throw new MapperException("Expected end -1 for specified role '" + otherRole + "'"); roleName = otherRole; } EStructuralFeature ref = parent.getEStructuralFeature(roleName); if (ref == null) throw new MapperException("Cannot find association " + roleName + " of class " + oTok.className()); String otherEndClassName = ModelUtil.getQualifiedClassName((EClass)((EReference)ref).getEType()); // extend the XPath used as a subset String otherSubset = oRep.subset() + "/" + roleName; Vector<Element> children = XMLUtil.namedChildElements(el, roleName); for (Iterator<Element> ic = children.iterator();ic.hasNext();) { Element child = ic.next(); objectRep cRep = new objectRep(child,otherEndClassName,otherSubset,this); result.add(cRep); } return result; } /** * @param path * @return the path wit the last step removed */ private String removeLastStep(String path) { String result = ""; StringTokenizer st = new StringTokenizer(path,"/"); while (st.hasMoreTokens()) { String step = st.nextToken(); if (st.hasMoreTokens()) result = result + "/" + step; } return result; } /** * @param path * @return the last step of the path */ private String lastStep(String path) { String result = ""; StringTokenizer st = new StringTokenizer(path,"/"); while (st.hasMoreTokens()) result = st.nextToken(); return result; } public String parameterClassName() throws MapperException { return ModelUtil.getQualifiedClassName(getEntryClass()); } //-------------------------------------------------------------------------------------------- // metaData methods //-------------------------------------------------------------------------------------------- /** * @return true if the V3 XML represents objects of this class name (qualified). * Assume that if it is in the Ecore model, the V3 represents it. */ public boolean representsObject(String className) {return (ModelUtil.getNamedClass(classModel, className) != null);} /** * * @param oRep an objectRep * @return true if the class is represented with the subset of the objectRep * (i.e is represented on a node with that path). * The result returned here depends on the XML instance */ private boolean representsObject(objectRep oRep) { Hashtable<String,Vector<objectRep>> objectReps = allObjectReps.get(oRep.className()); return (objectReps.get(oRep.subset())!= null); } /** * @return true if the V3 XML represents the property of the class with qualified name */ public boolean representsProperty(String className,String property) { boolean represents = false; EClass theClass = ModelUtil.getNamedClass(classModel, className); if (theClass != null) { for (Iterator<EAttribute> ia = theClass.getEAllAttributes().iterator();ia.hasNext();) if (ia.next().getName().equals(property)) represents = true; } return represents; } /** * A class always has the same properties, no matter what subset is represented * (i.e what V3 XML node it is represented on) */ public boolean representsProperty(objectRep oRep,String property) { if (representsObject(oRep)) return representsProperty(oRep.className(),property); return false; } /** * if any association exists in the V3 RMIM, it is represented in the XML ITS */ public boolean representsAssociationRole(String class1, String roleName, String class2) { EClass ec1 = ModelUtil.getNamedClass(classModel, class1); EClass ec2 = ModelUtil.getNamedClass(classModel, class2); EStructuralFeature ref = ec1.getEStructuralFeature(roleName); if ((ec1 == null)|(ec2 == null)|(ref == null)) return false; EClass ec3 = (EClass)((EReference)ref).getEType(); return (ec3.isSuperTypeOf(ec2)); } /** * A class always has the same associations, no matter what subset is represented * (i.e what node it is represented on) */ public boolean representsAssociationRole(objectRep oRep, String roleName, String class2) { if (representsObject(oRep)) return representsAssociationRole(oRep.className(), roleName, class2); return false; } /** * When the role name at end 1 is "" (as it always is in the V3 RMIM) the association name * is the same as the other end role name */ public boolean representsAssociation(String class1, String assocName, String class2) { return representsAssociationRole(class1, assocName, class2); } /** * @return all classSets represented for a class, in the instance; * answer depends on the instance */ public Hashtable<String,ClassSet> subsets(String className) { Hashtable<String,ClassSet> subsets = new Hashtable<String,ClassSet>(); Hashtable<String,Vector<objectRep>> objectReps = allObjectReps.get(className); if (objectReps != null) try { for (Enumeration<String> en = objectReps.keys();en.hasMoreElements();) { String sub = en.nextElement(); ClassSet cs = new ClassSet(className, sub); subsets.put(sub, cs); } } catch (Exception ex) {} // exception creating a ClassSet is not expected return subsets; } /** * @return the qualified class names for the classes with the same name in all packages */ public Vector<String> getQualifiedClassNames(String bareClassName) { Vector<String> qualNames = new Vector<String>(); // there is only one level of sub-packages in a V3 RMIM for (Iterator<EPackage> ip = classModel.getESubpackages().iterator();ip.hasNext();) { EPackage pack = ip.next(); EClass ec = (EClass) pack.getEClassifier(bareClassName); if (ec != null) qualNames.add(ModelUtil.getQualifiedClassName(ec)); } return qualNames; } //------------------------------------------------------------------------------------------- // For use by EMFInstanceFactory //------------------------------------------------------------------------------------------- /** * @return classSets of all object mappings in this mapping set (not imported) that * are not inside a containment relation to some other class * which also has an object mapping to the top mapping set * Key = string form of the [class,subset]. * For a V3 RMIM, only the entry class counts. */ public Vector<ClassSet> outerObjectClassSets() { Vector<ClassSet> outers = new Vector<ClassSet>(); try { String className = ModelUtil.getQualifiedClassName(getEntryClass()); Hashtable<String,Vector<objectRep>> objectReps = allObjectReps.get(className); if (objectReps != null) for (Enumeration<String> en = objectReps.keys();en.hasMoreElements();) { String sub = en.nextElement(); ClassSet cs = new ClassSet(className, sub); outers.add(cs); } } catch (Exception ex) {} // exception creating a ClassSet is not expected return outers; } /** * the XOReader which this objectGetter uses * @return */ public XOReader reader() throws MapperException {return this;} //------------------------------------------------------------------------------------------- // XMLWriter interface //------------------------------------------------------------------------------------------- /** * set the XML Output file for the writer * @param xout */ public void setXMLOutputFile(XMLOutputFile xout) { super.setXMLOutputFile(xout); try {setOutputNamespaces();} catch (MapperException ex) {System.out.println("Exception setting output namespaces: " + ex.getMessage());} } /** * write the object model information from the objectGetter (set in the constructor) * to an output XML * * @return the root Element of the created XML document * @exception MDLWriteException - any major problem detected in making the translation */ public Element makeXMLDOM() throws MapperException { xout = new XMLOutputFile(); xout.setNSSet(ms.getNamespaceSet()); //the data source should represent just one object of the entry class String entryClassName = ModelUtil.getQualifiedClassName(getEntryClass()); Vector<objectToken> rootTokens = oGet.getObjects(entryClassName); if (rootTokens.size() != 1) throw new MapperException("Data source represents " + rootTokens.size() + " objects of the RMIM entry class " + entryClassName); // the root element has the same name as the entry class Element root = xout.NSElement(V3NAMESPACEPREFIX, getEntryClass().getName(), V3NAMESPACEURI); // extend it with all child elements etc. root = extendXMLDOM(root,rootTokens.get(0)); xout.setTopOut(root); xout.addNamespaceAttributes(); // remove the element-ordering attributes, while putting the elements in the right order return MappedXMLWriter.orderOutputElements(root); } /** * Extend some Element of an output XML DOM (which represents some object * in the object model, or has an ancestor element which represents that object) * producing a subtree which represents the properties of that object, subordinate * objects related to it, and their properties. * * @param bareElement the Element to be extended * @param oTok objectToken for the parameter class object, which the Element * to be extended (or one of its ancestors) represents * @return the extended Element * @throws MapperException if there is any major problem */ public Element extendXMLDOM(Element bareElement, objectToken oTok) throws MapperException { // find where you are in the V3 RMIM EClass theClass = ModelUtil.getNamedClass(classModel, oTok.className()); if (theClass == null) throw new MapperException("Cannot find class " + oTok.className()); // add attributes or text content to the Element for properties of the object for (Iterator<EAttribute> ia = theClass.getEAllAttributes().iterator();ia.hasNext();) { String attName = ia.next().getName(); try { // will throw a notRepresentedException if the property is not represented in the data source String value = oGet.getPropertyValue(oTok, attName); if (!value.equals("")) { if (attName.equals("textContent")) { Text text = xout.outDoc().createTextNode(value); bareElement.appendChild(text); } else if (attName.equals("element_position")) { bareElement.setAttribute(ElementDefImpl.ELEMENT_POSITION_ATTRIBUTE, value); } else bareElement.setAttribute(attName, value); } } // silent catch if the property is not represented in the data source catch (MapperException ex) {} } // follow all containment references and recursively add child elements for (Iterator<EReference> ir = theClass.getEAllReferences().iterator();ir.hasNext();) { EReference ref = ir.next(); if (ref.isContainment()) try { String otherClass = ModelUtil.getQualifiedClassName((EClass)ref.getEType()); /* The starting end of the association is 1. Because one of the role names is "", * the association name is the same as the role name. * This call can throw a notRepresentedException */ Vector<objectToken> tokens = oGet.getAssociatedObjects(oTok, ref.getName(), otherClass, 1); for (Iterator<objectToken> it = tokens.iterator();it.hasNext();) { Element childEl = xout.NSElement(V3NAMESPACEPREFIX, ref.getName(), V3NAMESPACEURI); extendXMLDOM(childEl,it.next()); bareElement.appendChild(childEl); } } // silent catch if the association is not represented in the data source catch (MapperException ex) {} } return bareElement; } /** * All issues that were noted when running the translation * outer key = string form of root path * Inner key = a unique identifier for the issue * @return nothing - the Java notes no issues */ public Hashtable<String,Hashtable<String,RunIssue>> allRunIssues() { return new Hashtable<String,Hashtable<String,RunIssue>>(); } }