View Javadoc

1   /**
2    * The contents of this file are subject to the Mozilla Public License Version 1.1
3    * (the "License"); you may not use this file except in compliance with the License.
4    * You may obtain a copy of the License at http://www.mozilla.org/MPL/
5    * Software distributed under the License is distributed on an "AS IS" basis,
6    * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
7    * specific language governing rights and limitations under the License.
8    *
9    * The Original Code is ""  Description:
10   * ""
11   *
12   * The Initial Developer of the Original Code is University Health Network. Copyright (C)
13   * 2001.  All Rights Reserved.
14   *
15   * Contributor(s): ______________________________________.
16   *
17   * Alternatively, the contents of this file may be used under the terms of the
18   * GNU General Public License (the  "GPL"), in which case the provisions of the GPL are
19   * applicable instead of those above.  If you wish to allow use of your version of this
20   * file only under the terms of the GPL and not to allow others to use your version
21   * of this file under the MPL, indicate your decision by deleting  the provisions above
22   * and replace  them with the notice and other provisions required by the GPL License.
23   * If you do not delete the provisions above, a recipient may use your version of
24   * this file under either the MPL or the GPL.
25   */
26  package ca.uhn.hl7v2.testpanel.model.conn;
27  
28  import java.io.IOException;
29  import java.io.StringReader;
30  import java.io.StringWriter;
31  import java.net.BindException;
32  import java.util.ArrayList;
33  import java.util.Date;
34  import java.util.List;
35  
36  import javax.swing.SwingUtilities;
37  import javax.xml.bind.JAXB;
38  import javax.xml.bind.annotation.XmlAccessType;
39  import javax.xml.bind.annotation.XmlAccessorType;
40  import javax.xml.bind.annotation.XmlAttribute;
41  import javax.xml.bind.annotation.XmlType;
42  
43  import org.slf4j.Logger;
44  import org.slf4j.LoggerFactory;
45  
46  import ca.uhn.hl7v2.HL7Exception;
47  import ca.uhn.hl7v2.app.Application;
48  import ca.uhn.hl7v2.app.ApplicationException;
49  import ca.uhn.hl7v2.app.Connection;
50  import ca.uhn.hl7v2.app.ConnectionListener;
51  import ca.uhn.hl7v2.app.HL7Service;
52  import ca.uhn.hl7v2.conf.ProfileException;
53  import ca.uhn.hl7v2.conf.check.DefaultValidator;
54  import ca.uhn.hl7v2.conf.spec.RuntimeProfile;
55  import ca.uhn.hl7v2.model.Message;
56  import ca.uhn.hl7v2.parser.EncodingCharacters;
57  import ca.uhn.hl7v2.parser.Parser;
58  import ca.uhn.hl7v2.testpanel.model.ActivityIncomingMessage;
59  import ca.uhn.hl7v2.testpanel.model.ActivityInfoError;
60  import ca.uhn.hl7v2.testpanel.model.ActivityOutgoingMessage;
61  import ca.uhn.hl7v2.testpanel.model.ActivityValidationOutcome;
62  import ca.uhn.hl7v2.testpanel.model.conf.ProfileGroup;
63  import ca.uhn.hl7v2.testpanel.model.conf.ProfileGroup.Entry;
64  import ca.uhn.hl7v2.util.Terser;
65  
66  @XmlAccessorType(XmlAccessType.FIELD)
67  @XmlType(name = "InboundConnection")
68  public class InboundConnection extends AbstractConnection {
69  
70  	public static final String CONNECTIONS_PROPERTY = InboundConnection.class.getName() + "_CONNECTIONS_PROP";
71  
72  	private static final Logger ourLog = LoggerFactory.getLogger(InboundConnection.class);
73  	public static final String PROP_VALIDATE_INCOMING = InboundConnection.class.getName() + "_VALIDATE_INCOMING";
74  	private transient List<Connection> myConnections = new ArrayList<Connection>();
75  	private transient Handler myHandler = new Handler();
76  	private transient MonitorThread myMonitorThread;
77  	private transient Parser myParser;
78  	private transient HL7Service myService;
79  
80  	@XmlAttribute(name = "validateIncomingUsingProfileGroupId")
81  	private String myValidateIncomingUsingProfileGroupId;
82  
83  
84  	@Override
85  	public String exportConfigToXml() {
86  		StringWriter writer = new StringWriter();
87  		JAXB.marshal(this, writer);
88  		return writer.toString();
89  	}
90  
91  	/**
92  	 * @return the connections
93  	 */
94  	public List<Connection> getConnections() {
95  		return myConnections;
96  	}
97  
98  	/**
99  	 * @return the validateIncomingUsingProfileGroupId
100 	 */
101 	public String getValidateIncomingUsingProfileGroupId() {
102 		return myValidateIncomingUsingProfileGroupId;
103 	}
104 
105 	/**
106 	 * @param theValidateIncomingUsingProfileGroupId
107 	 *            the validateIncomingUsingProfileGroupId to set
108 	 */
109 	public void setValidateIncomingUsingProfileGroupId(String theValidateIncomingUsingProfileGroupId) {
110 		String oldValue = myValidateIncomingUsingProfileGroupId;
111 		myValidateIncomingUsingProfileGroupId = theValidateIncomingUsingProfileGroupId;
112 		firePropertyChange(PROP_VALIDATE_INCOMING, oldValue, theValidateIncomingUsingProfileGroupId);
113 	}
114 
115 	@Override
116 	public void start() {
117 		super.start();
118 
119 		if (myService != null) {
120 			return;
121 		}
122 
123 		myParser = createParser();
124 
125 		switch (getTransport()) {
126 		case DUAL_PORT_MLLP: {
127 			try {
128 				myService = createHapiContext().newServer(getIncomingOrSinglePort(), getOutgoingPort(), isTls());
129 			} catch (IOException e) {
130 				ourLog.error("Failed to create server socket", e);
131 				setStatus(StatusEnum.FAILED);
132 				setStatusLine("Failed to create server socket: " + e.getMessage());
133 				return;
134 			}
135 			break;
136 		}
137 		case SINGLE_PORT_MLLP:
138 		case HL7_OVER_HTTP: {
139 			try {
140 				myService = createHapiContext().newServer(getIncomingOrSinglePort(), isTls());
141 			} catch (IOException e) {
142 				ourLog.error("Failed to create server socket", e);
143 				setStatus(StatusEnum.FAILED);
144 				setStatusLine("Failed to create server socket: " + e.getMessage());
145 				return;
146 			}
147 
148 			break;
149 		}
150 		}
151 
152 		myService.registerApplication("*", "*", myHandler);
153 		myService.registerConnectionListener(myHandler);
154 
155 		myService.start();
156 
157 		myMonitorThread = new MonitorThread();
158 		myMonitorThread.start();
159 
160 		updateStatus();
161 	}
162 
163 	@Override
164 	public void stop() {
165 		super.stop();
166 
167 		if (myService != null) {
168 			myService.stop();
169 		}
170 		myService = null;
171 
172 		MonitorThread monitorThread = myMonitorThread;
173 		myMonitorThread = null;
174 
175 		if (myMonitorThread != null) {
176 			monitorThread.interrupt();
177 		}
178 
179 		setStatus(StatusEnum.STOPPED);
180 		setStatusLine("Stopped");
181 
182 	}
183 
184 	private void updateStatus() {
185 		if (myMonitorThread == null) {
186 			if (getStatus() != StatusEnum.FAILED) {
187 				setStatus(StatusEnum.STOPPED);
188 				setStatusLine("");
189 			}
190 		} else if (myConnections.size() > 1) {
191 			setStatus(StatusEnum.STARTED);
192 			setStatusLine("Listening on " + createDescription() + ", " + myConnections.size() + " connections");
193 		} else if (myConnections.size() > 0) {
194 			setStatus(StatusEnum.STARTED);
195 			setStatusLine("Listening on " + createDescription() + ", 1 connection");
196 		} else {
197 			setStatus(StatusEnum.TRYING_TO_START);
198 			setStatusLine("Listening on " + createDescription() + ", no connections");
199 		}
200 	}
201 
202 	public static InboundConnection fromXml(String theXml) {
203 		return JAXB.unmarshal(new StringReader(theXml), InboundConnection.class);
204 	}
205 
206 	/**
207 	 * Listens to the service for updates
208 	 */
209 	private class Handler implements Application, ConnectionListener {
210 
211 		public boolean canProcess(Message theIn) {
212 			return true;
213 		}
214 
215 		public void connectionDiscarded(Connection theC) {
216 			String msg = "Connection lost from " + theC.getRemoteAddress().toString();
217 			ourLog.info(msg);
218 			addActivityInfoInSwingThread(msg);
219 
220 			ArrayList<Connection> oldConnections = new ArrayList<Connection>(myConnections);
221 			myConnections.remove(theC);
222 
223 			updateStatus(oldConnections);
224 		}
225 
226 		public void connectionReceived(Connection theC) {
227 			String msg = "New connection received from " + theC.getRemoteAddress().toString();
228 			ourLog.info(msg);
229 			addActivityInfoInSwingThread(msg);
230 
231 			ArrayList<Connection> oldConnections = new ArrayList<Connection>(myConnections);
232 			myConnections.add(theC);
233 
234 			updateStatus(oldConnections);
235 		}
236 
237 		public Message processMessage(Message theIn) throws ApplicationException, HL7Exception {
238 			try {
239 				String controlId = new Terser(theIn).get("/MSH-10");
240 				ourLog.info("Received message with control ID: {}", controlId);
241 
242 				beforeProcessingNewMessageIn();
243 
244 				addActivity(new ActivityIncomingMessage(new Date(), getEncoding(), myParser.encode(theIn), EncodingCharacters.getInstance(theIn)));
245 
246 				final Message response = theIn.generateACK();
247 
248 				if (getValidateIncomingUsingProfileGroupId() != null) {
249 					ProfileGroup profileGroup = getController().getProfileFileList().getProfile(getValidateIncomingUsingProfileGroupId());
250 					Terser t = new Terser(theIn);
251 					String evtType = t.get("/MSH-9-1");
252 					String evtTrigger = t.get("/MSH-9-2");
253 					try {
254 						Entry profileEntry = profileGroup.getProfileForMessage(evtType, evtTrigger);
255 						RuntimeProfile profile = profileEntry.getProfileProxy().getProfile();
256 
257 						DefaultValidator validator = new DefaultValidator();
258 						if (profileEntry.getTablesId() != null) {
259 							validator.setCodeStore(getController().getTableFileList().getTableFile(profileEntry.getTablesId()));
260 						}
261 						HL7Exception[] problems = validator.validate(theIn, profile.getMessage());
262 						addActivity(new ActivityValidationOutcome(new Date(), problems));
263 
264 					} catch (ProfileException e) {
265 						ourLog.error("Failed to load profile", e);
266 					}
267 				}
268 
269 				addActivity(new ActivityOutgoingMessage(new Date(), getEncoding(), myParser.encode(response), EncodingCharacters.getInstance(response)));
270 
271 				SwingUtilities.invokeLater(new Runnable() {
272 					@Override
273 					public void run() {
274 						addNewMessage();
275 					}
276 				});
277 				
278 				String respControlId = new Terser(response).get("/MSH-10");
279 				ourLog.info("Responding with control ID: {}", respControlId);
280 				
281 				return response;
282 			} catch (IOException e) {
283 				throw new HL7Exception(e);
284 			}
285 		}
286 
287 		private void updateStatus(final ArrayList<Connection> theOldConnections) {
288 			SwingUtilities.invokeLater(new Runnable() {
289 
290 				public void run() {
291 					InboundConnection.this.updateStatus();
292 
293 					firePropertyChange(CONNECTIONS_PROPERTY, theOldConnections, myConnections);
294 				}
295 			});
296 		}
297 
298 	}
299 
300 
301 	private class MonitorThread extends Thread {
302 
303 		@Override
304 		public void run() {
305 
306 			boolean done = false;
307 			boolean notifiedOfStart = false;
308 			while (myMonitorThread == this && !done) {
309 
310 				if (!notifiedOfStart && myService.isRunning()) {
311 					addActivityInfoInSwingThread("Interface started");
312 					notifiedOfStart = true;
313 				}
314 
315 				final Throwable exception = myService.getServiceExitedWithException();
316 				if (exception != null) {
317 					done = true;
318 
319 					SwingUtilities.invokeLater(new Runnable() {
320 
321 						public void run() {
322 							addActivity(new ActivityInfoError(new Date(), "Interface stopped with error: " + exception.getMessage()));
323 							InboundConnection.this.stop();
324 
325 							if (exception instanceof BindException) {
326 								setStatusLine("Could not bind, " + createDescription() + " already in use");
327 							} else {
328 								setStatusLine(exception.getMessage());
329 							}
330 							setStatus(StatusEnum.FAILED);
331 						}
332 					});
333 				}
334 
335 				try {
336 					Thread.sleep(250);
337 				} catch (InterruptedException e) {
338 					// ignore
339 				}
340 			}
341 
342 		}
343 
344 	}
345 }