1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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
92
93 public List<Connection> getConnections() {
94 return myConnections;
95 }
96
97
98
99
100 public String getValidateIncomingUsingProfileGroupId() {
101 return myValidateIncomingUsingProfileGroupId;
102 }
103
104
105
106
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
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
338 }
339 }
340
341 }
342
343 }
344 }