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