package com.openMap1.mapper.health.actions; /** * This action is used on a mapping set to some V3 RMIM. * Wherever the mapping set has a mapping to some RMIM class, * and where the annotations on that class in the ECore model say * that some RIM structural attribute of the class has a fixed value, * this command adds a fixed value property mapping on the same node as the class * mapping. * * The effect is that an RMIM instance made from the mapped XML through the * mappings will have the correct values of RIM structural attributes, for all RMIM * class instances. * * Conversely, when writing the XML, the fixed values are required on an RMIM instance for * the XML writer to make the mapped node. * * When the RMIM is a CDA RMIM that has been constrained by templates, then for every node * in the XML mapped to a templated class, the template constraints (annotations on the templated * class) lead to fixed mappings to descendant classes, associations, and fixed properties */ import java.util.Iterator; import java.util.StringTokenizer; import org.eclipse.emf.common.util.EMap; import org.eclipse.emf.ecore.EAnnotation; import org.eclipse.emf.ecore.EAttribute; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EPackage; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.edit.domain.EditingDomain; import org.eclipse.jface.action.IAction; import org.eclipse.ui.IObjectActionDelegate; import com.openMap1.mapper.actions.AddMapperEditorActions; import com.openMap1.mapper.actions.MakeAssociationMappingAction; import com.openMap1.mapper.actions.MakeObjectMappingAction; import com.openMap1.mapper.actions.MapperActionDelegate; import com.openMap1.mapper.core.MapperException; import com.openMap1.mapper.presentation.MapperEditor; import com.openMap1.mapper.structures.MappableAssociation; import com.openMap1.mapper.util.ModelUtil; import com.openMap1.mapper.views.WorkBenchUtil; import com.openMap1.mapper.health.commands.MapStructuralAttributesCommand; import com.openMap1.mapper.AssocEndMapping; import com.openMap1.mapper.AssocMapping; import com.openMap1.mapper.ElementDef; import com.openMap1.mapper.FixedPropertyValue; import com.openMap1.mapper.MappedStructure; import com.openMap1.mapper.MapperFactory; import com.openMap1.mapper.MapperPackage; import com.openMap1.mapper.MappingCondition; import com.openMap1.mapper.NodeMappingSet; import com.openMap1.mapper.ObjMapping; import com.openMap1.mapper.ValueCondition; /** * This action is used on a mapping set to some V3 RMIM. * Wherever the mapping set has a mapping to some RMIM class, * and where the annotations on that class in the ECore model say * that some RIM structural attribute of the class has a fixed value, * this command adds a fixed value property mapping on the same node as the class * mapping. * * The effect is that an RMIM instance made from the mapped XML through the * mappings will have the correct values of RIM structural attributes, for all RMIM * class instances. * * Conversely, when writing the XML, the fixed values are required on an RMIM instance for * the XML writer to make the mapped node. * * When the RMIM is a CDA RMIM that has been constrained by templates, then for every node * in the XML mapped to a templated class, the template constraints (annotations on the templated * class) lead to fixed mappings to descendant classes, associations, and fixed properties */ public class MapRIMStructuralAttributesAction extends MapperActionDelegate implements IObjectActionDelegate{ private EditingDomain domain; public void run(IAction action) { try { // (1) Check if the editor is opened; if not, open it MapperEditor me = OpenMapperEditor(selection); if (me == null) return; domain = me.editingDomain(); MappedStructure ms = WorkBenchUtil.mappingRoot(me); addV3FixedValues(ms); } catch (Exception ex) { ex.printStackTrace(); showMessage("Failed to map RIM Structural attributes or template constraints: " + ex.getMessage()); } } public void addV3FixedValues(MappedStructure ms) throws MapperException { // (2) check that the mappings are to an RMIM class model EPackage classModel = ms.getClassModelRoot(); String RMIMtype = ModelUtil.getEAnnotationDetail(classModel, "RMIM"); boolean isRMIM = ((RMIMtype != null) && (RMIMtype.equals("true"))); if (!isRMIM) { showMessage("The mapped class model is not an RMIM class model."); return; } // (3) Make and execute one composite command to add the RIM Structural Attributes MapStructuralAttributesCommand msac = new MapStructuralAttributesCommand(domain,ms); domain.getCommandStack().execute(msac); /* (4) if the RMIM is templated, add the fixed mappings required by the templates, * in a series of separate commands */ String templated = ModelUtil.getEAnnotationDetail(classModel, "templated"); boolean isTemplated = ((templated != null) && (templated.equals("true"))); if (isTemplated) mapTemplateConstraints(ms, classModel); } /** * Find all nodes mapped to classes that have template annotations on them, * and make mappings to enforce the template constraints in an RMIM got * by reading the mapped XML * @param ms the mapped structure to which the fixed mappings will be added * @param classModel the RMIM class model, with annotations for template constraints * @throws MapperException */ private void mapTemplateConstraints(MappedStructure ms, EPackage classModel) throws MapperException { // iterate over all object mappings in the mapping set for (Iterator<EObject> it = ModelUtil.getEObjectsUnder(ms, MapperPackage.eINSTANCE.getObjMapping()).iterator();it.hasNext();) { ObjMapping om = (ObjMapping)it.next(); // find the mapped class and check if it is templated EClass mappedClass = ModelUtil.getNamedClass(classModel, om.getQualifiedClassName()); if (mappedClass != null) { // iterate over all constraint annotations for (Iterator<EAnnotation> iz = mappedClass.getEAnnotations().iterator();iz.hasNext();) { EAnnotation ea = iz.next(); EMap<String,String> ed = ea.getDetails(); for (Iterator<String> ik = ed.keySet().iterator(); ik.hasNext();) { String key = ik.next(); if (key.startsWith("constraint")) { // find the suffix, such as "" or "_1" etc, of the constraint String suffix = getSuffix(key); // there should be a template on the class with the same suffix String templateId = ModelUtil.getEAnnotationDetail(mappedClass, "template" + suffix); if (templateId == null) throw new MapperException("Class " + mappedClass.getName() + " has no template defined as supplying its constraints 'constraint" + suffix + "'"); String path = key.substring(("constraint" + suffix + ":").length()); String attValue = ed.get(key); applyTemplateConstraint(ms, classModel, mappedClass, om, path, attValue, templateId); } } } } } } /** * add all the fixed mappings necessary to make the RMIM instance * obey one template constraint. * @param ms the MappedStructure of all mappings (assumed no imports) * @param mappedClass the mapped class with a template constraint * @param om an object mapping for the class * @param path the RMIM association from the mapped class to the constrained property * @param attValue the value set by the constraint * @param templateId id of thhe template supplying the constraint */ private void applyTemplateConstraint(MappedStructure ms, EPackage classModel, EClass mappedClass, ObjMapping om, String path, String attValue, String templateId) throws MapperException { /* if the path has any association steps before its final attribute step, * traverse these associations to find the last class already mapped to any node in the subtree * below the node mapped to the class, with the required association mappings; this will often be the class itself */ ObjMapping lastMapping = om; boolean foundChildMapping = true; String shortPath = ""; StringTokenizer steps = new StringTokenizer(path,"/"); while (steps.hasMoreTokens()) { String step = steps.nextToken(); if (step.startsWith("@")) shortPath = shortPath + step; else { if (foundChildMapping) { ObjMapping nextObjMapping = getDescendantObjMapping(lastMapping,step,classModel); if (nextObjMapping != null) lastMapping = nextObjMapping; else foundChildMapping = false; } if (!foundChildMapping) shortPath = shortPath + step + "/"; } } // end of loop over steps in the full path // make the mappings, starting at the last class in the trail that is already mapped makeRequiredMappings(ms,classModel,lastMapping,shortPath,attValue,templateId); } /** * @param lastMapping an object mapping * @param step an association (role name) from the mapped class * @return any mapping to the class at the end of the association, in the subtree * below the mapped node, which also has an association mapping from the first mapped * class with the correct subsets; or null if there is none */ private ObjMapping getDescendantObjMapping(ObjMapping lastMapping,String step, EPackage classModel) throws MapperException { ObjMapping result = null; ElementDef mappedElement = (ElementDef)lastMapping.eContainer().eContainer(); EClass mappedClass = ModelUtil.getNamedClass(classModel, lastMapping.getQualifiedClassName()); EReference ref = getAssociationWithPossiblyChangedName(mappedClass,step); if (ref == null) throw new MapperException("Found no association '" + step + "' from class " + mappedClass.getName()); EClass target = (EClass)ref.getEType(); String targetClassName = ModelUtil.getQualifiedClassName(target); // find all object mappings to the child class under the original mapped node for (Iterator<EObject> it = ModelUtil.getEObjectsUnder(mappedElement, MapperPackage.eINSTANCE.getObjMapping()).iterator();it.hasNext();) { ObjMapping childMapping = (ObjMapping)it.next(); String mappedClassName = childMapping.getQualifiedClassName(); if (mappedClassName.equals(targetClassName)) { // check for an association mapping on the same node, with the required classes, subsets and association name NodeMappingSet nms = (NodeMappingSet)childMapping.eContainer(); for (Iterator<AssocMapping> ia = nms.getAssociationMappings().iterator();ia.hasNext();) { AssocMapping am = ia.next(); AssocEndMapping ae1 = am.getMappedEnd1(); AssocEndMapping ae2 = am.getMappedEnd2(); // end 1 is the outer end if ((ae1.getQualifiedClassName().equals(lastMapping.getQualifiedClassName())) && (ae1.getSubset().equals(lastMapping.getSubset())) && (ae2.getQualifiedClassName().equals(childMapping.getQualifiedClassName())) && (ae2.getSubset().equals(childMapping.getSubset())) && (ae2.getMappedRole().equals(ref.getName()))) result = childMapping; } } } return result; } /** * @param mappedClass * @param step an association name - which is the original in CDA before name changes were made * @return */ private EReference getAssociationWithPossiblyChangedName(EClass mappedClass,String step) { // first try to follow the association with unchanged name EStructuralFeature feature = mappedClass.getEStructuralFeature(step); if (feature != null) return (EReference)feature; // if there is no association with this name, look for an association which had that name before it was changed else if (feature == null) { for (Iterator<EReference> ir = mappedClass.getEAllReferences().iterator();ir.hasNext();) { EReference ref = ir.next(); String originalName = ModelUtil.getEAnnotationDetail(ref, "CDA_Name"); if ((originalName != null) && (step.equals(originalName))) return ref; } } return null; } /** * Make all the new mappings required for this template, on one node * (the node containing the last object mapping that already exists on the trail) * @param ms the mapped structure * @param classModel the CDA constrained RMIM * @param lastMapping the last mapping on the trail from the templated class which already exists * @param shortPath the path of associations and final name of the constrained attribute * @param attValue the value the attribute is to have * @param temmplateId id of the template that gave this constraint */ private void makeRequiredMappings(MappedStructure ms, EPackage classModel, ObjMapping lastMapping,String shortPath, String attValue, String templateId) throws MapperException { // description, to go on all added mappings String description = "from template " + templateId; // node to put all the new mappings on ElementDef mappedNode = (ElementDef)lastMapping.eContainer().eContainer(); // current mapped class EClass mappedClass = ModelUtil.getNamedClass(classModel, lastMapping.getQualifiedClassName()); // current object mapping ObjMapping currentObjMapping = lastMapping; // previous fixed value of property mapping String previousFixedValue = ""; StringTokenizer steps = new StringTokenizer(shortPath,"/"); while (steps.hasMoreTokens()) { // subset of current object mapping String parentSubset = currentObjMapping.getSubset(); String step = steps.nextToken(); // final step; add the property mapping if (step.startsWith("@")) { String attName = step.substring(1); EStructuralFeature feature = mappedClass.getEStructuralFeature(attName); if ((feature != null) && (feature instanceof EAttribute)) { // do not add a fixed value mapping if there is one already for this attribute boolean hasFixedValue = false; for (Iterator<FixedPropertyValue> iv = currentObjMapping.getFixedPropertyValues().iterator();iv.hasNext();) { FixedPropertyValue fpv = iv.next(); if (fpv.getMappedProperty().equals(attName)) { hasFixedValue = true; previousFixedValue = fpv.getFixedValue(); } } // check that the template constraint is not trying ot change the fixed value if (hasFixedValue) { if (!previousFixedValue.equals(attValue)) throw new MapperException("Attempt to change the fixed value of property '" + attName + "' of class " + mappedClass.getName() + " from '" + previousFixedValue + "' to '" + attValue + "' by template " + templateId); } // add the fixed value property mapping else if (!hasFixedValue) { FixedPropertyValue fpv = MapperFactory.eINSTANCE.createFixedPropertyValue(); fpv.setMappedProperty(attName); fpv.setFixedValue(attValue); fpv.setValueType("string"); currentObjMapping.getFixedPropertyValues().add(fpv); System.out.println("Setting property " + attName + " of class " + mappedClass.getName() + " to '" + attValue + "'"); } } else throw new MapperException("Cannot find attribute '" + attName + "' of class " + mappedClass.getName() + " constrained by template " + templateId); } // non-final association steps; add the object mapping and association mapping else if (!step.startsWith("@")) { EReference ref = getAssociationWithPossiblyChangedName(mappedClass,step); if (ref != null) { // make the object mapping for the child class, and then find it EClass childClass = (EClass)ref.getEType(); String childPackageName = childClass.getEPackage().getName(); String childSubset = AddMapperEditorActions.nextSubset(ms, ModelUtil.getQualifiedClassName(childClass)); new MakeObjectMappingAction(domain, mappedNode,childClass.getName(), childPackageName, childSubset,description).run(); ObjMapping childObjMapping = getObjectMapping(mappedNode,childClass.getName(), childPackageName, childSubset); if (childObjMapping == null) throw new MapperException("Cannot find object mapping just made for class " + childClass.getName()); // copy any value conditions from the current object mapping to the new object mapping copyValueConditions(currentObjMapping,childObjMapping); // make the association mapping to the child class, required for the child class boolean makeRequired = true; MappableAssociation mass = new MappableAssociation(mappedClass,parentSubset, childClass, childSubset,ref,makeRequired); new MakeAssociationMappingAction(domain,mappedNode,mass,description).run(); // be ready for the next step - association or property mappedClass = childClass; currentObjMapping = childObjMapping; } else throw new MapperException("Cannot find association '" + step + "' of class " + mappedClass.getName()+ " in constraint from template " + templateId); } } } /** * @param mappedNode * @param className * @param packageName * @param subset * @return an object mapping for the class, package and subset on the node, if it exists; * otherwise null */ private ObjMapping getObjectMapping(ElementDef mappedNode,String className, String packageName, String subset) { ObjMapping om = null; for (Iterator<ObjMapping> im = mappedNode.getNodeMappingSet().getObjectMappings().iterator();im.hasNext();) { ObjMapping next = im.next(); if ((next.getMappedClass().equals(className)) && (next.getMappedPackage().equals(packageName)) && (next.getSubset().equals(subset))) om = next; } return om; } /** * copy all value conditions from the current object mapping to the new object mapping */ private void copyValueConditions(ObjMapping currentObjMapping,ObjMapping childObjMapping) { for (Iterator<MappingCondition> im = currentObjMapping.getMappingConditions().iterator();im.hasNext();) { MappingCondition mc = im.next(); if (mc instanceof ValueCondition) { ValueCondition vc = (ValueCondition)mc; ValueCondition vcNew = MapperFactory.eINSTANCE.createValueCondition(); vcNew.setLeftPath(vc.getLeftPath()); vcNew.setDescription(vc.getDescription()); vcNew.setLeftFunction(vc.getLeftFunction()); vcNew.setRightValue(vc.getRightValue()); vcNew.setTest(vc.getTest()); childObjMapping.getMappingConditions().add(vcNew); } } } /** * @param key a key such as 'constraint_1:code/@code' * @return the suffix after 'constraint' and before the ':' */ private String getSuffix(String key) { StringTokenizer st = new StringTokenizer(key,":"); return (st.nextToken().substring("constraint".length())); } }