Spine Mini-Services Provider - Get Patient Details by NHSNumber

Black box view

Overview

In this scenario a client application is indirectly querying Spine PDS to obtain demographic information held for the provided NHSNumber. From a business perspective this is typically used to check (and/or potentially update) the locally held demographic details about the patient from the national Patient Demographic Service.

The following describes the logic required to invoke the ITK urn:nhs-itk:services:201005:getPatientDetailsByNHSNumber-v1-0 service.

From a black-box perspective the invocation of a Spine Mini-Service Provider web service interface is fairly trivial. The service requestor simply sends the appropriate ITK request message and receives the response synchronously. Likewise any exceptions will be returned as synchronous SOAP Faults.

Spine Mini-Services - Client

As the SMS client already has the pertient patient's NHSNumber in their local data store the query to get the latest demographics via the SMS Provider is relatively straightforward. The SMS Client builds the Get Patient Details By NHSNumber Request query message providing the patient's NHSNumber and Date of Birth.

Spine Mini-Services - Provider

The SMS Provider is a unique component in ITK - providing a bridge between the Spine PDS interfaces and Spine Mini Service Clients. In a real deployment, the SMSP would receieve the ITK Get Patient Details By NHSNumber Request, make one or more queries to Spine services the results of which are used construct the appropriate ITK Get Patient Details By NHSNumber Response.

In the samples application the SMSP is emulated via a simple XSLT that creates a response from the request

Get Patient Details By NHSNumber Request (Get example)

<soap:Envelope xmlns:itk="urn:nhs-itk:ns:201005" ...>
  <soap:Header>
    ...
    <wsa:To>http://www.example.com/sync</wsa:To>
    <wsa:From><wsa:Address>http://127.0.0.1:4000/syncsoap</wsa:Address></wsa:From>
  </soap:Header>
  <soap:Body>
    <itk:DistributionEnvelope>
      <itk:header service="urn:nhs-itk:services:201005:getPatientDetailsByNHSNumber-v1-0" ...>  ... </itk:header>
      <itk:payloads count="1">
        <itk:payload id="uuid_D1F95107-42FD-4AB5-936F-A3D2A489DD61">
          <getPatientDetailsByNHSNumberRequest-v1-0 xmlns="urn:hl7-org:v3" ...>
                    ...
          </getPatientDetailsByNHSNumberRequest-v1-0>
        </itk:payload>
      </itk:payloads>
    </itk:DistributionEnvelope>
  </soap:Body>
</soap:Envelope>

Get Patient Details By NHSNumber Response (Get example)

<soap:Envelope xmlns:wsa="http://www.w3.org/2005/08/addressing"...>
  <soap:Header>
    ...
    <wsa:Action>urn:nhs-itk:services:201005:getPatientDetailsByNHSNumber-v1-0Response</wsa:Action>
    ...
  </soap:Header>
  <soap:Body>
    <itk:DistributionEnvelope>
      <itk:header service="urn:nhs-itk:services:201005:getPatientDetailsByNHSNumber-v1-0Response" ...> ... </itk:header>
      <itk:payloads count="1">
        <itk:payload ...>
          <getPatientDetailsResponse-v1-0 xmlns="urn:hl7-org:v3" ...>
                  ...
          </getPatientDetailsResponse-v1-0>
        </itk:payload>
      </itk:payloads>
    </itk:DistributionEnvelope>
  </soap:Body>
</soap:Envelope>

Walkthrough

The following diagram shows how the SMSP emulation is achieved via the entry point uk.nhs.interoperability.consumer.smsp.SMSProviderServlet from the samples project. This class uses the reference implementation to process the call from a Spine Mini Services client.

Step 1

The ITK Reference implementation uses Servlets to manage the HTTP protocol layers. The tradional Java interception point for interacting with the HTTP request is via the doPost() method to manage the POSTed HTTP message (ITK currently only supports HTTP POST as per SOAP).

The specific Servlet for this scenario is the uk.nhs.interoperability.consumer.smsp.SMSProviderServlet however the doPost() method implementation is actually found within the reference implementation in the AbstractSimpleMessageServlet.

Aside from reading the request message and error handling the majority of the processing logic is conatined within the processMessage() method shown to the right.

The processMessage() method uses XPath to extract the Distribution Envelope from the request message (line 6). Further XPaths are then applied to extract the ITKMessageProperties from the Distribution Envelope (line 8). Once these properties have been extracted the Distrubtion Envelope can be discarded: we only need to pass the (sceanrio specific) business payload to the application layer (extracted on lines 13-15).

Prior to invoking the application we perform transport/distribution layer validation (line 11). The full validation methods can be viewed in the source code however we have extracted the ITK Profile Id check for a more in-depth view in listing 2a and 2b below.

As per the API contract we need to pass an ITKMessage to the application layer - the specific instance is constructed (line 18) and the onSyncMessage(ITKMessage request) API call appropriate to the synchronous pattern is invoked.

private ITKMessage processMessage(String requestString) throws ITKMessagingException { 
  ...
  Document doc = DomUtils.parse(requestString);
  ...
  //Extract the distribution envelope XML doc
  Document de = ITKMessagePropertiesImpl.extractDistributionEnvelopeFromSoap(doc);
  //Extract message properties from the Distribtion envelope XML
  itkMsgProps = (ITKMessagePropertiesImpl) ITKMessagePropertiesImpl.build(de);
  
  // Validate the message properties
  this.validateDistributionEnvelope(itkMsgProps);
  ...
  //Obtain the actual business payload
  Document busPayload = DomUtils.createDocumentFromNode((Node)XPaths.SOAP_WRAPPED_ITK_FIRST_PAYLOAD_XPATH.evaluate(...));
  String busPayloadXML = DomUtils.serialiseToXML(busPayload);
 
  //Construct the appropriate object for handing over to the application  
  ITKMessage request = new SimpleMessage(itkMsgProps, busPayloadXML);

  //Call the application layer (an instance of ITKMessageConsumer)
  ITKMessage response = this.messageConsumer.onSyncMessage(request);

  //Audit receipt event
  ITKSimpleAudit.getInstance().auditEvent(AuditService.MESSAGE_RECEIPT_EVENT...);

  //Add any wrappers such as the distribution envelope as necessary
  return this.addITKWrappers(response);	 
} 

Listing 1 deserialising the request message and invoking the destination application

private void loadProps(Properties props) {
  ...
  //Read in supported profiles from properties
  Properties props = ITKApplicationProperties.getPropertiesMatching(props, "profileId.");
  for (Map.Entry<Object, Object> entry : props.entrySet()) {
    String profileId = entry.getKey().toString().substring(...);
    String supportLevelStr = entry.getValue().toString();
    if (supportLevelStr.equalsIgnoreCase("ACCEPTED")) {
      profiles.put(profileId, new Integer(ITKProfileManager.ACCEPTED));
    } else if (... ) { ... } //deal with other supported levels
  }
}

Listing 2a loading in profile support configuration

public int getProfileSupportLevel(String profileId) {
  /*
   * If we know the profileId return the value from config,
   * otherwise default to not supported
   */
  return profiles.containsKey(profileId)
    ? profiles.get(profileId).intValue()
    : ITKProfileManager.NOT_SUPPORTED;
}

Listing 2b runtime profile Id check

Step 2

In the reference implementation we have made two design decisions about how the profileId check is managed.

  1. The support levels for a given profile is determined by simple configuration - no run-time analysis is performed on the profile manifest XML published in the ITK specifications (the analysis is effectively performed by a human actor).
  2. The transport / distribution layers manage the profile Id check on behalf of the application layer. This is for ease and consistency of implementation as the check is controlled by configuration anyway.

An example profile configuration is: profileId.urn:nhs-en:profile:getPatientDetailsByNHSNumberRequest-v1-0=ACCEPTED. The full set of profile configuration is contained within the profile.properties configuration file within the itk-samples.war.

Possible values for the profileId configuration are:

  • ACCEPTED: The profile id is known to be supported by the associated application layer components
  • DEPRECATED: The profile id is supported by the associated application layer components - however it is expected that the support will be removed within the near future (e.g. next six months).
  • NOT_SUPPORTED: The profile id is not supported by the associated application layer components - an exception should be raised. Lsting 2b (line 8) shows that this is also the default value if the profileId is not configured.

The rationale for having a DEPRECATED status against a profile is to allow specific behaviour to be associated with this state. This behaviour might include logging to a specific file to monitor live traffic or raising SNMP events to operational support. Strictly speaking there is no ITK requirement to support the DEPRECATED state - however supporting this in the reference implementation whilst maintaining alignment with the specification was fairly trivial.

The source code for the ITKProfileManagerImpl shows the full details of the implementation.

Step 3

In the samples application the Spine Mini-Service Provider is only a stubbed implementation - there are no calls to Spine PDS and it does not attempt to fufil the SMSP requirements.

The application layer components are bound to the transport layer via the getMessageConsumer() operation within the SMSProviderServlet. In this example this returns a SimpleApplicationEmulator which uses configuration (consumeremulator.properties) and XSLT to provide syntactically valid responses. In this scenario the getPatientDetailsByNHSNumber-v1-0 request message is converted into a getPatientDetailsResponse-v1-0 message which is subsequently returned to the transport / distribution layers.

Step 4

Once the response is receieved from the applicaiton layer - the transport / distribution layers only need to add the appropriate wrappers and serialise the response to the HTTP response.

Listing 4a - shows the logic to determine whether or not a Distribution Envelope is required (line 4) and, if it a DE is required, adding it (line 12). There is also additional logic to determine whether or not the business payload should be base64 encoded (line 6-11), however this is only required for pipe and hat HL7v2 messages.

Listing 4b - shows the additional SOAP wrapper layers being added by the HTTP/WS transport specific implementation.

protected ITKMessage addITKWrappers(ITKMessage itkMessage) throws ITKMessagingException {
  ...
  //Detemine whether to add Distribution Envelope (simple rules for now)
  if (!(itkMessage instanceof ITKSimpleMessageResponse)) {
    // Get the ITKService from the DirectoryOfService
    String serviceId = itkMessage.getMessageProperties().getServiceId()
    ITKService service = dos.getService(serviceId);
    if(service.isBase64()){
      String b64DocText = DatatypeConverter.printBase64Binary(...);
      itkMessage.setBusinessPayload(b64DocText);
    }
    return new DEWrappedMessage(service, itkMessage, true);      
  } 
}

Listing 4a adding Distribution Envelope


ITKMessage response = new WSSOAPMessageImpl(unwrappedResponseMsg, WSSOAPMessageImpl.SYNCRESP);

Listing 4b adding SOAP wrappers