Interceptors on MLLP endpoints
MLLP components are built on top of camel-mina (starting with IPF 4.4 camel-netty, and their additional eHealth-specific functionality is implemented in form of interceptors. Each MLLP interceptor is a Camel processor that wraps another processor. The latter can either be one of
- another MLLP interceptor
- standard Camel producer
- consumer instance
Each interceptor is responsible for calling the wrapped processor by itself — there is no chain execution entity. Both incoming and outgoing messages can be served by the same interceptor instance by executing some functionality before and/or after the execution of the wrapped processor. Each interceptor has an ID used to configure chains; IDs of interceptors provided by IPF correspond to their full class names.
Interceptor chains are constructed on the basis of the following sources:
- initial default consumer or producer chain as pre-configured in IPF
- additional interceptor instances provided by MLLP components via
getAdditionalConsumerInterceptors()
andgetAdditionalProducerInterceptors()
- user-defined interceptor instances referenced in the endpoint URI parameter interceptors as a comma-separated list of Spring bean names
- interceptor instances produced by user-defined factories referenced in the endpoint URI parameter interceptorFactories as a comma-separated list of Spring bean names
Each interceptor may define IDs of interceptors it should be placed before and after in the chain. When no such ID lists are provided, the interceptor instance is placed at the end of the chain. When it is not possible to appropriately consider the “before/after” configuration of an interceptor, chain creation fails.
Standard chains and interceptor IDs of producer and consumer sides will be described in the sections below. Note that each chain “starts” at the network endpoint and “ends” at the Camel route — this is important to know when configuring custom interceptors to be placed “before” or “after” standard ones.
Standard Interceptor Chains
This section describes standard MLLP interceptors of IPF. Their classes belong to the following packages, depending on the served endpoint type (producer/consumer) and usability in non-MLLP context:
org.openehealth.ipf.platform.camel.ihe.hl7v2.intercept.producer
org.openehealth.ipf.platform.camel.ihe.mllp.core.intercept.producer
org.openehealth.ipf.platform.camel.ihe.hl7v2.intercept.consumer
org.openehealth.ipf.platform.camel.ihe.mllp.core.intercept.consumer
All class names obey the pattern Producer<Function>
Interceptor and Consumer<Function>
Interceptor`
For the sake of shortness, in the table below, only variable parts of package (hl7v2/mllp.core
) and class names (placeholder <Function>
) will be given.
Interceptor IDs correspond to full class names if not otherwise stated.
Standard Producer-side Chain
The standard producer-side chain is shown in the figure below:
Package name part | <Function> (class name part) |
Optionality | Processing outgoing requests | Processing incoming responses |
---|---|---|---|---|
mllp.core |
OutputPayloadLogger |
only when explicitly configured, see payload logging | n/a | logs response message |
mllp.core |
InputPayloadLogger |
only when explicitly configured, see payload logging | logs request message | n/a |
mllp.core |
StringProcessing |
always | performs segment fragmentation, when necessary | converts message received from the network to String and performs segment defragmentation, when necessary |
mllp.core |
RequestFragmenter |
only when unsolicited request fragmentation is enabled | when corresponding conditions are met, plays out the scenario described in Section 2.10.2.2 of the HL7 v.2.5 specification | n/a |
hl7v2 |
Marshal |
only when interactive response continuation is disabled | converts message to String | converts message into ca.uhn.hl7v2.model.Message |
mllp.core |
MarshalAndInteractive ResponseReceiver |
only when interactive response continuation is enabled | converts message to String | when corresponding conditions are met, plays out the scenario described in Section 5.6.3 of the HL7 v.2.5 specification, then converts the aggregated message to ca.uhn.hl7v2.model.Message |
hl7v2 |
ResponseAcceptance |
always | n/a | checks whether the message is of acceptable type and version |
mllp.core |
Audit |
only when ATNA auditing is enabled | creates and pre-fills an ATNA audit record | enriches the ATNA audit record and sends it out |
hl7v2 |
RequestAcceptance |
always | checks whether the message is of acceptable type and version | n/a |
hl7v2 |
Adapting |
always | converts message received from the Camel route to a ca.uhn.hl7v2.model.Message | n/a |
Standard Consumer-side Chain
The standard producer-side chain is shown in the figure below:
Package name part | <Function> (class name part) |
Optionality | Processing incoming requests | Processing outgoing responses |
---|---|---|---|---|
mllp.core |
RequestInteractionSetter |
always | populates interaction ID as a Camel message header | n/a |
mllp.core |
InputPayloadLogger |
only when explicitly configured, see payload logging | logs request message | n/a |
mllp.core |
OutputPayloadLogger |
only when explicitly configured, see payload logging | n/a | logs response message |
mllp.core |
StringProcessing |
always | converts message received from the network to String and performs segment defragmentation, when necessary | performs segment fragmentation, when necessary |
mllp.core |
RequestDefragmenter |
only when unsolicited request fragmentation is enabled | when corresponding conditions are met, plays out the scenario described in Section 2.10.2.2 of the HL7 v.2.5 specification | n/a |
hl7v2 |
Marshal |
always | converts message to ca.uhn.hl7v2.model.Message |
converts message to String |
hl7v2 |
RequestAcceptance |
always | checks whether the message is of acceptable type and version | n/a |
mllp.core |
InteractiveResponseSender |
only when interactive response continuation is enabled | n/a | when corresponding conditions are met, plays out the scenario described in Section 5.6.3 of the HL7 v.2.5 specification |
mllp.core |
Audit |
only when ATNA auditing is enabled | creates and pre-fills an ATNA audit record | enriches the ATNA audit record and sends it out |
hl7v2 |
ResponseAcceptance |
always | n/a | checks whether the message is of acceptable type and version |
hl7v2 |
Adapting |
always | n/a | converts message received from the Camel route to ca.uhn.hl7v2.model.Message |
mllp.core |
AuthenticationFailure |
only when ATNA auditing is enabled | n/a | checks whether a MllpAuthenticationFailure has been thrown in the route; when yes, sends out a corresponding ATNA audit record |
Configuring Custom Interceptors
There is the possibility to enrich standard chains with custom (user-defined, project-specific) interceptors.
In order to be placed appropriately in the chain, custom interceptors should define lists of IDs of interceptors they should be placed “before” and “after”. Example:
public class MyInterceptor extends InterceptorSupport {
public MyInterceptor() {
addAfter(ConsumerStringProcessingInterceptor.class.getName(),
ConsumerRequestDefragmenterInterceptor.class.getName());
addBefore(ConsumerMarshalInterceptor.class.getName());
}
...
}
A list may contain multiple IDs when some of corresponding interceptors are optional. In general, the following rules are
applied when inserting a custom interceptor “X” into the given chain (see class ChainUtils
for implementation details):
- If the chain already contains an interceptor with the same ID as “X”, “X” will be ignored.
- All elements of the X’s “before” and “after” lists, which reference neither interceptors already present in the chain nor the ones waiting for inserting, are ignored.
- If the “after” list is not empty, but “before” is empty, “X” will be inserted after the “bottommost” interceptor mentioned there.
- If the “before” list is not empty, “X” will be inserted before the “uppermost” interceptor mentioned there.
- If neither “before” nor “after” are defined, “X” will be inserted at the end of the chain (thus providing compatible behavior with older IPF versions).
- If there are any conflicts, e.g. when the “bottommost” interceptor from the X’s “after” list is placed below the “uppermost” one from the “before” list, or when circular dependencies are discovered, the chain construction fails.
For thread-safety, the interceptor instances must be instantiated using a org.openehealth.ipf.platform.camel.ihe.core.InterceptorFactory
together with the interceptorFactories parameter:
from("pdq-iti21://0.0.0.0:18214?interceptorFactories=#myInterceptorFactory,#authenticationInterceptorFactory")
...
Each InterceptorFactory
must implement the getNewInstance()
method so that for each call a new instance
of the interceptor is returned. For convenience, there is an abstract class InterceptorFactorySupport
that takes the interceptor class as constructor parameter and instantiates interceptor instances by calling Class#newInstance()
.