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

GroupedObservationHandler created

parent 798ccf6f
package de.sekmi.histream.impl;
import java.util.function.Consumer;
import de.sekmi.histream.Observation;
import de.sekmi.histream.ObservationException;
import de.sekmi.histream.ObservationHandler;
import de.sekmi.histream.ext.Patient;
import de.sekmi.histream.ext.Visit;
/**
* Handle observations which are grouped by patient and visit.
* Abstract notification methods are provided if patient and visit changes between observations.
* <p>
* The callback methods are always called in the following order:
* {@link #beginStream()}, {@link #beginPatient(Patient)}, {@link #beginEncounter(Visit)}
* any number of {@link #onObservation(Observation)}, {@link #endEncounter(Visit)}
* optionally followed by the next encounter. {@link #endPatient(Patient)}.
* <p>
* Important: the final endEncounter, endPatient can only be called during close.
*
* @author Raphael
*
*/
public abstract class GroupedObservationHandler implements ObservationHandler, AutoCloseable {
private Consumer<ObservationException> errorHandler;
private Patient prevPatient;
private Visit prevVisit;
/**
* Called when the first observation is encountered
*/
protected abstract void beginStream()throws ObservationException;
protected abstract void beginPatient(Patient patient)throws ObservationException;
protected abstract void endPatient(Patient patient)throws ObservationException;
protected abstract void beginEncounter(Visit visit)throws ObservationException;
protected abstract void endEncounter(Visit visit)throws ObservationException;
protected abstract void onObservation(Observation observation)throws ObservationException;
protected void endStream()throws ObservationException{
if( prevVisit != null ){
try {
endEncounter(prevVisit);
} catch (ObservationException e) {
reportError(e);
}
prevVisit = null;
}
if( prevPatient != null ){
try {
endPatient(prevPatient);
} catch (ObservationException e) {
reportError(e);
}
prevPatient = null;
}
}
@Override
public void close(){
try {
endStream();
} catch (ObservationException e) {
reportError(e);
}
}
protected void reportError(ObservationException e){
if( errorHandler != null )errorHandler.accept(e);
else throw new RuntimeException("Exception encountered, no error handler", e);
}
@Override
public void setErrorHandler(Consumer<ObservationException> handler) {
this.errorHandler = handler;
}
@Override
public final void accept(Observation observation) {
Patient thisPatient = observation.getExtension(Patient.class);
Visit thisVisit = observation.getExtension(Visit.class);
if( prevPatient == null ){
// write start document, meta, patient
try {
beginStream();
} catch (ObservationException e) {
e.setObservation(observation);
reportError(e);
}
try {
beginPatient(thisPatient);
} catch (ObservationException e) {
e.setObservation(observation);
reportError(e);
}
prevPatient = thisPatient;
prevVisit = null;
}else if( !prevPatient.getId().equals(thisPatient.getId()) ){
// close patient, write new patient
try {
endEncounter(prevVisit);
} catch (ObservationException e) {
e.setObservation(observation);
reportError(e);
}
try {
endPatient(prevPatient);
} catch (ObservationException e) {
e.setObservation(observation);
reportError(e);
}
try {
beginPatient(thisPatient);
} catch (ObservationException e) {
e.setObservation(observation);
reportError(e);
}
prevPatient = thisPatient;
prevVisit = null;
}else{
// same patient as previous fact
// nothing to do
}
if( prevVisit == null ){
// first visit for patient
try {
beginEncounter(thisVisit);
} catch (ObservationException e) {
e.setObservation(observation);
reportError(e);
}
prevVisit = thisVisit;
}else if( !prevVisit.getId().equals(thisVisit.getId()) ){
try {
endEncounter(prevVisit); // close previous encounter
} catch (ObservationException e) {
e.setObservation(observation);
reportError(e);
}
try {
beginEncounter(thisVisit);
} catch (ObservationException e) {
e.setObservation(observation);
reportError(e);
}
prevVisit = thisVisit;
}else{
// same encounter as previous fact
// nothing to do
}
// exceptions are passed on
try {
onObservation(observation);
} catch (ObservationException e) {
e.setObservation(observation);
reportError(e);
}
}
}
package de.sekmi.histream.io;
import java.io.Closeable;
import java.io.IOException;
import java.io.OutputStream;
import javax.xml.XMLConstants;
......@@ -18,29 +16,36 @@ import de.sekmi.histream.ObservationException;
import de.sekmi.histream.ext.ExternalSourceType;
import de.sekmi.histream.ext.Patient;
import de.sekmi.histream.ext.Visit;
import de.sekmi.histream.impl.AbstractObservationHandler;
import de.sekmi.histream.impl.GroupedObservationHandler;
import de.sekmi.histream.impl.Meta;
import de.sekmi.histream.impl.ObservationImpl;
/**
* Writes observations to a single XML file. Observations must be grouped by patient and encounter.
* TODO write test case which reads+writes XML file and compares input+output literally.
* TODO prevent duplicate namespace declarations by JAXB.marshal of facts
*
* @author Raphael
*
*/
public class XMLWriter extends AbstractObservationHandler implements Closeable {
public class GroupedXMLWriter extends GroupedObservationHandler{
private static final String NAMESPACE = "http://sekmi.de/histream/ns/eav-data";
private Patient prevPatient;
private Visit prevVisit;
private boolean hasContent;
private Marshaller marshaller;
private XMLStreamWriter writer;
private boolean writeFormatted;
private int formattingDepth;
private Meta meta;
private XMLWriter() throws XMLStreamException{
/**
* Used to output XML
*/
protected XMLStreamWriter writer;
/**
* Constructor which doesn't initialize the {@link #writer}.
* Use this constructor to extend the class and provide a custom {@link XMLStreamWriter}.
*
* @throws XMLStreamException initialisation error
*/
protected GroupedXMLWriter() throws XMLStreamException{
try {
this.marshaller = JAXBContext.newInstance(ObservationImpl.class, Meta.class).createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
......@@ -56,7 +61,7 @@ public class XMLWriter extends AbstractObservationHandler implements Closeable {
* @param output output stream
* @throws XMLStreamException initialisation error
*/
public XMLWriter(OutputStream output) throws XMLStreamException{
public GroupedXMLWriter(OutputStream output) throws XMLStreamException{
this();
XMLOutputFactory factory = XMLOutputFactory.newInstance();
// enable repairing namespaces to remove duplicate namespace declarations by JAXB marshal
......@@ -69,28 +74,43 @@ public class XMLWriter extends AbstractObservationHandler implements Closeable {
* @param result result to receive XML stream events
* @throws XMLStreamException initialisation error
*/
public XMLWriter(Result result)throws XMLStreamException{
public GroupedXMLWriter(Result result)throws XMLStreamException{
this();
XMLOutputFactory factory = XMLOutputFactory.newInstance();
// enable repairing namespaces to remove duplicate namespace declarations by JAXB marshal
factory.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, Boolean.TRUE);
this.writer = factory.createXMLStreamWriter(result);
}
private void writeStartDocument() throws XMLStreamException, JAXBException{
writer.setDefaultNamespace(NAMESPACE);
writer.setPrefix("xsi", XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
writer.writeStartDocument();
formatNewline();
writer.writeStartElement(NAMESPACE, JAXBObservationSupplier.DOCUMENT_ROOT);
writer.writeDefaultNamespace(NAMESPACE);
writer.writeNamespace("xsi", XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
formatNewline();
formatPush();
formatIndent();
marshaller.marshal(meta, writer);
formatNewline();
/**
* Configure whether to write whitespace and newline for human readable output
* @param formattedOutput true for human readable output
*/
public void setFormatted(boolean formattedOutput){
this.writeFormatted = formattedOutput;
}
@Override
protected void beginStream() throws ObservationException{
hasContent = false;
try {
writer.setDefaultNamespace(NAMESPACE);
writer.setPrefix("xsi", XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
writer.writeStartDocument();
formatNewline();
writer.writeStartElement(NAMESPACE, JAXBObservationSupplier.DOCUMENT_ROOT);
writer.writeDefaultNamespace(NAMESPACE);
writer.writeNamespace("xsi", XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
formatNewline();
formatPush();
formatIndent();
marshaller.marshal(meta, writer);
formatNewline();
} catch (XMLStreamException | JAXBException e) {
throw new ObservationException(e);
}
}
@Override
public void setMeta(String key, String value) {
......@@ -111,142 +131,120 @@ public class XMLWriter extends AbstractObservationHandler implements Closeable {
formattingDepth --;
}
private void startEncounter(Visit visit)throws XMLStreamException{
formatIndent();
writer.writeStartElement(JAXBObservationSupplier.ENCOUNTER_ELEMENT);
writer.writeAttribute("id", visit.getId());
formatNewline();
formatPush();
if( visit.getStartTime() != null ){
@Override
protected void beginEncounter(Visit visit)throws ObservationException{
try {
formatIndent();
writer.writeStartElement("start");
writer.writeCharacters(visit.getStartTime().toPartialIso8601());
writer.writeEndElement();
writer.writeStartElement(JAXBObservationSupplier.ENCOUNTER_ELEMENT);
writer.writeAttribute("id", visit.getId());
formatNewline();
}
if( visit.getEndTime() != null ){
formatIndent();
writer.writeStartElement("end");
writer.writeCharacters(visit.getEndTime().toPartialIso8601());
writer.writeEndElement();
formatNewline();
}
if( visit.getLocationId() != null ){
formatPush();
if( visit.getStartTime() != null ){
formatIndent();
writer.writeStartElement("start");
writer.writeCharacters(visit.getStartTime().toPartialIso8601());
writer.writeEndElement();
formatNewline();
}
if( visit.getEndTime() != null ){
formatIndent();
writer.writeStartElement("end");
writer.writeCharacters(visit.getEndTime().toPartialIso8601());
writer.writeEndElement();
formatNewline();
}
if( visit.getLocationId() != null ){
formatIndent();
writer.writeStartElement("location");
writer.writeCharacters(visit.getLocationId());
writer.writeEndElement();
formatNewline();
}
// TODO implement provider
// TODO more data
formatIndent();
writer.writeStartElement("location");
writer.writeCharacters(visit.getLocationId());
writer.writeEndElement();
writer.writeStartElement(JAXBObservationSupplier.FACT_WRAPPER);
formatNewline();
formatPush();
} catch (XMLStreamException e) {
throw new ObservationException(e);
}
// TODO implement provider
// TODO more data
formatIndent();
writer.writeStartElement(JAXBObservationSupplier.FACT_WRAPPER);
formatNewline();
formatPush();
this.prevVisit = visit;
}
private void endEncounter() throws XMLStreamException{
formatPop();
formatIndent();
writer.writeEndElement(); // fact wrapper
formatNewline();
formatPop();
formatIndent();
writer.writeEndElement(); // encounter
formatNewline();
}
private void endPatient() throws XMLStreamException{
@Override
protected void endEncounter(Visit visit) throws ObservationException{
formatPop();
formatIndent();
writer.writeEndElement();
formatNewline();
}
private void startPatient(Patient patient) throws XMLStreamException{
formatIndent();
writer.writeStartElement(JAXBObservationSupplier.PATIENT_ELEMENT);
writer.writeAttribute("id", patient.getId());
formatNewline();
formatPush();
// TODO: how write dedicated source information
// write surname
// write given name
// gender
if( patient.getSex() != null ){
try {
formatIndent();
writer.writeStartElement("gender");
// TODO use acronym/special value
writer.writeCharacters(patient.getSex().name());
writer.writeEndElement();
writer.writeEndElement(); // fact wrapper
formatNewline();
}
// birth date
if( patient.getBirthDate() != null ){
formatPop();
formatIndent();
writer.writeStartElement("birthdate");
writer.writeCharacters(patient.getBirthDate().toPartialIso8601());
writer.writeEndElement();
writer.writeEndElement(); // encounter
formatNewline();
} catch (XMLStreamException e) {
throw new ObservationException(e);
}
if( patient.getDeathDate() != null ){
}
@Override
protected void endPatient(Patient patient) throws ObservationException{
formatPop();
try {
formatIndent();
writer.writeStartElement("deathdate");
writer.writeCharacters(patient.getDeathDate().toPartialIso8601());
writer.writeEndElement();
formatNewline();
} catch (XMLStreamException e) {
throw new ObservationException(e);
}
prevPatient = patient;
prevVisit = null; // clear previous encounter
}
@Override
protected void acceptOrException(Observation observation) throws ObservationException {
Patient thisPatient = observation.getExtension(Patient.class);
Visit thisVisit = observation.getExtension(Visit.class);
try {
if( prevPatient == null ){
// write start document, meta, patient
writeStartDocument();
startPatient(thisPatient);
}else if( !prevPatient.getId().equals(thisPatient.getId()) ){
// close patient, write new patient
endEncounter();
endPatient();
startPatient(thisPatient);
}else{
// same patient as previous fact
// nothing to do
protected void beginPatient(Patient patient) throws ObservationException{
hasContent = true;
try{
formatIndent();
writer.writeStartElement(JAXBObservationSupplier.PATIENT_ELEMENT);
writer.writeAttribute("id", patient.getId());
formatNewline();
formatPush();
// TODO: how write dedicated source information
// write surname
// write given name
// gender
if( patient.getSex() != null ){
formatIndent();
writer.writeStartElement("gender");
// TODO use acronym/special value
writer.writeCharacters(patient.getSex().name());
writer.writeEndElement();
formatNewline();
}
if( prevVisit == null ){
// first visit for patient
startEncounter(thisVisit);
}else if( !prevVisit.getId().equals(thisVisit.getId()) ){
endEncounter(); // close previous encounter
startEncounter(thisVisit);
}else{
// same encounter as previous fact
// nothing to do
// birth date
if( patient.getBirthDate() != null ){
formatIndent();
writer.writeStartElement("birthdate");
writer.writeCharacters(patient.getBirthDate().toPartialIso8601());
writer.writeEndElement();
formatNewline();
}
formatIndent();
marshalFactWithContext(observation, thisPatient, thisVisit, meta.source);
formatNewline();
} catch (JAXBException | XMLStreamException e ) {
throw new ObservationException(observation, e);
if( patient.getDeathDate() != null ){
formatIndent();
writer.writeStartElement("deathdate");
writer.writeCharacters(patient.getDeathDate().toPartialIso8601());
writer.writeEndElement();
formatNewline();
}
}catch( XMLStreamException e ){
throw new ObservationException(e);
}
}
/**
* Marshal a fact without writing context information from patient, visit and source.
* TODO move method to separate helper class
*
* @param fact fact
* @param patient patient context
......@@ -255,31 +253,52 @@ public class XMLWriter extends AbstractObservationHandler implements Closeable {
* @throws ObservationException for errors in fact
* @throws JAXBException errors during marshal operation
*/
public void marshalFactWithContext(Observation fact, Patient patient, Visit visit, ExternalSourceType source) throws ObservationException, JAXBException{
private void marshalFactWithContext(Observation fact, Visit visit, ExternalSourceType source) throws JAXBException{
// clone observation, remove patient/encounter/source information as it is contained in wrappers
ObservationImpl o = (ObservationImpl)fact;
o = o.clone();
o.removeContext(patient.getId(), visit.getId(), visit.getStartTime(), source);
o.removeContext(o.getPatientId(), o.getEncounterId(), visit.getStartTime(), source);
marshaller.marshal(o, writer);
}
@Override
public void close() throws IOException{
protected void endStream() throws ObservationException{
try {
if( prevPatient != null ){
if( hasContent ){
// at least one fact processed
// end facts, encounter, patient
for( int i=0; i<3; i++){
formatPop();
formatIndent();
formatIndent();
writer.writeEndElement();
formatNewline();
}
}
writer.writeEndDocument(); // automatically closes root element
writer.close();
} catch (XMLStreamException e) {
throw new IOException(e);
throw new ObservationException(e);
}
}
@Override
public void close(){
super.close();
try {
if( writer != null ){
writer.close();
writer = null;
}
} catch (XMLStreamException e) {
reportError(new ObservationException(e));
}
}
@Override
protected void onObservation(Observation observation) throws ObservationException {
try {
formatIndent();
marshalFactWithContext(observation, observation.getExtension(Visit.class), meta.source);
formatNewline();
} catch (JAXBException | XMLStreamException e) {
throw new ObservationException(e);
}
}
......
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