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 ca.uhn.hl7v2.HL7Exception;
29  import ca.uhn.hl7v2.app.Connection;
30  import ca.uhn.hl7v2.app.ConnectionListener;
31  import ca.uhn.hl7v2.app.HL7Service;
32  import ca.uhn.hl7v2.conf.ProfileException;
33  import ca.uhn.hl7v2.conf.check.DefaultValidator;
34  import ca.uhn.hl7v2.conf.spec.RuntimeProfile;
35  import ca.uhn.hl7v2.model.Message;
36  import ca.uhn.hl7v2.parser.EncodingCharacters;
37  import ca.uhn.hl7v2.parser.Parser;
38  import ca.uhn.hl7v2.protocol.ReceivingApplication;
39  import ca.uhn.hl7v2.protocol.ReceivingApplicationException;
40  import ca.uhn.hl7v2.testpanel.model.ActivityIncomingMessage;
41  import ca.uhn.hl7v2.testpanel.model.ActivityInfoError;
42  import ca.uhn.hl7v2.testpanel.model.ActivityOutgoingMessage;
43  import ca.uhn.hl7v2.testpanel.model.ActivityValidationOutcome;
44  import ca.uhn.hl7v2.testpanel.model.conf.ProfileGroup;
45  import ca.uhn.hl7v2.testpanel.model.conf.ProfileGroup.Entry;
46  import ca.uhn.hl7v2.util.Terser;
47  import org.slf4j.Logger;
48  import org.slf4j.LoggerFactory;
49  
50  import javax.swing.*;
51  import jakarta.xml.bind.JAXB;
52  import jakarta.xml.bind.annotation.XmlAccessType;
53  import jakarta.xml.bind.annotation.XmlAccessorType;
54  import jakarta.xml.bind.annotation.XmlAttribute;
55  import jakarta.xml.bind.annotation.XmlType;
56  import java.io.IOException;
57  import java.io.StringReader;
58  import java.io.StringWriter;
59  import java.net.BindException;
60  import java.util.ArrayList;
61  import java.util.Date;
62  import java.util.List;
63  import java.util.Map;
64  
65  @XmlAccessorType(XmlAccessType.FIELD)
66  @XmlType(name = "InboundConnection")
67  public class InboundConnection extends AbstractConnection {
68  
69  	public static final String CONNECTIONS_PROPERTY = InboundConnection.class.getName() + "_CONNECTIONS_PROP";
70  
71  	private static final Logger ourLog = LoggerFactory.getLogger(InboundConnection.class);
72  	public static final String PROP_VALIDATE_INCOMING = InboundConnection.class.getName() + "_VALIDATE_INCOMING";
73  	private transient List<Connection> myConnections = new ArrayList<Connection>();
74  	private transient Handler myHandler = new Handler();
75  	private transient MonitorThread myMonitorThread;
76  	private transient Parser myParser;
77  	private transient HL7Service myService;
78  
79  	@XmlAttribute(name = "validateIncomingUsingProfileGroupId")
80  	private String myValidateIncomingUsingProfileGroupId;
81  
82  
83  	@Override
84  	public String exportConfigToXml() {
85  		StringWriter writer = new StringWriter();
86  		JAXB.marshal(this, writer);
87  		return writer.toString();
88  	}
89  
90  	/**
91  	 * @return the connections
92  	 */
93  	public List<Connection> getConnections() {
94  		return myConnections;
95  	}
96  
97  	/**
98  	 * @return the validateIncomingUsingProfileGroupId
99  	 */
100 	public String getValidateIncomingUsingProfileGroupId() {
101 		return myValidateIncomingUsingProfileGroupId;
102 	}
103 
104 	/**
105 	 * @param theValidateIncomingUsingProfileGroupId
106 	 *            the validateIncomingUsingProfileGroupId to set
107 	 */
108 	public void setValidateIncomingUsingProfileGroupId(String theValidateIncomingUsingProfileGroupId) {
109 		String oldValue = myValidateIncomingUsingProfileGroupId;
110 		myValidateIncomingUsingProfileGroupId = theValidateIncomingUsingProfileGroupId;
111 		firePropertyChange(PROP_VALIDATE_INCOMING, oldValue, theValidateIncomingUsingProfileGroupId);
112 	}
113 
114 	@Override
115 	public void start() {
116 		super.start();
117 
118 		if (myService != null) {
119 			return;
120 		}
121 
122 		myParser = createParser();
123 
124 		switch (getTransport()) {
125 		case DUAL_PORT_MLLP: {
126 			try {
127 				myService = createHapiContext().newServer(getIncomingOrSinglePort(), getOutgoingPort(), isTls());
128 			} catch (IOException e) {
129 				ourLog.error("Failed to create server socket", e);
130 				setStatus(StatusEnum.FAILED);
131 				setStatusLine("Failed to create server socket: " + e.getMessage());
132 				return;
133 			}
134 			break;
135 		}
136 		case SINGLE_PORT_MLLP:
137 		case HL7_OVER_HTTP: {
138 			try {
139 				myService = createHapiContext().newServer(getIncomingOrSinglePort(), isTls());
140 			} catch (IOException e) {
141 				ourLog.error("Failed to create server socket", e);
142 				setStatus(StatusEnum.FAILED);
143 				setStatusLine("Failed to create server socket: " + e.getMessage());
144 				return;
145 			}
146 
147 			break;
148 		}
149 		}
150 
151 		myService.registerApplication("*", "*", myHandler);
152 		myService.registerConnectionListener(myHandler);
153 
154 		myService.start();
155 
156 		myMonitorThread = new MonitorThread();
157 		myMonitorThread.start();
158 
159 		updateStatus();
160 	}
161 
162 	@Override
163 	public void stop() {
164 		super.stop();
165 
166 		if (myService != null) {
167 			myService.stop();
168 		}
169 		myService = null;
170 
171 		MonitorThread monitorThread = myMonitorThread;
172 		myMonitorThread = null;
173 
174 		if (myMonitorThread != null) {
175 			monitorThread.interrupt();
176 		}
177 
178 		setStatus(StatusEnum.STOPPED);
179 		setStatusLine("Stopped");
180 
181 	}
182 
183 	private void updateStatus() {
184 		if (myMonitorThread == null) {
185 			if (getStatus() != StatusEnum.FAILED) {
186 				setStatus(StatusEnum.STOPPED);
187 				setStatusLine("");
188 			}
189 		} else if (myConnections.size() > 1) {
190 			setStatus(StatusEnum.STARTED);
191 			setStatusLine("Listening on " + createDescription() + ", " + myConnections.size() + " connections");
192 		} else if (myConnections.size() > 0) {
193 			setStatus(StatusEnum.STARTED);
194 			setStatusLine("Listening on " + createDescription() + ", 1 connection");
195 		} else {
196 			setStatus(StatusEnum.TRYING_TO_START);
197 			setStatusLine("Listening on " + createDescription() + ", no connections");
198 		}
199 	}
200 
201 	public static InboundConnection fromXml(String theXml) {
202 		return JAXB.unmarshal(new StringReader(theXml), InboundConnection.class);
203 	}
204 
205 	/**
206 	 * Listens to the service for updates
207 	 */
208 	private class Handler implements ReceivingApplication<Message>, ConnectionListener {
209 
210 		public boolean canProcess(Message theIn) {
211 			return true;
212 		}
213 
214 		public void connectionDiscarded(Connection theC) {
215 			String msg = "Connection lost from " + theC.getRemoteAddress().toString();
216 			ourLog.info(msg);
217 			addActivityInfoInSwingThread(msg);
218 
219 			ArrayList<Connection> oldConnections = new ArrayList<Connection>(myConnections);
220 			myConnections.remove(theC);
221 
222 			updateStatus(oldConnections);
223 		}
224 
225 		public void connectionReceived(Connection theC) {
226 			String msg = "New connection received from " + theC.getRemoteAddress().toString();
227 			ourLog.info(msg);
228 			addActivityInfoInSwingThread(msg);
229 
230 			ArrayList<Connection> oldConnections = new ArrayList<Connection>(myConnections);
231 			myConnections.add(theC);
232 
233 			updateStatus(oldConnections);
234 		}
235 
236 		public Message../../../../ca/uhn/hl7v2/model/Message.html#Message">Message processMessage(Message theIn, Map<String, Object> metadata)
237                 throws ReceivingApplicationException, 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 					public void run() {
273 						addNewMessage();
274 					}
275 				});
276 				
277 				String respControlId = new Terser(response).get("/MSH-10");
278 				ourLog.info("Responding with control ID: {}", respControlId);
279 				
280 				return response;
281 			} catch (IOException e) {
282 				throw new HL7Exception(e);
283 			}
284 		}
285 
286 		private void updateStatus(final ArrayList<Connection> theOldConnections) {
287 			SwingUtilities.invokeLater(new Runnable() {
288 
289 				public void run() {
290 					InboundConnection.this.updateStatus();
291 
292 					firePropertyChange(CONNECTIONS_PROPERTY, theOldConnections, myConnections);
293 				}
294 			});
295 		}
296 
297 	}
298 
299 
300 	private class MonitorThread extends Thread {
301 
302 		@Override
303 		public void run() {
304 
305 			boolean done = false;
306 			boolean notifiedOfStart = false;
307 			while (myMonitorThread == this && !done) {
308 
309 				if (!notifiedOfStart && myService.isRunning()) {
310 					addActivityInfoInSwingThread("Interface started");
311 					notifiedOfStart = true;
312 				}
313 
314 				final Throwable exception = myService.getServiceExitedWithException();
315 				if (exception != null) {
316 					done = true;
317 
318 					SwingUtilities.invokeLater(new Runnable() {
319 
320 						public void run() {
321 							addActivity(new ActivityInfoError(new Date(), "Interface stopped with error: " + exception.getMessage()));
322 							InboundConnection.this.stop();
323 
324 							if (exception instanceof BindException) {
325 								setStatusLine("Could not bind, " + createDescription() + " already in use");
326 							} else {
327 								setStatusLine(exception.getMessage());
328 							}
329 							setStatus(StatusEnum.FAILED);
330 						}
331 					});
332 				}
333 
334 				try {
335 					Thread.sleep(250);
336 				} catch (InterruptedException e) {
337 					// ignore
338 				}
339 			}
340 
341 		}
342 
343 	}
344 }