Coverage Report - ca.uhn.hl7v2.protocol.impl.ApplicationRouterImpl
 
Classes in this File Line Coverage Branch Coverage Complexity
ApplicationRouterImpl
92%
119/129
84%
54/64
3.053
ApplicationRouterImpl$Binding
100%
5/5
N/A
3.053
 
 1  
 /*
 2  
  * Created on 21-Apr-2004
 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 org.slf4j.Logger;
 13  
 import org.slf4j.LoggerFactory;
 14  
 
 15  
 import ca.uhn.hl7v2.AcknowledgmentCode;
 16  
 import ca.uhn.hl7v2.HL7Exception;
 17  
 import ca.uhn.hl7v2.Version;
 18  
 import ca.uhn.hl7v2.app.DefaultApplication;
 19  
 import ca.uhn.hl7v2.model.GenericMessage;
 20  
 import ca.uhn.hl7v2.model.Message;
 21  
 import ca.uhn.hl7v2.model.Segment;
 22  
 import ca.uhn.hl7v2.parser.GenericParser;
 23  
 import ca.uhn.hl7v2.parser.Parser;
 24  
 import ca.uhn.hl7v2.protocol.ApplicationRouter;
 25  
 import ca.uhn.hl7v2.protocol.MetadataKeys;
 26  
 import ca.uhn.hl7v2.protocol.ReceivingApplication;
 27  
 import ca.uhn.hl7v2.protocol.ReceivingApplicationExceptionHandler;
 28  
 import ca.uhn.hl7v2.protocol.Transportable;
 29  
 import ca.uhn.hl7v2.util.DeepCopy;
 30  
 import ca.uhn.hl7v2.util.Terser;
 31  
 
 32  
 /**
 33  
  * <p>A default implementation of <code>ApplicationRouter</code> </p>  
 34  
  * 
 35  
  * @author <a href="mailto:bryan.tripp@uhn.on.ca">Bryan Tripp</a>
 36  
  * @version $Revision: 1.2 $ updated on $Date: 2009-09-01 00:22:23 $ by $Author: jamesagnew $
 37  
  */
 38  
 public class ApplicationRouterImpl implements ApplicationRouter {
 39  
 
 40  1
         private static final Logger log = LoggerFactory.getLogger(ApplicationRouterImpl.class);
 41  
     
 42  
     /**
 43  
      * Key under which raw message text is stored in metadata Map sent to 
 44  
      * <code>ReceivingApplication</code>s. 
 45  
      */
 46  1
     public static String RAW_MESSAGE_KEY = MetadataKeys.IN_RAW_MESSAGE; 
 47  
 
 48  
     private List<Binding> myBindings;
 49  
     private Parser myParser;
 50  
 
 51  
         private ReceivingApplicationExceptionHandler myExceptionHandler;
 52  
     
 53  
 
 54  
     /**
 55  
      * Creates an instance that uses a <code>GenericParser</code>. 
 56  
      */
 57  11
     public ApplicationRouterImpl() {
 58  11
         init(new GenericParser());
 59  11
     }
 60  
     
 61  
     /**
 62  
      * Creates an instance that uses the specified <code>Parser</code>. 
 63  
      * @param theParser the parser used for converting between Message and 
 64  
      *      Transportable
 65  
      */
 66  18
     public ApplicationRouterImpl(Parser theParser) {
 67  18
         init(theParser);
 68  18
     }
 69  
     
 70  
     private void init(Parser theParser) {
 71  29
         myBindings = new ArrayList<Binding>(20);
 72  29
         myParser = theParser;
 73  29
     }
 74  
 
 75  
     /** 
 76  
      * @see ca.uhn.hl7v2.protocol.ApplicationRouter#processMessage(ca.uhn.hl7v2.protocol.Transportable)
 77  
      */
 78  
     public Transportable processMessage(Transportable theMessage) throws HL7Exception {
 79  125
         String[] result = processMessage(theMessage.getMessage(), theMessage.getMetadata());
 80  124
         Transportable response = new TransportableImpl(result[0]);
 81  
         
 82  124
         if (result[1] != null) {
 83  1
             response.getMetadata().put(METADATA_KEY_MESSAGE_CHARSET, result[1]);
 84  
         }
 85  
         
 86  124
         return response;
 87  
     }
 88  
     
 89  
     /**
 90  
      * Processes an incoming message string and returns the response message string.
 91  
      * Message processing consists of parsing the message, finding an appropriate
 92  
      * Application and processing the message with it, and encoding the response.
 93  
      * Applications are chosen from among those registered using
 94  
      * <code>bindApplication</code>.  
 95  
      * 
 96  
      * @return {text, charset}
 97  
      */
 98  
     private String[] processMessage(String incomingMessageString, Map<String, Object> theMetadata) throws HL7Exception {
 99  125
         Logger rawOutbound = LoggerFactory.getLogger("ca.uhn.hl7v2.raw.outbound");
 100  125
         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  125
         log.debug( "ApplicationRouterImpl got message: {}", incomingMessageString );
 106  125
         rawInbound.debug(incomingMessageString);
 107  
         
 108  125
         Message incomingMessageObject = null;
 109  125
         String outgoingMessageString = null;
 110  125
         String outgoingMessageCharset = null;
 111  
         try {
 112  125
             incomingMessageObject = myParser.parse(incomingMessageString);
 113  
             
 114  122
             Terser inTerser = new Terser(incomingMessageObject);
 115  122
             theMetadata.put(MetadataKeys.IN_MESSAGE_CONTROL_ID, inTerser.get("/.MSH-10"));
 116  
             
 117  
         }
 118  3
         catch (HL7Exception e) {
 119  
                         try {
 120  3
                                  outgoingMessageString = logAndMakeErrorMessage(e, myParser.getCriticalResponseData(incomingMessageString), myParser, myParser.getEncoding(incomingMessageString));               
 121  2
                         } catch (HL7Exception e2) {
 122  2
                                  outgoingMessageString = null;
 123  1
                         }
 124  3
                 if (myExceptionHandler != null) {
 125  1
                         outgoingMessageString = myExceptionHandler.processException(incomingMessageString, theMetadata, outgoingMessageString, e);
 126  1
                         if (outgoingMessageString == null) {
 127  0
                                 throw new HL7Exception("Application exception handler may not return null");
 128  
                         }
 129  
                 }
 130  122
         }
 131  
         
 132  125
         if (outgoingMessageString == null) {
 133  
             try {
 134  
                 //optionally check integrity of parse
 135  123
                 String check = System.getProperty("ca.uhn.hl7v2.protocol.impl.check_parse");
 136  123
                 if (check != null && check.equals("TRUE")) {
 137  0
                     ParseChecker.checkParse(incomingMessageString, incomingMessageObject, myParser);
 138  
                 }
 139  
                 
 140  
                 //message validation (in terms of optionality, cardinality) would go here ***
 141  
                 
 142  123
                 ReceivingApplication app = findApplication(incomingMessageObject);
 143  122
                 theMetadata.put(RAW_MESSAGE_KEY, incomingMessageString);
 144  
                 
 145  122
                 log.debug("Sending message to application: {}", app.toString());
 146  122
                 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  119
                 outgoingMessageString = myParser.encode(response, myParser.getEncoding(incomingMessageString));
 150  
                 
 151  119
                 Terser t = new Terser(response);
 152  119
                 outgoingMessageCharset = t.get(METADATA_KEY_MESSAGE_CHARSET); 
 153  3
             } catch (Exception e) {
 154  3
                 outgoingMessageString = handleProcessMessageException(incomingMessageString, theMetadata, incomingMessageObject, e);
 155  1
             } catch (Error e) {
 156  1
                     log.debug("Caught runtime exception of type {}, going to wrap it as HL7Exception and handle it", e.getClass());
 157  1
                     HL7Exception wrapped = new HL7Exception(e);
 158  1
                     outgoingMessageString = handleProcessMessageException(incomingMessageString, theMetadata, incomingMessageObject, wrapped);
 159  121
             }
 160  
         }
 161  
         
 162  124
         log.debug( "ApplicationRouterImpl sending message: {}", outgoingMessageString );
 163  124
         rawOutbound.debug(outgoingMessageString);
 164  
         
 165  124
         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  4
                 Segment inHeader = incomingMessageObject != null ? (Segment) incomingMessageObject.get("MSH") : null;
 171  4
                 outgoingMessageString = logAndMakeErrorMessage(e, inHeader, myParser, myParser.getEncoding(incomingMessageString));
 172  3
                 if (myExceptionHandler != null) {
 173  0
                         outgoingMessageString = myExceptionHandler.processException(incomingMessageString, theMetadata, outgoingMessageString, e);
 174  
                 }
 175  3
                 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  17
         boolean result = false;
 184  17
         ReceivingApplication app = findDestination(null, theRoutingData);
 185  17
         if (app != null) {
 186  8
             result = true;
 187  
         }
 188  17
         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  139
         ReceivingApplication result = null;
 197  229
         for (int i = 0; i < myBindings.size() && result == null; i++) {
 198  90
             Binding binding = (Binding) myBindings.get(i);
 199  90
             if (matches(theRoutingData, binding.routingData) && binding.active) {
 200  79
                     if (theMessage == null || binding.application.canProcess(theMessage)) {
 201  78
                             result = binding.application;
 202  
                     }
 203  
             }
 204  
         }
 205  139
         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  1
         Binding result = null;
 214  2
         for (int i = 0; i < myBindings.size() && result == null; i++) {
 215  1
             Binding binding = (Binding) myBindings.get(i);
 216  1
             if ( theRoutingData.equals(binding.routingData) ) {
 217  1
                 result = binding;
 218  
             }
 219  
         }
 220  1
         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  22
         Binding binding = new Binding(theRoutingData, true, theApplication);
 230  22
         myBindings.add(binding);
 231  22
     }
 232  
 
 233  
     /** 
 234  
      * @see ca.uhn.hl7v2.protocol.ApplicationRouter#disableBinding(ca.uhn.hl7v2.protocol.ApplicationRouter.AppRoutingData)
 235  
      */
 236  
     public void disableBinding(AppRoutingData theRoutingData) {
 237  1
         Binding b = findBinding(theRoutingData);
 238  1
         if (b != null) {
 239  1
             b.active = false;
 240  
         }
 241  1
     }
 242  
 
 243  
     /** 
 244  
      * @see ca.uhn.hl7v2.protocol.ApplicationRouter#enableBinding(ca.uhn.hl7v2.protocol.ApplicationRouter.AppRoutingData)
 245  
      */
 246  
     public void enableBinding(AppRoutingData theRoutingData) {
 247  0
         Binding b = findBinding(theRoutingData);
 248  0
         if (b != null) {
 249  0
             b.active = true;
 250  
         }
 251  0
     }
 252  
 
 253  
     /**
 254  
      * @see ca.uhn.hl7v2.protocol.ApplicationRouter#getParser()
 255  
      */
 256  
     public Parser getParser() {
 257  0
         return myParser;
 258  
     }
 259  
     
 260  
     /**
 261  
      * {@inheritDoc}
 262  
      */
 263  
     public void setExceptionHandler(ReceivingApplicationExceptionHandler theExceptionHandler) {
 264  1
             this.myExceptionHandler = theExceptionHandler;
 265  1
     }
 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  112
         boolean result = false;
 279  
         
 280  112
         ApplicationRouter.AppRoutingData ref = theReferenceData;
 281  112
         ApplicationRouter.AppRoutingData msg = theMessageData;
 282  
         
 283  112
         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  93
             result = true;
 289  
         }
 290  
         
 291  112
         return result;        
 292  
     }
 293  
 
 294  
     //support method for matches(AppRoutingData theMessageData, AppRoutingData theReferenceData)
 295  
     private static boolean matches(String theMessageData, String theReferenceData) {
 296  414
         boolean result = false;
 297  
 
 298  414
         String messageData = theMessageData;
 299  414
         if (messageData == null) {
 300  2
                 messageData = "";
 301  
         }
 302  
         
 303  414
                 if (messageData.equals(theReferenceData) || 
 304  
                 theReferenceData.equals("*") || 
 305  
                 Pattern.matches(theReferenceData, messageData)) {
 306  395
             result = true;
 307  
         }
 308  414
         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  123
         Terser t = new Terser(theMessage);
 316  122
         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  122
         ReceivingApplication app = findDestination(theMessage, msgData);
 320  
         
 321  
         //have to send back an application reject if no apps available to process
 322  122
         if (app == null) {
 323  52
             app = new DefaultApplication();
 324  
         }
 325  
         
 326  122
         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  22
         public Binding(AppRoutingData theRoutingData, boolean isActive, ReceivingApplication theApplication) {
 338  22
             routingData = theRoutingData;
 339  22
             active = isActive;
 340  22
             application = theApplication;
 341  22
         }
 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  5
                 log.error("Attempting to send error message to remote system.", e);
 356  
                 
 357  5
                 HL7Exception hl7e = e instanceof HL7Exception ? 
 358  
                                 (HL7Exception) e :
 359  
                                 new HL7Exception(e.getMessage(), e);
 360  
 
 361  
                 // create error message ...
 362  5
                 String errorMessage = null;
 363  
                 try {
 364  
                         
 365  
                         Message out;
 366  
                         Message in;
 367  5
                         if (inHeader != null) {
 368  4
                                 in = inHeader.getMessage();
 369  
                                 // the message may be a dummy message, whose MSH segment is incomplete
 370  4
                                 DeepCopy.copy(inHeader, (Segment)in.get("MSH"));                                
 371  
                         } else {
 372  1
                                 in = Version.highestAvailableVersionOrDefault().newGenericMessage(myParser.getFactory());
 373  1
                                 ((GenericMessage)in).initQuickstart("ACK", "", "");
 374  
                         }
 375  
                         
 376  5
                         out = in.generateACK(AcknowledgmentCode.AE, hl7e);
 377  
                         
 378  5
                         if (encoding != null) {
 379  4
                                 errorMessage = p.encode(out, encoding);
 380  
                         } else {
 381  1
                                 errorMessage = p.encode(out);
 382  
                         }
 383  
 
 384  0
                 } catch (IOException ioe) {
 385  0
                         throw new HL7Exception(
 386  
                                         "IOException creating error response message: "
 387  
                                                         + ioe.getMessage());
 388  4
                 }
 389  4
                 return errorMessage;
 390  
         }
 391  
 
 392  
 
 393  
     
 394  
 }