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    }