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    }