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>
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); }
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 } }
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; }
Step 2
In the reference implementation we have made two design decisions about how the profileId check is managed.
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:
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); } }
ITKMessage response = new WSSOAPMessageImpl(unwrappedResponseMsg, WSSOAPMessageImpl.SYNCRESP);