Commit 50825531 authored by R.W.Majeed's avatar R.W.Majeed
Browse files

Fixed bug: unmarshalled observations did not contain any modifiers.

Added function to replace conceptId. JAXB tests
parent 2250dc9f
<!--
#%L
histream
%%
Copyright (C) 2013 - 2015 R.W.Majeed
%%
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
#L%
-->
<dwh-eav xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >
<!-- TODO namespace xmlns="http://sekmi.de/histream/dwh-eav..." -->
<!-- chronologisch impliziert, dass der zeitstempel eines nachfolgenden elementes gr��er als alle vorangehenden elemente sein muss. Der Zeitstempel kann vor dem Encounter-Start liegen -->
<meta>
<!-- Zeitpunkt, an dem der Export erstellt wurde bzw. Datenstand -->
<etl strategy="replace-visit" />
<source timestamp="2015-04-21T08:58:00" id="test"/>
<!-- weitere metadaten n�tig? wertebereich, datentypen, ontologie, ...? -->
</meta>
<visit>
<patid>XX12345</patid>
<surname>Dampf</surname>
<names>A</names>
<birthdate>2001-01-01</birthdate>
<deathdate>2020</deathdate>
<sex>F</sex>
<encounter start="2014-01-01T10:30:00" end="2014-01-05T10:30:00">XXE12345</encounter>
<location>Zuhause</location>
<!-- TODO inpatient/outpatient -->
<provider>xxxa</provider>
<facts>
<fact concept="T:date:secs" start="2014-09-07T10:40:03"/>
<fact concept="T:date:mins" start="2014-09-07T10:40"/>
<fact concept="T:date:hours" start="2014-09-07T10"/>
<fact concept="T:date:day" start="2014-09-07"/>
<fact concept="T:date:month" start="2014-09"/>
<fact concept="T:date:year" start="2014"/>
<!-- test parsing of data types -->
<fact concept="T:type:str"><value xsi:type="string">abc123</value></fact>
<fact concept="T:type:int"><value xsi:type="numeric">123</value></fact>
<!-- value attributes can be used in elements fact and value -->
<fact concept="T:type:dec"><value xsi:type="numeric" unit="mm" flag="A">123.456</value></fact>
<fact concept="T:full" start="2010" end="2011" location="T:LOC"><value xsi:type="numeric" unit="mm" flag="A">123.456</value></fact>
<!--
<fact concept="T:type:enum" type="xsi:integer">1</fact>
-->
<!-- test group items -->
<fact concept="T:group:1">
<value xsi:type="string">groupvalue</value>
<modifier code="T:mod:1"/>
<modifier code="T:mod:2"><value xsi:type="string">def456</value></modifier>
<modifier code="T:mod:3"><value xsi:type="numeric" unit="mm" flag="A">78.9</value></modifier>
</fact>
<!-- group without value -->
<fact concept="T:group:2">
<modifier code="T:mod:1"/>
</fact>
</facts>
</visit>
<!-- weitere zeitstempel -->
</dwh-eav>
......@@ -42,6 +42,12 @@ public interface Observation extends ConceptValuePair, ExternalSourceType{
@Override
String getConceptId();
/**
* Replace the concept id. All modifiers will be removed from the observation.
* @param newConceptId new concept id
*/
void replaceConcept(String newConceptId);
@Override
Value getValue();
void setValue(Value value);
......
......@@ -23,7 +23,6 @@ package de.sekmi.histream.impl;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
......@@ -34,6 +33,7 @@ import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlType;
import de.sekmi.histream.DateTimeAccuracy;
import de.sekmi.histream.Modifier;
......@@ -49,6 +49,7 @@ import de.sekmi.histream.Value;
*/
@XmlRootElement(name="fact")
@XmlAccessorType(XmlAccessType.NONE)
@XmlType(propOrder={"abstractValue","modifierList"})
@XmlSeeAlso({StringValue.class,NumericValue.class})
public class ObservationImpl implements Observation{
@XmlTransient
......@@ -85,7 +86,7 @@ public class ObservationImpl implements Observation{
* Modifiers
*/
@XmlTransient // see getModifierList / setModifierList
protected Hashtable<String, ModifierImpl> modifiers;
protected List<ModifierImpl> modifiers;
/**
* Array of extensions, managed by the ObservationFactory
......@@ -149,13 +150,22 @@ public class ObservationImpl implements Observation{
@Override
public Modifier getModifier(String modifierId) {
if( modifiers != null )return modifiers.get(modifierId);
else return null;
if( modifiers == null ){
// no modifiers
return null;
}
for( Modifier m : modifiers ){
if( m.getConceptId().equals(modifierId) )return m;
}
// not found
return null;
}
@Override
public Iterator<Modifier> getModifiers(){
final Iterator<ModifierImpl> iter = modifiers.values().iterator();
final Iterator<ModifierImpl> iter = modifiers.iterator();
return new Iterator<Modifier>(){
@Override
......@@ -208,19 +218,20 @@ public class ObservationImpl implements Observation{
@Override
public Modifier addModifier(String modifierId, Value value) {
ModifierImpl m = new ModifierImpl(modifierId);
// lazy allocate modifiers
if( modifiers == null ){
modifiers = new Hashtable<>();
}
modifiers = new ArrayList<>();
}else
// check for duplicate assignment
if( modifiers.containsKey(modifierId) )
if( getModifier(modifierId) != null ){
throw new IllegalArgumentException("Duplicate modifier key");
}
ModifierImpl m = new ModifierImpl(modifierId);
m.setValue(value);
// add to modifier list
modifiers.put(modifierId, m);
modifiers.add(m);
return m;
}
......@@ -237,22 +248,6 @@ public class ObservationImpl implements Observation{
this.locationId = locationId;
}
/**
* Getter for JAXB
* @return modifier list
*/
@XmlElement(name="modifier")
protected List<ModifierImpl> getModifierList(){
if( modifiers == null || modifiers.isEmpty() )return null;
return new ArrayList<ModifierImpl>(modifiers.values());
}
protected void setModifierList(List<ModifierImpl> list){
modifiers = new Hashtable<String, ModifierImpl>();
for( ModifierImpl i : list ){
modifiers.put(i.getConceptId(), i);
}
}
/**
* Getter for JAXB
......@@ -267,6 +262,32 @@ public class ObservationImpl implements Observation{
this.value = value;
}
/**
* Getter for JAXB
* @return modifier list
*/
@XmlElement(name="modifier")
protected List<ModifierImpl> getModifierList(){
if( modifiers == null ){
// make sure the list exists,
// otherwise, JAXB unmarshal may fail as some JAXB implementation will populate this list
// without ever calling setModifierList (e.g. in JDK8)
modifiers = new ArrayList<>();
}
return modifiers;
}
protected void setModifierList(List<ModifierImpl> list){
modifiers = list;
}
@Override
public void replaceConcept(String newConceptId) {
this.conceptId = newConceptId;
this.modifiers = null;
// TODO notify extensions of concept change
}
}
package de.sekmi.histream.io;
import java.io.InputStream;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.stream.FactoryConfigurationError;
import javax.xml.stream.XMLStreamException;
import de.sekmi.histream.Observation;
import de.sekmi.histream.ObservationFactory;
import de.sekmi.histream.impl.ObservationImpl;
public class JAXBObservationSupplier extends XMLObservationSupplier {
private Unmarshaller unmarshaller;
public JAXBObservationSupplier(ObservationFactory factory, InputStream input)
throws XMLStreamException, FactoryConfigurationError, JAXBException {
super(factory, input);
unmarshaller = JAXBContext.newInstance(ObservationImpl.class).createUnmarshaller();
}
@Override
protected Observation readObservation()throws XMLStreamException{
if( reader.isWhiteSpace() ){
reader.nextTag();
}
// </facts> might occur after previous call to readObservation()
while( reader.isEndElement() ){
switch( reader.getLocalName() ){
case "facts":
// end of facts
reader.nextTag();
case "visit":
// end of visit
reader.nextTag();
if( reader.isStartElement() && reader.getLocalName().equals("visit") ){
// next visit
readVisit();
}
break;
case "dwh-eav":
// end of document
return null;
}
}
// start element of eav-item or eav-group
if( !reader.isStartElement()
|| !(reader.getLocalName().equals("fact")) ){
throw new XMLStreamException("Element fact expected instead of "+reader.getLocalName(), reader.getLocation());
}
ObservationImpl fact;
try {
fact = (ObservationImpl)unmarshaller.unmarshal(reader);
} catch (JAXBException e) {
throw new XMLStreamException( e);
}
if( fact.getPatientId() == null ){
fact.setPatientId(visitData.get("patid"));
}
if( fact.getStartTime() == null ){
fact.setStartTime(encounterStart);
}
// TODO set etc. from visit
// TODO set ObservationFactory, initialize extensions
return fact;
}
}
......@@ -76,7 +76,9 @@ class XMLObservationParser extends AbstractObservationParser{
protected AbnormalFlag valueFlag;
protected Value.Operator valueOp;
// cached visit for visit extension
protected Visit visit;
// cached patient for patient extension
protected Patient patient;
......
......@@ -37,7 +37,7 @@ import de.sekmi.histream.ObservationSupplier;
public class XMLObservationSupplier extends XMLObservationParser implements ObservationSupplier{
//private static final String namespaceURI = "http://sekmi.de/histream/dwh-eav";
private XMLStreamReader reader;
protected XMLStreamReader reader;
private AttributeAccessor atts;
......@@ -110,7 +110,7 @@ public class XMLObservationSupplier extends XMLObservationParser implements Obse
reader.nextTag();
}
private void readVisit()throws XMLStreamException{
protected void readVisit()throws XMLStreamException{
if( !reader.getLocalName().equals("visit") )throw new XMLStreamException("Element visit expected instead of "+reader.getLocalName(),reader.getLocation());
reader.nextTag();
while( !reader.getLocalName().equals("facts") ){
......@@ -131,7 +131,7 @@ public class XMLObservationSupplier extends XMLObservationParser implements Obse
// should be an observation
}
private Observation readObservation()throws XMLStreamException{
protected Observation readObservation()throws XMLStreamException{
// </facts> might occur after previous call to readObservation()
while( reader.isEndElement() ){
switch( reader.getLocalName() ){
......
......@@ -30,6 +30,9 @@ import org.junit.Test;
import org.xml.sax.SAXException;
import de.sekmi.histream.DateTimeAccuracy;
import de.sekmi.histream.Modifier;
import de.sekmi.histream.Observation;
import de.sekmi.histream.Value;
public class ObservationImplJAXBTest {
public static final File EXAMPLE_FACT_XSD = new File("examples/fact.xsd");
......@@ -81,6 +84,7 @@ public class ObservationImplJAXBTest {
DOMResult dom = new DOMResult();
ObservationImpl o = createObservation(i);
JAXB.marshal(o, dom);
}
}
@Test
......@@ -102,6 +106,20 @@ public class ObservationImplJAXBTest {
Assert.assertEquals(ObservationImpl.class, o2.getClass());
// verify values
Assert.assertEquals(o1.getValue(), ((ObservationImpl)o2).getValue());
ObservationImpl ou = (ObservationImpl)o2;
switch( i ){
case 0:
Assert.assertFalse(ou.hasModifiers());
break;
case 1:
Assert.assertFalse(ou.hasModifiers());
break;
case 2:
Assert.assertTrue(ou.hasModifiers());
break;
}
}
}
......@@ -112,7 +130,21 @@ public class ObservationImplJAXBTest {
for( int i=0; i<EXAMPLE_FACT_FILES.length; i++ ){
Object obj = u.unmarshal(EXAMPLE_FACT_FILES[i]);
Assert.assertEquals(ObservationImpl.class, obj.getClass());
//ObservationImpl o = (ObservationImpl)obj;
Observation o = (ObservationImpl)obj;
if( i == 0 ){
Assert.assertEquals("T:testconcept1", o.getConceptId());
Assert.assertEquals("12345", o.getPatientId());
Assert.assertNotNull(o.getValue());
Assert.assertEquals(Value.Type.Numeric, o.getValue().getType());
Assert.assertEquals(new BigDecimal(123), o.getValue().getNumericValue());
Assert.assertTrue(o.hasModifiers());
Modifier m = o.getModifier("M:test");
Assert.assertNotNull(m);
Assert.assertNotNull(m.getValue());
Assert.assertEquals(Value.Type.Text, m.getValue().getType());
Assert.assertEquals("123", m.getValue().getValue());
}
}
}
......@@ -133,7 +165,21 @@ public class ObservationImplJAXBTest {
validator.validate(new StreamSource(EXAMPLE_FACT_FILES[i]));
}
}
/*
@Test
public void testModifierListBug() throws JAXBException{
Marshaller m = jaxb.createMarshaller();
Unmarshaller u = jaxb.createUnmarshaller();
ObservationImpl o1 = createObservation(2);
StringWriter s = new StringWriter();
m.marshal(o1, s);
System.out.println("Marshalled:");
System.out.println(s);
Object o2 = u.unmarshal(new StringReader(s.toString()));
}
*/
public static void main(String args[]) throws JAXBException{
ObservationImplJAXBTest oj = new ObservationImplJAXBTest();
oj.initialize();
......
......@@ -31,6 +31,7 @@ import java.util.function.Supplier;
import java.util.stream.StreamSupport;
import java.math.BigDecimal;
import javax.xml.bind.JAXBException;
import javax.xml.stream.FactoryConfigurationError;
import javax.xml.stream.XMLStreamException;
......@@ -168,10 +169,13 @@ public class FileObservationProviderTest {
public ObservationFactory getFactory(){
return factory;
}
public ObservationSupplier getExampleSupplier() throws IOException{
return getExampleSupplier("examples/dwh-eav.xml");
}
public ObservationSupplier getExampleSupplier(String path) throws IOException{
try {
return new XMLObservationSupplier(factory, new FileInputStream("examples/dwh-eav.xml"));
return new XMLObservationSupplier(factory, new FileInputStream(path));
} catch (XMLStreamException | FactoryConfigurationError e) {
throw new IOException(e);
}
......@@ -193,6 +197,13 @@ public class FileObservationProviderTest {
xos.close();
}
@Test
public void testJAXBReader() throws FileNotFoundException, XMLStreamException, FactoryConfigurationError, JAXBException {
XMLObservationSupplier xos = new JAXBObservationSupplier(factory, new FileInputStream("examples/dwh-jaxb.xml"));
validateExample(xos);
xos.close();
}
@Test
public void testFlatReader() throws FileNotFoundException, IOException {
FlatObservationSupplier s = new FlatObservationSupplier(factory, new FileInputStream("examples/dwh-flat.txt"));
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment