package org.openehr.am.template; import java.io.File; import java.io.FileWriter; import java.io.InputStream; import java.util.HashMap; import java.util.List; import java.util.Map; import openEHR.v1.template.TEMPLATE; import org.apache.commons.io.FileUtils; import org.apache.log4j.Logger; import org.openehr.am.archetype.Archetype; import org.openehr.am.archetype.constraintmodel.ArchetypeConstraint; import org.openehr.am.archetype.constraintmodel.CAttribute; import org.openehr.am.archetype.constraintmodel.CComplexObject; import org.openehr.am.archetype.constraintmodel.CDomainType; import org.openehr.am.archetype.constraintmodel.CMultipleAttribute; import org.openehr.am.archetype.constraintmodel.CObject; import org.openehr.am.archetype.constraintmodel.CPrimitiveObject; import org.openehr.am.archetype.constraintmodel.Cardinality; import org.openehr.am.archetype.constraintmodel.ConstraintRef; import org.openehr.am.archetype.constraintmodel.primitive.CPrimitive; import org.openehr.am.archetype.constraintmodel.primitive.CString; import org.openehr.am.openehrprofile.datatypes.basic.CDvState; import org.openehr.am.openehrprofile.datatypes.quantity.CDvOrdinal; import org.openehr.am.openehrprofile.datatypes.quantity.CDvQuantity; import org.openehr.am.openehrprofile.datatypes.quantity.CDvQuantityItem; import org.openehr.am.openehrprofile.datatypes.text.CCodePhrase; import org.openehr.am.serialize.ADLSerializer; import org.openehr.rm.datatypes.text.CodePhrase; import org.openehr.rm.support.basic.Interval; import com.thoughtworks.xstream.XStream; import se.acode.openehr.parser.ADLParser; import junit.framework.TestCase; public class TemplateTestBase extends TestCase { TemplateTestBase() { try { loadArchetypeMap(); parser = new OETParser(); } catch(Exception e) { throw new RuntimeException(e); } } public void setUp() throws Exception { flattener = new Flattener(); serializer = new ADLSerializer(); } public void tearDown() throws Exception { template = null; } protected void flattenTemplate(String templateFilename) throws Exception { template = loadTemplate(templateFilename); flattened = flattener.toFlattenedArchetype(template, archetypeMap, templateMap); } protected void printPaths(String filter) { for (String key : flattened.getPathNodeMap().keySet()) { if (filter == null || (filter != null && key.contains(filter))) { System.out.println(key); } } } protected void assertRequired(String path) throws Exception { CObject cobj = assertCObject(path); assertTrue("expected to be required", cobj.isRequired()); } protected void assertNotAllowed(String path) throws Exception { CObject cobj = assertCObject(path); Interval<Integer> notAllowed = new Interval<Integer>(0,0); assertEquals(notAllowed, cobj.getOccurrences()); } protected CObject assertCObject(String path, Archetype archetype) { ArchetypeConstraint ac = archetype.node(path); assertTrue("unexpected non-CObject: " + (ac == null ? null : ac.getClass()) + " on path: [" + path + "]", ac instanceof CObject); CObject cobj = (CObject) ac; return cobj; } protected CObject assertCObject(String name, String path, Archetype archetype) { ArchetypeConstraint ac = archetype.node(path); assertTrue("unexpected non-CObject[ " + name + "] " + (ac == null ? null : ac.getClass()) + " on path: " + path, ac instanceof CObject); CObject cobj = (CObject) ac; return cobj; } protected CObject assertCObject(String path) { return assertCObject(path, flattened); } protected Archetype loadArchetype(String id) throws Exception { ADLParser adlParser = new ADLParser(fromClasspath(ARCHETYPE_PATH + id)); return adlParser.parse(); } /** * Load all archetypes from predefined path * * @throws Exception */ protected void loadAllArchetypes() throws Exception { archetypeMap = new HashMap<String, Archetype>(); File[] files = new File(ARCHETYPE_PATH).listFiles(); for(File file : files) { if(file.getName().contains(".adl")) { ADLParser adlParser = new ADLParser(file); Archetype archetype = adlParser.parse(); archetypeMap.put(archetype.getArchetypeId().toString(), archetype); } } } protected TEMPLATE loadTemplate(String id) throws Exception { template = parser.parseTemplate( fromClasspath(TEMPLATE_PATH + id)).getTemplate(); return template; } protected InputStream fromClasspath(String filename) throws Exception { return this.getClass().getClassLoader().getResourceAsStream(filename); } /** * Compares two CComplexObject by values * * The reason not to use the built-in equals() directly is * the path can be different between flattened archetype and expected * one directly loaded from ADL. */ protected void assertCComplexObjectEquals(String message, CComplexObject ccobj1, CComplexObject ccobj2) { assertEquals(message + ", ccobj.rmTypeName not equals at path: " + ccobj1.path(), ccobj1.getRmTypeName(), ccobj2.getRmTypeName()); assertEquals(message + "ccobj.nodeId not equals at path: " + ccobj1.path(), ccobj1.getNodeId(), ccobj2.getNodeId()); assertEquals(message + "ccobj.occurrences not equals at path: " + ccobj1.path(), ccobj1.getOccurrences(), ccobj2.getOccurrences()); assertEquals(message + ", ccobj.attributes total number not equals at path: " + ccobj1.path(), ccobj1.getAttributes().size(), ccobj2.getAttributes().size()); for(int i = 0, j = ccobj2.getAttributes().size(); i < j; i++) { CAttribute cattr1 = ccobj1.getAttributes().get(i); CAttribute cattr2 = ccobj2.getAttributes().get(i); assertCAttributeEquals(message, cattr1, cattr2); } } protected void assertCComplexObjectOfRmType(String message, ArchetypeConstraint ac, String rmType) throws Exception { assertTrue(message + ", unexpected constraint: " + ac, ac instanceof CComplexObject); CComplexObject ccobj = (CComplexObject) ac; assertEquals(message + ", unexpected rmType: " + ccobj.getRmTypeName(), rmType, ccobj.getRmTypeName()); } protected void assertCComplexObjectOfRmType(ArchetypeConstraint ac, String rmType) throws Exception { assertCComplexObjectOfRmType("Unexpected constraint type " + (ac == null ? "null" : ac.getClass() + " at " + ac.path()), ac, rmType); } protected void assertCompositionConstraint(ArchetypeConstraint ac) throws Exception { assertCComplexObjectOfRmType(ac, COMPOSITION); } protected void assertItemTreeConstraint(ArchetypeConstraint ac) throws Exception { assertCComplexObjectOfRmType(ac, ITEM_TREE); } protected void assertItemListConstraint(ArchetypeConstraint ac) throws Exception { assertCComplexObjectOfRmType(ac, ITEM_LIST); } protected void assertElementConstraint(ArchetypeConstraint ac) throws Exception { assertCComplexObjectOfRmType(ac, ELEMENT); } protected void assertSectionConstraint(ArchetypeConstraint ac) throws Exception { assertCComplexObjectOfRmType(ac, SECTION); } protected void assertObservationConstraint(ArchetypeConstraint ac) throws Exception { assertCComplexObjectOfRmType(ac, OBSERVATION); } protected void assertInstructionConstraint(ArchetypeConstraint ac) throws Exception { assertCComplexObjectOfRmType(ac, INSTRUCTION); } protected void assertEvaluationConstraint(ArchetypeConstraint ac) throws Exception { assertCComplexObjectOfRmType(ac, EVALUATION); } protected void assertActionConstraint(ArchetypeConstraint ac) throws Exception { assertCComplexObjectOfRmType(ac, ACTION); } protected void assertActivityConstraint(ArchetypeConstraint ac) throws Exception { assertCComplexObjectOfRmType(ac, ACTIVITY); } protected void assertDvTextConstraint(ArchetypeConstraint ac) throws Exception { assertCComplexObjectOfRmType(ac, DV_TEXT); } protected void assertDvCodedTextConstraint(ArchetypeConstraint ac) throws Exception { assertCComplexObjectOfRmType(ac, DV_TEXT); } protected void assertDvCountConstraint(ArchetypeConstraint ac) throws Exception { assertCComplexObjectOfRmType(ac, DV_COUNT); } protected void assertCDomainTypeEquals(String message, CDomainType cobj1, CDomainType cobj2) { if(cobj1 instanceof CDvQuantity) { assertTrue(message, cobj2 instanceof CDvQuantity); CDvQuantity cdq1 = (CDvQuantity) cobj1; CDvQuantity cdq2 = (CDvQuantity) cobj2; assertEquals(message, cdq1.getAssumedValue(), cdq2.getAssumedValue()); assertEquals(message, cdq1.getDefaultValue(), cdq2.getDefaultValue()); assertEquals(message, cdq1.getList(), cdq2.getList()); assertEquals(message, cdq1.getProperty(), cdq2.getProperty()); assertEquals(message, cdq1.getOccurrences(), cdq1.getOccurrences()); } else if(cobj1 instanceof CDvOrdinal) { assertTrue(message, cobj2 instanceof CDvOrdinal); CDvOrdinal cdq1 = (CDvOrdinal) cobj1; CDvOrdinal cdq2 = (CDvOrdinal) cobj2; assertEquals(message, cdq1.getAssumedValue(), cdq2.getAssumedValue()); assertEquals(message, cdq1.getDefaultValue(), cdq2.getDefaultValue()); assertEquals(message, cdq1.getList(), cdq2.getList()); assertEquals(message, cdq1.getOccurrences(), cdq1.getOccurrences()); } else if(cobj1 instanceof CCodePhrase) { assertTrue(message, cobj2 instanceof CCodePhrase); CCodePhrase cdq1 = (CCodePhrase) cobj1; CCodePhrase cdq2 = (CCodePhrase) cobj2; assertEquals(message, cdq1.getAssumedValue(), cdq2.getAssumedValue()); assertEquals(message, cdq1.getDefaultValue(), cdq2.getDefaultValue()); assertEquals(message, cdq1.getTerminologyId(), cdq2.getTerminologyId()); assertEquals(message, cdq1.getCodeList(), cdq2.getCodeList()); assertEquals(message, cdq1.getOccurrences(), cdq1.getOccurrences()); } else if(cobj1 instanceof CDvState) { assertTrue(message, cobj2 instanceof CDvState); CDvState cdq1 = (CDvState) cobj1; CDvState cdq2 = (CDvState) cobj2; assertEquals(message, cdq1.getAssumedValue(), cdq2.getAssumedValue()); assertEquals(message, cdq1.getDefaultValue(), cdq2.getDefaultValue()); assertEquals(message, cdq1.getValue(), cdq2.getValue()); assertEquals(message, cdq1.getOccurrences(), cdq1.getOccurrences()); } } protected void assertCAttributeEquals(String message, CAttribute cattr1, CAttribute cattr2) { assertEquals(message + ", cattribute.rmAttributeName not equal at path: " + cattr1.path(), cattr1.getRmAttributeName(), cattr2.getRmAttributeName()); assertEquals(message + ", cattribute.existence not equal at path: " + cattr1.path(), cattr1.getExistence(), cattr2.getExistence()); assertEquals(message + ", cattribute.children size not equal at path: " + cattr1.path(), cattr1.getChildren().size(), cattr2.getChildren().size()); for(int i = 0, j = cattr1.getChildren().size(); i < j; i++) { CObject cobj1 = cattr1.getChildren().get(i); CObject cobj2 = cattr2.getChildren().get(i); assertEquals(message + ", different cobj types at path: " + cobj1.path(),cobj1.getClass(), cobj2.getClass()); // Both complex types if(cobj1 instanceof CComplexObject && cobj2 instanceof CComplexObject) { assertCComplexObjectEquals(message, (CComplexObject) cobj1, (CComplexObject) cobj2); } else if(cobj1 instanceof CPrimitiveObject){ assertTrue(message, cobj2 instanceof CPrimitiveObject); CPrimitiveObject cpo1 = (CPrimitiveObject) cobj1; CPrimitiveObject cpo2 = (CPrimitiveObject) cobj2; assertEquals(message, cpo1.getItem(), cpo2.getItem()); assertEquals(message, cpo1.getOccurrences(), cpo2.getOccurrences()); } else if(cobj1 instanceof ConstraintRef){ assertTrue(message, cobj2 instanceof ConstraintRef); } else if(cobj1 instanceof CDomainType){ assertTrue(message, cobj2 instanceof CDomainType); CDomainType cdt1 = (CDomainType) cobj1; CDomainType cdt2 = (CDomainType) cobj2; assertCDomainTypeEquals(message, cdt1, cdt2); } else { // use the built-in equals assertEquals(message, cobj1, cobj2); } } } /** * Assert that the given ArchetypeConstraint is a CPrimitiveObject * with single string valued c_string item * * @param ac * @param onlyValue */ protected void assertCStringWithSingleValue(ArchetypeConstraint ac, String onlyValue) { CString cs = assertCString(ac); assertTrue("expected only value in CString", cs.getList().size() == 1); assertEquals(onlyValue, cs.getList().get(0)); } /** * Assert that the given ArchetypeConstraint is a CPrimitiveObject * with a default valued c_string item * * @param ac * @param defaultValue */ protected void assertCStringWithDefaultValue(ArchetypeConstraint ac, String defaultValue) { CString cs = assertCString(ac); assertEquals("wrong default value in c_string", defaultValue, cs.defaultValue()); } /** * Assert that the given ArchetypeConstraint is a CPrimitiveObject * with c_string item, then return c_string for further inspection * * @param ac * @param onlyValue */ protected CString assertCString(ArchetypeConstraint ac) { assertNotNull("null instead of CPrimitiveObject", ac); assertTrue("expected CPrimitiveObject instead of " + ac.getClass(), ac instanceof CPrimitiveObject); CPrimitiveObject cop = (CPrimitiveObject) ac; CPrimitive cp = cop.getItem(); assertTrue("expected CString instead of " + cp.getClass(), cp instanceof CString); CString cstring = (CString) cp; return cstring; } protected void assertCCodePhraseWithDefaultValue( ArchetypeConstraint ac, String terminologyId, String code) { assertTrue("unexpected constraint instead of CCodePhrase: " + (ac == null ? "null" : ac.getClass()), ac instanceof CCodePhrase); CCodePhrase ccp = (CCodePhrase) ac; CodePhrase cp = new CodePhrase(terminologyId, code); assertEquals("unexpected CCodePhrase.defaultValue", cp, ccp.getDefaultValue()); } protected void assertCDvQuantityWithSingleItem(ArchetypeConstraint ac, CDvQuantityItem item) throws Exception { assertTrue("unexpected constraint instead of CDvQuantity: " + (ac == null ? "null" : ac.getClass()), ac instanceof CDvQuantity); CDvQuantity cdq = (CDvQuantity) ac; assertNotNull("expecting non-empty list", cdq.getList()); assertEquals("expecting only one item", 1, cdq.getList().size()); assertEquals("cDvQuantityItem diff", item, cdq.getList().get(0)); } protected void assertCCodePhraseWithCodeList( ArchetypeConstraint ac, String terminologyId, List<String> codeList) { assertTrue("unexpected constraint instead of CCodePhrase: " + (ac == null ? "null" : ac.getClass()), ac instanceof CCodePhrase); CCodePhrase ccp = (CCodePhrase) ac; assertEquals("unexpected CCodePhrase.defaultValue", terminologyId, ccp.getTerminologyId().toString()); assertEquals("unexpected CCodePhrase.codeList", codeList, ccp.getCodeList()); } protected void loadArchetypeMap() throws Exception { String[] ids = { "openEHR-EHR-COMPOSITION.prescription.v1", "openEHR-EHR-INSTRUCTION.medication.v1", "openEHR-EHR-ITEM_TREE.medication.v1", "openEHR-EHR-ITEM_TREE.medication_mod.v1", "openEHR-EHR-SECTION.medications.v1", "openEHR-EHR-ITEM_TREE.medication_test_one.v1", "openEHR-EHR-ITEM_TREE.medication_test_three.v1", "openEHR-EHR-ITEM_TREE.medication_multiple_constraint_test.v1", "openEHR-EHR-ITEM_TREE.medication_multiple_constraint_test2.v1", "openEHR-EHR-SECTION.ad_hoc_heading.v1", "openEHR-EHR-SECTION.simple_section_name.v1", "openEHR-EHR-COMPOSITION.test.v1", "openEHR-EHR-EVALUATION.structured_summary.v1", "openEHR-EHR-EVALUATION.review_of_procedures.v1", "openEHR-EHR-ACTION.medication.v1", "openEHR-EHR-ITEM_TREE.medication.v1", "openEHR-EHR-ITEM_TREE.medication_quantity_test.v1", "openEHR-EHR-ITEM_TREE.medication_quantity_test2.v1", "openEHR-EHR-OBSERVATION.waist_hip.v2", "openEHR-EHR-OBSERVATION.lab_test.v1", "openEHR-EHR-OBSERVATION.heart_failure_stage.v2", "openEHR-EHR-EVALUATION.medication_review.v1" }; archetypeMap = new HashMap<String, Archetype>(); for(String id : ids) { Archetype archetype = loadArchetype(id + ".adl"); archetypeMap.put(archetype.getArchetypeId().toString(), archetype); } } /** * Print out given archetype in ADL for debugging purpose * * @param toPrint * @throws Exception */ protected void printADL(Archetype toPrint) throws Exception { serializer.output(toPrint, System.out); } /** * Print out given archetype in XML for debugging purpose * * @param toPrint * @throws Exception */ protected void printXML(Object toPrint) throws Exception { XStream xstream = new XStream(); String xml = xstream.toXML(toPrint); System.out.println(xml); } protected String toXML(Archetype toPrint) throws Exception { XStream xstream = new XStream(); String xml = xstream.toXML(toPrint); return xml; } protected void writeXML(Archetype archetype, String filename) throws Exception { FileUtils.writeStringToFile(new File(filename), toXML(archetype), "UTF-8"); } protected void writeADL(Archetype archetype, String filename) throws Exception { FileWriter writer = new FileWriter(filename); try { serializer.output(archetype, writer); } finally { writer.close(); } } /** * Assert all c_objects and named c_attributed on the path are required * * @param pathvalue * @throws Exception */ protected void assertTemplateLevelRequired(String key, String pathvalue) throws Exception { // check the occurrences of c_object assertSingleRequired(key, pathvalue); int i = pathvalue.lastIndexOf("/"); if(pathvalue.endsWith("']")) { int j = pathvalue.substring(0, pathvalue.length() - 2).lastIndexOf("'"); if(j > 0 && i > j) { // '/' inside name i = pathvalue.substring(0, i).lastIndexOf("/"); } } String attr = pathvalue.substring(i + 1); String newpath = null; if(i > 0) { newpath = pathvalue.substring(0, i); } else { newpath = "/"; } if(newpath.endsWith("name")) { i = newpath.lastIndexOf("/"); if(i > 0) { // name/value attr = newpath.substring(i + 1) + attr; newpath = newpath.substring(0, i); } } i = attr.indexOf("["); if(i > 0) { attr = attr.substring(0, i); } // check the existence and cardinality on c_attribute CObject cobj = assertCObject(newpath); if(cobj instanceof CComplexObject) { CComplexObject ccobj = (CComplexObject) cobj; CAttribute cattr = ccobj.getAttribute(attr); if(cattr == null) { fail("c_attribute of name " + attr + " not found on path: " + pathvalue); } assertTrue("unexpected optional attribute on path: " + cattr.path(), cattr.isRequired()); // No need to check cardinality //if(cattr instanceof CMultipleAttribute) { // CMultipleAttribute cma = (CMultipleAttribute) cattr; // Cardinality cardinality = cma.getCardinality(); // assertNotNull("unexpected null lower limit of cardinality on: " // + pathvalue, cardinality.getInterval().getLower()); // assertEquals("unexpected lower limit of cardinality on : " // + pathvalue, 1, cardinality.getInterval().getLower().intValue()); //} } if( ! "/".equals(newpath)) { // recursive assertTemplateLevelRequired(key, newpath); } } protected void assertTemplateLevelRequired(String path) throws Exception { assertTemplateLevelRequired(null, path); } protected void assertSingleRequired(String name, String path) throws Exception { assertMaxOccurrences(name, path, 1); assertMinOccurrences(name, path, 1); } protected void assertMaxOccurrences(String name, String path, int max) throws Exception { assertMaxOccurrences(null, path, max, flattened); } protected void assertMaxOccurrences(String name, String path, int max, Archetype archetype) throws Exception { CObject cobj = assertCObject(name, path, archetype); assertNotNull("unexpected null maximum occurrences at path [" + name + "] "+ path, cobj.getOccurrences().getUpper()); assertEquals("unexpected maximum occurrences at path [" + name + "] "+ path, max, cobj.getOccurrences().getUpper().intValue()); } protected void assertMinOccurrences(String path, int min, Archetype archetype) throws Exception { assertMinOccurrences(null, path, min, archetype); } protected void assertMinOccurrences(String name, String path, int min, Archetype archetype) throws Exception { CObject cobj = assertCObject(path, archetype); assertNotNull("unexpected null minimum occurrences at path [" + name + "] "+ path, cobj.getOccurrences().getLower()); assertEquals("unexpected minimum occurrences at path [" + name + "] "+ path, min, cobj.getOccurrences().getLower().intValue()); } protected void assertMinOccurrences(String name, String path, int min) throws Exception { assertMinOccurrences(name, path, min, flattened); } /* static constant fields */ protected static final String COMPOSITION = "COMPOSITION"; protected static final String SECTION = "SECTION"; protected static final String EVALUATION = "EVALUATION"; protected static final String OBSERVATION = "OBSERVATION"; protected static final String INSTRUCTION = "INSTRUCTION"; protected static final String ACTIVITY = "ACTIVITY"; protected static final String ACTION = "ACTION"; protected static final String ITEM_TREE = "ITEM_TREE"; protected static final String ITEM_LIST = "ITEM_LIST"; protected static final String ELEMENT = "ELEMENT"; protected static final String DV_TEXT = "DV_TEXT"; protected static final String DV_COUNT = "DV_COUNT"; protected static final String DV_CODED_TEXT = "DV_CODED_TEXT"; /* path for test fixtures */ private static final String ARCHETYPE_PATH = "archetypes/"; private static final String TEMPLATE_PATH = "templates/"; /* member fields */ protected TEMPLATE template; protected OETParser parser; protected Flattener flattener; protected ADLSerializer serializer; protected Map<String, Archetype> archetypeMap; protected Map<String, TEMPLATE> templateMap; protected Archetype flattened; protected Archetype archetype; protected Archetype expected; private static final Logger log = Logger.getLogger(TemplateTestBase.class); }