Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"properties": {
"id": { "index": 0, "kind": "attribute", "displayName": "Id", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The id of this node" },
"parser": { "index": 1, "kind": "attribute", "displayName": "Parser", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "ca.uhn.hl7v2.parser.Parser", "deprecated": false, "autowired": false, "secret": false, "description": "To use a custom HL7 parser" },
"validate": { "index": 2, "kind": "attribute", "displayName": "Validate", "group": "common", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to validate the HL7 message Is by default true." }
"validate": { "index": 2, "kind": "attribute", "displayName": "Validate", "group": "common", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to validate the HL7 message Is by default true." },
"targetFormat": { "index": 3, "kind": "attribute", "displayName": "Target Format", "group": "common", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "XML" ], "deprecated": false, "autowired": false, "secret": false, "description": "The target format for marshal output and unmarshal result type. By default, marshal encodes to HL7 ER7, and unmarshal returns a HAPI Message object. If this is set to XML, marshal encodes to HL7 XML, and unmarshal returns an XML DOM Document." }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"properties": {
"id": { "index": 0, "kind": "attribute", "displayName": "Id", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The id of this node" },
"parser": { "index": 1, "kind": "attribute", "displayName": "Parser", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "ca.uhn.hl7v2.parser.Parser", "deprecated": false, "autowired": false, "secret": false, "description": "To use a custom HL7 parser" },
"validate": { "index": 2, "kind": "attribute", "displayName": "Validate", "group": "common", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to validate the HL7 message Is by default true." }
"validate": { "index": 2, "kind": "attribute", "displayName": "Validate", "group": "common", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to validate the HL7 message Is by default true." },
"targetFormat": { "index": 3, "kind": "attribute", "displayName": "Target Format", "group": "common", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "XML" ], "deprecated": false, "autowired": false, "secret": false, "description": "The target format for marshal output and unmarshal result type. By default, marshal encodes to HL7 ER7, and unmarshal returns a HAPI Message object. If this is set to XML, marshal encodes to HL7 XML, and unmarshal returns an XML DOM Document." }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8920,6 +8920,17 @@ To use a custom HL7 parser.
<xs:documentation xml:lang="en">
<![CDATA[
Whether to validate the HL7 message Is by default true. Default value: true
]]>
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="targetFormat" type="xs:string">
<xs:annotation>
<xs:documentation xml:lang="en">
<![CDATA[
The target format for marshal output and unmarshal result type. By default, marshal encodes to HL7 ER7, and unmarshal
returns a HAPI Message object. If this is set to XML, marshal encodes to HL7 XML, and unmarshal returns an XML DOM
Document.
]]>
</xs:documentation>
</xs:annotation>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7583,6 +7583,17 @@ To use a custom HL7 parser.
<xs:documentation xml:lang="en">
<![CDATA[
Whether to validate the HL7 message Is by default true. Default value: true
]]>
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="targetFormat" type="xs:string">
<xs:annotation>
<xs:documentation xml:lang="en">
<![CDATA[
The target format for marshal output and unmarshal result type. By default, marshal encodes to HL7 ER7, and unmarshal
returns a HAPI Message object. If this is set to XML, marshal encodes to HL7 XML, and unmarshal returns an XML DOM
Document.
]]>
</xs:documentation>
</xs:annotation>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ private void registerConverters(TypeConverterRegistry registry) {
}
return answer;
});
addTypeConverter(registry, ca.uhn.hl7v2.model.Message.class, org.w3c.dom.Document.class, false,
(type, exchange, value) -> {
Object answer = org.apache.camel.component.hl7.HL7Converter.toMessage((org.w3c.dom.Document) value);
if (false && answer == null) {
answer = Void.class;
}
return answer;
});
addTypeConverter(registry, java.lang.String.class, ca.uhn.hl7v2.model.Message.class, false,
(type, exchange, value) -> {
Object answer = org.apache.camel.component.hl7.HL7Converter.toString((ca.uhn.hl7v2.model.Message) value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public class HL7DataFormatConfigurer extends org.apache.camel.support.component.
static {
Map<String, Object> map = new CaseInsensitiveMap();
map.put("Parser", ca.uhn.hl7v2.parser.Parser.class);
map.put("TargetFormat", java.lang.String.class);
map.put("Validate", boolean.class);
ALL_OPTIONS = map;
}
Expand All @@ -32,6 +33,8 @@ public boolean configure(CamelContext camelContext, Object obj, String name, Obj
HL7DataFormat target = (HL7DataFormat) obj;
switch (ignoreCase ? name.toLowerCase() : name) {
case "parser": target.setParser(property(camelContext, ca.uhn.hl7v2.parser.Parser.class, value)); return true;
case "targetformat":
case "targetFormat": target.setTargetFormat(property(camelContext, java.lang.String.class, value)); return true;
case "validate": target.setValidate(property(camelContext, boolean.class, value)); return true;
default: return false;
}
Expand All @@ -46,6 +49,8 @@ public Map<String, Object> getAllOptions(Object target) {
public Class<?> getOptionType(String name, boolean ignoreCase) {
switch (ignoreCase ? name.toLowerCase() : name) {
case "parser": return ca.uhn.hl7v2.parser.Parser.class;
case "targetformat":
case "targetFormat": return java.lang.String.class;
case "validate": return boolean.class;
default: return null;
}
Expand All @@ -56,6 +61,8 @@ public Object getOptionValue(Object obj, String name, boolean ignoreCase) {
HL7DataFormat target = (HL7DataFormat) obj;
switch (ignoreCase ? name.toLowerCase() : name) {
case "parser": return target.getParser();
case "targetformat":
case "targetFormat": return target.getTargetFormat();
case "validate": return target.isValidate();
default: return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"properties": {
"id": { "index": 0, "kind": "attribute", "displayName": "Id", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The id of this node" },
"parser": { "index": 1, "kind": "attribute", "displayName": "Parser", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "ca.uhn.hl7v2.parser.Parser", "deprecated": false, "autowired": false, "secret": false, "description": "To use a custom HL7 parser" },
"validate": { "index": 2, "kind": "attribute", "displayName": "Validate", "group": "common", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to validate the HL7 message Is by default true." }
"validate": { "index": 2, "kind": "attribute", "displayName": "Validate", "group": "common", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to validate the HL7 message Is by default true." },
"targetFormat": { "index": 3, "kind": "attribute", "displayName": "Target Format", "group": "common", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "XML" ], "deprecated": false, "autowired": false, "secret": false, "description": "The target format for marshal output and unmarshal result type. By default, marshal encodes to HL7 ER7, and unmarshal returns a HAPI Message object. If this is set to XML, marshal encodes to HL7 XML, and unmarshal returns an XML DOM Document." }
}
}
56 changes: 56 additions & 0 deletions components/camel-hl7/src/main/docs/hl7-dataformat.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,62 @@ separators anymore by converting `\n` to `\r`. If you +
`Expression` for this purpose.


=== Target Format

By default, the HL7 DataFormat works with HAPI `Message` objects and ER7 (pipe-delimited) encoding.
The `targetFormat` option can be set to `XML` to change the output format:

* *marshal* encodes the HAPI Message to HL7 XML bytes instead of ER7.
* *unmarshal* returns an `org.w3c.dom.Document` instead of a HAPI `Message`.

The input format is always auto-detected. Both ER7 and XML inputs are accepted by the parser.
For marshal, the input body can be a HAPI `Message` or an XML `Document` — the registered
TypeConverter handles `Document` to `Message` conversion automatically.

==== EDI to XML conversion

[source,java]
----
HL7DataFormat hl7xml = new HL7DataFormat();
hl7xml.setTargetFormat("XML");

from("direct:hl7in")
.unmarshal(hl7xml)
.to("direct:xmlout");
----

In this example, the HL7 EDI input is unmarshalled directly to an XML `Document`.

==== XML to Message conversion

[source,java]
----
HL7DataFormat hl7xml = new HL7DataFormat();
hl7xml.setTargetFormat("XML");

from("direct:messageIn")
.marshal(hl7xml)
.to("direct:xmlout");
----

Here, a HAPI `Message` is marshalled to HL7 XML bytes.

==== Round-trip EDI to XML to EDI

[source,java]
----
HL7DataFormat hl7xml = new HL7DataFormat();
hl7xml.setTargetFormat("XML");
HL7DataFormat hl7 = new HL7DataFormat();

from("direct:hl7in")
.unmarshal(hl7xml)
.marshal(hl7)
.to("direct:hl7out");
----

The EDI input is converted to an XML Document, then back to EDI.

=== Charset

Both `marshal and unmarshal` evaluate the charset
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,17 @@

import java.io.IOException;

import org.w3c.dom.Document;
import org.w3c.dom.NodeList;

import ca.uhn.hl7v2.DefaultHapiContext;
import ca.uhn.hl7v2.HL7Exception;
import ca.uhn.hl7v2.HapiContext;
import ca.uhn.hl7v2.model.Message;
import ca.uhn.hl7v2.parser.DefaultModelClassFactory;
import ca.uhn.hl7v2.parser.ParserConfiguration;
import ca.uhn.hl7v2.parser.UnexpectedSegmentBehaviourEnum;
import ca.uhn.hl7v2.parser.XMLParser;
import ca.uhn.hl7v2.validation.impl.ValidationContextFactory;
import org.apache.camel.Converter;
import org.apache.camel.Exchange;
Expand Down Expand Up @@ -72,4 +76,30 @@ public static Message toMessage(byte[] body, Exchange exchange) throws HL7Except
return DEFAULT_CONTEXT.getGenericParser().parse(IOConverter.toString(body, exchange));
}

@Converter
public static Message toMessage(Document doc) throws HL7Exception {
String version = getVersionFromDocument(doc);
return ((XMLParser) DEFAULT_CONTEXT.getXMLParser()).parseDocument(doc, version);
}

private static final String HL7_V2_XML_NAMESPACE = "urn:hl7-org:v2xml";

static String getVersionFromDocument(Document doc) {
NodeList nodes = doc.getElementsByTagNameNS(HL7_V2_XML_NAMESPACE, "VID.1");
if (nodes.getLength() > 0) {
String version = nodes.item(0).getTextContent();
if (version != null && !version.trim().isEmpty()) {
return version.trim();
}
}
nodes = doc.getElementsByTagNameNS(HL7_V2_XML_NAMESPACE, "MSH.12");
if (nodes.getLength() > 0) {
String version = nodes.item(0).getTextContent();
if (version != null && !version.trim().isEmpty()) {
return version.trim();
}
}
return null;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,15 @@
* <p/>
* Uses the <a href="https://hapifhir.github.io/hapi-hl7v2/index.html">HAPI (HL7 API)</a> for HL7 parsing.
* <p/>
* Uses the default GenericParser from the HAPI API. This DataFormat <b>only</b> supports both the EDI based HL7
* messages and the XML based messages.
* Uses the default GenericParser from the HAPI API. This DataFormat supports both the ER7 (pipe-delimited) and XML
* based HL7 messages. The input format is auto-detected by the GenericParser.
* <p/>
* The {@code targetFormat} option controls the output format. When set to {@code XML}:
* <ul>
* <li>marshal encodes the HAPI Message to HL7 XML bytes instead of ER7. The input body can be either a HAPI Message or
* an XML Document (converted to Message automatically via the registered TypeConverter).</li>
* <li>unmarshal returns an {@link org.w3c.dom.Document} instead of a HAPI Message.</li>
* </ul>
* <p/>
* The <tt>unmarshal</tt> operation adds these MSH fields as headers on the Camel message (key, MSH-field):
* <ul>
Expand Down Expand Up @@ -95,6 +102,7 @@ public class HL7DataFormat extends ServiceSupport implements DataFormat, DataFor
private HapiContext hapiContext;
private Parser parser;
private boolean validate = true;
private String targetFormat;

static {
HEADER_MAP.put(HL7_SENDING_APPLICATION, "MSH-3");
Expand All @@ -120,7 +128,12 @@ public String getDataFormatName() {
public void marshal(Exchange exchange, Object body, OutputStream outputStream) throws Exception {
Message message = ExchangeHelper.convertToMandatoryType(exchange, Message.class, body);
String charsetName = HL7Charset.getCharsetName(message, exchange);
String encoded = parser.encode(message);
String encoded;
if ("XML".equalsIgnoreCase(targetFormat)) {
encoded = hapiContext.getXMLParser().encode(message);
} else {
encoded = parser.encode(message);
}
outputStream.write(encoded.getBytes(charsetName));
}

Expand All @@ -138,6 +151,10 @@ public Object unmarshal(Exchange exchange, InputStream inputStream) throws Excep
}
exchange.getOut().setHeader(HL7_CONTEXT, hapiContext);
exchange.getOut().setHeader(Exchange.CHARSET_NAME, charsetName);

if ("XML".equalsIgnoreCase(targetFormat)) {
return hapiContext.getXMLParser().encodeDocument(message);
}
return message;
}

Expand Down Expand Up @@ -165,6 +182,14 @@ public void setParser(Parser parser) {
this.parser = parser;
}

public String getTargetFormat() {
return targetFormat;
}

public void setTargetFormat(String targetFormat) {
this.targetFormat = targetFormat;
}

@Override
protected void doStart() throws Exception {
if (hapiContext == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
*/
package org.apache.camel.component.hl7;

import org.w3c.dom.Document;

import ca.uhn.hl7v2.DefaultHapiContext;
import ca.uhn.hl7v2.HapiContext;
import ca.uhn.hl7v2.model.Message;
Expand All @@ -28,8 +30,7 @@
import org.apache.camel.test.junit6.CamelTestSupport;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.*;

public class HL7XmlDataFormatTest extends CamelTestSupport {

Expand Down Expand Up @@ -62,6 +63,67 @@ public void testUnmarshalOkXml() throws Exception {
assertEquals("O01", new Terser(received).get("MSH-9-2"));
}

@Test
public void testUnmarshalXmlFormat() throws Exception {
MockEndpoint mock = getMockEndpoint("mock:unmarshalXml");
mock.expectedMessageCount(1);

String body = createHL7AsString();
template.sendBody("direct:unmarshalXml", body);

MockEndpoint.assertIsSatisfied(context);
Object rawBody = mock.getReceivedExchanges().get(0).getIn().getBody();
assertNotNull(rawBody);
assertInstanceOf(Document.class, rawBody);
Document doc = (Document) rawBody;
assertEquals("ORM_O01", doc.getDocumentElement().getLocalName());
}

@Test
public void testMarshalXmlFromDocument() throws Exception {
MockEndpoint mock = getMockEndpoint("mock:marshalFromDoc");
mock.expectedMessageCount(1);

String body = createHL7AsString();
Message msg = hl7.getParser().parse(body);
String xml = hl7.getParser().encode(msg, "XML");
template.sendBody("direct:marshalFromDoc", xml);

MockEndpoint.assertIsSatisfied(context);
String edi = mock.getReceivedExchanges().get(0).getIn().getBody(String.class);
assertTrue(edi.contains("MSH|^~\\&|"));
assertTrue(edi.contains("ORM^O01"));
}

@Test
public void testMarshalXmlOutput() throws Exception {
MockEndpoint mock = getMockEndpoint("mock:marshalXmlOutput");
mock.expectedMessageCount(1);

String body = createHL7AsString();
Message msg = hl7.getParser().parse(body);
template.sendBody("direct:marshalXmlOutput", msg);

MockEndpoint.assertIsSatisfied(context);
String xml = mock.getReceivedExchanges().get(0).getIn().getBody(String.class);
assertTrue(xml.contains("<ORM_O01"));
assertTrue(xml.contains("urn:hl7-org:v2xml"));
}

@Test
public void testRoundTripEdiToXmlToEdi() throws Exception {
MockEndpoint mock = getMockEndpoint("mock:roundtrip");
mock.expectedMessageCount(1);

String body = createHL7AsString();
template.sendBody("direct:roundtrip", body);

MockEndpoint.assertIsSatisfied(context);
String edi = mock.getReceivedExchanges().get(0).getIn().getBody(String.class);
assertTrue(edi.contains("MSH|^~\\&|"));
assertTrue(edi.contains("ORM^O01"));
}

@Override
protected RouteBuilder createRouteBuilder() {
HapiContext hapiContext = new DefaultHapiContext();
Expand All @@ -70,10 +132,21 @@ protected RouteBuilder createRouteBuilder() {
hl7 = new HL7DataFormat();
hl7.setParser(p);

final HL7DataFormat hl7Xml = new HL7DataFormat();
hl7Xml.setTargetFormat("XML");
hl7Xml.setValidate(false);

final HL7DataFormat hl7Default = new HL7DataFormat();
hl7Default.setValidate(false);

return new RouteBuilder() {
public void configure() {
from("direct:unmarshalOk").unmarshal().hl7(false).to("mock:unmarshal");
from("direct:unmarshalOkXml").unmarshal(hl7).to("mock:unmarshal");
from("direct:unmarshalXml").unmarshal(hl7Xml).to("mock:unmarshalXml");
from("direct:marshalFromDoc").marshal(hl7Default).to("mock:marshalFromDoc");
from("direct:marshalXmlOutput").marshal(hl7Xml).to("mock:marshalXmlOutput");
from("direct:roundtrip").unmarshal(hl7Xml).marshal(hl7Default).to("mock:roundtrip");
}
};
}
Expand Down
Loading