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