package com.openMap1.mapper.actions; import java.util.HashMap; import java.util.Hashtable; import java.util.Iterator; import java.util.Map; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EPackage; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; import org.eclipse.emf.ecore.xmi.XMLResource; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IAction; import com.openMap1.mapper.AssocEndMapping; import com.openMap1.mapper.AssocMapping; import com.openMap1.mapper.AttributeDef; import com.openMap1.mapper.ElementDef; import com.openMap1.mapper.FixedPropertyValue; import com.openMap1.mapper.GlobalMappingParameters; import com.openMap1.mapper.MappedStructure; import com.openMap1.mapper.MapperFactory; import com.openMap1.mapper.MappingCondition; import com.openMap1.mapper.NodeMappingSet; import com.openMap1.mapper.ObjMapping; import com.openMap1.mapper.PropMapping; import com.openMap1.mapper.ValueCondition; import com.openMap1.mapper.core.MapperException; import com.openMap1.mapper.presentation.MapperEditor; import com.openMap1.mapper.util.FileUtil; import com.openMap1.mapper.views.ClassModelView; import com.openMap1.mapper.views.LabelledEClass; import com.openMap1.mapper.views.WorkBenchUtil; public class MappingProjectionAction extends Action implements IAction{ // the current mapper editor, with a full CDA mapping set open private MapperEditor mapperEditor; // the source mapped structure, open in the mapper editor (a full CDA mapped onto a green class model) private MappedStructure sourceMappedStructure; // the middle mapping structure, identified by the user (a green CDA, mapped onto some other class model such as FHIR) private MappedStructure middleMappedStructure; // the result mapping structure, holding the projected mappings private MappedStructure resultMappedStructure; public MappingProjectionAction() { super("Make Mapping Projections"); } /** * perform the menu action */ public void run() { mapperEditor = WorkBenchUtil.getOpenMapperEditor(); if (mapperEditor != null) { sourceMappedStructure = WorkBenchUtil.mappingRoot(mapperEditor); if (sourceMappedStructure.getRootElement() != null) try { // (1) get a location for the middle green mapping set from the user String[] modelExts= {"*.mapper"}; final String mappingSetPath = FileUtil.getFilePathFromUser(mapperEditor,modelExts,"Select mappings from green XML to other class model",false); if (mappingSetPath.equals("")) return; URI mappingSetURI = FileUtil.URIFromPath(mappingSetPath); middleMappedStructure = FileUtil.getMappingSet(mappingSetURI); // (2) check that the green tree structure of the middle mapping set matches the class model of the source mapping set checkClassModelMatches(); message("Mapped class model location: " + middleMappedStructure.getUMLModelURL()); // (3) get a location for the new projected mapping set from the user final String newMappingSetPath = FileUtil.getFilePathFromUser(mapperEditor,modelExts,"Select a location for the new projected mapping set",true); if (newMappingSetPath.equals("")) return; URI newMappingSetURI = FileUtil.URIFromPath(newMappingSetPath); // (4) create the new projected mapping set ResourceSet resourceSet = new ResourceSetImpl(); Resource resource = resourceSet.createResource(newMappingSetURI); resultMappedStructure = MapperFactory.eINSTANCE.createMappedStructure(); resource.getContents().add(resultMappedStructure); // (5) make the result mapped structure have the same XML structure as the source makeResultStructure(); // (6) create the appropriate mappings in the result mapped structure makeProjectedMappings(); // (8) Save the resource Map<Object, Object> options = new HashMap<Object, Object>(); options.put(XMLResource.OPTION_ENCODING, "UTF-8"); resource.save(options); } catch (Exception ex) { ex.printStackTrace(); WorkBenchUtil.showMessage("Error projecting mappings",ex.getMessage()); } } } /** * check that the tree structure of the middle mapping set matches the class model of the source mapping set */ private void checkClassModelMatches() throws MapperException { EPackage sourceClassModel = sourceMappedStructure.getClassModelRoot(); LabelledEClass entryClass = ClassModelView.getRootLabelledEClass(sourceClassModel); ElementDef root = middleMappedStructure.getRootElement(); if (!entryClass.eClass().getName().equals(root.getName())) throw new MapperException("Entry class name '" + entryClass.eClass().getName() + "' does not match mapping set entry node '" + root.getName() + "'"); checkChildMatches(entryClass,root); } /** * recursive descent to check that a mapped class model matches a mapped structure * @param lClass * @param elDef * @throws MapperException */ private void checkChildMatches(LabelledEClass lClass, ElementDef elDef) throws MapperException { int classChildren = lClass.getChildren().size(); int nodeChildren = elDef.getChildElements().size(); if (classChildren < nodeChildren) throw new MapperException("Class " + lClass.eClass().getName() + " has " + classChildren + " child classes, but the mapping node has " + nodeChildren + " child nodes."); // for each child node, find the mapping node which matches the association name for (Iterator<LabelledEClass> it = lClass.getChildren().iterator(); it.hasNext();) { LabelledEClass childClass = it.next(); String childName = childClass.associationName(); ElementDef childNode = elDef.getNamedChildElement(childName); if (childNode != null) checkChildMatches(childClass,childNode); // missing nodes in the middle mapping set are not a problem // else throw new MapperException("Class " + childName + " has no corresponding mapped node."); } } /** * make the result mapped structure have the same XML tree as the source. */ private void makeResultStructure() throws MapperException { ElementDef sourceRoot = sourceMappedStructure.getRootElement(); ElementDef resultRoot = copyNodeTree(sourceRoot); resultMappedStructure.setRootElement(resultRoot); resultMappedStructure.setClassModelRoot(middleMappedStructure.getClassModelRoot()); resultMappedStructure.setUMLModelURL(middleMappedStructure.getUMLModelURL()); GlobalMappingParameters sourceParams = sourceMappedStructure.getMappingParameters(); GlobalMappingParameters resultParams = sourceParams.cloneNamespacesOnly(); resultParams.setWrapperClass(sourceParams.getWrapperClass()); resultMappedStructure.setMappingParameters(resultParams); } /** * recursive descent of a mapping structure tree, copying all ElementDefs and AttributeDefs * @param sourceEl * @return */ private ElementDef copyNodeTree(ElementDef sourceEl) { ElementDef copyEl = MapperFactory.eINSTANCE.createElementDef(); copyEl.setName(sourceEl.getName()); copyEl.setMinMultiplicity(sourceEl.getMinMultiplicity()); copyEl.setMaxMultiplicity(sourceEl.getMaxMultiplicity()); copyEl.setDescription(sourceEl.getDescription()); copyEl.setType(sourceEl.getType()); for (Iterator<AttributeDef> it = sourceEl.getAttributeDefs().iterator();it.hasNext();) { AttributeDef sourceAtt = it.next(); AttributeDef copyAtt = MapperFactory.eINSTANCE.createAttributeDef(); copyAtt.setName(sourceAtt.getName()); copyAtt.setMinMultiplicity(sourceAtt.getMinMultiplicity()); copyAtt.setDescription(sourceAtt.getDescription()); copyAtt.setType(sourceAtt.getType()); copyEl.getAttributeDefs().add(copyAtt); } for (Iterator<ElementDef> it = sourceEl.getChildElements().iterator();it.hasNext();) copyEl.getChildElements().add(copyNodeTree(it.next())); return copyEl; } /** * */ private void makeProjectedMappings() throws MapperException { ElementDef sourceElDef = sourceMappedStructure.getRootElement(); ElementDef middleElDef = middleMappedStructure.getRootElement(); ElementDef resultElDef = resultMappedStructure.getRootElement(); projectMappings(sourceElDef,middleElDef,resultElDef); } /** * * @param sourceElDef * @param middleElDef * @param resultElDef */ private void projectMappings(ElementDef sourceElDef,ElementDef middleElDef,ElementDef resultElDef) throws MapperException { ElementDef nextMiddleElDef = middleElDef; ObjMapping sourceObjMapping = null; AssocMapping sourceAssocMapping = null; NodeMappingSet sourceNMS = sourceElDef.getNodeMappingSet(); if (sourceNMS != null) { // full => green mappings have at most one object mapping and one association mapping per node int nAssocMappings = sourceNMS.getAssociationMappings().size(); if (nAssocMappings > 1) throw new MapperException("More than one association mapping at source path " + sourceElDef.getPath()); if (nAssocMappings == 1) { sourceAssocMapping = sourceNMS.getAssociationMappings().get(0); String assocName = sourceAssocMapping.getMappedEnd2().getMappedRole(); nextMiddleElDef = middleElDef.getNamedChildElement(assocName); if (nextMiddleElDef == null) throw new MapperException("Cannot find child '" + assocName + "' of node at green path " + middleElDef.getPath() + " and full path " + sourceElDef.getPath()); } // full => green mappings have at most one object mapping per node, and if so they may have one mapping of an attribute 'textContent' int nObjMappings = sourceNMS.getObjectMappings().size(); if (nObjMappings > 1) throw new MapperException("More than one object mapping at source path " + sourceElDef.getPath()); if (nObjMappings == 1) { sourceObjMapping = sourceNMS.getObjectMappings().get(0); NodeMappingSet middleNMS = nextMiddleElDef.getNodeMappingSet(); if (middleNMS != null) { transferMappings(resultElDef,sourceObjMapping,middleNMS); // 'textContent' property mappings on ElementDefs int nPropMappings = sourceNMS.getPropertyMappings().size(); if (nPropMappings > 1) throw new MapperException("More than one property mapping at source path " + sourceElDef.getPath()); if (nPropMappings == 1) { PropMapping sourcePropMap = sourceNMS.getPropertyMappings().get(0); if (!sourcePropMap.getMappedProperty().equals("textContent")) throw new MapperException("Property mapping at source path " + sourceElDef.getPath() + " is not to property textContent"); if (middleNMS.getPropertyMappings().size() > 0) { PropMapping middlePropMap = middleNMS.getPropertyMappings().get(0); resultElDef.getNodeMappingSet().getPropertyMappings().add(copyPropMapping(middlePropMap)); if (middleNMS.getPropertyMappings().size() > 1) message("More than 1 property mapping at path " + nextMiddleElDef.getPath()); } } } } } // deal with property mappings on AttributeDefs for (Iterator<AttributeDef> it = sourceElDef.getAttributeDefs().iterator(); it.hasNext();) { AttributeDef sourceAttDef = it.next(); AttributeDef resultAttDef = resultElDef.getNamedAttribute(sourceAttDef.getName()); if (resultAttDef == null) throw new MapperException("No result attribute " + sourceAttDef.getName()); NodeMappingSet sourceAttNMS = sourceAttDef.getNodeMappingSet(); // some attributeDefs in the source mapping set are just for fixed values, e.g. classCode if (sourceAttNMS != null) { int attMappings = sourceAttNMS.getPropertyMappings().size(); if (attMappings != 1) throw new MapperException(attMappings + " mappings on AttributeDef " + sourceAttDef.getName()); PropMapping sourceAttMap = sourceAttNMS.getPropertyMappings().get(0); String middleAttName = sourceAttMap.getMappedProperty(); if (middleAttName.equals("order_property")) middleAttName = "element_position_virtual_att"; AttributeDef middleAttDef = nextMiddleElDef.getNamedAttribute(middleAttName); if (middleAttDef != null) { NodeMappingSet middleAttNMS = middleAttDef.getNodeMappingSet(); if (middleAttNMS != null) { PropMapping middlePropMapping = middleAttNMS.getPropertyMappings().get(0); NodeMappingSet resultAttNMS = MapperFactory.eINSTANCE.createNodeMappingSet(); resultAttNMS.getPropertyMappings().add(copyPropMapping(middlePropMapping)); resultAttDef.setNodeMappingSet(resultAttNMS); if (middleAttNMS.getPropertyMappings().size() > 1) message("More than 1 property mapping on attribute at path " + nextMiddleElDef.getPath()); } } // this can happen if the middle node tree has not been fully expanded else message("Missing attribute " + middleAttName + " at path " + nextMiddleElDef.getPath()); } } // recursive step for (Iterator<ElementDef> it = sourceElDef.getChildElements().iterator();it.hasNext();) { ElementDef sourceChild = it.next(); ElementDef resultChild = resultElDef.getNamedChildElement(sourceChild.getName()); if (resultChild == null) throw new MapperException("Cannot find result mapping set node '" + sourceChild.getName() + "'"); if (hasMappingsInSubtree(nextMiddleElDef)) projectMappings(sourceChild, nextMiddleElDef, resultChild); } } /** * * @param resultElDef * @param sourceObjMapping * @param middleNMS */ private void transferMappings(ElementDef resultElDef,ObjMapping sourceObjMapping,NodeMappingSet middleNMS) throws MapperException { // key = the name of some mapped class; value = true if it is independent Hashtable<String,Boolean> representedClasses = new Hashtable<String,Boolean>(); NodeMappingSet resultNMS = MapperFactory.eINSTANCE.createNodeMappingSet(); // transfer all object mappings, and record which classes are represented for (Iterator<ObjMapping> it = middleNMS.getObjectMappings().iterator();it.hasNext();) { ObjMapping om = it.next(); representedClasses.put(om.getMappedClass(), new Boolean(true)); resultNMS.getObjectMappings().add(copyObjMapping(om)); } // transfer all association mappings, and record which classes are independent for (Iterator<AssocMapping> it = middleNMS.getAssociationMappings().iterator();it.hasNext();) { AssocMapping am = it.next(); makeClassesDependent(am,representedClasses); resultNMS.getAssociationMappings().add(copyAssocMapping(am)); } // move the fixed value conditions to the object mapping of the (one) class which is independent int numberIndependent = 0; for (Iterator<ObjMapping> it = resultNMS.getObjectMappings().iterator();it.hasNext();) { ObjMapping om = it.next(); if (representedClasses.get(om.getMappedClass()).booleanValue()) { numberIndependent++; // transfer all fixed value conditions from the full => green mapping set, to the full => other mapping set for (Iterator<MappingCondition> iu = sourceObjMapping.getMappingConditions().iterator();iu.hasNext();) { MappingCondition mc = iu.next(); if (mc instanceof ValueCondition) { ValueCondition sourceVC = (ValueCondition)mc; ValueCondition resultVC = MapperFactory.eINSTANCE.createValueCondition(); resultVC.setLeftPath(sourceVC.getLeftPath()); resultVC.setRightValue(sourceVC.getRightValue()); om.getMappingConditions().add(resultVC); } } } } // if there are any classes represented on a node of the middle mapping set, just one of them must be independent if ((representedClasses.size() > 0) && (numberIndependent != 1)) throw new MapperException("There are " + numberIndependent + " independent object mappings on the node at full path " + resultElDef.getPath()); resultElDef.setNodeMappingSet(resultNMS); } /** * note any represented class which is rendered dependent by this mapping, * i.e owned by another class represented on the same node * @param am * @param representedClasses */ private void makeClassesDependent(AssocMapping am, Hashtable<String,Boolean> representedClasses) { if (am.getMappedEnd2().isRequiredForObject()) { String owningClass = am.getMappedEnd1().getMappedClass(); String ownedClass = am.getMappedEnd2().getMappedClass(); // if any class is 'owned' by one of the other represented classes, it is not independent if (representedClasses.get(owningClass) != null) representedClasses.put(ownedClass, new Boolean(false)); } } /** * * @param middleMapping * @return */ private ObjMapping copyObjMapping(ObjMapping middleMapping) { ObjMapping resultMapping = MapperFactory.eINSTANCE.createObjMapping(); resultMapping.setMappedClass(middleMapping.getMappedClass()); resultMapping.setMappedPackage(middleMapping.getMappedPackage()); resultMapping.setSubset(middleMapping.getSubset()); // transfer all fixed values for (Iterator<FixedPropertyValue> it = middleMapping.getFixedPropertyValues().iterator();it.hasNext();) { FixedPropertyValue fpv = it.next(); FixedPropertyValue nfpv =MapperFactory.eINSTANCE.createFixedPropertyValue(); nfpv.setMappedProperty(fpv.getMappedProperty()); nfpv.setFixedValue(fpv.getFixedValue()); nfpv.setValueType(fpv.getValueType()); resultMapping.getFixedPropertyValues().add(nfpv); } /* will throw ConcurrentModificationException - and not sure that transferring mapping conditions will work - they depend on paths. for (Iterator<MappingCondition> it = middleMapping.getMappingConditions().iterator();it.hasNext();) resultMapping.getMappingConditions().add(it.next()); */ return resultMapping; } /** * * @param middleMapping * @return */ private PropMapping copyPropMapping(PropMapping middleMapping) { PropMapping resultMapping = MapperFactory.eINSTANCE.createPropMapping(); resultMapping.setMappedClass(middleMapping.getMappedClass()); resultMapping.setMappedPackage(middleMapping.getMappedPackage()); resultMapping.setSubset(middleMapping.getSubset()); resultMapping.setMappedProperty(middleMapping.getMappedProperty()); resultMapping.setLocalPropertyConversion(middleMapping.getLocalPropertyConversion()); return resultMapping; } /** * * @param middleMapping * @return */ private AssocMapping copyAssocMapping(AssocMapping middleMapping) throws MapperException { AssocMapping resultMapping = MapperFactory.eINSTANCE.createAssocMapping(); for (int e = 0; e < 2; e++) { AssocEndMapping sourceEnd = middleMapping.getMappedEnd(e); AssocEndMapping resultEnd = MapperFactory.eINSTANCE.createAssocEndMapping(); resultEnd.setMappedClass(sourceEnd.getMappedClass()); resultEnd.setMappedPackage(sourceEnd.getMappedPackage()); resultEnd.setMappedRole(sourceEnd.getMappedRole()); resultEnd.setSubset(sourceEnd.getSubset()); resultEnd.setRequiredForObject(sourceEnd.isRequiredForObject()); if (e == 0) resultMapping.setMappedEnd1(resultEnd); if (e == 1) resultMapping.setMappedEnd2(resultEnd); } return resultMapping; } /** * * @param elDef * @return */ private boolean hasMappingsInSubtree(ElementDef elDef) { boolean hasMappings = (elDef.getNodeMappingSet() != null); if (!hasMappings) { for (Iterator<ElementDef> it = elDef.getChildElements().iterator(); it.hasNext();) if (hasMappingsInSubtree(it.next())) hasMappings = true; } return hasMappings; } private void message(String s) {System.out.println(s);} }