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}