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 sample application uk.nhs.interoperability.client.samples.smsp.SpineMiniServicesClient from the samples project uses the reference implementation to make a call to a Spine Mini-Service Provider.
Step 1
In order to invoke the Spine mini-service the application layer first needs to collect and populate the query parameters. The sample application does this from a simple web form (see http://services.developer.nhs.uk/itk-samples/GetPatientDetailsByNHSNumber.html) however how real world applications obtain the query parameters is outside the scope of the API and reference implementation.
Once the application has constructed the appropriate query object it needs to be handed over to the transport layers for the actual technical invocation of the service.
The actual specific on-the-wire format of the query message is not the responsibility of the transport layers to determine or manage. The request.serialise() method at line 6 populates the payload for the ITK invocation - in this case a getPatientDetailsByNHSNumber-v1-0 XML message with the appropriate parameters set.
It is an application layer responsibility to set the audit identity, to and from address details, profileId and business payload id (line 10). Many of these details are omitted here for clarity - for reference the ITKMessageProperties provides getters and setters to allow the message meta-data and addressing information to be passed between the application and transport layers.
Once the properties have been associated with the payload (line 14) the distribution and transport layers are invoked via the ITKMessageSender sendSync API call.
public GetPatientDetailsByNHSNumberResponse getPatientDetailsByNHSNumber(GetPatientDetailsByNHSNumberRequest request){ GetPatientDetailsByNHSNumberResponse response = null; ITKMessage msg = new SimpleMessage(); msg.setBusinessPayload(request.serialise()); // Build the message properties. ITKMessageProperties mp = new ITKMessagePropertiesImpl(); mp.setAuditIdentity(AUDITID); ... //Other properties also set // Add the properties to the message msg.setMessageProperties(mp); // Create the sender ITKMessageSender sender = new ITKMessageSenderImpl(); try { ITKMessage resp = sender.sendSync(msg); response = new GetPatientDetailsByNHSNumberResponse(resp); } catch (ITKMessagingException e) { Logger.error("Error Sending ITK Message",e); } return response; }
public ITKMessage sendSync(ITKMessage request) throws ITKMessagingException { // Get the ITKService from the DirectoryOfService String serviceId = request.getMessageProperties().getServiceId(); ITKService service = directoryOfServices.getService(serviceId); ... //Validate that the service is configured and supports sync //Determine the physical transport route ITKTransportRoute route = getRoute(request); //Get the transport specific ITKSender implementation ITKSender sender = getSender(route); //Add the necessary ITK wrappers, base64 encode where required ITKMessage message = buildMessage(request, route, service); //Send the message ITKMessage response = sender.sendSync(route, message); //Deserialise and unwrap ITKMessage simpleResponse = buildResponse(response); return simpleResponse; }
Step 2
Note this step and the majority of the subsequent steps are common to all synchronous invocations via the Reference Implementation.
The application request payload is passed into the framework via the ITKMessage. In order to succesfully call the service provider we need to resolve the service properties, obtain the route appropriate to the service provider, add the necessary wrappers and call the appropriate transport specific implementation classess.
The resolution of the route (line 9) is shown in more detail in Step 3 (listing 3a below). Having obtained the route we then get a reference to the transport specific ITKSender (line 12). Note the 0.1 release of the reference implementation only contains an implementaiton for HTTP/SOAP Web services. The reference implementation could be be extended in the future to support DTS, TMS and other transports as specified by ITK.
The details of building the ITK Distribution Envelope and on-the-wire encoding (line 15) are shown in detail in listing 3b below. Once the appropriate ITK message has been created the transport specific ITKSender is used to actual send the message (line 18). For this scenario the HTTP/WS invocation is shown in detail in Step 4 below
Once the transport layers have returned with the synchronous response this is unwrapped / deserialised via the buildResponse(response) method (line 21).
Step 3
Listing 3a shows the logic for resolving the ITKTransportRoute, whilst Listing 3b shows the simple logic used to determine whether or not the payload should be wrapped in an ITK Distribution Envelope and base64 encoded.
The ITKTransportRoute may have already been pre-resolved from the application layer (listing 3, line 9) in which case we do not need to look it up. The pre-resolved route may have been determined via application layer configuration or because the application has maintained the transport return address details from a corresponding request in an asynchronous invocation.
In this synchronous invocation scenario the logical destination address provided by the application layer will need to be resolved to an HTTP URL for the web service invocation. This is achieved via the call to the DirectoryOfService on line 15. In the reference implementation the directory is a simple properties file wrapped by some simple logic - see ITKSimpleDOS.
The DirectoryOfService is responsible for returning a fully populated ITKTransportRoute which contains details of the under lying transport type, the physical destination address and whether or not a distribution envelope is required.
Listing 3b shows the logic for building the transport independent ITK message.
Line 4 determines whether a distribution envelope is required. If the message needs to be wrapped by a Distribution Envelope a new ITKMessage including the DistributionEnvelope is built via the DEWrappedMessage constructor (line 12).
Prior to building the complete Distribution Envelope wrapped message is may be that the business payload needs to be base64 encoded. Logically this is a property of the ITKService (determined via configuration), however in terms of the current ITK Specifications this is only required for HL7v2 messages exchanged in pipe and hat (|^) format. In this getPatientDetailsByNHSNumber scenario the business payload message is XML based and no base64 encoding is required.
private ITKTransportRoute getRoute(ITKMessage message) throws ITKMessagingException { String serviceId = message.getMessageProperties().getServiceId(); /* * Has the transport route already been resolved? * E.g. route may already be set on a response */ ITKTransportRoute route = message.getPreresolvedRoute(); ITKAddress toAddress = message.getMessageProperties().getToAddress(); //If the route has not been pre-resolved look it up if (route == null) { // Resolve Destination Service in terms of a TransportRoute route = directoryOfServices.resolveDestination(serviceId,toAddress); } //Throw an exception if we cannot resolve the route if (route == null) { throw new ITKMessagingException("No route found ..."); } return route; }
private ITKMessage buildMessage(ITKMessage message, ITKTransportRoute route, ITKService service) { // Does the message need a wrapper? if (route.getWrapperType().equals(ITKTransportRoute.DISTRIBUTION_ENVELOPE)) { //Do we need to base64 encode the business payload? if (service.isBase64()) { //Use standard javax.xml.bind libraries to base64 encode String b64DocText = DatatypeConverter.printBase64Binary(message.getBusinessPayload().getBytes()); message.setBusinessPayload(b64DocText); } return new DEWrappedMessage(service, message, DEWrappedMessage.DE_ADDRESSED); } else { //Send the business payload as is return request; } }
public ITKMessage sendSync(ITKTransportRoute destination, ITKMessage request) throws ITKMessagingException { // Add the soap wrappers WSSOAPMessageImpl msg = new WSSOAPMessageImpl(destination, request, ...); ... //Obtain the on-the-wire message String soapPayload = msg.getFullPayload(); try { //Send the on-the-wire message via HTTP Document responseDoc = transportSend(destination, soapPayload); ... // Extract the SOAP Body Content via XPath Document businessPayloadDoc = DomUtils.createDocumentFromNode((Node)XPaths.SOAP_BODY_CONTENT_XPATH.evaluate(responseDoc...)); // Get string representation of the response business payload String responseStr = DomUtils.serialiseToXML(businessPayloadDoc); //Construct the appropriate object for the application layer return new SimpleMessage(responseStr); } catch (...) { ... } }
Step 4
Listing 4 shows the code within the ITKSenderWSImpl which is responsible for adding the SOAP and WS-Addressing wrappers, for invoking the appropriate web-service endpoint as determined by the ITKTransportRoute and managing HTTP and SOAPFaults or other exceptions arising from the invocation.
In Step 3 we created a transport independent ITK message. The logic of this class is specific to ITK Web services and requires that we add SOAP Wrappers including the appropate ws-addressing elements as per the ITK Specifications (line 4).
The call on line 7 to obtain the full payload invokes an XSLT transform that creates the complete on-the-wire SOAP message with the ITK Distribution Envelope wrapped getPatientDetailsByNHSNumber within the SOAP body.
The web service is actually invoked via the transportSend(destination, soapPayload) method call.
As this is a synchronous call there is no information in the response message SOAP header pertinent to the client so we directly extract the response payload (line 14) which is then passed back up the call stack via the returned SimpleMessage (line 18).