1 | |
|
2 | |
|
3 | |
|
4 | |
package ca.uhn.hl7v2.protocol.impl; |
5 | |
|
6 | |
import java.io.IOException; |
7 | |
import java.util.ArrayList; |
8 | |
import java.util.List; |
9 | |
import java.util.Map; |
10 | |
import java.util.regex.Pattern; |
11 | |
|
12 | |
import ca.uhn.hl7v2.AcknowledgmentCode; |
13 | |
import ca.uhn.hl7v2.HL7Exception; |
14 | |
import ca.uhn.hl7v2.HapiContext; |
15 | |
import ca.uhn.hl7v2.Version; |
16 | |
import ca.uhn.hl7v2.app.DefaultApplication; |
17 | |
import ca.uhn.hl7v2.model.GenericMessage; |
18 | |
import ca.uhn.hl7v2.model.Message; |
19 | |
import ca.uhn.hl7v2.model.Segment; |
20 | |
import ca.uhn.hl7v2.parser.GenericParser; |
21 | |
import ca.uhn.hl7v2.parser.Parser; |
22 | |
import ca.uhn.hl7v2.protocol.*; |
23 | |
import ca.uhn.hl7v2.util.DeepCopy; |
24 | |
import ca.uhn.hl7v2.util.Terser; |
25 | |
import org.slf4j.Logger; |
26 | |
import org.slf4j.LoggerFactory; |
27 | |
|
28 | |
|
29 | |
|
30 | |
|
31 | |
|
32 | |
|
33 | |
|
34 | |
public class ApplicationRouterImpl implements ApplicationRouter { |
35 | |
|
36 | |
|
37 | |
|
38 | |
|
39 | |
|
40 | 5 | public static final AcknowledgmentCode DEFAULT_EXCEPTION_ACKNOWLEDGEMENT_CODE = AcknowledgmentCode.AE; |
41 | |
|
42 | 5 | private static final Logger log = LoggerFactory.getLogger(ApplicationRouterImpl.class); |
43 | |
|
44 | |
|
45 | |
|
46 | |
|
47 | |
|
48 | |
public static final String RAW_MESSAGE_KEY = MetadataKeys.IN_RAW_MESSAGE; |
49 | |
|
50 | |
private List<Binding> myBindings; |
51 | |
private Parser myParser; |
52 | |
private ReceivingApplicationExceptionHandler myExceptionHandler; |
53 | |
private HapiContext myContext; |
54 | 225 | private AcknowledgmentCode defaultAcknowledgementMode = DEFAULT_EXCEPTION_ACKNOWLEDGEMENT_CODE; |
55 | |
|
56 | |
|
57 | |
|
58 | |
|
59 | |
|
60 | |
@Deprecated |
61 | |
public ApplicationRouterImpl() { |
62 | 75 | this(new GenericParser()); |
63 | 75 | } |
64 | |
|
65 | |
|
66 | |
|
67 | |
|
68 | |
|
69 | |
|
70 | |
|
71 | |
public ApplicationRouterImpl(Parser theParser) { |
72 | 220 | this(theParser.getHapiContext(), theParser); |
73 | 220 | } |
74 | |
|
75 | |
public ApplicationRouterImpl(HapiContext theContext) { |
76 | 5 | this(theContext, theContext.getGenericParser()); |
77 | 5 | } |
78 | |
|
79 | |
|
80 | |
|
81 | |
|
82 | |
|
83 | |
|
84 | |
|
85 | |
|
86 | |
|
87 | 225 | public ApplicationRouterImpl(HapiContext theContext, Parser theParser) { |
88 | 225 | init(theParser); |
89 | 225 | myContext = theContext; |
90 | 225 | } |
91 | |
|
92 | |
private void init(Parser theParser) { |
93 | 225 | myBindings = new ArrayList<Binding>(20); |
94 | 225 | myParser = theParser; |
95 | 225 | } |
96 | |
|
97 | |
public void setDefaultAcknowledgementMode(AcknowledgmentCode defaultAcknowledgementMode) { |
98 | 0 | this.defaultAcknowledgementMode = defaultAcknowledgementMode; |
99 | 0 | } |
100 | |
|
101 | |
|
102 | |
|
103 | |
|
104 | |
public Transportable processMessage(Transportable theMessage) throws HL7Exception { |
105 | 683 | String[] result = processMessage(theMessage.getMessage(), theMessage.getMetadata()); |
106 | 683 | Transportable response = new TransportableImpl(result[0]); |
107 | |
|
108 | 683 | if (result[1] != null) { |
109 | 5 | response.getMetadata().put(METADATA_KEY_MESSAGE_CHARSET, result[1]); |
110 | |
} |
111 | |
|
112 | 683 | return response; |
113 | |
} |
114 | |
|
115 | |
|
116 | |
|
117 | |
|
118 | |
|
119 | |
|
120 | |
|
121 | |
|
122 | |
|
123 | |
|
124 | |
private String[] processMessage(String incomingMessageString, Map<String, Object> theMetadata) throws HL7Exception { |
125 | 683 | Logger rawOutbound = LoggerFactory.getLogger("ca.uhn.hl7v2.raw.outbound"); |
126 | 683 | Logger rawInbound = LoggerFactory.getLogger("ca.uhn.hl7v2.raw.inbound"); |
127 | |
|
128 | |
|
129 | |
|
130 | |
|
131 | 683 | log.debug("ApplicationRouterImpl got message: {}", incomingMessageString); |
132 | 683 | rawInbound.debug(incomingMessageString); |
133 | |
|
134 | 683 | Message incomingMessageObject = null; |
135 | 683 | String outgoingMessageString = null; |
136 | 683 | String outgoingMessageCharset = null; |
137 | |
try { |
138 | 683 | incomingMessageObject = myParser.parse(incomingMessageString); |
139 | |
|
140 | 668 | Terser inTerser = new Terser(incomingMessageObject); |
141 | 668 | theMetadata.put(MetadataKeys.IN_MESSAGE_CONTROL_ID, inTerser.get("/.MSH-10")); |
142 | |
|
143 | 15 | } catch (HL7Exception e) { |
144 | 15 | log.debug("Exception parsing incoming message", e); |
145 | |
try { |
146 | 15 | outgoingMessageString = logAndMakeErrorMessage(e, myParser.getCriticalResponseData(incomingMessageString), myParser, myParser.getEncoding(incomingMessageString)); |
147 | 5 | } catch (HL7Exception e2) { |
148 | 5 | log.error("Exception occurred while logging parse failure", e2); |
149 | 5 | outgoingMessageString = null; |
150 | 10 | } |
151 | 15 | if (myExceptionHandler != null) { |
152 | 5 | outgoingMessageString = myExceptionHandler.processException(incomingMessageString, theMetadata, outgoingMessageString, e); |
153 | 5 | if (outgoingMessageString == null) { |
154 | 0 | throw new HL7Exception("Application exception handler may not return null"); |
155 | |
} |
156 | |
} |
157 | 668 | } |
158 | |
|
159 | |
|
160 | 683 | if (outgoingMessageString == null) { |
161 | |
try { |
162 | |
|
163 | 668 | String check = System.getProperty("ca.uhn.hl7v2.protocol.impl.check_parse"); |
164 | 668 | if (check != null && check.equals("TRUE")) { |
165 | 0 | ParseChecker.checkParse(incomingMessageString, incomingMessageObject, myParser); |
166 | |
} |
167 | |
|
168 | |
|
169 | |
|
170 | 668 | ReceivingApplication<Message> app = findApplication(incomingMessageObject); |
171 | 668 | theMetadata.put(RAW_MESSAGE_KEY, incomingMessageString); |
172 | |
|
173 | 668 | log.debug("Sending message to application: {}", app.toString()); |
174 | 668 | Message response = app.processMessage(incomingMessageObject, theMetadata); |
175 | |
|
176 | |
|
177 | 643 | outgoingMessageString = myParser.encode(response, myParser.getEncoding(incomingMessageString)); |
178 | |
|
179 | 643 | Terser t = new Terser(response); |
180 | 643 | outgoingMessageCharset = t.get(METADATA_KEY_MESSAGE_CHARSET); |
181 | 15 | } catch (Exception e) { |
182 | 15 | outgoingMessageString = handleProcessMessageException(incomingMessageString, theMetadata, incomingMessageObject, e); |
183 | 10 | } catch (Error e) { |
184 | 10 | log.debug("Caught runtime exception of type {}, going to wrap it as HL7Exception and handle it", e.getClass()); |
185 | 10 | HL7Exception wrapped = new HL7Exception(e); |
186 | 10 | outgoingMessageString = handleProcessMessageException(incomingMessageString, theMetadata, incomingMessageObject, wrapped); |
187 | 658 | } |
188 | |
} |
189 | |
|
190 | 683 | log.debug("ApplicationRouterImpl sending message: {}", outgoingMessageString); |
191 | 683 | rawOutbound.debug(outgoingMessageString); |
192 | |
|
193 | 683 | return new String[]{outgoingMessageString, outgoingMessageCharset}; |
194 | |
} |
195 | |
|
196 | |
private String handleProcessMessageException(String incomingMessageString, Map<String, Object> theMetadata, Message incomingMessageObject, Exception e) throws HL7Exception { |
197 | |
String outgoingMessageString; |
198 | 25 | Segment inHeader = incomingMessageObject != null ? (Segment) incomingMessageObject.get("MSH") : null; |
199 | 25 | outgoingMessageString = logAndMakeErrorMessage(e, inHeader, myParser, myParser.getEncoding(incomingMessageString)); |
200 | 25 | if (outgoingMessageString != null && myExceptionHandler != null) { |
201 | 0 | outgoingMessageString = myExceptionHandler.processException(incomingMessageString, theMetadata, outgoingMessageString, e); |
202 | |
} |
203 | 25 | return outgoingMessageString; |
204 | |
} |
205 | |
|
206 | |
|
207 | |
|
208 | |
|
209 | |
|
210 | |
public boolean hasActiveBinding(AppRoutingData theRoutingData) { |
211 | 105 | boolean result = false; |
212 | 105 | ReceivingApplication<? extends Message> app = findDestination(null, theRoutingData); |
213 | 105 | if (app != null) { |
214 | 50 | result = true; |
215 | |
} |
216 | 105 | return result; |
217 | |
} |
218 | |
|
219 | |
|
220 | |
|
221 | |
|
222 | |
|
223 | |
|
224 | |
private <T extends Message> ReceivingApplication<T> findDestination(T theMessage, AppRoutingData theRoutingData) { |
225 | 773 | ReceivingApplication<? extends Message> result = null; |
226 | 1291 | for (int i = 0; i < myBindings.size() && result == null; i++) { |
227 | 518 | Binding binding = myBindings.get(i); |
228 | 518 | if (matches(theRoutingData, binding.routingData) && binding.active) { |
229 | 463 | if (theMessage == null || ((ReceivingApplication<T>)binding.application).canProcess(theMessage)) { |
230 | 458 | result = binding.application; |
231 | |
} |
232 | |
} |
233 | |
} |
234 | 773 | return (ReceivingApplication<T>)result; |
235 | |
} |
236 | |
|
237 | |
|
238 | |
|
239 | |
|
240 | |
|
241 | |
private Binding findBinding(ReceivingApplication<? extends Message> application) { |
242 | 5 | Binding result = null; |
243 | 10 | for (int i = 0; i < myBindings.size() && result == null; i++) { |
244 | 5 | Binding binding = myBindings.get(i); |
245 | 5 | if (application == binding.application) { |
246 | 5 | result = binding; |
247 | |
} |
248 | |
} |
249 | 5 | return result; |
250 | |
} |
251 | |
|
252 | |
|
253 | |
|
254 | |
|
255 | |
|
256 | |
private Binding findBinding(AppRoutingData theRoutingData) { |
257 | 20 | Binding result = null; |
258 | 30 | for (int i = 0; i < myBindings.size() && result == null; i++) { |
259 | 10 | Binding binding = myBindings.get(i); |
260 | 10 | if (theRoutingData.equals(binding.routingData)) { |
261 | 10 | result = binding; |
262 | |
} |
263 | |
} |
264 | 20 | return result; |
265 | |
} |
266 | |
|
267 | |
|
268 | |
|
269 | |
|
270 | |
|
271 | |
|
272 | |
public void bindApplication(AppRoutingData theRoutingData, ReceivingApplication<? extends Message> theApplication) { |
273 | 180 | Binding binding = new Binding(theRoutingData, true, theApplication); |
274 | 180 | myBindings.add(binding); |
275 | 180 | } |
276 | |
|
277 | |
|
278 | |
|
279 | |
|
280 | |
|
281 | |
public boolean unbindApplication(AppRoutingData theRoutingData) { |
282 | 15 | Binding b = findBinding(theRoutingData); |
283 | 15 | return b != null && myBindings.remove(b); |
284 | |
} |
285 | |
|
286 | |
|
287 | |
|
288 | |
|
289 | |
public boolean unbindApplication(ReceivingApplication<? extends Message> theApplication) { |
290 | 5 | Binding b = findBinding(theApplication); |
291 | 5 | return b != null && myBindings.remove(b); |
292 | |
} |
293 | |
|
294 | |
|
295 | |
|
296 | |
|
297 | |
public void disableBinding(AppRoutingData theRoutingData) { |
298 | 5 | Binding b = findBinding(theRoutingData); |
299 | 5 | if (b != null) { |
300 | 5 | b.active = false; |
301 | |
} |
302 | 5 | } |
303 | |
|
304 | |
|
305 | |
|
306 | |
|
307 | |
public void enableBinding(AppRoutingData theRoutingData) { |
308 | 0 | Binding b = findBinding(theRoutingData); |
309 | 0 | if (b != null) { |
310 | 0 | b.active = true; |
311 | |
} |
312 | 0 | } |
313 | |
|
314 | |
|
315 | |
|
316 | |
|
317 | |
public Parser getParser() { |
318 | 0 | return myParser; |
319 | |
} |
320 | |
|
321 | |
|
322 | |
|
323 | |
|
324 | |
public void setExceptionHandler(ReceivingApplicationExceptionHandler theExceptionHandler) { |
325 | 5 | this.myExceptionHandler = theExceptionHandler; |
326 | 5 | } |
327 | |
|
328 | |
|
329 | |
|
330 | |
|
331 | |
|
332 | |
|
333 | |
|
334 | |
|
335 | |
public static boolean matches(AppRoutingData theMessageData, |
336 | |
AppRoutingData theReferenceData) { |
337 | |
|
338 | 628 | boolean result = false; |
339 | |
|
340 | 628 | if (matches(theMessageData.getMessageType(), theReferenceData.getMessageType()) |
341 | 603 | && matches(theMessageData.getTriggerEvent(), theReferenceData.getTriggerEvent()) |
342 | 563 | && matches(theMessageData.getProcessingId(), theReferenceData.getProcessingId()) |
343 | 548 | && matches(theMessageData.getVersion(), theReferenceData.getVersion())) { |
344 | |
|
345 | 533 | result = true; |
346 | |
} |
347 | |
|
348 | 628 | return result; |
349 | |
} |
350 | |
|
351 | |
|
352 | |
private static boolean matches(String theMessageData, String theReferenceData) { |
353 | 2342 | boolean result = false; |
354 | |
|
355 | 2342 | String messageData = theMessageData; |
356 | 2342 | if (messageData == null) { |
357 | 10 | messageData = ""; |
358 | |
} |
359 | |
|
360 | 2342 | if (messageData.equals(theReferenceData) || |
361 | 1817 | theReferenceData.equals("*") || |
362 | 145 | Pattern.matches(theReferenceData, messageData)) { |
363 | 2247 | result = true; |
364 | |
} |
365 | 2342 | return result; |
366 | |
} |
367 | |
|
368 | |
|
369 | |
|
370 | |
|
371 | |
private <T extends Message> ReceivingApplication<T> findApplication(T theMessage) throws HL7Exception { |
372 | 668 | Terser t = new Terser(theMessage); |
373 | 668 | AppRoutingData msgData = |
374 | 668 | new AppRoutingDataImpl(t.get("/MSH-9-1"), t.get("/MSH-9-2"), t.get("/MSH-11-1"), t.get("/MSH-12")); |
375 | |
|
376 | 668 | ReceivingApplication<T> app = findDestination(theMessage, msgData); |
377 | |
|
378 | |
|
379 | 668 | if (app == null) { |
380 | 260 | app = (ReceivingApplication<T>)new DefaultApplication(); |
381 | |
} |
382 | |
|
383 | 668 | return app; |
384 | |
} |
385 | |
|
386 | |
|
387 | |
|
388 | |
|
389 | |
private static class Binding { |
390 | |
public AppRoutingData routingData; |
391 | |
public boolean active; |
392 | |
public ReceivingApplication<? extends Message> application; |
393 | |
|
394 | 180 | public Binding(AppRoutingData theRoutingData, boolean isActive, ReceivingApplication<? extends Message> theApplication) { |
395 | 180 | routingData = theRoutingData; |
396 | 180 | active = isActive; |
397 | 180 | application = theApplication; |
398 | 180 | } |
399 | |
} |
400 | |
|
401 | |
|
402 | |
|
403 | |
|
404 | |
|
405 | |
|
406 | |
|
407 | |
|
408 | |
|
409 | |
|
410 | |
|
411 | |
|
412 | |
|
413 | |
public String logAndMakeErrorMessage(Exception e, Segment inHeader, |
414 | |
Parser p, String encoding) throws HL7Exception { |
415 | |
|
416 | 35 | switch (myContext.getServerConfiguration().getApplicationExceptionPolicy()) { |
417 | |
case DO_NOT_RESPOND: |
418 | 10 | log.error("Application exception detected, not going to send a response back to the client", e); |
419 | 10 | return null; |
420 | |
case DEFAULT: |
421 | |
default: |
422 | 25 | log.error("Attempting to send error message to remote system.", e); |
423 | |
break; |
424 | |
} |
425 | |
|
426 | 25 | HL7Exception hl7e = e instanceof HL7Exception ? |
427 | |
(HL7Exception) e : |
428 | 0 | new HL7Exception(e.getMessage(), e); |
429 | |
|
430 | |
try { |
431 | 25 | Message out = hl7e.getResponseMessage(); |
432 | 25 | if (out == null) { |
433 | 20 | Message in = getInMessage(inHeader); |
434 | 20 | out = in.generateACK(defaultAcknowledgementMode, hl7e); |
435 | |
} |
436 | 25 | return encoding != null ? p.encode(out, encoding) : p.encode(out); |
437 | |
|
438 | 0 | } catch (IOException ioe) { |
439 | 0 | throw new HL7Exception( |
440 | |
"IOException creating error response message: " |
441 | 0 | + ioe.getMessage()); |
442 | |
} |
443 | |
|
444 | |
} |
445 | |
|
446 | |
private Message getInMessage(Segment inHeader) throws HL7Exception, IOException { |
447 | |
Message in; |
448 | 20 | if (inHeader != null) { |
449 | 20 | in = inHeader.getMessage(); |
450 | |
|
451 | 20 | DeepCopy.copy(inHeader, (Segment) in.get("MSH")); |
452 | |
} else { |
453 | 0 | in = Version.highestAvailableVersionOrDefault().newGenericMessage(myParser.getFactory()); |
454 | 0 | ((GenericMessage) in).initQuickstart("ACK", "", ""); |
455 | |
} |
456 | 20 | return in; |
457 | |
} |
458 | |
|
459 | |
|
460 | |
} |