001 /*
002 * Created on 21-Apr-2004
003 */
004 package ca.uhn.hl7v2.protocol.impl;
005
006 import java.util.ArrayList;
007 import java.util.List;
008 import java.util.Map;
009 import java.util.regex.Pattern;
010
011 import org.slf4j.Logger;
012 import org.slf4j.LoggerFactory;
013
014 import ca.uhn.hl7v2.HL7Exception;
015 import ca.uhn.hl7v2.app.DefaultApplication;
016 import ca.uhn.hl7v2.app.Responder;
017 import ca.uhn.hl7v2.model.Message;
018 import ca.uhn.hl7v2.model.Segment;
019 import ca.uhn.hl7v2.parser.GenericParser;
020 import ca.uhn.hl7v2.parser.Parser;
021 import ca.uhn.hl7v2.protocol.ApplicationRouter;
022 import ca.uhn.hl7v2.protocol.ReceivingApplication;
023 import ca.uhn.hl7v2.protocol.Transportable;
024 import 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 */
035 public 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 }