package com.openMap1.mapper.fhir;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import java.util.Vector;
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.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
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.impl.XMIResourceFactoryImpl;
import org.hl7.fhir.instance.formats.Composer;
import org.hl7.fhir.instance.formats.XmlComposer;
import org.hl7.fhir.instance.formats.XmlParser;
import org.hl7.fhir.instance.model.AtomFeed;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import com.openMap1.mapper.MappedStructure;
import com.openMap1.mapper.core.ClassSet;
import com.openMap1.mapper.core.MapperException;
import com.openMap1.mapper.core.RunIssue;
import com.openMap1.mapper.reader.AbstractReaderWriter;
import com.openMap1.mapper.reader.EMFInstanceFactory;
import com.openMap1.mapper.reader.EMFInstanceFactoryImpl;
import com.openMap1.mapper.reader.EObjectRep;
import com.openMap1.mapper.reader.GenericEMFInstanceFactoryImpl;
import com.openMap1.mapper.reader.XOReader;
import com.openMap1.mapper.reader.objectRep;
import com.openMap1.mapper.reader.objectToken;
import com.openMap1.mapper.util.EclipseFileUtil;
import com.openMap1.mapper.util.ModelUtil;
import com.openMap1.mapper.util.XMLUtil;
import com.openMap1.mapper.util.messageChannel;
import com.openMap1.mapper.writer.XMLWriter;
import com.openMap1.mapper.writer.objectGetter;
public class FHIRMapper extends AbstractReaderWriter implements XOReader, objectGetter, XMLWriter{
private EObject fhirInstance;
// key = qualified class name; value = vector of all objectTokens for the class
private Hashtable<String,Vector<objectToken>> allObjectTokens;
/* key 1 = node in the FHIR instance
* key 2 = class of an object with an EReference to it
* key 3 = name of the EReference
* element = Vector of objectTokens for EObjects in the class with the EReferecne to the first EObject */
private Hashtable<EObject,Hashtable<String,Hashtable<String,Vector<objectToken>>>> inverseRelations;
private objectToken outerObjectToken;
private boolean doCheck = true;
private boolean tracing = true;
private static String TEMPORARY_READ_FILE = "/eclipseTempReadFile.xml";
private static String TEMPORARY_WRITE_FILE = "/eclipseTempWriteFile.xml";
private EcoreReferenceBridge bridge;
//---------------------------------------------------------------------------------
// Constructor for XOReader
//---------------------------------------------------------------------------------
/**
* constructor for XOReader and objectGetter uses
*/
public FHIRMapper(Element XMLFileRoot, MappedStructure ms,
EPackage classModel, messageChannel mChan) throws MapperException
{
super(XMLFileRoot, ms, classModel, mChan);
trace("Done superclass constructor");
setRoot(XMLFileRoot);
}
/**
* this method:
* (a) converts the root element to an inputStream, by an ugly method
* (b) reads the stream into an AtomFeed instance of the Java FHIR reference implementation
* (c) converts the reference implementation instance into an EMF model instance
*/
public void setRoot(Node el) throws MapperException
{
if (!(el.getLocalName().equals("feed"))) throw new MapperException("Root node is not a 'feed' element");
// write out the DOM in order to read it in as a stream for the AtomParser
trace("writing out DOM");
String tempFileLocation = EclipseFileUtil.workspaceRoot() + TEMPORARY_WRITE_FILE;
XMLUtil.writeOutput(el.getOwnerDocument(),tempFileLocation,false);
trace("written DOM to stream");
try{
FileInputStream fileStream = new FileInputStream(tempFileLocation);
// parse the input to get an AtomFeed object of the reference implementation
trace("Parsing input stream");
XmlParser parser = new XmlParser();
// allow unknown new tag names; but does not work for contained resources (new parser instance)
parser.setAllowUnknownContent(true);
AtomFeed feed = parser.parseGeneral(fileStream).getFeed();
// delete the temporary file
File file = new File(tempFileLocation);
file.delete();
// convert the reference model instance to an ECore model instance
trace("converting to Ecore model instance");
bridge = new EcoreReferenceBridge(classModel());
fhirInstance = bridge.getEcoreModelInstance(feed);
trace("Ecore model instance created");
// testWriteInstance(fhirInstance,classModel);
}
catch (Exception ex)
{
ex.printStackTrace();
throw new MapperException("Failed either to make AtomFeed Instance, or to convert it to an ECore instance: " + ex.getMessage());
}
// refresh the object tokens
makeAllObjectTokens();
}
public void setInputRoot(Node el) throws MapperException {setRoot(el);}
/**
* make the tables of all ObjectTokens needed for this to function
* as an XOReader of objectGetter
*/
private void makeAllObjectTokens()
{
allObjectTokens = new Hashtable<String,Vector<objectToken>>() ;
inverseRelations = new Hashtable<EObject,Hashtable<String,Hashtable<String,Vector<objectToken>>>>() ;
outerObjectToken = makeObjectToken(fhirInstance);
writeAllObjectTokens();
}
/**
* recursive descent of containment relations in the EMF instance,
* to make all objectTokens
* @param obj
*/
@SuppressWarnings("unchecked")
private objectToken makeObjectToken(EObject obj)
{
EClass theClass = obj.eClass();
// qualified class names
String className = theClass.getEPackage().getName() + "." + theClass.getName();
trace("Making object token for " + className);
Vector<objectToken> tokens = allObjectTokens.get(className);
if (tokens == null) tokens = new Vector<objectToken>();
objectToken result = new EObjectRep(obj,this);
tokens.add(result);
allObjectTokens.put(className,tokens);
for (Iterator<EStructuralFeature> it = theClass.getEStructuralFeatures().iterator();it.hasNext();)
{
EStructuralFeature feat = it.next();
if (feat instanceof EReference)
{
EReference ref = (EReference)feat;
String refName = ref.getName();
Object value = obj.eGet(ref);
if (value != null)
{
if (ref.getUpperBound() == 1)
{
EObject eValue = (EObject)value;
if (ref.isContainment()) makeObjectToken(eValue);
addInverseRelation(eValue,className,refName,result);
}
else if (value instanceof List)
{
List<Object> lVal = (List<Object>)value;
for (Iterator<Object> iu = lVal.iterator(); iu.hasNext();)
{
EObject eValue = (EObject)iu.next();
if (ref.isContainment()) makeObjectToken(eValue);
addInverseRelation(eValue,className,refName,result);
}
}
}
}
}
return result;
}
/**
*
* @param eValue EObject whose inverse relations are being recorded
* @param className qualified class name for the object at the other end
* @param refName name of the relation whose inverse is being stored
* @param owner objectToken for the object at the other end of the relation
*
* e.g. eValue is a Patient resource object; className = feed.AtomFeed; refName = 'patient'; owner = objectToken for the AtomFeed object
*/
private void addInverseRelation(EObject eValue, String className,String refName, objectToken owner)
{
Hashtable<String,Hashtable<String,Vector<objectToken>>> inverseRelationsForObject = inverseRelations.get(eValue);
if (inverseRelationsForObject == null) inverseRelationsForObject = new Hashtable<String,Hashtable<String,Vector<objectToken>>>();
Hashtable<String,Vector<objectToken>> inversesForObjectAndClass = inverseRelationsForObject.get(className);
if (inversesForObjectAndClass == null) inversesForObjectAndClass = new Hashtable<String,Vector<objectToken>>();
Vector<objectToken> inversesForObjectClassRef = inversesForObjectAndClass.get(refName);
if (inversesForObjectClassRef == null) inversesForObjectClassRef = new Vector<objectToken>();
inversesForObjectClassRef.add(owner);
inversesForObjectAndClass.put(refName, inversesForObjectClassRef);
inverseRelationsForObject.put(className,inversesForObjectAndClass);
inverseRelations.put(eValue, inverseRelationsForObject);
}
/**
*
* @param eValue
* @param className
* @param refName
* @return objectTokens for objects of a named class that have a named EReferecne to this object
*/
private Vector<objectToken> getInverseRelatedObjects(EObject eValue, String className, String refName)
{
Vector<objectToken> result = new Vector<objectToken>();
Hashtable<String,Hashtable<String,Vector<objectToken>>> inverseRelationsForObject = inverseRelations.get(eValue);
if (inverseRelationsForObject != null)
{
Hashtable<String,Vector<objectToken>> inversesForObjectAndClass = inverseRelationsForObject.get(className);
if (inversesForObjectAndClass != null)
{
Vector<objectToken> inversesForObjectClassRef = inversesForObjectAndClass.get(refName);
if (inversesForObjectClassRef != null) result = inversesForObjectClassRef;
}
}
return result;
}
/**
* 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
* @exception MapperException - class not represented in the XML
* - you ignored some exception on creating XOReader
*/
public Vector<objectToken> getAllObjectTokens(String className)
throws MapperException {
Vector<objectToken> tokens = allObjectTokens.get(className);
if (tokens == null) tokens = new Vector<objectToken>();
return tokens;
}
/**
* there cannot be importing or imported FHIR mapping sets (yet)
* so 'local' objects are the same as all objects
*/
public Vector<objectToken> getAllLocalObjectTokens(String className)
throws MapperException {
return getAllObjectTokens(className);
}
/**
* String value of a property of some represented object
*
* @param oTok - the objectToken for the object
* @param propertyName - the name of the property
*
**/
public String getPropertyValue(objectToken oTok, String propertyName)
throws MapperException
{
String propVal = null;
EObject theObject = ((EObjectRep)oTok).theObject();
EClass theClass = theObject.eClass();
EStructuralFeature feat = theClass.getEStructuralFeature(propertyName);
if (feat == null) throw new MapperException("No feature '" + propertyName + "' of class '" + theClass.getName() + "'in FHIR class model");
if (feat instanceof EAttribute)
{
Object result = theObject.eGet(feat);
if (result != null)
{
if (result instanceof String) propVal = (String)result;
else if (result instanceof Boolean) propVal = result.toString();
else if (result instanceof Integer) propVal = result.toString();
else throw new MapperException("Value of property '" + propertyName + "' of class '"
+ theObject.eClass().getName() + "' is not handled yet in the FHIR objectGetter: " + result.getClass().getName());
}
}
else throw new MapperException("Feature '" + propertyName + "' is an association in the FHIR class model");
return propVal;
}
/**
* Vector of objectTokens representing objects related to the current object by some association.
*
* @param oRep - the input object at one end of the association
* @param otherClassQualifiedName - class or superclass of the objects to be retrieved
* @param otherRole - the role played by the other-end object in the association
*
*/
public Vector<objectToken> getAssociatedObjectTokens(objectToken oTok,
String otherClassQualifiedName, String otherRole) throws MapperException
{
EClass otherClass = ModelUtil.getNamedClass(classModel, otherClassQualifiedName);
Vector<objectToken> result = new Vector<objectToken>();
EObject theObject = ((EObjectRep)oTok).theObject();
EClass theClass = theObject.eClass();
EStructuralFeature feat = theClass.getEStructuralFeature(otherRole);
if (feat == null)
{
throw new MapperException("No feature '" + otherRole
+ "' from class '" + theClass.getName() + "' to class '"
+ otherClassQualifiedName + "' in FHIR Ecore class model");
}
if (feat instanceof EReference)
{
EReference ref = (EReference)feat;
EClassifier otherEnd = ref.getEType(); // may be a superclass of the supplied other class
String otherEndName = otherEnd.getEPackage().getName() + "." + otherEnd.getName();
// if the two classes are not the same, check the superclass relation
if (!otherClassQualifiedName.equals(otherEndName))
{
boolean foundSuper = false;
for(Iterator<EClass> ic = otherClass.getESuperTypes().iterator(); ic.hasNext();)
if (ic.next().getName().equals(otherEnd.getName())) foundSuper = true;
if (!foundSuper) throw new MapperException("Other end class " + otherClassQualifiedName
+ " is not " + otherEnd.getName() + " in association role "
+ otherRole + " from class " + oTok.className());
}
Object value = theObject.eGet(feat);
// if you find one or more objects at the end of the association, you need to check they are in the right class
if (value != null)
{
if (ref.getUpperBound() == 1)
{
EObject eVal = (EObject)value;
EClass foundClass = eVal.eClass();
String foundClassName = foundClass.getEPackage().getName() + "." + foundClass.getName();
if (foundClassName.equals(otherClassQualifiedName)) result.add(new EObjectRep(eVal,this));
}
else if (value instanceof List)
{
List<Object> lVal = (List<Object>)value;
for (Iterator<Object> iu = lVal.iterator(); iu.hasNext();)
{
Object next = iu.next();
EObject eVal = (EObject)next;
EClass foundClass = eVal.eClass();
String foundClassName = foundClass.getEPackage().getName() + "." + foundClass.getName();
if (foundClassName.equals(otherClassQualifiedName)) result.add(new EObjectRep(eVal,this));
}
}
}
}
else throw new MapperException("Feature '" + otherRole + "' is an EAttribute in the FHIR class model");
return result;
}
/**
* From ModelUtil: 'The association name is the role name if the reference has no opposite.'
* Sometimes called with thisEnd = 2 when checking association conditions. In these cases, must look up
* object related by inverse associations, as these are not present in the Ecore class model
*/
public Vector<objectToken> getAssociatedObjectTokens(objectToken oTok,
String assocName, String otherClass, int thisEnd)
throws MapperException
{
String otherRole = assocName;
EObject theObject = ((EObjectRep)oTok).theObject();
if (thisEnd == 2) return getInverseRelatedObjects(theObject, otherClass, assocName);
else return getAssociatedObjectTokens(oTok,otherClass,otherRole);
}
/**
* n method required by the abstract superclass but should not be used
*/
public Vector<objectToken> getTheAssociatedObjectReps(objectToken oTok, String assocName,
String otherClass, int thisEnd, String otherRole) throws MapperException
{
if (doCheck) throw new MapperException("Method getTheAssociatedObjectReps in class FHIRMapper should not be called");
return new Vector<objectToken>();
}
/**
* If the mapping set of this XOReader it imported by some
* other mapping set, the qualified name of the parameter class.
* The importing node or some ancestor of it must represent an instance of the
* parameter class.
* @return
* @throws MapperException
*/
public String parameterClassName() throws MapperException {
return "No parameter class";
}
/**
*
*/
public boolean representsObject(String qualifiedClassName) {
EClass theClass = ModelUtil.getNamedClass(classModel, qualifiedClassName);
return (theClass != null);
}
private String bareClassName(String qualifiedClassName)
{
String bareName = "";
StringTokenizer st = new StringTokenizer(qualifiedClassName,".");
while (st.hasMoreTokens()) bareName = st.nextToken();
return bareName;
}
/**
*
*/
public boolean representsProperty(String className, String property) {
boolean represents = false;
EClass theClass = ModelUtil.getNamedClass(classModel, className);
if (theClass != null)
{
represents = theClass.getEStructuralFeature(property) != null;
}
return represents;
}
/**
*
*/
public boolean representsProperty(objectRep oRep, String property) {
if (doCheck) message("Method representsProperty(objectRep oRep, String property) should not be called");
return false;
}
/**
*
*/
public boolean representsAssociationRole(String class1, String roleName,
String class2) {
boolean represents = false;
EClass theClass = ModelUtil.getNamedClass(classModel, class1);
if (theClass != null)
{
represents = theClass.getEStructuralFeature(roleName) != null;
}
return represents;
}
/**
*
*/
public boolean representsAssociationRole(objectRep oRep, String roleName,
String class2) {
if (doCheck) message("Method representsAssociationRole(objectRep oRep,... should not be called");
return false;
}
/**
*
*/
public boolean representsAssociation(String class1, String assocName,
String class2) {
if (doCheck) message("Method representsAssociation(String class1, String assocName,... should not be called");
return false;
}
/**
*
*/
public Hashtable<String, ClassSet> subsets(String className) {
if (doCheck) message("Method subsets(String className) should not be called");
return null;
}
/**
*
*/
public Vector<String> getQualifiedClassNames(String bareClassName) {
Vector<String> qualNames = new Vector<String>();
for (Iterator<EPackage> it = classModel.getESubpackages().iterator();it.hasNext();)
{
EPackage pack = it.next();
if (pack.getEClassifier(bareClassName) != null) qualNames.add(pack.getName() + "." + bareClassName);
}
return qualNames;
}
/**
*
*/
public Vector<ClassSet> outerObjectClassSets() {
Vector<ClassSet> outers = new Vector<ClassSet>();
outers.add(outerObjectToken.cSet());
return outers;
}
public XOReader reader() {return this;}
//----------------------------------------------------------------------------------------------------------
// Constructor and methods for XMLWriter interface
//----------------------------------------------------------------------------------------------------------
/**
* Constructor for use as an XMLWriter
* @param oGet
* @param ms
* @param classModel
* @param mChan
* @param doRunTracing
* @throws MapperException
*/
public FHIRMapper(objectGetter oGet, MappedStructure ms,
EPackage classModel, messageChannel mChan, Boolean doRunTracing)
throws MapperException
{
super(oGet,ms,classModel,mChan, doRunTracing);
}
@Override
public Element makeXMLDOM() throws MapperException {
// find the top AtomFeed object represented by the input
// ((FHIRMapper)oGet).writeClasses(); // diagnostic
Vector<objectToken> atomFeedObjects = oGet.getObjects("feed.AtomFeed");
if (atomFeedObjects.size() != 1) throw new MapperException("There should be just one AtomFeed object represented, not " + atomFeedObjects.size());
objectToken topObjectToken = atomFeedObjects.get(0);
// use the objectGetter's reader to make an Ecore model instance
EMFInstanceFactory factory = new GenericEMFInstanceFactoryImpl();
Resource modelResource = factory.createModelInstance(oGet.reader(),EMFInstanceFactoryImpl.DO_NOT_SAVE_URI(), topObjectToken);
EObject modelInstance = modelResource.getContents().get(0);
// convert the Ecore model instance to an instance of the Java Reference Implementation
EcoreReferenceBridge bridge = new EcoreReferenceBridge(classModel);
AtomFeed feed = bridge.getReferenceModelFeed(modelInstance);
// serialize the reference implementation instance
String tempFileLocation = EclipseFileUtil.workspaceRoot() + TEMPORARY_READ_FILE;
try{
FileOutputStream stream = new FileOutputStream(tempFileLocation);
Composer composer = new XmlComposer();
composer.compose(stream, feed, false);
}
catch (Exception ex) {throw new MapperException("Failed to serialize reference implementation instance: " + ex.getMessage());}
Element root = XMLUtil.getRootElement(tempFileLocation);
File file = new File(tempFileLocation);
file.delete();
return root;
}
@Override
public Element extendXMLDOM(Element bareElement, objectToken oTok)
throws MapperException {
if (doCheck) throw new MapperException("FHIRMapper does not support method extendXMLDOM");
return null;
}
public Hashtable<String, Hashtable<String, RunIssue>> allRunIssues() {
return new Hashtable<String, Hashtable<String, RunIssue>>();
}
private void writeAllObjectTokens()
{
trace("\nObjects represented");
for (Enumeration<String> en = this.allObjectTokens.keys();en.hasMoreElements();)
{
String className = en.nextElement();
Vector<objectToken> toks = allObjectTokens.get(className);
trace(className + ": " + toks.size());
}
}
// diagnostic
private void writeClasses()
{
message("\nAll classes");
for (Enumeration<String> en = allObjectTokens.keys(); en.hasMoreElements();) message(en.nextElement());
}
/**
* write out an instance of an ECore model for test purposes
* @param instance
* @param classModel
*/
private void testWriteInstance(EObject instance,EPackage classModel) throws MapperException
{
// make a location for the instance, in the same folder as the class model
URI modelUri = classModel.eResource().getURI();
StringTokenizer st = new StringTokenizer(modelUri.toString(),".");
String extension = "mod";
URI instanceUri = URI.createURI(st.nextToken() + "." + extension);
message("URI for test instance: " + instanceUri.toString());
// make a resource set etc.
ResourceSet resourceSet = new ResourceSetImpl();
resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(extension, new XMIResourceFactoryImpl());
Resource instanceResource = resourceSet.createResource(instanceUri);
instanceResource.getContents().add(instance);
// save it
try {instanceResource.save(null);}
catch (IOException ex)
{throw new MapperException("Failed to save EMF model resource: " + ex.getMessage());}
message("Wrote test instance");
}
private void trace(String s) {if (tracing) System.out.println(s);}
private void message(String s) {System.out.println(s);}
}