View Javadoc

1   /*
2      Licensed under the Apache License, Version 2.0 (the "License");
3      you may not use this file except in compliance with the License.
4      You may obtain a copy of the License at
5   
6        http://www.apache.org/licenses/LICENSE-2.0
7   
8      Unless required by applicable law or agreed to in writing, software
9      distributed under the License is distributed on an "AS IS" BASIS,
10     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11     See the License for the specific language governing permissions and
12     limitations under the License.
13  */
14  package uk.nhs.interoperability.source;
15  
16  import java.io.IOException;
17  
18  import javax.xml.bind.DatatypeConverter;
19  import javax.xml.parsers.ParserConfigurationException;
20  import javax.xml.xpath.XPathConstants;
21  import javax.xml.xpath.XPathExpressionException;
22  
23  import org.w3c.dom.Document;
24  import org.w3c.dom.Node;
25  import org.xml.sax.SAXException;
26  
27  import uk.nhs.interoperability.capabilities.DirectoryOfServices;
28  import uk.nhs.interoperability.infrastructure.ITKAddress;
29  import uk.nhs.interoperability.infrastructure.ITKCommsException;
30  import uk.nhs.interoperability.infrastructure.ITKIdentityImpl;
31  import uk.nhs.interoperability.infrastructure.ITKMessageProperties;
32  import uk.nhs.interoperability.infrastructure.ITKMessagePropertiesImpl;
33  import uk.nhs.interoperability.infrastructure.ITKMessagingException;
34  import uk.nhs.interoperability.payload.DEWrappedMessage;
35  import uk.nhs.interoperability.payload.ITKMessage;
36  import uk.nhs.interoperability.payload.SimpleMessage;
37  import uk.nhs.interoperability.service.ITKService;
38  import uk.nhs.interoperability.service.ITKSimpleDOS;
39  import uk.nhs.interoperability.transport.ITKSender;
40  import uk.nhs.interoperability.transport.ITKTransportRoute;
41  import uk.nhs.interoperability.transport.WS.ITKSenderWSImpl;
42  import uk.nhs.interoperability.util.Logger;
43  import uk.nhs.interoperability.util.xml.DomUtils;
44  import uk.nhs.interoperability.util.xml.XPaths;
45  
46  /**
47   * The Class ITKMessageSenderImpl.
48   *
49   * @author Michael Odling-Smee
50   * @author Nicholas Jones
51   * @since 0.1
52   */
53  public class ITKMessageSenderImpl implements ITKMessageSender {
54  	
55  	/** The dos. */
56  	private DirectoryOfServices directoryOfServices;
57  	
58  	/**
59  	 * Instantiates a new iTK message sender impl.
60  	 */
61  	public ITKMessageSenderImpl() {
62  		this.directoryOfServices = new ITKSimpleDOS();
63  	}
64  
65  	
66  	/* (non-Javadoc)
67  	 * @see uk.nhs.interoperability.source.ITKMessageSender#send(uk.nhs.interoperability.payload.ITKMessage)
68  	 */
69  	@Override
70  	public void send(ITKMessage request) throws ITKMessagingException {
71  
72  		// Get the ITKService from the DirectoryOfService
73  		String serviceId = request.getMessageProperties().getServiceId();
74  		ITKService service = directoryOfServices.getService(serviceId);
75  		if (service==null){
76  			Logger.trace("Invalid Service");
77  			throw new ITKMessagingException("Service " + serviceId + " is not a configured service (see service.properties).");	
78  		}
79  
80  		ITKTransportRoute route = getRoute(request);
81  
82  		ITKSender sender = getSender(route);
83  
84  		ITKMessage message = buildMessage(request, route, service);
85  
86  		Logger.trace("Sending via WS: " + message.getFullPayload());
87  
88  		sender.send(route, message);
89  		
90  	}
91  	
92  	/* (non-Javadoc)
93  	 * @see uk.nhs.interoperability.source.ITKMessageSender#sendSync(uk.nhs.interoperability.payload.ITKMessage)
94  	 */
95  	@Override
96  	public ITKMessage sendSync(ITKMessage request) throws ITKMessagingException {
97  
98  		// Get the ITKService from the DirectoryOfService
99  		String serviceId = request.getMessageProperties().getServiceId();
100 		ITKService service = directoryOfServices.getService(serviceId);
101 		if (service==null){
102 			Logger.trace("Invalid Service");
103 			throw new ITKMessagingException("Service " + serviceId + " is not a configured service (see service.properties).");	
104 		}
105 
106 		if (!service.supportsSync()){
107 			Logger.trace("Invalid Service Call");
108 			throw new ITKMessagingException("Service " + service.getServiceId() + " can not be called Synchronously");
109 		}
110 
111 		ITKTransportRoute route = getRoute(request);
112 
113 		ITKSender sender = getSender(route);
114 
115 		ITKMessage message = buildMessage(request, route, service);
116 
117 		Logger.trace("Sending via configured transport: " + message.getFullPayload());
118 		ITKMessage response = sender.sendSync(route, message);
119 		
120 		ITKMessage simpleResponse = buildResponse(response);
121 		
122 		return simpleResponse;
123 
124 	}
125 	
126 	/* (non-Javadoc)
127 	 * @see uk.nhs.interoperability.source.ITKMessageSender#sendAsync(uk.nhs.interoperability.payload.ITKMessage)
128 	 */
129 	@Override
130 	public void sendAsync(ITKMessage request) throws ITKMessagingException {
131 
132 		// Get the ITKService from the DirectoryOfService
133 		String serviceId = request.getMessageProperties().getServiceId();
134 		ITKService service = directoryOfServices.getService(serviceId);
135 		if (service==null){
136 			Logger.trace("Invalid Service");
137 			throw new ITKMessagingException("Service " + serviceId + " is not a configured service (see service.properties).");	
138 		}
139 
140 		if (!service.supportsAsync()){
141 			Logger.trace("Invalid Service Call");
142 			throw new ITKMessagingException("Service " + service.getServiceId() + " can not be called Asynchronously");
143 		}
144 
145 		ITKTransportRoute route = getRoute(request);
146 
147 		ITKSender sender = getSender(route);
148 
149 		ITKMessage message = buildMessage(request, route, service);
150 
151 		Logger.trace("Sending via configured transport: " + message.getFullPayload());
152 		sender.sendAysnc(route, message);
153 
154 	}
155 
156 	/**
157 	 * Gets the route.
158 	 *
159 	 * @param request the request
160 	 * @return the route
161 	 * @throws ITKMessagingException the iTK messaging exception
162 	 */
163 	private ITKTransportRoute getRoute(ITKMessage message) throws ITKMessagingException {
164 
165 		String serviceId = message.getMessageProperties().getServiceId();
166 
167 		//Has the transport route already been resolved? This may be set on a response
168 		ITKTransportRoute route = message.getPreresolvedRoute();
169 		ITKAddress toAddress = message.getMessageProperties().getToAddress();
170 		
171 		
172 		//If the route has not been pre-resolved look it up
173 		
174 		if (route == null) {
175 			
176 		    Logger.trace("TransportRoute has not been pre-resolved - looking up");
177 		    			
178 			// Resolve Destination Service in terms of a TransportRoute
179 			route = directoryOfServices.resolveDestination(serviceId, toAddress);
180 			
181 		} else {
182 			
183 			Logger.debug("Pre-resolved transport route on object " + message.getClass().getSimpleName() +  " is " + route);
184 
185 		}
186 		
187 		if (route == null){
188 			String msgStr = "No route found for service (" + serviceId + ") and recipient ("+ toAddress + ")";
189 			Logger.trace(msgStr);
190 			throw new ITKMessagingException(msgStr);
191 		} else {
192 			
193 			Logger.debug("Found transport route:" + route);
194 			
195 		}
196 		
197 		
198 		return route;
199 	}
200 	
201 	/**
202 	 * Builds the message.
203 	 *
204 	 * @param request the request
205 	 * @param route the route
206 	 * @param service the service
207 	 * @return the iTK message
208 	 */
209 	private ITKMessage buildMessage(ITKMessage request, ITKTransportRoute route, ITKService service){
210 		ITKMessage message = null;
211 
212 		// Does the message need a wrapper?
213 		// TODO: Is wrapper type really dependent on the Route? Or the Service? This feels wrong
214 		if (route.getWrapperType().equals(ITKTransportRoute.DISTRIBUTION_ENVELOPE)){
215 			Logger.trace("Adding distribution envelope wrapper");
216 			
217 			if (service.isBase64()) {
218 				String b64DocText = DatatypeConverter.printBase64Binary(request.getBusinessPayload().getBytes());
219 				request.setBusinessPayload(b64DocText);
220 			}
221 			message = new DEWrappedMessage(service, request, DEWrappedMessage.DE_ADDRESSED); 
222 		} else {
223 			Logger.trace("No DE wrapper required");
224 			message = request;
225 		}
226 		return message;		
227 	}
228 	
229 	/**
230 	 * Gets the sender.
231 	 *
232 	 * @param route the route
233 	 * @return the sender
234 	 * @throws ITKMessagingException the iTK messaging exception
235 	 */
236 	private ITKSender getSender(ITKTransportRoute route) throws ITKMessagingException {
237 		
238 		ITKSender sender = null;
239 		// Resolve the sender implementation according to the transport type
240 		if (route.getTransportType().equals(ITKTransportRoute.HTTP_WS)){
241 			sender = new ITKSenderWSImpl();
242 		}
243 
244 		if (sender == null){
245 			Logger.trace("No Transport Sender Found");
246 			throw new ITKMessagingException("No transport implementation found for configured route");
247 		}
248 
249 		return sender;
250 	}
251 
252 
253 	/**
254 	 * Builds the response.
255 	 *
256 	 * @param response the response
257 	 * @return the iTK message
258 	 * @throws ITKMessagingException the iTK messaging exception
259 	 */
260 	private ITKMessage buildResponse(ITKMessage response) throws ITKMessagingException {
261 		
262 		ITKMessage simpleResponse = new SimpleMessage();
263 		ITKMessageProperties itkMessageProperties = null;
264 		
265 		try {
266 			
267 			String responsePayloadString = "";
268 			Document responseDoc = DomUtils.parse(response.getBusinessPayload());
269 			String responsePayloadName = responseDoc.getDocumentElement().getLocalName();
270 			Logger.trace("Received:"+responsePayloadName);
271 			
272 			if (responsePayloadName.equalsIgnoreCase("DistributionEnvelope")){
273 				Logger.trace("Processing DistributionEnvelope");
274 	
275 				//Extract message properties from the response
276 				itkMessageProperties = ITKMessagePropertiesImpl.build(responseDoc);
277 	
278 				// Check plain payload
279 				String mimetype = (String)XPaths.ITK_FIRST_MIMETYPE_XPATH.evaluate(responseDoc);
280 				Logger.debug("MimeType: " + mimetype);
281 				
282 				if (mimetype.equalsIgnoreCase("text/plain")){
283 					// Get the payload as a text node
284 					String payload = (String)XPaths.ITK_FIRST_PAYLOAD_TEXT_XPATH.evaluate(responseDoc);
285 					responsePayloadString = payload;
286 					
287 					// Check base64 encoding
288 					String base64 = (String)XPaths.ITK_FIRST_BASE64_XPATH.evaluate(responseDoc);
289 					Logger.debug("Base64: " + base64);
290 					if (base64.equalsIgnoreCase("true")){
291 						byte[] payloadBytes = javax.xml.bind.DatatypeConverter.parseBase64Binary(payload);
292 						responsePayloadString = new String(payloadBytes);
293 					}
294 					
295 				} else {
296 					// Get the first payload from the distribution envelope
297 					Document businessPayloadDocument = DomUtils.createDocumentFromNode((Node)XPaths.ITK_FIRST_PAYLOAD_XPATH.evaluate(responseDoc, XPathConstants.NODE));
298 					
299 					if (businessPayloadDocument == null){
300 						// exception
301 						ITKMessagingException unknownResponseException = new ITKMessagingException(
302 								ITKMessagingException.PROCESSING_ERROR_NOT_RETRYABLE_CODE, "No payload in Distribution Envelope");
303 						Logger.error("Unknown response type",unknownResponseException);
304 						throw unknownResponseException;
305 					}
306 					
307 					//Show the extracted payload in the trace log
308 					String businessPayloadName = businessPayloadDocument.getDocumentElement().getLocalName();
309 					Logger.trace("Business Payload Name is:"+businessPayloadName);
310 					responsePayloadString = DomUtils.serialiseToXML(businessPayloadDocument);
311 				}
312 				
313 			} else if (responsePayloadName.equalsIgnoreCase("SimpleMessageResponse")){
314 				Logger.trace("Processing SimpleMessageResponse");
315 				// Set up a dummy MessageProperties for the audit process. There is no audit identifier on a Simple Message Response
316 				itkMessageProperties = new ITKMessagePropertiesImpl();
317 				itkMessageProperties.setAuditIdentity(new ITKIdentityImpl("NOT SPECIFIED"));
318 				responsePayloadString = (String)XPaths.ITK_SIMPLE_MESSAGE_RESPONSE_CONTENT_XPATH.evaluate(responseDoc);
319 				
320 			} else {
321 				Logger.trace("Processing UNKNOWN");
322 				ITKMessagingException unknownResponseException = new ITKMessagingException(
323 						ITKMessagingException.PROCESSING_ERROR_NOT_RETRYABLE_CODE, "Neither DistributionEnvelope nor SimpleMessageResponse Found");
324 				Logger.error("Unknown response type",unknownResponseException);
325 				throw unknownResponseException;
326 				
327 			}
328 			
329 			simpleResponse.setBusinessPayload(responsePayloadString);
330 			simpleResponse.setMessageProperties(itkMessageProperties);
331 			
332 		} catch (SAXException se) {
333 			Logger.error("SAXException processing response from Transport", se);
334 			throw new ITKCommsException("XML Error Processing ITK Response");
335 		} catch (IOException ioe) {
336 			Logger.error("IOException on Transport", ioe);
337 			throw new ITKCommsException("Transport error sending ITK Message");
338 		} catch (ParserConfigurationException pce) {
339 			Logger.error("ParseConfigurationException on Transport", pce);
340 			throw new ITKCommsException("XML Configuration Error Processing ITK Response");
341 		} catch (XPathExpressionException xpe) {
342 			Logger.error("XPathExpressionException reading payload on Transport Response", xpe);
343 			throw new ITKCommsException("No Payload found in ITK Response");
344 		}
345 		
346 		return simpleResponse;
347 	}
348 
349 }