package com.openMap1.mapper.health.actions; import java.util.Enumeration; import java.util.Hashtable; import java.util.Iterator; import java.util.StringTokenizer; import java.util.Vector; import org.eclipse.core.resources.IFile; import org.eclipse.emf.common.util.BasicEList; 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.eclipse.emf.ecore.EcorePackage; import org.eclipse.jface.action.IAction; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.ui.IObjectActionDelegate; import org.w3c.dom.Element; import com.openMap1.mapper.actions.MapperActionDelegate; import com.openMap1.mapper.core.MapperException; import com.openMap1.mapper.health.cda.TrifoliaConstraint; import com.openMap1.mapper.health.cda.TrifoliaTemplate; import com.openMap1.mapper.util.EclipseFileUtil; import com.openMap1.mapper.util.FileUtil; import com.openMap1.mapper.util.GenUtil; import com.openMap1.mapper.util.ModelUtil; import com.openMap1.mapper.util.XMLUtil; public class AddTrifoliaTemplates extends MapperActionDelegate implements IObjectActionDelegate{ // the class model that is being extended, and will be saved to a new location EPackage classModel; // the templates that have been read in private Vector<TrifoliaTemplate> templates; // templates keyed by template id private Hashtable<String,TrifoliaTemplate> templatesById; // key = a template id; value = all EClasses having that template private Hashtable<String,Vector<EClass>> classesByTemplate; // key = an EClass; value = all EReferences to that class private Hashtable<EClass,Vector<EReference>> associationsByTargetClass; // key = a templateId; value = a list of the templateIds that contain it private Hashtable<String,Vector<TrifoliaTemplate>> allContainingTemplates; // content of a csv file with one row per template constraint private Vector<String[]> csvOfConstraints; // column headers for csv file private String[] csvHeader = {"Template_Name","Template_id","XPath","Constraint_id","Card.","Value","Comment","Response"}; public void run(IAction action) { try { if (selection instanceof IStructuredSelection) { Object el = ((IStructuredSelection)selection).getFirstElement(); if (el instanceof IFile) { // (1) get the selected Ecore class model classModel = EclipseFileUtil.getClassModel((IFile)el); // (2) get the Trifolia XML file String[] exts = {"*.xml"}; String path = FileUtil.getFilePathFromUser(targetPart,exts,"Select Trifolia template export file",false); if (path.equals("")) return; Element xmlRoot = XMLUtil.getRootElement(path); if (xmlRoot == null) throw new MapperException("Could not open Trifolia XML"); String topTagName = xmlRoot.getLocalName(); if (!(topTagName.equals("TemplateExport"))) throw new MapperException("Trifolia root element must be 'TemplateExport'"); // (3) read the templates templates = new Vector<TrifoliaTemplate>() ; templatesById = new Hashtable<String,TrifoliaTemplate>(); Vector<Element> templateEls = XMLUtil.namedChildElements(xmlRoot, "Template"); for (int i = 0; i < templateEls.size(); i++) { TrifoliaTemplate template = new TrifoliaTemplate(templateEls.get(i)); templatesById.put(template.oid(), template); templates.add(template); } // (4) pre-index the Ecore class model indexClassModel(); // (5) index the templates by where they occur indexTemplates(); // (6) Create the extra classes and associations createTemplateClasses(); // (7) Prune the sections pruneSections(); // (8) Save the modified Ecore model String sourceModelLocation = classModel.eResource().getURI().toString(); String templatedModelLocation = ModelUtil.addSuffixToFileName(sourceModelLocation,"trifolia"); ModelUtil.savePackage(templatedModelLocation, classModel); // (9) save the csv file of constraints int len = templatedModelLocation.length(); // to strip off 'ecore' and add 'csv' String csvFileLocation = templatedModelLocation.substring(0,len - 5) + "csv"; if (csvFileLocation.startsWith("file:")) csvFileLocation = csvFileLocation.substring(6); FileUtil.writeCSVFile(csvFileLocation, csvOfConstraints); } } } catch (Exception ex) { showMessage("Error","Failed to capture Trifolia templates: " + ex.getMessage()); ex.printStackTrace(); } } /** * * @throws MapperException */ private void createTemplateClasses() throws MapperException { // prepare to make the csv file of constraints csvOfConstraints = new Vector<String[]>(); csvOfConstraints.add(csvHeader); // first pass - pick up templates which are further constraints on C-CDA templates, at document and section level only for (Iterator<TrifoliaTemplate> it = templates.iterator();it.hasNext();) { TrifoliaTemplate template = it.next(); // do nothing for (C-CDA) templates which are already in the templated model if (classesByTemplate.get(template.oid()) == null) { if (template.templateType().equals("document")) makeDocumentBCTPSClass(template); // pick up only templates with a non-empty implied template oid, section level String impliedOID = template.impliedTemplateOid(); if ((!(impliedOID.equals(""))) && (template.templateType().equals("section"))) { template.setHandled(true); // find the classes using the C-CDA template, to be further constrained Vector<EClass> classesToConstrain = classesByTemplate.get(impliedOID); if (classesToConstrain == null) throw new MapperException("Cannot find C-CDA template " + impliedOID + " for " + template.title() + " to constrain."); for (Iterator<EClass> iu = classesToConstrain.iterator();iu.hasNext();) { // this class will be an act clone EClass classToConstrain = iu.next(); // make a template clone and add it to the same package EClass templateClone = makeTemplateClone(classToConstrain,template); // make the nested template classes for section templates makeNestedTemplateClasses(templateClone,template); // find the association to it from an ActRelationship clone, unless it is the root Vector<EReference> refs = associationsByTargetClass.get(classToConstrain); { if (refs.size() > 1) message(refs.size() + " associations to C-CDA class for template " + template.title()); for (int r = 0; r < refs.size(); r++) { EReference refToAct = refs.get(r); // find the ActRelationship parent EClass pinkParent = (EClass)refToAct.eContainer(); // find all associations to the parent (there may be several, from different templated classes) Vector<EReference> parentRefs = associationsByTargetClass.get(pinkParent); if (parentRefs == null) throw new MapperException("No associations to parent C-CDA class for template " + template.title()); for (int p = 0; p < parentRefs.size(); p++) { EReference refToParent = parentRefs.get(p); // find the grandparent EClass EClass grandParent = (EClass)refToParent.eContainer(); // make the new association to an ActRelationship, a new ActRelationship, and a new association to the templated class makeCloneChain(grandParent,refToParent,pinkParent,refToAct,templateClone,template); } } } } } } } } /** * * @param template * @throws MapperException */ private void makeDocumentBCTPSClass(TrifoliaTemplate template) throws MapperException { // the package in which the entry package must be EPackage headerPackage = ModelUtil.getNamedPackage(classModel, "cdaHeader"); if (headerPackage== null) throw new MapperException("No CDA header package"); // General Header constraints is the entry class EClass genHeader = (EClass)headerPackage.getEClassifier("GeneralHeaderConstraints"); if (genHeader== null) throw new MapperException("No general header constraints class"); if (!(ModelUtil.getMIFAnnotation(genHeader, "template").equals(template.impliedTemplateOid()))) throw new MapperException("Unexpected template id for general header constraints"); // make a template clone and add it to the same package EClass templateClone = makeTemplateClone(genHeader,template); // re-define the one entry class ModelUtil.removeMIFAnnotation(genHeader, "entry"); ModelUtil.addMIFAnnotation(templateClone, "entry", "true"); // there are templates beneath the 'component' node, so change its name EStructuralFeature feat = templateClone.getEStructuralFeature("component"); if (feat != null) { feat.setName("component_T"); ModelUtil.addMIFAnnotation(feat, "CDA_Name", "component"); } } /** * recursive descent of BCTPS templates, making template classes and chains of associations * and intermediate classes to them * @param BCTPSClass * @param outerTemplate * @throws MapperException */ private void makeNestedTemplateClasses(EClass BCTPSClass, TrifoliaTemplate outerTemplate) throws MapperException { for (Enumeration<String> en = outerTemplate.nestedTemplates().keys();en.hasMoreElements();) { String nestedTemplateId = en.nextElement(); TrifoliaTemplate template = templatesById.get(nestedTemplateId); if (isBCTPSTemplate(nestedTemplateId)) { String path = outerTemplate.nestedTemplates().get(nestedTemplateId); // follow the path of the template constraint, storing results in arrays EClass[] classes = new EClass[10]; EReference[] refs = new EReference[10]; classes[0] = BCTPSClass; StringTokenizer steps = new StringTokenizer(path, "/"); if (steps.countTokens() < 2) throw new MapperException("Path " + path + " is too short"); int s = 0; boolean following = true; while ((steps.hasMoreTokens()) && (following)) { String step = steps.nextToken(); EStructuralFeature feat = classes[s].getEStructuralFeature(step); if (feat == null) { following = false; message("**** Cannot follow step '" + step + "' of path " + path + " from class " + classes[s].getName()); } else { refs[s] = (EReference)feat; classes[s+1] = (EClass)(refs[s].getEType()); s++; } } // if the whole path was followed successfully, make a clone class and a path to it if (following) { // make a templated clone of the last class in the chain, or a templated class EClass classToCopy = classes[s]; Vector<EClass> templatedClassesToCopy = classesByTemplate.get(template.impliedTemplateOid()); // FIXME; should not pick just the first; pick the one with only one templateId if (templatedClassesToCopy != null) classToCopy = templatedClassesToCopy.get(0); EClass templateClone = makeTemplateClone(classToCopy,template); message("Made template class for " + template.title() + " of type " + template.templateType()); if (s > 2) { message("Long path " + path + " for template " + template.title()); makeLongCloneChain(classes,refs, s, templateClone,template); } // make clone classes and associations for the end of the path else makeCloneChain(classes[s-2],refs[s-2],classes[s-1],refs[s-1],templateClone,template); // recursive step makeNestedTemplateClasses(templateClone,template); } } } } private boolean isBCTPSTemplate(String templateId) { return (templateId.startsWith("2.16.840.1.113883.10.20.30")); } /** * @param classToConstrain * @param template * @return template-constrained class */ private EClass makeTemplateClone(EClass classToConstrain,TrifoliaTemplate template) throws MapperException { // the name of the new class depends only on the template, and begins with 'S_' or 'E_' String newName = template.bookmark(); // clone the class, associations and attributes in the same package as its source EClass tClone = ModelUtil.cloneEClass(classToConstrain, newName); // record that this class has this template id (should also record that it has other template ids - but that does not matter) addClassByTemplate(template.oid(),tClone); // add the new templateId, with the lowest possible 'template_x' key boolean added = false; for (int i = 0; i < 8; i++) if (!added) { String mifKey = "template_" + i; if (i==0) mifKey = "template"; if (ModelUtil.getMIFAnnotation(tClone, mifKey) == null) { added = true; ModelUtil.addMIFAnnotation(tClone, mifKey, template.oid()); } } // reflect the template fixed value constraints Hashtable<String,String> fixedValues = template.allFixedValueConstraints(); for (Enumeration<String> en = fixedValues.keys(); en.hasMoreElements();) { String path = en.nextElement(); // if any path is not valid, just write a message; do not repeat template id constraints if ((checkPath(classToConstrain,path)) && (!(path.endsWith("templateId/@root")))) ModelUtil.addMIFAnnotation(tClone, "constraint:" + path, fixedValues.get(path)); } // reflect the cardinality constraints of the top-level template constraints, in the cloned class for (Iterator<TrifoliaConstraint> iu = template.getConstraints().iterator();iu.hasNext();) { TrifoliaConstraint constraint = iu.next(); // add to the csv file for this constraint and all nested constraints constraint.addToCSVFile(tClone.getName(), template.oid(),csvOfConstraints); String featName = constraint.context(); if (featName.startsWith("@")) featName = featName.substring(1); EStructuralFeature feat = tClone.getEStructuralFeature(featName); if (feat != null) { if (constraint.minCardinality() == 1) { feat.setLowerBound(1); // message(tClone.getName() + "." + featName + " lower 1"); } if ((constraint.maxCardinality() == 1) && (feat instanceof EReference)) { ((EReference)feat).setUpperBound(1); // message(tClone.getName() + "." + featName + " upper 1"); } } else if (feat == null) message("Cannot find feature '" + featName + "' of class " + tClone.getName()); } return tClone; } /** * check that a path is valid from a class * @param theClass * @param path * @return false, and write a message, if the path is not valid */ private boolean checkPath(EClass theClass,String path) { boolean result = true; StringTokenizer steps = new StringTokenizer(path,"/"); EClass current = theClass; while ((steps.hasMoreTokens()) && (result)) { String step = steps.nextToken(); String featName = step; if (step.startsWith("@")) featName = featName.substring(1); EStructuralFeature feat = current.getEStructuralFeature(featName); if (feat == null) { result = false; message("*** Cannot follow path " + path + " from class " + theClass.getName() + " at step " + step); } if ((!step.startsWith("@")) && (result)) current = (EClass)(((EReference)feat).getEType()); } return result; } /** * * @param grandParent * @param refToParent * @param pinkParent * @param refToAct * @param classToConstrain * @param template */ private void makeCloneChain(EClass grandParent, EReference refToParent, EClass pinkParent, EReference refToAct, EClass templateClone, TrifoliaTemplate template) { // make the intermediate ActRelationship class String pinkName = pinkClassName(pinkParent,template); EClass clonedPinkParent = ModelUtil.cloneEClass(pinkParent, pinkName); // add the association from the grandparent String pinkAssocName = GenUtil.initialLowerCase(pinkName); EReference clonedRefToParent = ModelUtil.cloneEReference(refToParent); clonedRefToParent.setName(pinkAssocName); clonedRefToParent.setEType(clonedPinkParent); // if the original ref to the parent had no CDA name change, the cloned ERef needs one if (ModelUtil.getMIFAnnotation(refToParent, "CDA_Name") == null) ModelUtil.addMIFAnnotation(clonedRefToParent, "CDA_Name", refToParent.getName()); grandParent.getEStructuralFeatures().add(clonedRefToParent); // alter the association from the ActRelationship to the Act, to have the required name and target class String previousName = refToAct.getName(); EReference refToAlter = (EReference)clonedPinkParent.getEStructuralFeature(previousName); refToAlter.setName(GenUtil.initialLowerCase(templateClone.getName())); // if the original ref to the act had no CDA name change, the cloned ERef needs one if (ModelUtil.getMIFAnnotation(refToAct, "CDA_Name") == null) ModelUtil.addMIFAnnotation(refToAlter, "CDA_Name", previousName); // alter the target class refToAlter.setEType(templateClone); } /** * for a chain of length len (typically 2 or 4) the classes are * classes[0] classes[1] templateClone, or * classes[0] classes[1] classes[2] classes[3] templateClone, * with 2 or 4 refs, from each class to the next. * Make copies of all classes except [0] and templateClone, and copies of all refs * * @param classes * @param refs * @param len * @param templateClone * @param template */ private void makeLongCloneChain(EClass[] classes, EReference[] refs, int len, EClass templateClone, TrifoliaTemplate template) { EClass[] newClass = new EClass[10]; // make the chain of classes for (int d = 0; d < len; d++) { String newClassName = makeNewClassName(d,len,classes[d],template); if (d == 0) newClass[d] = classes[d]; else newClass[d] = ModelUtil.cloneEClass(classes[d], newClassName); } // make the chain of associations for (int d = 0; d < len; d++) { EClass source = newClass[d]; EClass target = templateClone; if (d < len - 1) target = newClass[d+1]; EReference newRef = ModelUtil.cloneEReference(refs[d]); String refName = GenUtil.initialLowerCase(target.getName()); newRef.setName(refName); newRef.setEType(target); // if the original ref had no CDA name change, the cloned ERef needs one if (ModelUtil.getMIFAnnotation(refs[d], "CDA_Name") == null) ModelUtil.addMIFAnnotation(newRef, "CDA_Name", refs[d].getName()); source.getEStructuralFeatures().add(newRef); } } // index to make sure cloned classes have different names private int tp_index = 1; private String makeNewClassName(int d, int len, EClass oldClass, TrifoliaTemplate template) { String newClassName = oldClass.getName(); if (d > 0) { // for len = 4 this covers d = 1 (ActRelationship) and 2 (Act) if (d < len - 1) { newClassName = newClassName + "_TP" + tp_index; tp_index++; } // for len = 4 this covers d = 3 (ActRelationship) else newClassName = newClassName + "_" + template.bookmark(); } return newClassName; } /** * * @param pinkParent * @param template * @return a suitable name for an ActRelationship class */ private String pinkClassName(EClass pinkParent, TrifoliaTemplate template) { // strip off any 'S_' or 'E_' from template bookmark; String newName = template.bookmark(); if ((newName.startsWith("S_"))||(newName.startsWith("E_"))) newName = newName.substring(2); String pinkName = pinkParent.getName(); if (pinkName.startsWith("Component")) newName = "Component_" + newName; else if (pinkName.startsWith("EntryRelationship")) newName = "EntryRel_" + newName; else if (pinkName.startsWith("Entry")) newName = "Entry_" + newName; else newName = pinkName + newName; return newName; } //------------------------------------------------------------------------------------------------------------------- // Pre-indexing //------------------------------------------------------------------------------------------------------------------- /** * * @throws MapperException */ private void indexClassModel() throws MapperException { classesByTemplate = new Hashtable<String,Vector<EClass>>(); associationsByTargetClass = new Hashtable<EClass,Vector<EReference>>(); int templateUses = 0; int refCount = 0; int classCount = 0; String isRMIM = ModelUtil.getMIFAnnotation(classModel, "RMIM"); if (!((isRMIM != null) && (isRMIM.equals("true")))) throw new MapperException("Class model is not an RMIM-style model"); for (Iterator<EPackage> it = classModel.getESubpackages().iterator();it.hasNext();) { EPackage subPackage = it.next(); for (Iterator<EClassifier> iu = subPackage.getEClassifiers().iterator();iu.hasNext();) { EClassifier next = iu.next(); if (next instanceof EClass) { EClass theClass = (EClass)next; classCount++; // index classes by their template ids. // assume there can be no more than 6 templates on a class; record template annotations for (int i = 0; i < 7; i++) { // how templates are described in MIF annotations String templateKey = "template_" + i; if (i == 0) templateKey = "template"; String templateId = ModelUtil.getMIFAnnotation(theClass, templateKey); if (templateId != null) { addClassByTemplate(templateId,theClass); templateUses++; } } // index associations by their target class for (Iterator<EStructuralFeature> iv = theClass.getEStructuralFeatures().iterator();iv.hasNext();) { EStructuralFeature feat = iv.next(); if (feat instanceof EReference) { EReference ref = (EReference)feat; EClass target = (EClass)ref.getEType(); if (target != null) { Vector<EReference> references = associationsByTargetClass.get(target); if (references == null) references = new Vector<EReference>(); references.add(ref); associationsByTargetClass.put(target, references); refCount++; } } } } } } message("Template uses: " + templateUses + "; ref count: " + refCount + "; class count: " + classCount); } private void addClassByTemplate(String templateId, EClass theClass) { Vector<EClass> classes = classesByTemplate.get(templateId); if (classes == null) classes = new Vector<EClass>(); classes.add(theClass); classesByTemplate.put(templateId, classes); } /** * index all template nesting constraints */ private void indexTemplates() { allContainingTemplates = new Hashtable<String,Vector<TrifoliaTemplate>>(); int links = 0; for (Iterator<TrifoliaTemplate> it = templates.iterator();it.hasNext();) { TrifoliaTemplate template = it.next(); Hashtable<String,String> containedTemplatePaths = template.nestedTemplates(); for (Enumeration<String> en = containedTemplatePaths.keys();en.hasMoreElements();) { String containedOid = en.nextElement(); Vector<TrifoliaTemplate> outerTemplates = allContainingTemplates.get(containedOid); if (outerTemplates == null) outerTemplates = new Vector<TrifoliaTemplate>(); outerTemplates.add(template); allContainingTemplates.put(containedOid, outerTemplates); links++; } } message("Template nesting constraints: " + links); } /** * retain only a few sections under StructuredBody */ private void pruneSections() throws MapperException { // find the 'StructuredBody' class in some package EClass sBody = null; for (Iterator<EPackage> it = classModel.getESubpackages().iterator();it.hasNext();) { EClassifier next = it.next().getEClassifier("StructuredBody"); if ((next != null) && (next instanceof EClass)) sBody = (EClass)next; } if (sBody == null) throw new MapperException("Cannot find StructuredBody class"); // make a List of names of all the features we want to retain Vector<String> wantedFeatures = new Vector<String>(); Vector<EStructuralFeature> allFeatures = new Vector<EStructuralFeature>(); for (Iterator<EStructuralFeature> it = sBody.getEStructuralFeatures().iterator();it.hasNext();) { EStructuralFeature feat = it.next(); allFeatures.add(feat); String featName = feat.getName(); if (feat instanceof EAttribute) wantedFeatures.add(featName); else if (feat instanceof EReference) { EReference ref = (EReference)feat; EClass target = (EClass)ref.getEType(); if (featName.contains("BCTPS")) wantedFeatures.add(featName); else if (featName.contains("AllergiesSectionEntriesOptional")) wantedFeatures.add(featName); else if (featName.contains("SocialHistorySection")) wantedFeatures.add(featName); else if (featName.equals("typeId")) wantedFeatures.add(featName); else if (target.getEPackage().getName().equals("datatypes")) wantedFeatures.add(featName); } } // remove all EReferences not marked for retention for (Iterator<EStructuralFeature> it = allFeatures.iterator();it.hasNext();) { EStructuralFeature feat = it.next(); if (!GenUtil.inVector(feat.getName(), wantedFeatures)) sBody.getEStructuralFeatures().remove(feat); } } private void message(String s) {System.out.println(s);} }