Commit 1921119e authored by R.W.Majeed's avatar R.W.Majeed

JAXB marshalling/unmarshalling of export descriptor

parent d3fb5649
......@@ -66,4 +66,15 @@ public class FactClassAnnotator {
((Element)fact).setAttribute("class", clazz);
}
}
public void annotateFactSiblings(Node first){
annotateFact(first);
Node next = first.getNextSibling();
while( next != null ){
if( next.getNodeType() == Node.ELEMENT_NODE && next.getLocalName().equals("fact") ){
annotateFact(next);
}
next = next.getNextSibling();
}
}
}
......@@ -2,13 +2,23 @@ package de.sekmi.histream.export.config;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlTransient;
@XmlTransient
@XmlAccessorType(XmlAccessType.NONE)
public abstract class AbstractTable {
public abstract String[] getHeaders();
public abstract AbstractColumn getColumn(int index);
public abstract AbstractColumn getColumnByHeader(String header);
@XmlElement(name="column")
Column[] columns;
public String[] getHeaders(){
String[] headers = new String[columns.length];
for( int i=0; i<columns.length; i++ ){
headers[i] = columns[i].header;
}
return headers;
}
public Column getColumn(int index){
return columns[index];
}
}
......@@ -3,13 +3,9 @@ package de.sekmi.histream.export.config;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.bind.annotation.XmlTransient;
@XmlTransient
@XmlAccessorType(XmlAccessType.NONE)
@XmlSeeAlso({SequenceColumn.class, IdColumn.class})
public abstract class AbstractColumn {
public class Column {
/**
* Column header name
......@@ -28,25 +24,16 @@ public abstract class AbstractColumn {
String na;
/**
* Prepare the column for the next row. This
* method is called once for every row.
* <p> The default implementation does nothing.</p>
* XPath expression to select the column's value
*/
protected void prepareRow(){
}
@XmlAttribute(required=true)
String xpath;
/**
* Initializes the column. This method will be called
* only once and before any other method.
*/
protected void initialize(){
public Column(String header, String xpath){
this.header = header;
this.xpath = xpath;
}
// constructor for JAXB
protected Column(){
}
/**
* Get the column value for the current row.
* @return String value or {@code null} for NA.
*/
public abstract Object getValueString();
}
package de.sekmi.histream.export.config;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
@XmlAccessorType(XmlAccessType.NONE)
public class Concept {
@XmlAttribute
String iri;
@XmlAttribute
String notation;
@XmlAttribute(name="wildcard-notation")
String wildcardNotation;
public static Concept newWildcard(String wildcardNotation){
Concept c = new Concept();
c.wildcardNotation = wildcardNotation;
return c;
}
public static Concept newIRI(String iri){
Concept c = new Concept();
c.iri = iri;
return c;
}
public static Concept newNotation(String notation){
Concept c = new Concept();
c.notation = notation;
return c;
}
}
package de.sekmi.histream.export.config;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlID;
public class ConceptGroup {
public ConceptGroup(String clazz){
this.clazz = clazz;
this.concepts = new ArrayList<>();
}
// constructor for JAXB
protected ConceptGroup(){
}
@XmlID
@XmlAttribute(name="class")
String clazz;
@XmlElement(name="concept")
List<Concept> concepts;
}
\ No newline at end of file
package de.sekmi.histream.export.config;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
@XmlAccessorType(XmlAccessType.NONE)
public class Concepts {
@XmlElement(name="group")
List<ConceptGroup> groups;
@XmlElement(name="concept")
List<Concept> concepts;
Iterable<Concept> allConcepts(){
return new IterableIterable<Concept,ConceptGroup>(groups, group -> group.concepts.iterator(), concepts);
}
}
package de.sekmi.histream.export.config;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlID;
import javax.xml.bind.annotation.XmlIDREF;
public class EavTable extends AbstractTable{
@XmlID
@XmlAttribute
String id;
@XmlIDREF
@XmlAttribute(name="class")
ConceptGroup clazz;
}
......@@ -7,11 +7,14 @@ import javax.xml.bind.annotation.XmlRootElement;
public class ExportDescriptor {
@XmlElement
Concepts concepts;
@XmlElement(name="patient-table")
PatientTable patient;
@XmlElement
@XmlElement(name="visit-table")
VisitTable visit;
@XmlElement(name="table")
FactTable[] tables;
@XmlElement(name="eav-table")
EavTable[] tables;
}
package de.sekmi.histream.export.config;
public class FactTable {
}
package de.sekmi.histream.export.config;
/**
* Column which outputs intrinsic ID values. This
* column can only be used in {@link PatientTable} and
* {@link VisitTable}.
*
* @author R.W.Majeed
*
*/
public class IdColumn extends AbstractColumn{
@Override
public Object getValueString() {
// TODO Auto-generated method stub
return null;
}
}
package de.sekmi.histream.export.config;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.function.Function;
class IterableIterable<T,U> implements Iterable<T>{
Function<U, Iterator<T>> extractor;
Iterable<T> direct;
Iterable<U> indirect;
public IterableIterable(Iterable<U> indirect, Function<U,Iterator<T>> extractor, Iterable<T> direct){
this.indirect = indirect;
this.direct = direct;
this.extractor = extractor;
}
@Override
public Iterator<T> iterator() {
return new Iterator<T>() {
Iterator<U> outer = indirect.iterator();
Iterator<T> current = null;
boolean last = false;
@Override
public boolean hasNext() {
while( current == null || !current.hasNext() ){
if( outer.hasNext() ){
current = extractor.apply(outer.next());
}else if( !last ){
current = direct.iterator();
last = true;
}else{
return false;
}
}
return true;
}
@Override
public T next() {
if( hasNext() ){
return current.next();
}else{
throw new NoSuchElementException();
}
}
};
}
}
\ No newline at end of file
package de.sekmi.histream.export.config;
import javax.xml.bind.annotation.XmlElement;
public class PatientTable {
public class PatientTable extends AbstractTable{
@XmlElement(name="column")
AbstractColumn[] columns;
}
package de.sekmi.histream.export.config;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name="sequence")
public class SequenceColumn extends AbstractColumn{
@XmlElement(required=true)
String id;
@XmlElement
Long start;
long value;
protected SequenceColumn(){
}
public SequenceColumn(String id){
this.id = id;
}
@Override
protected void initialize(){
if( start != null ){
value = start;
}else{
value = 1;
}
}
@Override
public Object getValueString() {
return value;
// TODO increment value somewhere else
}
}
package de.sekmi.histream.export.config;
public class VisitTable {
public class VisitTable extends AbstractTable{
}
......@@ -3,6 +3,13 @@
*
*/
@javax.xml.bind.annotation.XmlSchema(
namespace="http://sekmi.de/ns/histream/export"
)
namespace="http://sekmi.de/ns/histream/export-v1",
elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED,
xmlns = {
@javax.xml.bind.annotation.XmlNs(
namespaceURI = "http://sekmi.de/ns/histream/export-v1",
prefix = "")
}
)
package de.sekmi.histream.export.config;
\ No newline at end of file
package de.sekmi.histream.export.config;
import java.util.ArrayList;
import java.util.stream.StreamSupport;
import javax.xml.bind.JAXB;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import org.junit.Assert;
import org.junit.Test;
public class TestMarshal {
@Test
public void verifyMarshal() throws JAXBException{
private ExportDescriptor createDescriptor(){
ExportDescriptor e = new ExportDescriptor();
e.concepts = new Concepts();
e.concepts.concepts = new ArrayList<>();
e.concepts.concepts.add(Concept.newNotation("ABC"));
e.concepts.concepts.add(Concept.newWildcard("CEDIS:*"));
e.concepts.groups = new ArrayList<>();
ConceptGroup d = new ConceptGroup("diag");
d.concepts.add(Concept.newWildcard("ICD10:*"));
e.concepts.groups.add(d);
e.patient = new PatientTable();
e.patient.columns = new AbstractColumn[2];
e.patient.columns[0] = new IdColumn();
e.patient.columns[0].header = "pid";
e.patient.columns[1] = new SequenceColumn("seq1");
e.patient.columns[1].header = "seq";
e.patient.columns = new Column[2];
e.patient.columns[0] = new Column("pid","@id");
e.patient.columns[1] = new Column("dob","birthdate");
return e;
}
@Test
public void verifyMarshal() throws JAXBException{
ExportDescriptor e = createDescriptor();
JAXBContext j = JAXBContext.newInstance(ExportDescriptor.class);
Marshaller m = j.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
m.marshal(e, System.out);
}
@Test
public void verifyIterableIterable(){
ExportDescriptor e = createDescriptor();
long count = StreamSupport.stream(e.concepts.allConcepts().spliterator(), false).count();
Assert.assertEquals(3, count);
}
@Test
public void verifyUnmarshall(){
ExportDescriptor e;
// first example
e = JAXB.unmarshal(getClass().getResourceAsStream("/export1.xml"), ExportDescriptor.class);
Assert.assertEquals(3, StreamSupport.stream(e.concepts.allConcepts().spliterator(), false).count());
// second example
e = JAXB.unmarshal(getClass().getResourceAsStream("/export2.xml"), ExportDescriptor.class);
Assert.assertEquals(5, StreamSupport.stream(e.concepts.allConcepts().spliterator(), false).count());
}
}
<?xml version="1.0" encoding="UTF-8"?>
<export xmlns="http://sekmi.de/ns/histream/export">
<patient>
<column header="pid" type="id"/>
<column header="seq" type="sequence">
<id>seq1</id>
</column>
</patient>
<visit>
<column header="pid" type="patient-ref"/>
<column header="visit" type="id"/>
<!-- concepts for the visit table must occur only once
per visit and may not repeat -->
<column header="start" type="attribute">
<property>start</property>
</column>
<!-- What is better? -->
<!-- (a) First column, then concepts with value -->
<column header="diagnostik_labort_ts" na="NULL" type="fact">
<concept code="LOINC:26436-6"/>
<concept code="LOINC:26436-6:NEG"/>
<value type="script">
<eval>fact.conceptId.substring(6)</eval>
</value>
</column>
<!-- (b) first concepts then columns with value -->
<group>
<concept code="LOINC:26436-6"/>
<concept code="LOINC:26436-6:NEG"/>
<column header="lab_ts" type="attribute">
<attribute>start</attribute>
</column>
<!-- We want this:
6:NEG -> not tested
6/mod=OPB -> tested non pathological
6/mod=PB -> tested pathological
-->
<column header="lab_ergebnis" type="attribute">
<modifier>PB</modifier>
<attribute>start</attribute>
</column>
</group>
</visit>
<table id="diagnosen">
<!-- makes more sense for fact tables to specify
the list of concepts first. -->
<concept wildcard="ICD10:*"/>
<concept code="ICD9:123"/>
<column header="pid" type="patient-ref"/>
<column header="visit" type="visit-ref"/>
<column header="start" type="attribute">
<attribute>start</attribute>
</column>
<column header="primary" type="attribute" na="nein">
<modifier>fuehrend</modifier>
<!-- if a modifier element is provided, the property
will use the context of that modifier -->
<constant-value>ja</constant-value>
</column>
<column header="text" type="attribute">
<modifier>originalText</modifier>
<property>value</property>
</column>
<!-- reference sequence from patient -->
<column header="seq1" type="sequence">
<sequence ref="seq1"/>
</column>
</table>
</export>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:export xmlns:ns2="http://sekmi.de/ns/histream/export-v1">
<concepts>
<group class="diag">
<concept wildcard-notation="ICD10:*"/>
</group>
<concept notation="ABC"/>
<concept wildcard-notation="CEDIS:*"/>
</concepts>
<patient-table>
<column header="pid" xpath="@id"/>
<column header="dob" xpath="birthdate"/>
</patient-table>
</ns2:export>
......@@ -18,14 +18,17 @@ to construct table data.
-->
<concepts>
<group class="d_lab">
<concept id="LOINC:26436-6" />
<concept id="LOINC:26436-6:NEG" />
<concept notation="LOINC:26436-6" />
<concept notation="LOINC:26436-6:NEG" />
</group>
<group class="diag">
<concept wildcard="ICD10:*" />
<concept wildcard-notation="ICD10:*" />
<concept iri="http://data.dzl.de/ns/ont/xxx"/>
</group>
<concept notation="XYZ" />
</concepts>
<patient-table>
<column header="pid" xpath="@id"/>
<column header="birthdate" xpath="birthdate"/>
......@@ -53,12 +56,12 @@ to construct table data.
-->
</visit-table>
<!-- export separate table for repeating elements -->
<repeating-table id="diag" class="diag">
<eav-table id="diag" class="diag">
<!-- context for XPath expressions is each fact node -->
<column header="pid" xpath="patient-ref"/>
<column header="visit" xpath="visit-ref"/>
<column header="start" type="@start"/>
<column header="code" type="@concept"/>
<column header="primary" xpath="modifier[@code='fuehrend']/@code"/>
</repeating-table>
</eav-table>
</export>
\ No newline at end of file
Markdown is supported
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