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.consumer;
15  
16  import java.io.IOException;
17  
18  import javax.servlet.ServletException;
19  import javax.servlet.http.HttpServletRequest;
20  import javax.servlet.http.HttpServletResponse;
21  import javax.xml.parsers.ParserConfigurationException;
22  import javax.xml.xpath.XPathConstants;
23  import javax.xml.xpath.XPathExpressionException;
24  
25  import org.w3c.dom.Document;
26  import org.w3c.dom.Node;
27  import org.xml.sax.SAXException;
28  
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.ITKMessage;
35  import uk.nhs.interoperability.payload.SimpleMessage;
36  import uk.nhs.interoperability.source.ITKCallbackHandler;
37  import uk.nhs.interoperability.transport.WS.ITKSOAPException;
38  import uk.nhs.interoperability.util.Logger;
39  import uk.nhs.interoperability.util.ServletUtils;
40  import uk.nhs.interoperability.util.xml.DomUtils;
41  import uk.nhs.interoperability.util.xml.XPaths;
42  
43  import com.xmlsolutions.annotation.Requirement;
44  
45  /**
46   *
47   * @author Nick Jones
48   * @author Mike Odling-Smee
49   * @since 0.1
50   * 
51   * This abstract class can be extended by a user written class which must implement the abstract method getCallbackHandler.
52   * 
53   * See the samples project for examples of how this may be achieved.
54   */
55  @SuppressWarnings("serial")
56  public abstract class AbstractCallbackListenerServlet extends ITKServlet {
57  
58  	//Reference to the message consumer that will handle the request
59  	/** The callback handler. */
60  	private ITKCallbackHandler callbackHandler;
61  	
62  	/* (non-Javadoc)
63  	 * @see uk.nhs.interoperability.consumer.ITKServlet#init()
64  	 */
65  	@Override
66  	public void init() throws ServletException {
67  		this.callbackHandler = getCallbackHandler();
68  		super.init();
69  	}
70  	
71  	/**
72  	 * Gets the callback handler.
73  	 *
74  	 * @return the callback handler
75  	 */
76  	public abstract ITKCallbackHandler getCallbackHandler();
77  	
78  	/* (non-Javadoc)
79  	 * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
80  	 */
81  	@Override
82  	@Requirement(traceTo={"WS-PAT-02"})
83  	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
84  		Logger.debug("Received a message"); 
85  		String requestString = new String(ServletUtils.readRequestContent(req));
86  		try {
87  			this.processSOAPMessage(requestString);
88  			resp.setStatus(HttpServletResponse.SC_ACCEPTED);
89  			Logger.debug("Completed processing");
90  			return;
91  		} catch (ITKMessagingException e) {
92  			Logger.error("Could not process Callback message", e);
93  			resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
94  			resp.getWriter().write(new ITKSOAPException(e).serialiseXML());
95  		} catch (Throwable t) {
96  			Logger.error("Could not process Callback message - general error", t);
97  			resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
98  			resp.getWriter().write(t.getLocalizedMessage());
99  		}
100 	}
101 	
102 	// This method is responsible for processing the SOAP message before handing off to the ITK Layer.
103 	// (Mirrors ITKSenderWSImpl)
104 	/**
105 	 * Process soap message.
106 	 *
107 	 * @param requestString the request string
108 	 * @throws ITKMessagingException the iTK messaging exception
109 	 */
110 	@Requirement(traceTo={"WS-PAT-02"})
111 	private void processSOAPMessage(String requestString) throws ITKMessagingException {	
112 
113 		ITKMessageProperties itkMessageProperties = null;
114 		ITKMessage requestMsg = null;
115 
116 		try {
117 			//Parse the request
118 			Document requestDocument = DomUtils.parse(requestString);
119 
120 			if (requestDocument == null){
121 				
122 				// No requestDocument means the servlet received no content - this is an exception.
123 				ITKMessagingException unknownRequestException = new ITKMessagingException(
124 						ITKMessagingException.PROCESSING_ERROR_NOT_RETRYABLE_CODE, "No Content Received.");
125 				Logger.error("Unknown request type",unknownRequestException);
126 				throw unknownRequestException;
127 				
128 			} else {
129 				
130 				// Pretty print the request message
131 				Logger.trace(DomUtils.serialiseToXML(requestDocument, DomUtils.PRETTY_PRINT));
132 
133 				// Extract the SOAP Body Content
134 				Document businessPayloadDocument = DomUtils.createDocumentFromNode((Node)XPaths.SOAP_BODY_CONTENT_XPATH.evaluate(requestDocument, XPathConstants.NODE));
135 
136 				if (businessPayloadDocument == null){
137 					// exception
138 					ITKMessagingException unknownRequestException = new ITKMessagingException(
139 							ITKMessagingException.PROCESSING_ERROR_NOT_RETRYABLE_CODE, "No payload in SOAP Body");
140 					Logger.error("Unknown request type",unknownRequestException);
141 					throw unknownRequestException;
142 				}
143 				
144 				String requestPayloadString = DomUtils.serialiseToXML(businessPayloadDocument);
145 				
146 				//Construct the appropriate object for handing over to the application
147 				requestMsg = new SimpleMessage();
148 				requestMsg.setBusinessPayload(requestPayloadString);
149 				
150 				processITKMessage(requestMsg);
151 
152 			}
153 	
154 		} catch (XPathExpressionException e) {
155 			throw new ITKMessagingException(itkMessageProperties, ITKMessagingException.PROCESSING_ERROR_NOT_RETRYABLE_CODE, "Could not extract values from request", e);
156 		} catch (IOException e) {
157 			throw new ITKMessagingException(itkMessageProperties, ITKMessagingException.PROCESSING_ERROR_NOT_RETRYABLE_CODE, "Could not parse request", e);
158 		} catch (ParserConfigurationException e) {
159 			throw new ITKMessagingException(itkMessageProperties, ITKMessagingException.PROCESSING_ERROR_NOT_RETRYABLE_CODE, "XML parser configuration error", e);
160 		} catch (SAXException e) {
161 			throw new ITKMessagingException(itkMessageProperties, ITKMessagingException.PROCESSING_ERROR_NOT_RETRYABLE_CODE, "Error parsing request XML", e);
162 		}
163 	}
164 	
165 	// This method is responsible for processing the ITK message before handing off to the app.
166 	// (Mirrors ITKMessageSenderImpl)
167 	/**
168 	 * Process itk message.
169 	 *
170 	 * @param requestMessage the request message
171 	 * @throws ITKMessagingException the iTK messaging exception
172 	 */
173 	private void processITKMessage(ITKMessage requestMessage) throws ITKMessagingException {	
174 
175 		try {
176 			
177 			Document requestDocument = DomUtils.parse(requestMessage.getBusinessPayload());
178 			String requestPayloadName = requestDocument.getDocumentElement().getLocalName();
179 			Logger.trace("Received:"+requestPayloadName);
180 			
181 			if (requestPayloadName.equalsIgnoreCase("DistributionEnvelope")){
182 				
183 				Logger.trace("Processing DistributionEnvelope");
184 				processDistributionEnvelope(requestDocument);
185 				
186 			} else if (requestPayloadName.equalsIgnoreCase("SimpleMessageResponse")){
187 				
188 				Logger.trace("Processing SimpleMessageResponse");
189 				
190 				ITKMessage applicationMessage = new SimpleMessage();
191 				String payload = (String)XPaths.ITK_SIMPLE_MESSAGE_RESPONSE_CONTENT_XPATH.evaluate(requestDocument);	
192 				applicationMessage.setBusinessPayload(payload);
193 
194 				// Set up a dummy MessageProperties for the audit process. There is no audit identifier on a Simple Message Response
195 				ITKMessageProperties itkMessageProperties = new ITKMessagePropertiesImpl();
196 				itkMessageProperties.setAuditIdentity(new ITKIdentityImpl("NOT SPECIFIED"));
197 				applicationMessage.setMessageProperties(itkMessageProperties);
198 				
199 				this.callbackHandler.onMessage(applicationMessage);
200 				
201 			} else {
202 				
203 				Logger.trace("Processing UNKNOWN");
204 				ITKMessagingException unknownResponseException = new ITKMessagingException(
205 						ITKMessagingException.PROCESSING_ERROR_NOT_RETRYABLE_CODE, "Neither DistributionEnvelope nor SimpleMessageResponse Found");
206 				Logger.error("Unknown response type",unknownResponseException);
207 				throw unknownResponseException;
208 				
209 			}
210 			
211 			// TODO: AUDIT?
212 			
213 		} catch (SAXException se) {
214 			Logger.error("SAXException processing response from Transport", se);
215 			throw new ITKCommsException("XML Error Processing ITK Response");
216 		} catch (IOException ioe) {
217 			Logger.error("IOException on Transport", ioe);
218 			throw new ITKCommsException("Transport error sending ITK Message");
219 		} catch (ParserConfigurationException pce) {
220 			Logger.error("ParseConfigurationException on Transport", pce);
221 			throw new ITKCommsException("XML Configuration Error Processing ITK Response");
222 		} catch (XPathExpressionException xpe) {
223 			Logger.error("XPathExpressionException reading payload on Transport Response", xpe);
224 			throw new ITKCommsException("No Payload found in ITK Response");
225 		}
226 		
227 	}
228 
229 	/**
230 	 * Process distribution envelope.
231 	 *
232 	 * @param distributionEnvelope the distribution envelope
233 	 * @throws ITKMessagingException the iTK messaging exception
234 	 */
235 	private void processDistributionEnvelope(Document distributionEnvelope) throws ITKMessagingException {	
236 
237 		ITKMessage applicationMessage = new SimpleMessage();
238 		
239 		try {
240 			
241 			// Extract message properties from the distribution envelope
242 			ITKMessageProperties itkMessageProperties = ITKMessagePropertiesImpl.build(distributionEnvelope);
243 			applicationMessage.setMessageProperties(itkMessageProperties);
244 	
245 			// Check plain payload
246 			String mimetype = (String)XPaths.ITK_FIRST_MIMETYPE_XPATH.evaluate(distributionEnvelope);
247 			Logger.debug("MimeType: " + mimetype);
248 			
249 			if (mimetype.equalsIgnoreCase("text/plain")){
250 				
251 				// Get the payload as a text node
252 				String payload = (String)XPaths.ITK_FIRST_PAYLOAD_TEXT_XPATH.evaluate(distributionEnvelope);
253 				
254 				// Check base64 encoding
255 				String base64 = (String)XPaths.ITK_FIRST_BASE64_XPATH.evaluate(distributionEnvelope);
256 				Logger.debug("Base64: " + base64);
257 				if (base64.equalsIgnoreCase("true")){
258 					byte[] payloadBytes = javax.xml.bind.DatatypeConverter.parseBase64Binary(payload);
259 					applicationMessage.setBusinessPayload(new String(payloadBytes));
260 				} else {
261 					applicationMessage.setBusinessPayload(payload);
262 				}
263 				this.callbackHandler.onMessage(applicationMessage);
264 				
265 			} else {
266 				// This is an XML payload
267 				// Get the first payload from the distribution envelope
268 				Document businessPayloadDocument = DomUtils.createDocumentFromNode((Node)XPaths.ITK_FIRST_PAYLOAD_XPATH.evaluate(distributionEnvelope, XPathConstants.NODE));
269 				
270 				if (businessPayloadDocument == null){
271 					// exception
272 					ITKMessagingException unknownResponseException = new ITKMessagingException(
273 							ITKMessagingException.PROCESSING_ERROR_NOT_RETRYABLE_CODE, "No payload in Distribution Envelope");
274 					Logger.error("Unknown response type",unknownResponseException);
275 					throw unknownResponseException;
276 				}
277 	
278 				String businessPayloadName = distributionEnvelope.getDocumentElement().getLocalName();
279 				Logger.trace("ITK Payload 1:"+businessPayloadName);
280 				applicationMessage.setBusinessPayload(DomUtils.serialiseToXML(businessPayloadDocument));
281 				
282 				if (businessPayloadName.equalsIgnoreCase("InfrastructureResponse")){
283 					Logger.trace("Processing InfrastructureResponse");
284 					
285 					// For an Infrastructure Acknowledgement, check if it is a ACK or a NACK, extract the pertinent details and pass
286 					// as an ITKAckDetails object to the application defined handlers
287 	
288 					String result = distributionEnvelope.getDocumentElement().getAttribute("result");
289 					if (result.equals("OK")){
290 						// Pass to APP for correlation etc.
291 						//TODO - Build the Acknowledgement
292 						this.callbackHandler.onAck(null);
293 					} else {
294 						//TODO - Build the Acknowledgement
295 						this.callbackHandler.onNack(null);
296 					}
297 					
298 				} else if (businessPayloadName.equalsIgnoreCase("BusinessResponseMessage")){
299 					
300 					Logger.trace("Processing BusinessResponseMessage (BusinessACK)");
301 					// A Business Acknowledgement is handled by the application layer and requires an Infrastructure Acknowledgement as it
302 					// is a Routed message
303 			
304 					// Call the application defined message handler.
305 					// Note: Exceptions in the handler will bubble up to the doPost where a SOAP or http exception will result
306 					this.callbackHandler.onMessage(applicationMessage);    
307 				
308 					// Now create and Send InfrastructureAck
309 					this.sendInfrastructureAck(applicationMessage.getMessageProperties());
310 					
311 				}	else {
312 					
313 					// Normal business message - send to the application
314 					this.callbackHandler.onMessage(applicationMessage);
315 	
316 				}
317 			}
318 		} catch (ITKMessagingException e){
319 			
320 		} catch (XPathExpressionException xpe) {
321 			Logger.error("XPathExpressionException reading DE on Transport Response", xpe);
322 			throw new ITKCommsException("Error processing Distribution Envelope");
323 		} catch (ParserConfigurationException pce) {
324 			Logger.error("ParseConfigurationException on DE", pce);
325 			throw new ITKCommsException("XML Configuration Error Processing Distribution Envelope");
326 		}
327 		
328 	}
329 	
330 }