package com.openMap1.mapper.fhir; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.Calendar; 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.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.EObject; import org.eclipse.emf.ecore.EPackage; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.EStructuralFeature; import org.hl7.fhir.instance.model.AtomEntry; import org.hl7.fhir.instance.model.AtomFeed; import org.hl7.fhir.instance.model.Code; import org.hl7.fhir.instance.model.DateAndTime; import org.hl7.fhir.instance.model.Resource; import org.hl7.fhir.instance.model.ResourceFactory; import org.hl7.fhir.instance.model.String_; import org.hl7.fhir.instance.model.Type; import com.openMap1.mapper.core.MapperException; import com.openMap1.mapper.util.GenUtil; import com.openMap1.mapper.util.ModelUtil; /** * This class provides a bridge between: * (a) any Ecore class model generated (as a mapping target) from a FHIR resource or profile * (b) the class model of the FHIR Java reference implementation * * Given an instance of (a), it will return an instance of (b). * Given an instance of (b), it will return an instance of (a). * * @author RPW * */ public class EcoreReferenceBridge { private boolean tracing = false; private EPackage eCoreModel; public static String REFERENCE_MODEL_PACKAGE = "org.hl7.fhir.instance.model."; /** * resource, component and datatype classes of the reference implementation required by the Ecore model * key - class name; value = the Class object */ private Hashtable<String,Class<?>> refModelClasses; /** * code binding classes of the reference implementation required by the Ecore model * key - class name; value = the Class object */ private Hashtable<String,Class<?>> bindingClasses; /** * code binding factory classes of the reference implementation required by the Ecore model * key - class name; value = the Class object */ private Hashtable<String,Class<?>> bindingFactories; /** * setter and getter methods of the reference model classes * first key = class name; second key = feature name in Ecore model; value = setter or getter method */ private Hashtable<String,Hashtable<String,Method>> getters; private Hashtable<String,Hashtable<String,Method>> setters; // these reference model classes are sometimes needed as arguments to setter methods private String[] specialClasses = {"ResourceReference","Type"}; // lookup table from Ecore resources to the ids they were given when making the reference model instance private Hashtable<EObject,String> readEcoreResourceToId; /* lookup table from read in resource id to created Ecore form of resource. * In case two resources of different resource type have the same id, store multiple resources per id */ private Hashtable<String,Vector<EObject>> readIdToCreatedEcoreResource; // names of fhir packages public static String[] packageNames = {"feed","resources","components","complexTypes","primitiveTypes"}; // fhir packages private EPackage[] fhirPackages = new EPackage[5]; // indexes of FHIR packages public static int FEED = 0; public static int RESOURCES = 1; public static int COMPONENTS = 2; public static int COMPLEXTYPES = 3; public static int PRIMITIVETYPES = 4; public Hashtable<String,String> getModelErrors() {return modelErrors;} private Hashtable<String,String> modelErrors; public Hashtable<String,String> getInstanceErrors() {return instanceErrors;} private Hashtable<String,String> instanceErrors; //--------------------------------------------------------------------------------------------------- // Constructor and checks of the Ecore model //--------------------------------------------------------------------------------------------------- /** * constructor; checks that all the necessary classes can be found in the reference implementation * @param eCoreModel * @throws MapperException if any classes in the Ecore model do not have equivalents * in the Java reference implementation */ public EcoreReferenceBridge(EPackage eCoreModel) throws MapperException { this.eCoreModel = eCoreModel; initialise(); checkModel(); } /** * initialisation */ private void initialise() { // reference model classes refModelClasses = new Hashtable<String,Class<?>>() ; bindingClasses = new Hashtable<String,Class<?>>() ; bindingFactories = new Hashtable<String,Class<?>>() ; // setter and getter methods of the reference model classes getters = new Hashtable<String,Hashtable<String,Method>>() ; setters = new Hashtable<String,Hashtable<String,Method>>() ; // tables needed to resolve resource references (which may be made before the resources) readEcoreResourceToId = new Hashtable<EObject,String>(); readIdToCreatedEcoreResource = new Hashtable<String,Vector<EObject>>() ; // recording errors in checking the model or making an instance modelErrors = new Hashtable<String,String>(); instanceErrors = new Hashtable<String,String>(); } /** * check that all classes in the Ecore model can be found in the FHIR Java reference implementation * @throws MapperException */ public void checkModel() throws MapperException { // model should have a top package 'fhir' containing all other packages String packageName = eCoreModel.getName(); if (!packageName.equals("fhir")) throw new MapperException("Top package is called '" + packageName + "' but should be called 'fhir"); // find other packages in order int p = 0; boolean AtomFeedFound = false; for (Iterator<EPackage> ip = eCoreModel.getESubpackages().iterator();ip.hasNext();) { fhirPackages[p] = ip.next(); if (!fhirPackages[p].getName().equals(packageNames[p])) throw new MapperException("Unexpected sub-package name: " + fhirPackages[p].getName()); // check all classes in the Ecore model for (Iterator<EClassifier> it = fhirPackages[p].getEClassifiers().iterator();it.hasNext();) { EClassifier next = it.next(); if (next instanceof EClass) { EClass theClass = (EClass)next; // String classType = ModelUtil.getMIFAnnotation(theClass, "type"); if (theClass.getName().equals("AtomFeed")) AtomFeedFound = true; checkClass(theClass); /* for any bindings on EAttributes of primitive type 'code', find the binding Enumeration and factory. * Complex data type Demographics has bindings on associations gender and maritalStatus, but * these do not have binding enumerations in the reference implementation */ for (Iterator<EStructuralFeature> iu = theClass.getEStructuralFeatures().iterator();iu.hasNext();) { EStructuralFeature feat = iu.next(); String binding = ModelUtil.getMIFAnnotation(feat, "Binding"); if ((binding != null) && (!binding.equals(""))) { String primitiveType = ModelUtil.getMIFAnnotation(feat, "PrimitiveType"); if ((primitiveType != null) && (primitiveType.equals("code")) && (feat instanceof EAttribute)) getBindingClass(binding,theClass); } } } } p++; } // find a few special classes in the reference model for (int c = 0; c < specialClasses.length;c++) { String className = specialClasses[c]; Class<?> special = getSpecialReferenceClass(className); refModelClasses.put(className, special); } // primitive data type classes for (int c = 0; c < PrimitiveTypes.PRIMITIVETYPES.length;c++) { String type = PrimitiveTypes.PRIMITIVETYPES[c]; String className = PrimitiveTypes.referenceClassName(type); Class<?> special = getSpecialReferenceClass(className); refModelClasses.put(className, special); } /* when all reference model classes have been found, find the setter methods * (which need to have classes as their arguments)*/ findAllSetterMethods(); if (!AtomFeedFound) throw new MapperException("Class model has no 'AtomFeed' class"); } /** * * @param theClass * @throws MapperException */ private void checkClass(EClass theClass) throws MapperException { String className = theClass.getName(); String inResource = ModelUtil.getMIFAnnotation(theClass, "inResource"); if ((inResource != null) && (inResource.equals("List"))) inResource = "List_"; Class<?> refClass = makeReferenceClass(theClass,inResource); if (refClass != null) refModelClasses.put(className, refClass); } //-------------------------------------------------------------------------------------------------------- // Getting the Classes and Methods of the Reference Implementation //-------------------------------------------------------------------------------------------------------- /** * * @param className * @return * @throws MapperException */ private Class<?> makeReferenceClass(EClass theClass, String inResource) throws MapperException { String fullClassName = theClass.getName(); // deal with special class 'List'; inResource has already been fixed for this if (fullClassName.equals("List")) fullClassName = "List_"; if (inResource != null) fullClassName = inResource + "$" + fullClassName; Class<?> refClass = null; String referenceClassName = REFERENCE_MODEL_PACKAGE + fullClassName; try { refClass = Class.forName(referenceClassName); trace("Found class " + referenceClassName); Hashtable<String,Method> classGetters = new Hashtable<String,Method>(); for (Iterator<EStructuralFeature> it = theClass.getEStructuralFeatures().iterator();it.hasNext();) { EStructuralFeature feature = it.next(); if ((fullClassName.equals("AtomFeed")) && (feature instanceof EReference)) {} // the special attribute 'fhir_id' is not in the referecne implementation else if (!(feature.getName().equals("fhir_id"))) { Method getter = getGetter(refClass,feature); if (getter != null) classGetters.put(feature.getName(),getter); } } trace("Getters: " + writeFoundMethods(classGetters)); getters.put(theClass.getName(), classGetters); } catch (Exception ex) { trace("Cannot find reference model class " + referenceClassName + "; " + ex.getMessage()); modelErrors.put(referenceClassName, "Cannot find reference model class " + referenceClassName); } return refClass; } /** * * @param binding * @param theClass */ private void getBindingClass(String binding, EClass theClass) { // find the class containing the binding Enum. If the direct owning class is a Component class, the containing resource class holds the binding String containingClassName = theClass.getName(); String inResource = ModelUtil.getMIFAnnotation(theClass, "inResource"); if (inResource != null) containingClassName = inResource; if (containingClassName.equals("List")) containingClassName = "List_"; // there is no consistent rule for binding class names; so this hack is necessary String modBinding = binding; String[] badBindings = {"SensitivityType", "SensitivityStatus", "ParticipantRequired", "ParticipationStatus", "SlotStatus", "ActStatus", "QuantityCompararator"}; //sic if (GenUtil.inArray(binding, badBindings)) modBinding = GenUtil.initialUpperCase(binding.toLowerCase()); String bindingClassName = REFERENCE_MODEL_PACKAGE + containingClassName + "$" + modBinding; String bindingFactoryName = bindingClassName + "EnumFactory"; try { Class<?> bindingClass = Class.forName(bindingClassName); trace("Found binding class " + bindingClassName); if (bindingClasses.get(binding) != null) trace("More than one class for binding " + binding); bindingClasses.put(binding, bindingClass); Class<?> bindingFactory = Class.forName(bindingFactoryName); trace("Found binding factory " + bindingFactoryName); if (bindingFactories.get(binding) != null) trace("More than one factory class for binding " + binding); bindingFactories.put(binding, bindingFactory); } catch (Exception ex) { // two bindings in data type 'Attachment' will not be found as they are not FHIR-defined if (binding.equals("Language")) {} else if (binding.equals("MimeType")) {} else { message("*** Failed to find binding class " + bindingClassName + " in class " + containingClassName); modelErrors.put(theClass.getName(), "Cannot find binding class " + bindingClassName + " in class " + containingClassName); } } } /** * * @param className * @return */ private Class<?> getSpecialReferenceClass(String className) { Class<?> refClass = null; String referenceClassName = REFERENCE_MODEL_PACKAGE + className; try { refClass = Class.forName(referenceClassName); trace("Found special reference class or primitive data type class " + referenceClassName); } catch (Exception ex) { modelErrors.put(referenceClassName, "Cannot find reference model class " + referenceClassName); trace("Cannot find reference model class " + referenceClassName ); } return refClass; } /** * * @param refClass * @param feature * @return */ private Method getGetter(Class<?> refClass, EStructuralFeature feature) { Method getter = null; String featName = feature.getName(); // naming convention in reference implementation if (featName.equals("class")) featName = "class_"; String refModelName = ModelUtil.getMIFAnnotation(feature, "RefModelName"); if (refModelName != null) featName = refModelName; String getterName = "get" + GenUtil.initialUpperCase(featName); // getter methods have no arguments Class<?>[] args = new Class<?>[0]; try { // getMethod, rather than getDeclaredMethod, allows for methods inherited from superclasses getter = refClass.getMethod(getterName, args); } catch (Exception ex) { modelErrors.put(refClass.getName(), "Cannot find getter method " + getterName); trace("Cannot find getter method " + getterName + " of class " + refClass.getName()); } return getter; } /** * * @param methods * @return a string of Ecore model feature names , for which getters or setters have been found */ private String writeFoundMethods(Hashtable<String,Method> methods) { String methodNames = ""; for (Enumeration<String> en = methods.keys(); en.hasMoreElements();) methodNames = methodNames + en.nextElement() + "; "; return methodNames; } /** * */ private void findAllSetterMethods() { for (Enumeration<String> en = refModelClasses.keys(); en.hasMoreElements();) { String ecoreClassName = en.nextElement(); Class<?> refClass = refModelClasses.get(ecoreClassName); EClass theClass = getNamedClass(ecoreClassName); Hashtable<String,Method> classSetters = new Hashtable<String,Method>(); if (theClass != null) // there is no Ecore class for the special classes { for (Iterator<EStructuralFeature> it = theClass.getEStructuralFeatures().iterator();it.hasNext();) { EStructuralFeature feature = it.next(); // there are no setter methods for associations with unbounded multiplicity - use the getter and add if ((feature instanceof EReference) && (((EReference)feature).getUpperBound() == -1)) {} else { // do nothing for the associations from AtomFeed to resources - use entiry in stead if ((ecoreClassName.equals("AtomFeed")) && ((feature instanceof EReference))) {} // the special attribute 'fhir_id' is not in the reference model else if (!(feature.getName().equals("fhir_id"))) { Method setter = getSetter(theClass,refClass,feature); if (setter != null) classSetters.put(feature.getName(),setter); } } } trace("Setters for Ecore class " + ecoreClassName + ": " + writeFoundMethods(classSetters)); setters.put(theClass.getName(), classSetters); } } } /** * get a named class in any package, assuming that the same class name * does not occur in different packages * @param className * @return */ private EClass getNamedClass(String className) { EClass namedClass = null; for (int p = 0; p < packageNames.length; p++) { EClassifier next = fhirPackages[p].getEClassifier(className); if ((next != null) && (next instanceof EClass)) namedClass = (EClass)next; } return namedClass; } /** * * @param refClass * @param feature * @return */ private Method getSetter(EClass ownerClass, Class<?> refClass, EStructuralFeature feature) { String refClassName = refClass.getName(); String featureName = feature.getName(); Method setter = null; EClassifier type = feature.getEType(); String refModelName = ModelUtil.getMIFAnnotation(feature, "RefModelName"); if (refModelName != null) featureName = refModelName; String setterName = "set" + GenUtil.initialUpperCase(featureName); if (setterName.equals("setClass")) setterName = "setClass_"; Class<?>[] args = new Class<?>[1]; // EReferences with upper bound 1, whose type is a class if (type instanceof EClass) try { args[0] = refModelClasses.get(type.getName()); // correct argument type for resource references if (ModelUtil.getMIFAnnotation(type, "type").equals("Resource")) args[0] = refModelClasses.get("ResourceReference"); if (args[0] != null) setter = refClass.getDeclaredMethod(setterName, args); else { modelErrors.put(refClassName, "Cannot find class " + type.getName() + " as argument of " + setterName); trace("Cannot find class " + type.getName() + " as argument of " + setterName + " in class " + refClassName); } } catch (Exception ex) { modelErrors.put(refClassName,"Cannot find EReference setter method " + setterName); trace("Cannot find EReference setter method " + setterName + " of class " + refClassName); } // EAttributes; get the class for the primitive data type if (type instanceof EDataType) try { Hashtable<String,Method> classGetters = getters.get(ownerClass.getName()); if (classGetters == null) throw new MapperException("No getters found for class " + refClassName); Method getter = classGetters.get(featureName); if (getter == null) throw new MapperException("No getter found for feature " + featureName + " of class " + refClassName); // make the setter argument type the same as the getter result type args[0] = getter.getReturnType(); // getMethod, rather than getDeclaredMethod, also gets inherited methods setter = refClass.getMethod(setterName, args); } catch (Exception ex) { modelErrors.put(refClassName,"Exception finding setter method " + setterName); trace("Exception finding setter method " + setterName + " of class " + refClassName + ";" + ex.getMessage()); } return setter; } //--------------------------------------------------------------------------------------------------- // Convert an Ecore model instance to a FHIR reference implementation instance //--------------------------------------------------------------------------------------------------- /** * convert an instance of the Ecore model into an instance of the FHIR Java reference implementation * @param inputObject the Ecore representation of a bundle of resources * @return the FHIR reference implementation representation of a bundle */ public AtomFeed getReferenceModelFeed(EObject inputObject) throws MapperException { EClass inputClass = inputObject.eClass(); // input EObject must be an instance of ECore class 'AtomFeed' String topClassName = inputClass.getName(); if (!topClassName.equals("AtomFeed")) throw new MapperException("top Ecore model object is not of class 'AtomFeed'"); // the package of the input object must be the same as the package read in the constructor EPackage instancePackage = (EPackage)inputObject.eClass().getEPackage().eContainer(); compareEcoreModels(instancePackage,eCoreModel); // eCoreModel = instancePackage; /* if (!instancePackage.equals(eCoreModel)) throw new MapperException("Ecore instance is not an instance of the model given to the Ecore to reference implementation bridge"); */ AtomFeed feed = new AtomFeed(); String authorName = (String)inputObject.eGet(inputClass.getEStructuralFeature("authorName")); if (authorName != null) feed.setAuthorName(authorName); String authorUri = (String)inputObject.eGet(inputClass.getEStructuralFeature("authorUri")); if (authorUri != null) feed.setAuthorUri(authorUri); String id = (String)inputObject.eGet(inputClass.getEStructuralFeature("id")); if (id != null) feed.setId(id); String title = (String)inputObject.eGet(inputClass.getEStructuralFeature("title")); if (title != null) feed.setTitle(title); // the time the AtomFeed was updated is now - ignore any value from the EObject feed.setUpdated(new DateAndTime(Calendar.getInstance())); //feed.setUpdated(Calendar.getInstance()); // follow EReferences of the Ecore AtomFeed object to resources for (Iterator<EStructuralFeature> it = inputObject.eClass().getEStructuralFeatures().iterator(); it.hasNext();) { EStructuralFeature feat = it.next(); // resources of the allowed types if (feat instanceof EReference) try { Object value = inputObject.eGet(feat); // all EReferences from AtomFeed have max multiplicity unbounded, so should deliver lists if ((value != null) && (value instanceof List<?>)) { List<?> vList = (List<?>) value; for (Iterator<?> ir = vList.iterator(); ir.hasNext();) { Object next = ir.next(); if (next instanceof EObject) { EObject resource = (EObject)next; // this starts the recursion down into the resource structure Resource refResource = makeReferenceModelResource(resource); EStructuralFeature id_feat = resource.eClass().getEStructuralFeature("fhir_id"); if (id_feat == null) throw new MapperException("Resource " + resource.eClass().getName() + " has not any 'fhir_id' feature for the FHIR logical id"); String resourceId = (String)resource.eGet(id_feat); if (resourceId == null)throw new MapperException("Resource " + resource.eClass().getName() + " has no FHIR logical id"); readEcoreResourceToId.put(resource, resourceId); // title of the AtomFeed entry is the resource type, followed by its id String entryTitle = resource.eClass().getName() + ": " + resourceId; addResource(feed, refResource, entryTitle, resourceId); } } } else if (!(value instanceof List<?>)) {throw new MapperException("Value of feature " + feat.getName() + " is not a list.");} } catch (Exception ex) {ex.printStackTrace(); throw new MapperException("failed to add resource " + feat.getName());} } return feed; } /** * * @param resource * @return * @throws MapperException */ public Resource makeReferenceModelResource(EObject resource) throws MapperException { return (Resource)makeReferenceModelObject(resource, null); } /** * recursive descent down the ECore resource object structure, making the reference model object structure * @param eCoreObject * @return * @throws MapperException */ private Object makeReferenceModelObject(EObject eCoreObject, Object resourceObject) throws MapperException { Object result = null; EClass eCoreClass = eCoreObject.eClass(); String eCoreClassName = eCoreClass.getName(); Hashtable<String,Method> settersOfClass = setters.get(eCoreClassName); Hashtable<String,Method> gettersOfClass = getters.get(eCoreClassName); String objectType = ModelUtil.getMIFAnnotation(eCoreClass, "type"); if (objectType == null) throw new MapperException("Ecore object of class " + eCoreClassName + " has no reference model type"); Class<?> referenceModelClass = refModelClasses.get(eCoreClassName); if (referenceModelClass == null) throw new MapperException("Reference model class " + eCoreClassName + " not found"); try { if (objectType.equals("Resource")) { result = ResourceFactory.createResource(eCoreClassName); } else if (objectType.equals("Component")) { /* make the component; there is no factory in the reference implementation to do this. * Component classes, being inner classes, have a constructor with one parameter which * is the instance of the outer class. * They may also have other constructors with more parameters */ if (resourceObject == null) throw new MapperException("Component class " + eCoreClassName + "has no containing resource"); Class<?>[] constructorArgtypes = new Class[1]; constructorArgtypes[0] = resourceObject.getClass(); // trace("predicted param class: " + constructorArgtypes[0].getName()); Object[] constructorArgs = new Object[1]; constructorArgs[0] = resourceObject; Constructor<?>[] constructors = referenceModelClass.getConstructors(); int foundConstructors = 0; // look for the constructor with only one parameter, of the correct class for (int c = 0; c < constructors.length;c++) { Constructor<?> cons = constructors[c]; Class<?>[] paramTypes = cons.getParameterTypes(); showParamTypes(paramTypes); if (paramTypes.length == 0) { foundConstructors++; constructorArgs = new Object[0]; result = cons.newInstance(constructorArgs); } else if (paramTypes.length == 1) { String actualParamType = resourceObject.getClass().getName(); String expectedParamType = paramTypes[0].getName(); if (actualParamType.equals(expectedParamType)) { foundConstructors++; result = cons.newInstance(constructorArgs); } } } if (foundConstructors != 1) throw new MapperException(foundConstructors + " constructors with one parameter for class " + referenceModelClass.getName()); } else if (objectType.equals("ComplexDataType")) { result = ResourceFactory.createType(eCoreClassName); } else if (objectType.equals("PrimitiveDataType")) { result = makePrimitiveTypeValue(eCoreObject,eCoreClassName); } else { trace("Unrecognised type of ECore model object:" + objectType); } // cast the reference model object to the correct class (is this necessary?) result = referenceModelClass.cast(result); } catch (Exception ex) { ex.printStackTrace(); instanceErrors.put(eCoreClassName, "failed to make reference model object"); trace("failed to make reference model object of class " + eCoreClassName + ": " + ex.getMessage()); } // primitive data type objects have one attribute 'value' which has already been dealt with by makePrimitiveTypeValue if ((result != null) && (!objectType.equals("PrimitiveDataType"))) for (Iterator<EStructuralFeature> it = eCoreObject.eClass().getEStructuralFeatures().iterator();it.hasNext();) { EStructuralFeature feat = it.next(); String featureName = feat.getName(); Object value = eCoreObject.eGet(feat); if (value != null) { // EAttributes of resources, components and complex types are all marked with a primitive type, and have a setter method if (feat instanceof EAttribute) { String primitiveType = ModelUtil.getMIFAnnotation(feat,"PrimitiveType"); if (primitiveType == null) throw new MapperException("Attribute '" + featureName + "' of class " + eCoreClassName + " has no primitive type "); Object refValue = PrimitiveTypes.typeValue(value, primitiveType); applySetterMethod(settersOfClass, feat, eCoreObject.eClass(),result, eCoreClassName,resourceObject, refValue); } /* EReferences with max cardinality 1 deliver a value and have a setter method (unless they are a resource reference); those with max cardinality > 1 deliver a list, and do not have a setter */ else if (feat instanceof EReference) { if (feat.getUpperBound() == 1) { EObject target = (EObject)value; //recursive step if (resourceObject == null) resourceObject = result; Object refValue = makeReferenceModelObject(target,resourceObject); if (refValue != null) applySetterMethod(settersOfClass, feat, eCoreObject.eClass(),result, eCoreClassName,resourceObject, refValue); } else if (feat.getUpperBound() == -1) { if (value instanceof List) { List<?> lValue = (List<?>)value; for (Iterator<?> iv = lValue.iterator(); iv.hasNext();) { Object next = iv.next(); EObject target = (EObject)next; // recursive step if (resourceObject == null) resourceObject = result; Object refValue = makeReferenceModelObject(target,resourceObject); if (refValue != null) addToGetterResult(gettersOfClass,featureName,result,eCoreClassName,refValue); } } else throw new MapperException("Value of feature " + featureName + " of class " + eCoreClassName + " is not a list"); } } } } return result; } /** * show the parameter types of a constructor * @param paramTypes */ private void showParamTypes(Class<?>[] paramTypes) { String types = "Parameter types "; for (int p = 0; p < paramTypes.length;p++) types = types + paramTypes[p].getName() + " "; message(types); } /** * * @param setter * @param featureName * @param target * @param targetClassName * @param value * @throws MapperException */ private void applySetterMethod(Hashtable<String,Method> settersOfClass, EStructuralFeature feat, EClass theClass, Object target, String targetClassName, Object resourceObject, Object value) { String featureName = feat.getName(); try { if (settersOfClass == null) {throw new MapperException("No setter methods for class " + targetClassName);} Method setter = settersOfClass.get(featureName); if (setter != null) { Object setValue = value; String binding = ModelUtil.getMIFAnnotation(feat, "Binding"); String primitiveType = ModelUtil.getMIFAnnotation(feat, "PrimitiveType"); // ignore bindings except for EAttributes of primitive type 'code' if (binding != null) trace("Binding " + binding + " of feature " + feat.getName() + " of class " + theClass.getName() + " with primitive type " + primitiveType); if ((binding != null) && (primitiveType != null) && (primitiveType.equals("code"))) { // boundValue returns null for an invalid code value trace("getting bound value"); setValue = boundValue(value,binding,feat,theClass,target,resourceObject); } if (setValue != null) { // setter methods have one parameter Class<?> setterParam = setter.getParameterTypes()[0]; // cast the value to that class Object[] params = new Object[1]; params[0] = setterParam.cast(setValue); // apply the setter method setter.invoke(target, params); } } else { instanceErrors.put(targetClassName, "No setter method for feature " + featureName); trace("No setter method for feature " + featureName + " of class " + targetClassName); } } catch (Exception ex) { ex.printStackTrace(); instanceErrors.put(targetClassName, "Failed to apply setter method " + featureName); trace("Failed to apply setter method " + featureName + " of class " + targetClassName + "; "); } } /** * * @param value * @param binding * @param feat * @param target * @param resourceObject * @return * @throws MapperException */ private Object boundValue(Object value,String binding, EStructuralFeature feat,EClass theClass, Object target, Object resourceObject) throws Exception { Object setValue = null; // if there is no binding, return this value if (binding == null) return value; else if (binding.equals("ResourceType")) { return value; } else if ((!binding.equals("")) && (feat instanceof EAttribute)) { if (!(value instanceof Code)) throw new MapperException("Bound value is not a code"); // get the binding factory class Class<?> bindingFactory = bindingFactories.get(binding); if (bindingFactory == null) throw new MapperException("Cannot find binding factory class " + binding); // message("Binding factory class: " + bindingFactory.getName()); /* make an instance of the binding factory class; * if it is a static class, the constructor has no parameters, * or if it is not, the constructor has one argument, the enclosing resource or complex data type object */ Constructor<?> binder = bindingFactory.getConstructors()[0]; //assume there is only one constructor Class<?>[] paramTypes = binder.getParameterTypes(); int nTypes = paramTypes.length; Object[] constructorArgs = new Object[nTypes]; if (nTypes == 1)// when the binding factory class is not static (does this ever occur?) { constructorArgs[0] = target; // if the target object is of a component class, use the containing resource in the constructor if ((resourceObject != null) && (target.getClass().getName().contains("$"))) {constructorArgs[0] = resourceObject;} } Object binderInstance = binder.newInstance(constructorArgs); // use the binder instance to make an Enum Class<?>[] argtypes = new Class[1]; argtypes[0] = Class.forName("java.lang.String"); Object[] args = new Object[1]; args[0] = ((Code)value).getValue(); Method codeSetter = null; try {codeSetter = bindingFactory.getDeclaredMethod("fromCode", argtypes);} catch (Exception ex) {throw new MapperException("Binding factory for '" + binding + "' has no 'fromCode' method");} // exception will be thrown by an invalid code value Object res = null; try { res = codeSetter.invoke(binderInstance, args); } catch (Exception ex) { res = null; setValue = null; instanceErrors.put(theClass.getName(), "Invalid code value '" + (String)args[0] + "' for binding '" + binding + "'"); message ("Invalid code value '" + (String)args[0] + "' for binding '" + binding + "'"); } // convert the instance to an Enumeration, depending on the binding if (res != null) { setValue = makeEnumerationGeneric(binding, res, feat, theClass); } } return setValue; // null if the code value was invalid } /** * * @param binding * @param res * @param feat * @param theClass * @return * @throws MapperException */ private org.hl7.fhir.instance.model.Enumeration<?> makeEnumerationGeneric(String binding, Object res, EStructuralFeature feat, EClass theClass) throws MapperException { // message("\nMaking Generic Enumeration for binding " + binding); org.hl7.fhir.instance.model.Enumeration<?> instance = null; try { Class<?> enumerationClass = Class.forName("org.hl7.fhir.instance.model.Enumeration"); Constructor<?>[] cons = enumerationClass.getConstructors(); // find a constructor with one argument for (int c = 0; c < cons.length; c++) { Constructor<?> con = cons[c]; Class<?>[] argClasses = con.getParameterTypes(); if (argClasses.length == 1) { Object[] args = new Object[1]; args[0] = (Enum<?>)res; instance = (org.hl7.fhir.instance.model.Enumeration<?>) con.newInstance(args); } } } catch (Exception ex) {ex.printStackTrace();} return instance; } /** * * @param gettersOfClass * @param featureName * @param target * @param targetClassName * @param value */ @SuppressWarnings("unchecked") private void addToGetterResult(Hashtable<String,Method> gettersOfClass, String featureName,Object target, String targetClassName, Object value) throws MapperException { try { if (gettersOfClass == null) {throw new MapperException("No getter methods for class " + targetClassName);} Method getter = gettersOfClass.get(featureName); if (getter != null) { // getter methods have no parameters Object[] params = new Object[0]; // apply the getter method which gives a list result, and add to it ((List<Object>)getter.invoke(target, params)).add(value); } else trace("No getter method for feature " + featureName + " of class " + targetClassName); } catch (Exception ex) {throw new MapperException("Failed to add to result of getter method " + featureName + " of class " + targetClassName + "; " + ex.getMessage());} } /** * all Ecore classes representing primitive types should have an EAnnotation PrimitiveType, defining their primitive type, * and a single attribute 'value' of the appropriate EDataType * */ private Type makePrimitiveTypeValue(EObject eCorePrimitiveTypeObject, String parentEcoreClassName) throws MapperException { EClass eCoreClass = eCorePrimitiveTypeObject.eClass(); String ecoreClassName = eCoreClass.getName(); String primitiveType = ModelUtil.getMIFAnnotation(eCoreClass,"PrimitiveType"); if (primitiveType == null) throw new MapperException("Object of class " + ecoreClassName + " has no primitive type "); EStructuralFeature valueAtt = eCoreClass.getEStructuralFeature("value"); if (valueAtt == null) throw new MapperException("Class " + ecoreClassName + " has no value attribute "); Object value = eCorePrimitiveTypeObject.eGet(valueAtt); if (value == null) {message("Null value attribute of class " + ecoreClassName); return null;} else return PrimitiveTypes.typeValue(value, primitiveType); } //--------------------------------------------------------------------------------------------------- // Convert a FHIR reference implementation instance to an ECore model instance //--------------------------------------------------------------------------------------------------- @SuppressWarnings("unchecked") /** * * @param feed * @return * @throws MapperException */ public EObject getEcoreModelInstance(AtomFeed feed) throws MapperException { EClass atomFeedClass = (EClass)fhirPackages[FEED].getEClassifier("AtomFeed"); EObject atomFeedObject = createModelObject("feed.AtomFeed"); Hashtable<String,Method> atomFeedGetters = getters.get("AtomFeed"); // some string attributes of the AtomFeed for (Iterator<EStructuralFeature> it = atomFeedClass.getEStructuralFeatures().iterator();it.hasNext();) { EStructuralFeature feat = it.next(); String featName = feat.getName(); if (feat instanceof EAttribute) try { EAttribute att = (EAttribute)feat; Method getter = atomFeedGetters.get(featName); if (getter == null) throw new MapperException("Missing AtomFeed getter: " + featName); Class<?> resultType = getter.getReturnType(); Object[] args = new Object[0]; if (resultType.getName().equals("String_")) { String_ val = (String_)getter.invoke(feed, args); if (val != null) atomFeedObject.eSet(att, val.getValue()); } else if (resultType.getName().equals("java.lang.String")) { String val = (String)getter.invoke(feed, args); trace("AtomFeed attribute: " + featName + "; value: " + val + "; changeable: " + att.isChangeable()); if (val != null) atomFeedObject.eSet(att, val); } // else message("Non-string AtomFeed attribute: " + featName + " of class " + resultType.getName()); } catch (Exception ex) {ex.printStackTrace();throw new MapperException("Failed to find AtomFeed attribute " + featName);} } // resources bundled in the feed for (Iterator<AtomEntry<? extends Resource>> it = feed.getEntryList().iterator();it.hasNext();) { AtomEntry entry = it.next(); String id = entry.getId(); Resource resource = entry.getResource(); addResourceToECoreModel(resource,atomFeedClass,atomFeedObject,id); addContainedResourcesToECoreModel(resource,atomFeedClass,atomFeedObject); } return atomFeedObject; } /** * * @param resource * @param atomFeedObject * @param id */ @SuppressWarnings("unchecked") private void addResourceToECoreModel(Resource resource,EClass atomFeedClass, EObject atomFeedObject,String id) throws MapperException { String resourceType = resource.getResourceType().name(); // EReference name is the resource name with all lower case EStructuralFeature feat = atomFeedClass.getEStructuralFeature(resourceType.toLowerCase()); if (feat != null) { // all features of the AtomFeed Ecore object should be lists, to hold many resources if (atomFeedObject.eGet(feat) instanceof List<?>) { EObject resourceObject = makeEcoreObject(resource, (EReference)feat,""); // set the FHIR id as a special attribute of the resource object EStructuralFeature id_feat = resourceObject.eClass().getEStructuralFeature("fhir_id"); if (id_feat == null) throw new MapperException("Resource has no attribute 'fhir_id'"); resourceObject.eSet(id_feat, id); // note the object with its id, to resolve resource references Vector<EObject> objectsWithId = readIdToCreatedEcoreResource.get(id); if (objectsWithId == null) objectsWithId = new Vector<EObject>(); objectsWithId.add(resourceObject); readIdToCreatedEcoreResource.put(id, objectsWithId); ((List<EObject>)atomFeedObject.eGet(feat)).add(resourceObject); } else throw new MapperException("Resource EReference of atomfeed should have upper bound -1"); } else trace("Ecore model does not have resource " + resourceType); } /** * if any resource has contained resources in the reference implementation, * de-contain them and convert them into child object of the AtomFeed bundle * in the Ecore model instance * @param resource * @param atomFeedObject * @throws MapperException */ private void addContainedResourcesToECoreModel(Resource resource,EClass atomFeedClass, EObject atomFeedObject) throws MapperException { for (Iterator<Resource> it = resource.getContained().iterator();it.hasNext();) { Resource next = it.next(); String id = next.getXmlId(); addResourceToECoreModel(next,atomFeedClass,atomFeedObject,id); addContainedResourcesToECoreModel(next, atomFeedClass, atomFeedObject); } } /** * * @param refModelObject * @param parentFeature * @return * @throws MapperException */ @SuppressWarnings("unchecked") private EObject makeEcoreObject(Object refModelObject, EReference parentFeature,String path) throws MapperException { String newPath = path + parentFeature.getName() + "."; EClass eCoreClass = (EClass)parentFeature.getEType(); String className = eCoreClass.getName(); String qualifiedClassName = eCoreClass.getEPackage().getName() + "." + className; EObject result = createModelObject(qualifiedClassName); Class<?> refModelClass = refModelClasses.get(className); if (refModelClass == null) throw new MapperException("No reference model class " + className); refModelObject = refModelClass.cast(refModelObject); if (refModelObject == null) throw new MapperException("Cannot cast reference model object to class " + className); // iterate over all attributes and associations found in the reference model Hashtable<String,Method> classGetters = getters.get(className); if (classGetters != null) { for (Enumeration<String> en = classGetters.keys();en.hasMoreElements();) { // find the getter method and result type in the reference model String featName = en.nextElement(); Method getter = classGetters.get(featName); if (getter == null) throw new MapperException("Cannot find getter method " + featName + " of class " + eCoreClass.getName()); // this only works if the getter does not return a list Class<?> resultType = getter.getReturnType(); // find the corresponding feature and result type in the Ecore model EStructuralFeature feat = eCoreClass.getEStructuralFeature(featName); if (feat == null) throw new MapperException("Cannot find Ecore model feature " + featName + " of class " + eCoreClass.getName() + " at path " + path); EClassifier featureType = feat.getEType(); if (featureType == null) throw new MapperException("Null type of Ecore model feature " + featName + " of class " + eCoreClass.getName() + " at path " + path); if (feat instanceof EReference) { //objectType = ModelUtil.getMIFAnnotation(featureType, "type"); resultType = refModelClasses.get(featureType.getName()); // works for all EReferences } if (resultType == null) trace("Null result type for feature " + featName + " of class " + eCoreClass.getName()); // get the result value Object valObj = null; Object[] args = new Object[0]; Class<?>[] argTypes = new Class[0]; try { // use the getter for the (resource or component or complex data type) class to get an object of a primitive data type object valObj = getter.invoke(refModelObject, args); } catch (Exception ex) {throw new MapperException("Method invocation failure: " + ex.getMessage());} if ((valObj != null) && (resultType != null)) { // multiple values if (valObj instanceof List) { for (Iterator<?> io = ((List<?>)valObj).iterator();io.hasNext();) { Object next = io.next(); next = resultType.cast(next); if (next == null) throw new MapperException("Cannot cast multiple value to class " + resultType.getClass().getName()); // recursive step EObject child = makeEcoreObject(next,(EReference)feat,newPath); Object featureVal = result.eGet(feat); if (featureVal instanceof List<?>) ((List<EObject>)featureVal).add(child); } } // single values else { if (feat instanceof EReference) { valObj = resultType.cast(valObj); if (valObj == null) throw new MapperException("Cannot cast single value to class " + resultType.getClass().getName()); // recursive step EObject child = makeEcoreObject(valObj,(EReference)feat,newPath); result.eSet(feat,child); } else if (feat instanceof EAttribute) { boolean resultCanBeConverted = true; // valObj may be an instance of a Primitive data type class, or a String, or an enumerated code Object resultObj = valObj; // Strings can be passed direct to eSet if (resultObj instanceof String) {} // all primitive data type classes have a method 'getValue' else if (PrimitiveTypes.isPrimitiveDataTypeClass(valObj.getClass())) { try { Method valueMethod = valObj.getClass().getMethod("getValue", argTypes); // find the result of 'getValue' on the primitive data type object resultObj = valueMethod.invoke(valObj, args); } catch (Exception ex) {throw new MapperException("Could not apply getValue method of primitive data type object: " + ex.getMessage());} } // codes with bindings return FHIR Enumerations which need to be converted to String codes else if (valObj instanceof org.hl7.fhir.instance.model.Enumeration) { String binding = ModelUtil.getMIFAnnotation(feat, "Binding"); if ((binding != null) && (!binding.equals(""))) resultObj = getEnumeratedCode((org.hl7.fhir.instance.model.Enumeration<?>)valObj,binding); else { trace("No binding found for enumeration value of feature " + feat.getName()); resultCanBeConverted = false; } } else throw new MapperException("Failed to recognise feature type " + valObj.getClass().getName() + " of EAttribute " + feat.getName()); if (resultCanBeConverted) PrimitiveTypes.setEcoreFeature(result,(EAttribute)feat,resultObj); } } } } } else trace("No getter methods for class " + className); return result; } /** * If a feature value is a code with bindings, * convert the Enumeration returned by the * @param feat * @param resultObj * @return */ private String getEnumeratedCode(org.hl7.fhir.instance.model.Enumeration<?> valObj,String binding) throws MapperException { String result = ""; // get the binding class and conversion method Class<?> bindingClass = bindingClasses.get(binding); if (bindingClass == null) throw new MapperException("Cannot find binding class for '" + binding + "'"); Class<?>[] argtypes = new Class[0]; Object[] args = new Object[0]; Method codeGetter = null; try {codeGetter = bindingClass.getDeclaredMethod("toCode", argtypes);} catch (Exception ex) {throw new MapperException("Binding class for '" + binding + "' has no 'toCode' method");} Object next = valObj.getValue(); String elementType = next.getClass().getName(); next = bindingClass.cast(next); if (next == null) {throw new MapperException("Enumeration delivers type '" + elementType + "' for binding '" + binding + "'");} try {result = (String)codeGetter.invoke(next, args);} catch (Exception ex) {throw new MapperException("Failed to invoke 'toCode' method for binding '" + binding + "'");} return result; } //--------------------------------------------------------------------------------------------------- // Utilities //--------------------------------------------------------------------------------------------------- /** create a model object with the right class (without using a generated package)*/ private EObject createModelObject(String qualifiedClassName) throws MapperException { StringTokenizer st = new StringTokenizer(qualifiedClassName,"."); if (st.countTokens() != 2) throw new MapperException("No package name"); String packageName = st.nextToken(); String className = st.nextToken(); EPackage thePackage = null; for (int p = 0; p< packageNames.length; p++) if (packageNames[p].equals(packageName)) thePackage = fhirPackages[p]; if (thePackage == null) throw new MapperException("Package " + packageName + " not found"); EClass theClass = (EClass)thePackage.getEClassifier(className); if (theClass != null) { if (!theClass.isAbstract()) return thePackage.getEFactoryInstance().create(theClass); else { System.out.println("Abstract class " + className); return null; } } else {System.out.println("Null EObject of class " + className);} return null; } /** * a crude comparison of two Ecore models to check they are the same model - * even if they are different Java objects * @param model1 * @param model2 * @throws MapperException if any difference is found */ private void compareEcoreModels(EPackage model1,EPackage model2) throws MapperException { if (!model1.getName().equals(model2.getName())) throw new MapperException("Package names " + model1.getName() + " and " + model2.getName() + " do not match"); for (Iterator<EClassifier> it = model1.getEClassifiers().iterator();it.hasNext();) { EClassifier c1 = it.next(); EClassifier c2 = model2.getEClassifier(c1.getName()); if (c2 == null) throw new MapperException("Model 2 does not have EClassifier " + c1.getName()); if ((c1 instanceof EClass) && (c2 instanceof EClass)) { EClass cc1 = (EClass)c1; EClass cc2 = (EClass)c2; for (Iterator<EStructuralFeature> iu = cc1.getEStructuralFeatures().iterator();iu.hasNext();) { EStructuralFeature f1 = iu.next(); EStructuralFeature f2 = cc2.getEStructuralFeature(f1.getName()); if (f2 == null) throw new MapperException("Class " + cc2.getName() + " has missing feature " + f1.getName() + " in model 2"); } if (cc1.getEStructuralFeatures().size() != cc1.getEStructuralFeatures().size()) throw new MapperException("Class " + cc1.getName() + " has non-matching numbers of features"); } } if (model1.getEClassifiers().size() != model2.getEClassifiers().size()) throw new MapperException("Numbers of EClassifiers do not match"); } /** * diagnostic * @param theClass */ private void writeAllMethods(Class<?> theClass) { message("\nAll constructors of class " + theClass.getName()); Constructor<?>[] allConstructors = theClass.getConstructors(); for (int i = 0; i < allConstructors.length; i++) { message("number of parameters: " + allConstructors[i].getParameterTypes().length); for (int j = 0; j < allConstructors[i].getParameterTypes().length; j++) { Class<?> param = allConstructors[i].getParameterTypes()[j]; message("Parameter class: " + param.getName()); } } message("\nAll declared methods of class " + theClass.getName()); Method[] allMethods = theClass.getDeclaredMethods(); for (int i = 0; i < allMethods.length; i++) { message(allMethods[i].getName()); } message("\nAll member methods of class " + theClass.getName()); Method[] allMemberMethods = theClass.getMethods(); for (int i = 0; i < allMemberMethods.length; i++) { message(allMemberMethods[i].getName()); } } /** * add any Resource to an atom feed * @param feed * @param r * @param title * @param id * @return */ private String addResource(AtomFeed feed, Resource r, String title, String id) { AtomEntry e = new AtomEntry(); e.setUpdated(new DateAndTime(Calendar.getInstance())); //e.setUpdated(Calendar.getInstance()); e.setResource(r); e.setTitle(title); e.setId(id); // e.setCategory(r.getResourceType().toString()); // removed as method no longer recognised feed.getEntryList().add(e); return id; } private void message(String s) {System.out.println(s);} private void trace(String s) {if (tracing) message(s);} }