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.capabilities.AuditException;
30  import uk.nhs.interoperability.capabilities.AuditService;
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.infrastructure.ITKTransportPropertiesImpl;
35  import uk.nhs.interoperability.payload.ITKMessage;
36  import uk.nhs.interoperability.payload.ITKSimpleMessageResponse;
37  import uk.nhs.interoperability.payload.SimpleMessage;
38  import uk.nhs.interoperability.service.ITKSimpleAudit;
39  import uk.nhs.interoperability.transport.ITKTransportProperties;
40  import uk.nhs.interoperability.transport.WS.ITKSOAPException;
41  import uk.nhs.interoperability.transport.WS.WSSOAPMessageImpl;
42  import uk.nhs.interoperability.util.Logger;
43  import uk.nhs.interoperability.util.ServletUtils;
44  import uk.nhs.interoperability.util.xml.DomUtils;
45  import uk.nhs.interoperability.util.xml.XPaths;
46  
47  /**
48   * The Class AbstractRoutedMessageServlet.
49   *
50   * @author Michael Odling-Smee
51   * @author Nicholas Jones
52   * @since 0.1
53   */
54  @SuppressWarnings("serial")
55  public abstract class AbstractRoutedMessageServlet extends ITKServlet {
56  	
57  	//Reference to the message consumer that will handle the request
58  	/** The message consumer. */
59  	private ITKMessageConsumer messageConsumer;
60  	
61  	/* (non-Javadoc)
62  	 * @see uk.nhs.interoperability.consumer.ITKServlet#init()
63  	 */
64  	@Override
65  	public void init() throws ServletException {
66  		this.messageConsumer = getMessageConsumer();
67  		super.init();
68  	}
69  	
70  	/**
71  	 * Gets the message consumer.
72  	 *
73  	 * @return the message consumer
74  	 */
75  	public abstract ITKMessageConsumer getMessageConsumer();
76  
77  	/* (non-Javadoc)
78  	 * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
79  	 */
80  	@Override
81  	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
82  		Logger.debug("Received a routed message"); 
83  		
84  		try {
85  			byte[] requestMsgBytes = ServletUtils.readRequestContent(req);
86  			if (requestMsgBytes == null) {
87  				throw new ITKMessagingException(ITKMessagingException.INVALID_MESSAGE_CODE, "The request did not contain any content");
88  			}
89  			String requestString = new String(requestMsgBytes);
90  			ITKMessage requestMsg = this.createRequestMessage(requestString);
91  			ITKMessage responseMsg = this.processMessage(requestMsg);
92  
93  			//Now create and Send InfrastructureAck (note this could be handle by another thread - done inline for simplicity)
94  			this.sendInfrastructureAck(requestMsg.getMessageProperties());
95  
96  			//Send back the returned message having added the SOAP wrappers
97  			// No destination object is needed to respond on the same channel
98  			ITKMessage response = new WSSOAPMessageImpl(null, responseMsg, WSSOAPMessageImpl.SYNCRESP);
99  
100 			resp.getWriter().write(response.getFullPayload());
101 			
102 			return;
103 		} catch (ITKMessagingException e) {
104 			Logger.error("Could not process message", e);
105 			resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
106 			/*
107 			 * TODO - to validate
108 			 * Raise a SOAP fault - if this is a multi-hop assume
109 			 * intermediary will convert SOAP Fault into Infrastructure NACK
110 			 */
111 			resp.getWriter().write(new ITKSOAPException(e).serialiseXML());
112 		} catch (Throwable t) {
113 			Logger.error("Could not process message - general error", t);
114 			resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
115 			resp.getWriter().write(t.getLocalizedMessage());
116 		} finally {
117 			Logger.debug("Completed processing");
118 		}
119 	}
120 	
121 	/**
122 	 * Creates the request message.
123 	 *
124 	 * @param requestString the request string
125 	 * @return the iTK message
126 	 * @throws ITKMessagingException the iTK messaging exception
127 	 */
128 	private ITKMessage createRequestMessage(String requestString) throws ITKMessagingException {
129 		ITKTransportProperties itkTransportProperties = null;
130 		ITKMessagePropertiesImpl itkMessageProperties = null;
131 		try {
132 
133 			// 1) PROCESS THE REQUEST DOCUMENT
134 			//Parse the request
135 			Document doc = DomUtils.parse(requestString);
136 			//Pretty print the request message
137 			Logger.trace(DomUtils.serialiseToXML(doc, DomUtils.PRETTY_PRINT));
138 			
139 			// 2) PROCESS THE SOAP HEADERS
140 			//Extract the transport properties
141 			itkTransportProperties = ITKTransportPropertiesImpl.buildFromSoap(doc);
142 
143 			// 3) PROCESS THE DISTRIBUTION ENVELOPE
144 			//Extract the distribution envelope
145 			Document de = ITKMessagePropertiesImpl.extractDistributionEnvelopeFromSoap(doc);
146 			//Extract message properties from the request
147 			itkMessageProperties = (ITKMessagePropertiesImpl) ITKMessagePropertiesImpl.build(de);
148 			// Validate the message properties
149 			this.validateDistributionEnvelope(itkMessageProperties);
150 			//Attach the associated transport properties so that they are available for asynchronous invocations
151 			itkMessageProperties.setInboundTransportProperties(itkTransportProperties);
152 			
153 			
154 			// 4) PROCESS THE PAYLOAD
155 			//Obtain the actual business payload
156 			Document businessPayload = DomUtils.createDocumentFromNode((Node)XPaths.SOAP_WRAPPED_ITK_FIRST_PAYLOAD_XPATH.evaluate(doc, XPathConstants.NODE));
157 			//Show the extracted payload in the trace log
158 			Logger.trace(DomUtils.serialiseToXML(businessPayload, DomUtils.PRETTY_PRINT));
159 			
160 			//Construct the appropriate object for handing over to the application
161 			ITKMessage requestMsg = new SimpleMessage();
162 			requestMsg.setBusinessPayload(DomUtils.serialiseToXML(businessPayload));
163 			requestMsg.setMessageProperties(itkMessageProperties);
164 			
165 			return requestMsg;
166 			
167 		} catch (XPathExpressionException e) {
168 			throw new ITKMessagingException(itkTransportProperties, itkMessageProperties, ITKMessagingException.PROCESSING_ERROR_NOT_RETRYABLE_CODE, "Could not extract values from request", e);
169 		} catch (IOException e) {
170 			throw new ITKMessagingException(itkTransportProperties, itkMessageProperties, ITKMessagingException.PROCESSING_ERROR_NOT_RETRYABLE_CODE, "Could not parse request", e);
171 		} catch (ParserConfigurationException e) {
172 			throw new ITKMessagingException(itkTransportProperties, itkMessageProperties, ITKMessagingException.PROCESSING_ERROR_NOT_RETRYABLE_CODE, "XML parser configuration error", e);
173 		} catch (SAXException e) {
174 			throw new ITKMessagingException(itkTransportProperties, itkMessageProperties, ITKMessagingException.PROCESSING_ERROR_NOT_RETRYABLE_CODE, "Error parsing request XML", e);
175 		}
176 	}
177 	
178 	/**
179 	 * Process message.
180 	 *
181 	 * @param requestMsg the request msg
182 	 * @return the iTK message
183 	 * @throws ITKMessagingException the iTK messaging exception
184 	 */
185 	private ITKMessage processMessage(ITKMessage requestMsg) throws ITKMessagingException {
186 		ITKMessageProperties itkMessageProperties = requestMsg.getMessageProperties();
187 		ITKTransportProperties itkTransportProperties = itkMessageProperties.getInboundTransportProperties();
188 		try {
189 			//Pass the routed message to the application
190 			this.messageConsumer.onMessage(requestMsg);
191 				
192 			//Audit receipt event
193 			ITKSimpleAudit.getInstance().auditEvent(AuditService.MESSAGE_RECEIPT_EVENT, System.currentTimeMillis(), itkMessageProperties);
194 				
195 			//Create and return simple message response
196 			//TODO - should this be a transport layer responsibility? This is inconsistent with the sync/simple async channel
197 			return new ITKSimpleMessageResponse(itkMessageProperties, true);
198 			
199 		} catch (AuditException e) {
200 			throw new ITKMessagingException(itkTransportProperties, itkMessageProperties, ITKMessagingException.PROCESSING_ERROR_RETRYABLE_CODE, "Failed to write audit", e);
201 		} 
202 	}
203 	
204 
205 }