001/* 002 * Created on 21-Apr-2004 003 */ 004package ca.uhn.hl7v2.protocol.impl; 005 006import java.util.ArrayList; 007import java.util.List; 008import java.util.Map; 009import java.util.regex.Pattern; 010 011import org.slf4j.Logger; 012import org.slf4j.LoggerFactory; 013 014import ca.uhn.hl7v2.HL7Exception; 015import ca.uhn.hl7v2.app.DefaultApplication; 016import ca.uhn.hl7v2.app.Responder; 017import ca.uhn.hl7v2.model.Message; 018import ca.uhn.hl7v2.model.Segment; 019import ca.uhn.hl7v2.parser.GenericParser; 020import ca.uhn.hl7v2.parser.Parser; 021import ca.uhn.hl7v2.protocol.ApplicationRouter; 022import ca.uhn.hl7v2.protocol.ReceivingApplication; 023import ca.uhn.hl7v2.protocol.Transportable; 024import ca.uhn.hl7v2.util.Terser; 025 026/** 027 * <p>A default implementation of <code>ApplicationRouter</code> </p> 028 * 029 * <p>Note that ParseChecker is used for each inbound message, iff the system 030 * property ca.uhn.hl7v2.protocol.impl.check_parse = "TRUE". </p> 031 * 032 * @author <a href="mailto:bryan.tripp@uhn.on.ca">Bryan Tripp</a> 033 * @version $Revision: 1.2 $ updated on $Date: 2009-09-01 00:22:23 $ by $Author: jamesagnew $ 034 */ 035public class ApplicationRouterImpl implements ApplicationRouter { 036 037 private static final Logger log = LoggerFactory.getLogger(ApplicationRouterImpl.class); 038 039 /** 040 * Key under which raw message text is stored in metadata Map sent to 041 * <code>ReceivingApplication</code>s. 042 */ 043 public static String RAW_MESSAGE_KEY = "raw-message"; 044 045 private List<Binding> myBindings; 046 private Parser myParser; 047 048 049 /** 050 * Creates an instance that uses a <code>GenericParser</code>. 051 */ 052 public ApplicationRouterImpl() { 053 init(new GenericParser()); 054 } 055 056 /** 057 * Creates an instance that uses the specified <code>Parser</code>. 058 * @param theParser the parser used for converting between Message and 059 * Transportable 060 */ 061 public ApplicationRouterImpl(Parser theParser) { 062 init(theParser); 063 } 064 065 private void init(Parser theParser) { 066 myBindings = new ArrayList<Binding>(20); 067 myParser = theParser; 068 } 069 070 /** 071 * @see ca.uhn.hl7v2.protocol.ApplicationRouter#processMessage(ca.uhn.hl7v2.protocol.Transportable) 072 */ 073 public Transportable processMessage(Transportable theMessage) throws HL7Exception { 074 String[] result = processMessage(theMessage.getMessage(), theMessage.getMetadata()); 075 Transportable response = new TransportableImpl(result[0]); 076 077 if (result[1] != null) { 078 response.getMetadata().put("MSH-18", result[1]); 079 } 080 081 return response; 082 } 083 084 /** 085 * Processes an incoming message string and returns the response message string. 086 * Message processing consists of parsing the message, finding an appropriate 087 * Application and processing the message with it, and encoding the response. 088 * Applications are chosen from among those registered using 089 * <code>bindApplication</code>. 090 * 091 * @return {text, charset} 092 */ 093 private String[] processMessage(String incomingMessageString, Map<String, Object> theMetadata) throws HL7Exception { 094 Logger rawOutbound = LoggerFactory.getLogger("ca.uhn.hl7v2.raw.outbound"); 095 Logger rawInbound = LoggerFactory.getLogger("ca.uhn.hl7v2.raw.inbound"); 096 097 log.info( "ApplicationRouterImpl got message: {}", incomingMessageString ); 098 rawInbound.info(incomingMessageString); 099 100 Message incomingMessageObject = null; 101 String outgoingMessageString = null; 102 String outgoingMessageCharset = null; 103 try { 104 incomingMessageObject = myParser.parse(incomingMessageString); 105 } 106 catch (HL7Exception e) { 107 outgoingMessageString = Responder.logAndMakeErrorMessage(e, myParser.getCriticalResponseData(incomingMessageString), myParser, myParser.getEncoding(incomingMessageString)); 108 } 109 110 if (outgoingMessageString == null) { 111 try { 112 //optionally check integrity of parse 113 String check = System.getProperty("ca.uhn.hl7v2.protocol.impl.check_parse"); 114 if (check != null && check.equals("TRUE")) { 115 ParseChecker.checkParse(incomingMessageString, incomingMessageObject, myParser); 116 } 117 118 //message validation (in terms of optionality, cardinality) would go here *** 119 120 ReceivingApplication app = findApplication(incomingMessageObject); 121 theMetadata.put(RAW_MESSAGE_KEY, incomingMessageString); 122 123 log.debug("Sending message to application: {}", app.toString()); 124 Message response = app.processMessage(incomingMessageObject, theMetadata); 125 126 //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 127 outgoingMessageString = myParser.encode(response, myParser.getEncoding(incomingMessageString)); 128 129 Terser t = new Terser(response); 130 outgoingMessageCharset = t.get("MSH-18"); 131 } 132 catch (Exception e) { 133 outgoingMessageString = Responder.logAndMakeErrorMessage(e, (Segment) incomingMessageObject.get("MSH"), myParser, myParser.getEncoding(incomingMessageString)); 134 } 135 } 136 137 log.info( "ApplicationRouterImpl sending message: {}", outgoingMessageString ); 138 rawOutbound.info(outgoingMessageString); 139 140 return new String[] {outgoingMessageString, outgoingMessageCharset}; 141 } 142 143 144 /** 145 * @see ca.uhn.hl7v2.protocol.ApplicationRouter#hasActiveBinding(ca.uhn.hl7v2.protocol.ApplicationRouter.AppRoutingData) 146 */ 147 public boolean hasActiveBinding(AppRoutingData theRoutingData) { 148 boolean result = false; 149 ReceivingApplication app = findDestination(theRoutingData); 150 if (app != null) { 151 result = true; 152 } 153 return result; 154 } 155 156 /** 157 * @param theRoutingData 158 * @return the application from the binding with a WILDCARD match, if one exists 159 */ 160 private ReceivingApplication findDestination(AppRoutingData theRoutingData) { 161 ReceivingApplication result = null; 162 for (int i = 0; i < myBindings.size() && result == null; i++) { 163 Binding binding = (Binding) myBindings.get(i); 164 if (matches(theRoutingData, binding.routingData) && binding.active) { 165 result = binding.application; 166 } 167 } 168 return result; 169 } 170 171 /** 172 * @param theRoutingData 173 * @return the binding with an EXACT match on routing data if one exists 174 */ 175 private Binding findBinding(AppRoutingData theRoutingData) { 176 Binding result = null; 177 for (int i = 0; i < myBindings.size() && result == null; i++) { 178 Binding binding = (Binding) myBindings.get(i); 179 if ( theRoutingData.equals(binding.routingData) ) { 180 result = binding; 181 } 182 } 183 return result; 184 185 } 186 187 /** 188 * @see ca.uhn.hl7v2.protocol.ApplicationRouter#bindApplication( 189 * ca.uhn.hl7v2.protocol.ApplicationRouter.AppRoutingData, ca.uhn.hl7v2.protocol.ReceivingApplication) 190 */ 191 public void bindApplication(AppRoutingData theRoutingData, ReceivingApplication theApplication) { 192 Binding binding = new Binding(theRoutingData, true, theApplication); 193 myBindings.add(binding); 194 } 195 196 /** 197 * @see ca.uhn.hl7v2.protocol.ApplicationRouter#disableBinding(ca.uhn.hl7v2.protocol.ApplicationRouter.AppRoutingData) 198 */ 199 public void disableBinding(AppRoutingData theRoutingData) { 200 Binding b = findBinding(theRoutingData); 201 if (b != null) { 202 b.active = false; 203 } 204 } 205 206 /** 207 * @see ca.uhn.hl7v2.protocol.ApplicationRouter#enableBinding(ca.uhn.hl7v2.protocol.ApplicationRouter.AppRoutingData) 208 */ 209 public void enableBinding(AppRoutingData theRoutingData) { 210 Binding b = findBinding(theRoutingData); 211 if (b != null) { 212 b.active = true; 213 } 214 } 215 216 /** 217 * @see ca.uhn.hl7v2.protocol.ApplicationRouter#getParser() 218 */ 219 public Parser getParser() { 220 return myParser; 221 } 222 223 /** 224 * @param theMessageData routing data related to a particular message 225 * @param theReferenceData routing data related to a binding, which may include 226 * wildcards 227 * @param exact if true, each field must match exactly 228 * @return true if the message data is consist with the reference data, ie all 229 * values either match or are wildcards in the reference 230 */ 231 public static boolean matches(AppRoutingData theMessageData, 232 AppRoutingData theReferenceData) { 233 234 boolean result = false; 235 236 ApplicationRouter.AppRoutingData ref = theReferenceData; 237 ApplicationRouter.AppRoutingData msg = theMessageData; 238 239 if (matches(msg.getMessageType(), ref.getMessageType()) 240 && matches(msg.getTriggerEvent(), ref.getTriggerEvent()) 241 && matches(msg.getProcessingId(), ref.getProcessingId()) 242 && matches(msg.getVersion(), ref.getVersion())) { 243 244 result = true; 245 } 246 247 return result; 248 } 249 250 //support method for matches(AppRoutingData theMessageData, AppRoutingData theReferenceData) 251 private static boolean matches(String theMessageData, String theReferenceData) { 252 boolean result = false; 253 if (theMessageData.equals(theReferenceData) || 254 theReferenceData.equals("*") || 255 Pattern.matches(theReferenceData, theMessageData)) { 256 result = true; 257 } 258 return result; 259 } 260 261 /** 262 * Returns the first Application that has been bound to messages of this type. 263 */ 264 private ReceivingApplication findApplication(Message theMessage) throws HL7Exception { 265 Terser t = new Terser(theMessage); 266 AppRoutingData msgData = 267 new AppRoutingDataImpl(t.get("/MSH-9-1"), t.get("/MSH-9-2"), t.get("/MSH-11-1"), t.get("/MSH-12")); 268 269 ReceivingApplication app = findDestination(msgData); 270 271 //have to send back an application reject if no apps available to process 272 if (app == null) 273 app = new AppWrapper(new DefaultApplication()); 274 return app; 275 } 276 277 /** 278 * A structure for bindings between routing data and applications. 279 */ 280 private static class Binding { 281 public AppRoutingData routingData; 282 public boolean active; 283 public ReceivingApplication application; 284 285 public Binding(AppRoutingData theRoutingData, boolean isActive, ReceivingApplication theApplication) { 286 routingData = theRoutingData; 287 active = isActive; 288 application = theApplication; 289 } 290 } 291 292}