001    package ca.uhn.hl7v2.validation.app;
002    
003    import ca.uhn.hl7v2.util.Terser;
004    import ca.uhn.hl7v2.model.*;
005    import ca.uhn.hl7v2.parser.PipeParser;
006    import ca.uhn.hl7v2.HL7Exception;
007    import org.apache.commons.logging.*;
008    import org.apache.log4j.NDC;
009    
010    /**
011     * An application intended for testing messages.  The intended use is to route a copy 
012     * of (selected) messages to a test application, which identifies and acts on problems independently 
013     * of the normal error acknowledgement path (for example by notifying an administrator).  
014     * This makes the most sense when used within an interface engine, for example if the 
015     * sending nor receiving system use HAPI, but it is desired to route messages to HAPI in 
016     * parallel so that they can be fully validated.  
017     * @author Bryan Tripp
018     */
019    public abstract class TestApplication implements ca.uhn.hl7v2.app.Application {
020           
021        private PipeParser parser; 
022        
023        /** Creates a new instance of TestApplication */
024        public TestApplication() {
025            parser = new PipeParser();
026        }
027        
028        /**
029         * Returns true if this Application wishes to accept the message.  By returning
030         * true, this Application declares itself the recipient of the message, accepts
031         * responsibility for it, and must be able to respond appropriately to the sending system.
032         */
033        public abstract boolean canProcess(Message in); 
034    
035        
036        /** 
037         * <p>Calls test(Message in), routes resulting exceptions to configured destinations, and 
038         * returns an ack (which should not normally be used since the test app is intended 
039         * to operate in parallel with system-to-system communication).  </p>
040         * <p>Notification routing is performed using log4j, so you need appropriate settings in a log4j 
041         * config file (by default, ./log4j.properties).  Different types of exceptions 
042         * are all given the same severity (ERROR) but they have different loggers, based 
043         * on the exception class name.  Specifically, the loggers will be named 
044         * ca.uhn.hl7v2.validation.error.{exception class name}.  For example: 
045         * "ca.uhn.hl7v2.validation.error.DataTypeException".  Note that this allows default 
046         * settings for all validation errors using the logger "ca.uhn.hl7v2.validation.error".  
047         * The intent is for different exceptions to result in different actions, e.g. a 
048         * ProfileNotHL7CompliantException should probably just be logged or ignored, while a
049         * ProfileNotFollowedException should probably be emailed to an administrator. </p>
050         */
051        public Message processMessage(Message in) throws HL7Exception {
052            String context = null;
053            try {
054                context = this.parser.encode(in);
055            } catch (HL7Exception e) {
056                context = "message not encodable";
057            }
058            //update logging context with message text
059            NDC.push(context);
060            
061            LogFactory.getLog("ca.uhn.hl7v2.validation.error").info("Testing message");
062            
063            HL7Exception[] problems = test(in);        
064            sendNotifications(problems);
065            
066            NDC.pop();
067            
068            Message ack = null;
069            try {
070                ack = ca.uhn.hl7v2.app.DefaultApplication.makeACK((Segment) in.get("MSH"));
071                addProblemsToACK(ack, problems);
072            } catch (java.io.IOException e) {
073                throw new HL7Exception(e);
074            }
075            return ack;
076        }
077        
078        /**
079         * <p>Send notification of problems to specified destinations (e.g. log file, email).  
080         */
081        private void sendNotifications(HL7Exception[] problems) {
082            for (int i = 0; i < problems.length; i++) {
083                String exName = problems[i].getClass().getName();
084                String logName = "ca.uhn.hl7v2.validation.error" + exName.substring(exName.lastIndexOf('.'));
085                LogFactory.getLog(logName).error("message validation failure", problems[i]);
086            }
087        }
088        
089        private void addProblemsToACK(Message ack, HL7Exception[] problems) throws HL7Exception {
090            Terser t = new Terser(ack);
091            
092            if (problems.length > 0) {
093                t.set("MSA-1", "AE");        
094                t.set("MSA-3", "Errors were encountered while testing the message");
095            }
096            
097            Segment err = (Segment) ack.get("ERR");
098            for (int i = 0; i < problems.length; i++) {
099                // problems[i].populate(err); FIXME: broken! needs database
100            }
101        }
102        
103        /**
104         * Tests the message in some way (as defined by implementing class).  
105         * @return exceptions that describe any identified problems with the message 
106         * @throws HL7Exception if the message can't be tested (not for errors disovered
107         *      during testing)
108         */
109        public abstract HL7Exception[] test(Message in) throws HL7Exception;
110            
111    }