package com.openMap1.mapper.health.v3;
import java.util.Iterator;
import java.util.Vector;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EcoreFactory;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.xsd.XSDSchema;
import com.openMap1.mapper.converters.CDAConverter;
import com.openMap1.mapper.core.MapperException;
import com.openMap1.mapper.core.namespace;
import com.openMap1.mapper.impl.ElementDefImpl;
import com.openMap1.mapper.structures.XSDStructure;
import com.openMap1.mapper.util.GenUtil;
import com.openMap1.mapper.util.ModelUtil;
import com.openMap1.mapper.AttributeDef;
import com.openMap1.mapper.ElementDef;
import com.openMap1.mapper.MappedStructure;
import com.openMap1.mapper.MapperFactory;
import com.openMap1.mapper.MapperPackage;
import com.openMap1.mapper.MinMult;
/**
* reads V3 data types from a data types schema; makes the
* datatypes package in the ECore model, and the mappings of the
* V3 XML ITS onto these classes (the mappings are generally not used,
* because the V3 XML ITS is encapsulated in a Java mapper class)
*
* @author robert
*
*/
public class V3DataTypeHandler {
boolean tracing = false;
private String projectName;
private XSDStructure dataTypeStructureDefinition;
private RMIMReader rmimReader;
private EPackage dataTypePackage;
public static String[] DATATYPES_WITH_ORDERED_CHILDREN = {"AD","EN","PN"};
/* normally mixed type in the schema indicates possible text content; but it appears some
* other types have text content as well. */
public static String[] DATATYPES_WITH_TEXT_CONTENT = {"AD","EN","PN","ED","ST"};
private boolean hasOrderedChildren(String typeName)
{return GenUtil.inArray(typeName, DATATYPES_WITH_ORDERED_CHILDREN);}
public static String V3_CHILD_ORDER_PROPERTY = "element_position";
// if true, elements without prefixes are in the HL7 V3 namespace
private boolean putElementsInV3Namespace;
protected MapperPackage mapperPackage = MapperPackage.eINSTANCE;
protected MapperFactory mapperFactory = mapperPackage.getMapperFactory();
public V3DataTypeHandler(RMIMReader rmimReader,String projectName,boolean putElementsInV3Namespace)
{
this.rmimReader = rmimReader;
this.projectName = projectName;
this.putElementsInV3Namespace = putElementsInV3Namespace;
}
/**
*
* @param ecoreFilePath
* @param saveMappings
* @return
* @throws MapperException
*/
public EPackage readDataTypeSchema(String ecoreFilePath, boolean saveMappings) throws MapperException
{
// (1) Make the data types package and attach it the the RMIM ecore model
dataTypePackage = EcoreFactory.eINSTANCE.createEPackage();
dataTypePackage.setName(RMIMReader.DATATYPE_PACKAGE_NAME);
rmimReader.topPackage().getESubpackages().add(dataTypePackage);
// (2) Open the schema as an XSDStructure
String schemaLocation = "platform:/resource/" + projectName + "/Structures/coreschemas/datatypes.xsd";
URI uri = URI.createURI(schemaLocation);
XSDSchema theSchema = XSDStructure.getXSDRoot(uri);
if (theSchema != null) dataTypeStructureDefinition = new XSDStructure(theSchema);
else throw new MapperException("Cannot open V3 data types schema at '" + schemaLocation + "'");
// if elements without prefixes are to be in the HL7 V3 namespace....(eg for CDA)
if (putElementsInV3Namespace)
dataTypeStructureDefinition.NSSet().addNamespace(new namespace("",CDAConverter.V3NAMESPACEURI));
// (3) Make a 'V3DataTypes' subfolder to hold the data type V3-V3 mappings
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
IProject project = root.getProject(projectName);
IFolder mappingsFolder = project.getFolder("MappingSets");
IFolder V3DataTypesFolder = mappingsFolder.getFolder("V3DataTypes");
if ((saveMappings) && (!V3DataTypesFolder.exists()))
{
try {V3DataTypesFolder.create(true, true, null);} // force, local, no progress monitor
catch (Exception ex) {throw new MapperException("Cannot make folder for data type mapping sets");}
}
// (4) iterate over all data types
String[] typeNames = dataTypeStructureDefinition.topComplexTypes();
String names = "Data types: ";
for (int i = 0; i < typeNames.length; i++)
{
String typeName = typeNames[i];
handleDataType(typeName,schemaLocation,V3DataTypesFolder,ecoreFilePath,saveMappings);
names = names + typeName + " ";
}
// (4) for data types with ordered children, add the property to child classes and map it
return dataTypePackage;
}
/**
* Make the mapping set, Ecore class, and mappings for a data type,
* if they have not been made already
* @param typeName
*/
private void handleDataType(String typeName, String schemaLocation,
IFolder V3DataTypesFolder, String ecoreFilePath, boolean saveMappings)
throws MapperException
{
IFile existingMappingSet = V3DataTypesFolder.getFile(typeName + ".mapper");
/* if the mapping set exists, make it but do not save it,
* so as to add the data type classes to the class model */
if (hasOwnMappingSet(typeName))
{
// (1) Make an empty mapping set in a 'V3DataTypes' folder of the MappingSets folder
String mappingSetLocation = RMIMReader.getDataTypeMappingSetLocation(projectName,typeName);
URI uri = URI.createURI(mappingSetLocation);
Resource mappingSet = ModelUtil.makeNewMappingSet(uri);
MappedStructure mappedStructure = (MappedStructure)mappingSet.getContents().get(0);
mappedStructure.setUMLModelURL(ecoreFilePath);
// (2) make the data types schema the structure definition for the mapping set
/* if the structure URL is set to the true location of the data types schema, then
* there is a memory overflow on validating more than one data type schema.
* So the location is set to "", which produces one validation error per mapping set*/
mappedStructure.setStructureURL("");
mappedStructure.setStructureDefinition(dataTypeStructureDefinition);
// (3) make the data type name the top complex type for the schema, and expand it
mappedStructure.setTopElementType(typeName);
ElementDef newStructure = dataTypeStructureDefinition.typeStructure(typeName);
newStructure.setExpanded(true);
newStructure.setName("top"); // needs some name to prevent '//' in XPaths
mappedStructure.setRootElement(newStructure);
/* (4) make the data type class, with its properties and associations, and make mappings
* if saveMappings = false, make the mappings but do not save them. */
EClass dataTypeClass = makeDataTypeClassAndMappings(mappedStructure,typeName);
// (5) Add a parameter class to the top node, to make this mapping set importable by others
RMIMReader.addParameterClass(mappedStructure, dataTypeClass);
// (6) save the mapping set, if it does not exist already
if ((saveMappings) && (!existingMappingSet.exists())) ModelUtil.saveMappingSet(mappingSet);
}
}
private boolean hasOwnMappingSet(String typeName)
{
boolean hasOwn = true;
if (typeName.startsWith("adxp")) hasOwn = false;
if (typeName.startsWith("en")) hasOwn = false;
if (typeName.startsWith("thumb")) hasOwn = false;
if (typeName.equals("")) hasOwn = false;
return hasOwn;
}
/**
* @param type a V3 data type
* @return true if this type has to be some aggregate of a parameter type
*/
public static boolean isAggregateType(String type)
{
if (type.equals("LIST")) return true;
if (type.equals("BAG")) return true;
if (type.equals("SET")) return true;
if (type.equals("DSET")) return true;
return false;
}
//------------------------------------------------------------------------------------------
// making the data type class, properties, associations and mappings
//------------------------------------------------------------------------------------------
private EClass makeDataTypeClassAndMappings(MappedStructure mappedStructure,String typeName)
throws MapperException
{
trace("Data type '" + typeName + "'");
// find or make the class (it might have been made already)
EClass dataTypeClass = findOrMakeEClass(dataTypePackage,typeName);
// make the object mapping on the top node
ElementDef root = mappedStructure.getRootElement();
String subset = rmimReader.getSubset(typeName,typeName,RMIMReader.DATATYPE_PACKAGE_NAME);
RMIMReader.addObjectMapping(root,dataTypePackage,typeName,subset);
// for each attribute of the root, make a property of the EClass and a mapping to it
boolean isOrdered = false; // this class has no ordering property; its children might have
makePropertiesAndMappings(dataTypePackage,root, dataTypeClass,subset,isOrdered);
// for each child element of the root, make an object mapping and an association mapping
Vector<String> classNames = new Vector<String>();
classNames.add(dataTypeClass.getName()); // to avoid infinite recursion
makeChildObjectMappings(typeName,dataTypePackage,root, dataTypeClass,subset,classNames,hasOrderedChildren(typeName));
return dataTypeClass;
}
/**
* find a named class in a package, if it exists; or if it does not, make it.
* @param aPackage a package
* @param className a class name
* @return
*/
private EClass findOrMakeEClass(EPackage aPackage,String className)
{
EClass aClass = null;
EClassifier ec = aPackage.getEClassifier(className);
if ((ec != null) && (ec instanceof EClass)) aClass = (EClass)ec;
if (aClass == null)
{
aClass = EcoreFactory.eINSTANCE.createEClass();
aClass.setName(className);
aPackage.getEClassifiers().add(aClass);
}
return aClass;
}
/**
* @param aPackage the package that all classes are to be in
* @param node an ElementDef in the mapped structure
* @param aClass the EClass it is mapped to
* @param isOrdered if true, the ElementDef should have an AttributeDef
* for a virtual attribute, mapped to an ordinal position property.
*
* For every AttributeDef under an ElementDef, make a property on the EClass
* and a mapping to it.
*/
private void makePropertiesAndMappings(EPackage aPackage,ElementDef node, EClass aClass, String subset, boolean isOrdered)
{
boolean makeAttributeTypes = true;
// add a property for every AttributeDef of the ElementDef
for (Iterator<AttributeDef> it = node.getAttributeDefs().iterator(); it.hasNext();)
{
AttributeDef attDef = it.next();
String propertyName = attDef.getName();
String propertyType = attDef.getType();
boolean isOptional = (attDef.getMinMultiplicity() == MinMult.ZERO);
String fixedValue = attDef.getFixedValue();
// add the property to the EClass
trace("Add property " + propertyName + " with type " + propertyType);
addProperty(aClass,propertyName,propertyType,makeAttributeTypes,isOptional,fixedValue);
// add the mapping from the AttributeDef to the property
RMIMReader.addPropertyMapping(attDef, aClass, propertyName,subset);
}
// add some xsi:type properties to class PQ, which somehow does not get read in from the data types schema
addXSITypeProperty(aClass,makeAttributeTypes);
/* for child classes with a virtual position attribute, add the virtual attribute to the
* structure, add the property to the EClass, and add the mapping between them. */
if (isOrdered)
{
// make the virtual AttributeDef for ordinal position and and it to the ElementDef
AttributeDef positionAtt = MapperFactory.eINSTANCE.createAttributeDef();
positionAtt.setName(ElementDefImpl.ELEMENT_POSITION_ATTRIBUTE);
node.getAttributeDefs().add(positionAtt);
// add the property to the EClass
addProperty(aClass,V3_CHILD_ORDER_PROPERTY,"EString",makeAttributeTypes,true,"");
// add the mapping
RMIMReader.addPropertyMapping(positionAtt, aClass, V3_CHILD_ORDER_PROPERTY,subset);
}
// if the Element has mixed type, add a property 'textContent' mapped to the Element
boolean hasTextContent = ((node.isMixed()|
(GenUtil.inArray(node.getType(), DATATYPES_WITH_TEXT_CONTENT))));
if (hasTextContent)
{
String propertyName = "textContent";
String propertyType = "st";
// add the property to the EClass
addProperty(aClass,propertyName,propertyType,makeAttributeTypes,true, null);
// add the mapping
RMIMReader.addPropertyMapping(node, aClass, propertyName,subset);
}
}
/**
* for data types PQ (but not INT), there should be a property 'xsi:type' with fixed value 'PQ' or 'INT'.
* For some reason this is not picked up for these classes when reading the data types schema. Add it.
* @param aClass
* @param makeAttributeTypes
*/
private void addXSITypeProperty(EClass aClass,boolean makeAttributeTypes)
{
String className = aClass.getName();
if ((className.equals("PQ"))/* |(className.equals("INT"))*/)
addProperty(aClass,"xsi:type","string",makeAttributeTypes,false,className);
// fixed value is the same as the class name
}
// add a property to the class, if it has not been added already
private void addProperty(EClass aClass,String propertyName,String propertyType,
boolean makeAttributeTypes, boolean isOptional, String fixedValue)
{
if (aClass.getEStructuralFeature(propertyName) == null)
{
EAttribute att = EcoreFactory.eINSTANCE.createEAttribute();
att.setName(propertyName);
if (isOptional) att.setLowerBound(0); else att.setLowerBound(1);
if (makeAttributeTypes) att.setEType(getEcoreDataType(propertyType));
if ((fixedValue != null) && (!fixedValue.equals("")))
ModelUtil.addMIFAnnotation(att, "fixed value", fixedValue);
aClass.getEStructuralFeatures().add(att);
}
}
/**
*
* @param propertyType V3 data type attribute type
* @return corresponding ECore data type.
* Most of the strange V3 types like 'set_TelecommunicationAddressUse'
* just convert to EString.
*/
private EDataType getEcoreDataType(String propertyType)
{
EDataType aType = EcorePackage.eINSTANCE.getEString();
if (propertyType != null)
{
if (propertyType.equals("real")) aType = EcorePackage.eINSTANCE.getEFloat();
if (propertyType.equals("probability")) aType = EcorePackage.eINSTANCE.getEFloat();
if (propertyType.equals("bl")) aType = EcorePackage.eINSTANCE.getEBoolean();
if (propertyType.equals("int")) aType = EcorePackage.eINSTANCE.getEInt();
}
return aType;
}
/**
* @param mappingSetName the name of the mapping set
* @param aPackage the package that all classes are to be in
* @param node an ElementDef in the mapped structure
* @param aClass the EClass it is mapped to
* @param aSubset the subset allocated to that class in its mapping
* @param hasChildOrder: if true, the child classes must have an order property, mapped to
* a virtual order element.
* for every child ElementDef of an ElementDef, make or find the class,
* make an object mapping to it and an association mapping.
*/
private void makeChildObjectMappings(String mappingSetName,EPackage aPackage,
ElementDef node, EClass aClass, String aSubset, Vector<String> classNames, boolean hasChildOrder)
throws MapperException
{
for (Iterator<ElementDef> id = node.getChildElements().iterator();id.hasNext();)
{
// add the child class and its object mapping
ElementDef child = id.next();
EClass childClass = null;
String childType = child.getType();
/* if the schema uses anonymous local types, make up a type name from the node name,
* and give the child class attributes to match those of the child node */
if (childType == null)
{
childType = child.getName();
childClass = findOrMakeEClass(aPackage,childType);
addAttributesToClass(childClass, child);
}
else childClass = findOrMakeEClass(aPackage,childType);
String childSubset = rmimReader.getSubset(mappingSetName, childType, RMIMReader.DATATYPE_PACKAGE_NAME);
RMIMReader.addObjectMapping(child,aPackage,childType,childSubset);
/* add the containment association to the child class
* (with no EOpposite association), if it has not been added already */
if (aClass.getEStructuralFeature(child.getName()) == null)
{
EReference ref = EcoreFactory.eINSTANCE.createEReference();
ref.setName(child.getName());
ref.setEType(childClass);
int min = new Integer(child.getMinMultiplicity().getLiteral()).intValue();
int max = new Integer(child.getMaxMultiplicity().getLiteral()).intValue();
ref.setLowerBound(min);
ref.setUpperBound(max);
ref.setContainment(true);
aClass.getEStructuralFeatures().add(ref);
}
// add the association mapping on the child node
RMIMReader.addAssociationMapping(child, aClass, aSubset, childClass, childSubset);
// if the child data type has its own mapping set, import the mapping set on the child node
if (hasOwnMappingSet(childType))
{
String mappingSetURI = RMIMReader.getDataTypeMappingSetLocation(projectName,childType);
RMIMReader.addImportMappingSet(child, mappingSetURI, childClass, childSubset);
}
/* if the child data type does not have has its own mapping set,
* extend this mapping set tree; */
else if (!hasOwnMappingSet(childType))
{
// extend the mappedStructure tree by the structure for the type
ElementDef newStructure = dataTypeStructureDefinition.typeStructure(childType);
child.setExpanded(true);
// two-stage transfer of child elements
Vector<ElementDef> els = new Vector<ElementDef>();
for (Iterator<ElementDef> it = newStructure.getChildElements().iterator(); it.hasNext();)
els.add(it.next());
for (Iterator<ElementDef> it = els.iterator();it.hasNext();)
child.getChildElements().add(it.next());
// two-stage transfer of attributes
Vector<AttributeDef> atts = new Vector<AttributeDef>();
for (Iterator<AttributeDef> it = newStructure.getAttributeDefs().iterator(); it.hasNext();)
atts.add(it.next());
for (Iterator<AttributeDef> it = atts.iterator();it.hasNext();)
child.getAttributeDefs().add(it.next());
// make the child have mixed content if its type does
child.setIsMixed(newStructure.isMixed());
// for each attribute of the child node, make a property of the EClass and a mapping to it
makePropertiesAndMappings(dataTypePackage,child, childClass,childSubset,hasChildOrder);
/* for each child element of the child node, make an object mapping and association mapping,
* avoiding infinite recursion through data types that don't have their own mapping sets */
if (!GenUtil.inVector(childClass.getName(), classNames))
{
Vector<String> newClassNames = new Vector<String>();
for (Iterator<String> is = classNames.iterator();is.hasNext();) newClassNames.add(is.next());
newClassNames.add(childClass.getName());
// 'false' mean children of child classes are never ordered.
makeChildObjectMappings(mappingSetName,dataTypePackage,child, childClass,childSubset,newClassNames,false);
}
}
}
}
/**
* A class has been made without any attributes or associations, because it has no associated complex type.
* But the necessary attributes are already present in the mapped structure for the parent type.
* Add those attributes to the child class, and add a textContent attribute in case it is needed.
* FIXME does not yet handle associations of the child class? (could easily do so)
* writes a trace if there are any needed
* @param childClass
* @param child
*/
private void addAttributesToClass(EClass childClass, ElementDef child) throws MapperException
{
boolean makeAttributeTypes = true;
trace("Adding properties to class '" + childClass.getName() + "' with anonymous type.");
for (Iterator<AttributeDef> it = child.getAttributeDefs().iterator();it.hasNext();)
{
AttributeDef attDef = it.next();
EStructuralFeature ea = childClass.getEStructuralFeature(attDef.getName());
if (ea == null) // the class should never have the attribute already, but just to be safe....
{
String propertyName = attDef.getName();
String propertyType = attDef.getType();
boolean isOptional = (attDef.getMinMultiplicity() == MinMult.ZERO);
String fixedValue = attDef.getFixedValue();
// add the property to the EClass
trace("Add property " + propertyName + " with type " + propertyType);
addProperty(childClass,propertyName,propertyType,makeAttributeTypes,isOptional,fixedValue);
}
// add a textContent property, in case it is needed
String propertyName = "textContent";
String propertyType = "st";
addProperty(childClass,propertyName,propertyType,makeAttributeTypes,true, null);
}
int childEls = child.getChildElements().size();
if (childEls > 0)
trace("Does not yet handle the " + childEls + " associations of class '" + childClass.getName() + "' with anonymous types");
}
private void trace(String s) {if (tracing) System.out.println(s);}
}