001/* 002 * Created on 21-Apr-2004 003 */ 004package ca.uhn.hl7v2.protocol.impl; 005 006import java.io.IOException; 007import java.util.ArrayList; 008import java.util.List; 009import java.util.Map; 010import java.util.regex.Pattern; 011 012import ca.uhn.hl7v2.AcknowledgmentCode; 013import ca.uhn.hl7v2.HL7Exception; 014import ca.uhn.hl7v2.HapiContext; 015import ca.uhn.hl7v2.Version; 016import ca.uhn.hl7v2.app.DefaultApplication; 017import ca.uhn.hl7v2.model.GenericMessage; 018import ca.uhn.hl7v2.model.Message; 019import ca.uhn.hl7v2.model.Segment; 020import ca.uhn.hl7v2.parser.GenericParser; 021import ca.uhn.hl7v2.parser.Parser; 022import ca.uhn.hl7v2.protocol.*; 023import ca.uhn.hl7v2.util.DeepCopy; 024import ca.uhn.hl7v2.util.Terser; 025import org.slf4j.Logger; 026import org.slf4j.LoggerFactory; 027 028/** 029 * <p>A default implementation of <code>ApplicationRouter</code> </p> 030 * 031 * @author <a href="mailto:bryan.tripp@uhn.on.ca">Bryan Tripp</a> 032 * @version $Revision: 1.2 $ updated on $Date: 2009-09-01 00:22:23 $ by $Author: jamesagnew $ 033 */ 034public class ApplicationRouterImpl implements ApplicationRouter { 035 036 /** 037 * The default acknowledgment code used in MSA-1 when generating a NAK (negative ACK) message 038 * as a result of a processing exception. 039 */ 040 public static final AcknowledgmentCode DEFAULT_EXCEPTION_ACKNOWLEDGEMENT_CODE = AcknowledgmentCode.AE; 041 042 private static final Logger log = LoggerFactory.getLogger(ApplicationRouterImpl.class); 043 044 /** 045 * Key under which raw message text is stored in metadata Map sent to 046 * <code>ReceivingApplication</code>s. 047 */ 048 public static final String RAW_MESSAGE_KEY = MetadataKeys.IN_RAW_MESSAGE; 049 050 private List<Binding> myBindings; 051 private Parser myParser; 052 private ReceivingApplicationExceptionHandler myExceptionHandler; 053 private HapiContext myContext; 054 private AcknowledgmentCode defaultAcknowledgementMode = DEFAULT_EXCEPTION_ACKNOWLEDGEMENT_CODE; 055 056 057 /** 058 * Creates an instance that uses a <code>GenericParser</code>. 059 */ 060 @Deprecated 061 public ApplicationRouterImpl() { 062 this(new GenericParser()); 063 } 064 065 /** 066 * Creates an instance that uses the specified <code>Parser</code>. 067 * 068 * @param theParser the parser used for converting between Message and 069 * Transportable 070 */ 071 public ApplicationRouterImpl(Parser theParser) { 072 this(theParser.getHapiContext(), theParser); 073 } 074 075 public ApplicationRouterImpl(HapiContext theContext) { 076 this(theContext, theContext.getGenericParser()); 077 } 078 079 /** 080 * Creates an instance that uses the specified <code>Parser</code>. 081 * 082 * @param theContext HAPI context 083 * @param theParser the parser used for converting between Message and 084 * Transportable 085 * @deprecated define parser over context 086 */ 087 public ApplicationRouterImpl(HapiContext theContext, Parser theParser) { 088 init(theParser); 089 myContext = theContext; 090 } 091 092 private void init(Parser theParser) { 093 myBindings = new ArrayList<Binding>(20); 094 myParser = theParser; 095 } 096 097 public void setDefaultAcknowledgementMode(AcknowledgmentCode defaultAcknowledgementMode) { 098 this.defaultAcknowledgementMode = defaultAcknowledgementMode; 099 } 100 101 /** 102 * @see ca.uhn.hl7v2.protocol.ApplicationRouter#processMessage(ca.uhn.hl7v2.protocol.Transportable) 103 */ 104 public Transportable processMessage(Transportable theMessage) throws HL7Exception { 105 String[] result = processMessage(theMessage.getMessage(), theMessage.getMetadata()); 106 Transportable response = new TransportableImpl(result[0]); 107 108 if (result[1] != null) { 109 response.getMetadata().put(METADATA_KEY_MESSAGE_CHARSET, result[1]); 110 } 111 112 return response; 113 } 114 115 /** 116 * Processes an incoming message string and returns the response message string. 117 * Message processing consists of parsing the message, finding an appropriate 118 * Application and processing the message with it, and encoding the response. 119 * Applications are chosen from among those registered using 120 * <code>bindApplication</code>. 121 * 122 * @return {text, charset} 123 */ 124 private String[] processMessage(String incomingMessageString, Map<String, Object> theMetadata) throws HL7Exception { 125 Logger rawOutbound = LoggerFactory.getLogger("ca.uhn.hl7v2.raw.outbound"); 126 Logger rawInbound = LoggerFactory.getLogger("ca.uhn.hl7v2.raw.inbound"); 127 128 // TODO: add a way to register an application handler and 129 // invoke it any time something goes wrong 130 131 log.debug("ApplicationRouterImpl got message: {}", incomingMessageString); 132 rawInbound.debug(incomingMessageString); 133 134 Message incomingMessageObject = null; 135 String outgoingMessageString = null; 136 String outgoingMessageCharset = null; 137 try { 138 incomingMessageObject = myParser.parse(incomingMessageString); 139 140 Terser inTerser = new Terser(incomingMessageObject); 141 theMetadata.put(MetadataKeys.IN_MESSAGE_CONTROL_ID, inTerser.get("/.MSH-10")); 142 143 } catch (HL7Exception e) { 144 log.debug("Exception parsing incoming message", e); 145 try { 146 outgoingMessageString = logAndMakeErrorMessage(e, myParser.getCriticalResponseData(incomingMessageString), myParser, myParser.getEncoding(incomingMessageString)); 147 } catch (HL7Exception e2) { 148 log.error("Exception occurred while logging parse failure", e2); 149 outgoingMessageString = null; 150 } 151 if (myExceptionHandler != null) { 152 outgoingMessageString = myExceptionHandler.processException(incomingMessageString, theMetadata, outgoingMessageString, e); 153 if (outgoingMessageString == null) { 154 throw new HL7Exception("Application exception handler may not return null"); 155 } 156 } 157 } 158 159 // At this point, no exception has occurred and the message is processed normally 160 if (outgoingMessageString == null) { 161 try { 162 //optionally check integrity of parse 163 String check = System.getProperty("ca.uhn.hl7v2.protocol.impl.check_parse"); 164 if (check != null && check.equals("TRUE")) { 165 ParseChecker.checkParse(incomingMessageString, incomingMessageObject, myParser); 166 } 167 168 //message validation (in terms of optionality, cardinality) would go here *** 169 170 ReceivingApplication<Message> app = findApplication(incomingMessageObject); 171 theMetadata.put(RAW_MESSAGE_KEY, incomingMessageString); 172 173 log.debug("Sending message to application: {}", app.toString()); 174 Message response = app.processMessage(incomingMessageObject, theMetadata); 175 176 //Here we explicitly use the same encoding as that of the inbound message - this is important with GenericParser, which might use a different encoding by default 177 outgoingMessageString = myParser.encode(response, myParser.getEncoding(incomingMessageString)); 178 179 Terser t = new Terser(response); 180 outgoingMessageCharset = t.get(METADATA_KEY_MESSAGE_CHARSET); 181 } catch (Exception e) { 182 outgoingMessageString = handleProcessMessageException(incomingMessageString, theMetadata, incomingMessageObject, e); 183 } catch (Error e) { 184 log.debug("Caught runtime exception of type {}, going to wrap it as HL7Exception and handle it", e.getClass()); 185 HL7Exception wrapped = new HL7Exception(e); 186 outgoingMessageString = handleProcessMessageException(incomingMessageString, theMetadata, incomingMessageObject, wrapped); 187 } 188 } 189 190 log.debug("ApplicationRouterImpl sending message: {}", outgoingMessageString); 191 rawOutbound.debug(outgoingMessageString); 192 193 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 Segment inHeader = incomingMessageObject != null ? (Segment) incomingMessageObject.get("MSH") : null; 199 outgoingMessageString = logAndMakeErrorMessage(e, inHeader, myParser, myParser.getEncoding(incomingMessageString)); 200 if (outgoingMessageString != null && myExceptionHandler != null) { 201 outgoingMessageString = myExceptionHandler.processException(incomingMessageString, theMetadata, outgoingMessageString, e); 202 } 203 return outgoingMessageString; 204 } 205 206 207 /** 208 * @see ca.uhn.hl7v2.protocol.ApplicationRouter#hasActiveBinding(ca.uhn.hl7v2.protocol.ApplicationRouter.AppRoutingData) 209 */ 210 public boolean hasActiveBinding(AppRoutingData theRoutingData) { 211 boolean result = false; 212 ReceivingApplication<? extends Message> app = findDestination(null, theRoutingData); 213 if (app != null) { 214 result = true; 215 } 216 return result; 217 } 218 219 /** 220 * @param theMessage message for which a destination is looked up 221 * @param theRoutingData routing data 222 * @return the application from the binding with a WILDCARD match, if one exists 223 */ 224 private <T extends Message> ReceivingApplication<T> findDestination(T theMessage, AppRoutingData theRoutingData) { 225 ReceivingApplication<? extends Message> result = null; 226 for (int i = 0; i < myBindings.size() && result == null; i++) { 227 Binding binding = myBindings.get(i); 228 if (matches(theRoutingData, binding.routingData) && binding.active) { 229 if (theMessage == null || ((ReceivingApplication<T>)binding.application).canProcess(theMessage)) { 230 result = binding.application; 231 } 232 } 233 } 234 return (ReceivingApplication<T>)result; 235 } 236 237 /** 238 * @param application receiving application 239 * @return the binding that forwards to the receiving application 240 */ 241 private Binding findBinding(ReceivingApplication<? extends Message> application) { 242 Binding result = null; 243 for (int i = 0; i < myBindings.size() && result == null; i++) { 244 Binding binding = myBindings.get(i); 245 if (application == binding.application) { 246 result = binding; 247 } 248 } 249 return result; 250 } 251 252 /** 253 * @param theRoutingData routing data 254 * @return the binding with an EXACT match on routing data if one exists 255 */ 256 private Binding findBinding(AppRoutingData theRoutingData) { 257 Binding result = null; 258 for (int i = 0; i < myBindings.size() && result == null; i++) { 259 Binding binding = myBindings.get(i); 260 if (theRoutingData.equals(binding.routingData)) { 261 result = binding; 262 } 263 } 264 return result; 265 } 266 267 268 /** 269 * @see ca.uhn.hl7v2.protocol.ApplicationRouter#bindApplication( 270 *ca.uhn.hl7v2.protocol.ApplicationRouter.AppRoutingData, ca.uhn.hl7v2.protocol.ReceivingApplication) 271 */ 272 public void bindApplication(AppRoutingData theRoutingData, ReceivingApplication<? extends Message> theApplication) { 273 Binding binding = new Binding(theRoutingData, true, theApplication); 274 myBindings.add(binding); 275 } 276 277 /** 278 * 279 * @see ca.uhn.hl7v2.protocol.ApplicationRouter#unbindApplication(ca.uhn.hl7v2.protocol.ApplicationRouter.AppRoutingData) 280 */ 281 public boolean unbindApplication(AppRoutingData theRoutingData) { 282 Binding b = findBinding(theRoutingData); 283 return b != null && myBindings.remove(b); 284 } 285 286 /** 287 * @see ca.uhn.hl7v2.protocol.ApplicationRouter#unbindApplication(ca.uhn.hl7v2.protocol.ReceivingApplication) 288 */ 289 public boolean unbindApplication(ReceivingApplication<? extends Message> theApplication) { 290 Binding b = findBinding(theApplication); 291 return b != null && myBindings.remove(b); 292 } 293 294 /** 295 * @see ca.uhn.hl7v2.protocol.ApplicationRouter#disableBinding(ca.uhn.hl7v2.protocol.ApplicationRouter.AppRoutingData) 296 */ 297 public void disableBinding(AppRoutingData theRoutingData) { 298 Binding b = findBinding(theRoutingData); 299 if (b != null) { 300 b.active = false; 301 } 302 } 303 304 /** 305 * @see ca.uhn.hl7v2.protocol.ApplicationRouter#enableBinding(ca.uhn.hl7v2.protocol.ApplicationRouter.AppRoutingData) 306 */ 307 public void enableBinding(AppRoutingData theRoutingData) { 308 Binding b = findBinding(theRoutingData); 309 if (b != null) { 310 b.active = true; 311 } 312 } 313 314 /** 315 * @see ca.uhn.hl7v2.protocol.ApplicationRouter#getParser() 316 */ 317 public Parser getParser() { 318 return myParser; 319 } 320 321 /** 322 * {@inheritDoc} 323 */ 324 public void setExceptionHandler(ReceivingApplicationExceptionHandler theExceptionHandler) { 325 this.myExceptionHandler = theExceptionHandler; 326 } 327 328 /** 329 * @param theMessageData routing data related to a particular message 330 * @param theReferenceData routing data related to a binding, which may include 331 * wildcards 332 * @return true if the message data is consist with the reference data, ie all 333 * values either match or are wildcards in the reference 334 */ 335 public static boolean matches(AppRoutingData theMessageData, 336 AppRoutingData theReferenceData) { 337 338 boolean result = false; 339 340 if (matches(theMessageData.getMessageType(), theReferenceData.getMessageType()) 341 && matches(theMessageData.getTriggerEvent(), theReferenceData.getTriggerEvent()) 342 && matches(theMessageData.getProcessingId(), theReferenceData.getProcessingId()) 343 && matches(theMessageData.getVersion(), theReferenceData.getVersion())) { 344 345 result = true; 346 } 347 348 return result; 349 } 350 351 //support method for matches(AppRoutingData theMessageData, AppRoutingData theReferenceData) 352 private static boolean matches(String theMessageData, String theReferenceData) { 353 boolean result = false; 354 355 String messageData = theMessageData; 356 if (messageData == null) { 357 messageData = ""; 358 } 359 360 if (messageData.equals(theReferenceData) || 361 theReferenceData.equals("*") || 362 Pattern.matches(theReferenceData, messageData)) { 363 result = true; 364 } 365 return result; 366 } 367 368 /** 369 * Returns the first Application that has been bound to messages of this type. 370 */ 371 private <T extends Message> ReceivingApplication<T> findApplication(T theMessage) throws HL7Exception { 372 Terser t = new Terser(theMessage); 373 AppRoutingData msgData = 374 new AppRoutingDataImpl(t.get("/MSH-9-1"), t.get("/MSH-9-2"), t.get("/MSH-11-1"), t.get("/MSH-12")); 375 376 ReceivingApplication<T> app = findDestination(theMessage, msgData); 377 378 //have to send back an application reject if no apps available to process 379 if (app == null) { 380 app = (ReceivingApplication<T>)new DefaultApplication(); 381 } 382 383 return app; 384 } 385 386 /** 387 * A structure for bindings between routing data and applications. 388 */ 389 private static class Binding { 390 public AppRoutingData routingData; 391 public boolean active; 392 public ReceivingApplication<? extends Message> application; 393 394 public Binding(AppRoutingData theRoutingData, boolean isActive, ReceivingApplication<? extends Message> theApplication) { 395 routingData = theRoutingData; 396 active = isActive; 397 application = theApplication; 398 } 399 } 400 401 /** 402 * Logs the given exception and creates an error message to send to the 403 * remote system. 404 * 405 * @param e exception 406 * @param inHeader MSH segment of incoming message 407 * @param p parser to be used 408 * @param encoding The encoding for the error message. If <code>null</code>, uses 409 * default encoding 410 * @return error message as string 411 * @throws ca.uhn.hl7v2.HL7Exception if an error occured during generation of the error message 412 */ 413 public String logAndMakeErrorMessage(Exception e, Segment inHeader, 414 Parser p, String encoding) throws HL7Exception { 415 416 switch (myContext.getServerConfiguration().getApplicationExceptionPolicy()) { 417 case DO_NOT_RESPOND: 418 log.error("Application exception detected, not going to send a response back to the client", e); 419 return null; 420 case DEFAULT: 421 default: 422 log.error("Attempting to send error message to remote system.", e); 423 break; 424 } 425 426 HL7Exception hl7e = e instanceof HL7Exception ? 427 (HL7Exception) e : 428 new HL7Exception(e.getMessage(), e); 429 430 try { 431 Message out = hl7e.getResponseMessage(); 432 if (out == null) { 433 Message in = getInMessage(inHeader); 434 out = in.generateACK(defaultAcknowledgementMode, hl7e); 435 } 436 return encoding != null ? p.encode(out, encoding) : p.encode(out); 437 438 } catch (IOException ioe) { 439 throw new HL7Exception( 440 "IOException creating error response message: " 441 + ioe.getMessage()); 442 } 443 444 } 445 446 private Message getInMessage(Segment inHeader) throws HL7Exception, IOException { 447 Message in; 448 if (inHeader != null) { 449 in = inHeader.getMessage(); 450 // the message may be a dummy message, whose MSH segment is incomplete 451 DeepCopy.copy(inHeader, (Segment) in.get("MSH")); 452 } else { 453 in = Version.highestAvailableVersionOrDefault().newGenericMessage(myParser.getFactory()); 454 ((GenericMessage) in).initQuickstart("ACK", "", ""); 455 } 456 return in; 457 } 458 459 460}