001    /**
002    The contents of this file are subject to the Mozilla Public License Version 1.1 
003    (the "License"); you may not use this file except in compliance with the License. 
004    You may obtain a copy of the License at http://www.mozilla.org/MPL/ 
005    Software distributed under the License is distributed on an "AS IS" basis, 
006    WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the 
007    specific language governing rights and limitations under the License. 
008    
009    The Original Code is "HL7Service.java".  Description: 
010    "Accepts incoming TCP/IP connections and creates Connection objects" 
011    
012    The Initial Developer of the Original Code is University Health Network. Copyright (C) 
013    2001.  All Rights Reserved. 
014    
015    Contributor(s): Kyle Buza 
016    
017    Alternatively, the contents of this file may be used under the terms of the 
018    GNU General Public License (the  ???GPL???), in which case the provisions of the GPL are 
019    applicable instead of those above.  If you wish to allow use of your version of this 
020    file only under the terms of the GPL and not to allow others to use your version 
021    of this file under the MPL, indicate your decision by deleting  the provisions above 
022    and replace  them with the notice and other provisions required by the GPL License.  
023    If you do not delete the provisions above, a recipient may use your version of 
024    this file under either the MPL or the GPL. 
025    
026    */
027    
028    package ca.uhn.hl7v2.app;
029    
030    import java.io.*;
031    import java.util.StringTokenizer;
032    import java.util.NoSuchElementException;
033    import java.util.Vector;
034    import java.util.ArrayList;
035    import java.util.Iterator;
036    import ca.uhn.hl7v2.llp.LowerLayerProtocol;
037    import ca.uhn.hl7v2.HL7Exception;
038    import ca.uhn.hl7v2.parser.*;
039    
040    /**
041     * <p>An HL7 service.  Accepts incoming TCP/IP connections and creates Connection 
042     * objects.  Uses a single MessageTypeRouter object (for all Connections) to define 
043     * the Applications to which message are sent.  To configure, use registerApplication() 
044     * or loadApplicationsFromFile().  </p>
045     * </p>A separate thread looks for Connections that have been closed (locally or remotely) 
046     * and discards them. </p>
047     * @author Bryan Tripp
048     */
049    public abstract class HL7Service implements Runnable {
050    
051        private Vector connections;
052        private boolean keepRunning;
053        protected Parser parser;
054        protected LowerLayerProtocol llp;
055        private MessageTypeRouter router;
056        private ArrayList listeners;
057    
058        /** Creates a new instance of Server */
059        public HL7Service(Parser parser, LowerLayerProtocol llp) {
060            connections = new Vector();
061            listeners = new ArrayList();
062            this.parser = parser;
063            this.llp = llp;
064            this.router = new MessageTypeRouter();
065        }
066    
067        /** 
068         * Implemented by subclasses to accept remote connections, create Connection
069         * objects, and call <code>newConnection()</code>.  Must stop running when 
070         * <code>keepRunning()</code> returns false, and clean up all resources before
071         * exiting.  If this method quits because of an exception, it must call 
072         * HL7Service.stop() before exiting.  
073         */
074        public abstract void run();
075    
076        /** 
077         * Returns true if the thread should continue to run, false otherwise (ie if stop() has been called).
078         * 
079         * @deprecated Use {@link #isRunning()}. Deprecated as of version 0.6.
080         */
081        protected boolean keepRunning() {
082            return keepRunning;
083        }
084    
085        /**
086         * @return Returns <code>true</code> if the server has been started, and has not yet been stopped.
087         */
088        public boolean isRunning() {
089            return keepRunning;
090        }
091    
092        /** 
093         * Starts the server listening for connections in a new thread.  This continues 
094         * until <code>stop()</code> is called.  
095         */
096        public void start() {
097            Thread thd = new Thread(this);
098            this.keepRunning = true;
099            thd.start();
100            //Fix for bug 960101:  Don't start the cleaner thread until the server is started.
101            Thread cleaner = new Thread(new HL7Service.ConnectionCleaner(this));
102            cleaner.start();
103        }
104    
105        /**
106         * Stops the server from listening for new connections, and closes all existing
107         * Connections.  
108         */
109        public void stop() {
110            this.keepRunning = false;
111            for (int i = 0; i < connections.size(); i++) {
112                ((Connection) connections.get(i)).close();
113            }
114        }
115        
116        /** 
117         * Called by subclasses when a new Connection is made.  Registers the 
118         * MessageTypeRouter with the given Connection and stores it. 
119         */
120        public synchronized void newConnection(Connection c) {
121            c.getResponder().registerApplication(router);
122            this.connections.add(c); //keep track of connections  
123            notifyListeners(c);
124        }
125    
126        private synchronized void discardConnection(Connection c) {
127            this.connections.remove(c);
128            notifyListeners(c);
129        }
130    
131        /**
132         * Returns a connection to a remote host that was initiated by the given remote host.  
133         * If the connection has not been made, this method blocks until the remote host 
134         * connects.  
135         */
136        public Connection getRemoteConnection(String IP) {
137            Connection conn = null;
138            while (conn == null) {
139                //check all connections ...
140                int c = 0;
141                synchronized (this) {
142                    while (conn == null && c < connections.size()) {
143                        Connection nextConn = (Connection) connections.get(c);
144                        if (nextConn.getRemoteAddress().getHostAddress().equals(IP))
145                            conn = nextConn;
146                        c++;
147                    }
148                }
149    
150                if (conn == null) {
151                    try {
152                        Thread.sleep(100);
153                    }
154                    catch (InterruptedException e) {
155                    }
156                }
157            }
158            return conn;
159        }
160    
161        /** Returns all currently active connections. */
162        public synchronized Vector getRemoteConnections() {
163            return connections;
164        }
165    
166        /**
167         * Registers the given ConnectionListener with the HL7Service - when a remote host
168         * makes a new Connection, all registered listeners will be notified.  
169         */
170        public synchronized void registerConnectionListener(ConnectionListener listener) {
171            this.listeners.add(listener);
172        }
173    
174        /** Notifies all listeners that a Connection is new or discarded. */
175        private void notifyListeners(Connection c) {
176            for (int i = 0; i < listeners.size(); i++) {
177                ConnectionListener cl = (ConnectionListener) listeners.get(i);
178                if (c.isOpen()) {
179                    cl.connectionReceived(c);
180                }
181                else {
182                    cl.connectionDiscarded(c);
183                }
184            }
185        }
186    
187        /**
188         * Registers the given application to handle messages corresponding to the given type
189         * and trigger event.  Only one application can be registered for a given message type
190         * and trigger event combination.  A repeated registration for a particular combination 
191         * of type and trigger event over-writes the previous one.  Note that the wildcard "*" 
192         * for messageType or triggerEvent means any type or event, respectively.   
193         */
194        public synchronized void registerApplication(String messageType, String triggerEvent, Application handler) {
195            this.router.registerApplication(messageType, triggerEvent, handler);
196        }
197    
198        /**
199         * <p>A convenience method for registering applications (using <code>registerApplication()
200         * </code>) with this service.  Information about which Applications should handle which 
201         * messages is read from the given text file.  Each line in the file should have the 
202         * following format (entries tab delimited):</p>
203         * <p>message_type &#009; trigger_event &#009; application_class</p>
204         * <p>message_type &#009; trigger_event &#009; application_class</p>
205         * <p>Note that message type and event can be the wildcard "*", which means any.</p>
206         * <p>For example, if you write an Application called org.yourorganiztion.ADTProcessor
207         * that processes several types of ADT messages, and another called 
208         * org.yourorganization.ResultProcessor that processes result messages, you might have a 
209         * file that looks like this: </p>
210         * <p>ADT &#009; * &#009; org.yourorganization.ADTProcessor<br>
211         * ORU &#009; R01 &#009; org.yourorganization.ResultProcessor</p>
212         * <p>Each class listed in this file must implement Application and must have a zero-argument
213         * constructor.</p>
214         */
215        public void loadApplicationsFromFile(File f)
216            throws IOException, HL7Exception, ClassNotFoundException, InstantiationException, IllegalAccessException {
217            BufferedReader in = new BufferedReader(new FileReader(f));
218            String line = null;
219            while ((line = in.readLine()) != null) {
220                //parse application registration information 
221                StringTokenizer tok = new StringTokenizer(line, "\t", false);
222                String type = null, event = null, className = null;
223    
224                if (tok.hasMoreTokens()) { //skip blank lines 
225                    try {
226                        type = tok.nextToken();
227                        event = tok.nextToken();
228                        className = tok.nextToken();
229                    }
230                    catch (NoSuchElementException ne) {
231                        throw new HL7Exception(
232                            "Can't register applications from file "
233                                + f.getName()
234                                + ". The line '"
235                                + line
236                                + "' is not of the form: message_type [tab] trigger_event [tab] application_class.",
237                            HL7Exception.APPLICATION_INTERNAL_ERROR);
238                    }
239    
240                    Class appClass = Class.forName(className); //may throw ClassNotFoundException 
241                    Object appObject = appClass.newInstance();
242                    Application app = null;
243                    try {
244                        app = (Application) appObject;
245                    }
246                    catch (ClassCastException cce) {
247                        throw new HL7Exception(
248                            "The specified class, " + appClass.getName() + ", doesn't implement Application.",
249                            HL7Exception.APPLICATION_INTERNAL_ERROR);
250                    }
251    
252                    this.registerApplication(type, event, app);
253                }
254            }
255        }
256    
257        /** 
258         * Runnable that looks for closed Connections and discards them. 
259         * It would be nice to find a way to externalize this safely so that it could be 
260         * re-used by (for example) TestPanel.  It could take a Vector of Connections
261         * as an argument, instead of an HL7Service, but some problems might arise if 
262         * other threads were iterating through the Vector while this one was removing
263         * elements from it.  
264         */
265        private class ConnectionCleaner implements Runnable {
266    
267            HL7Service service;
268    
269            public ConnectionCleaner(HL7Service service) {
270                this.service = service;
271            }
272    
273            public void run() {
274                while (keepRunning()) {
275                    try {
276                        Thread.sleep(500);
277                    }
278                    catch (InterruptedException e) {
279                    }
280    
281                    synchronized (service) {
282                        Iterator it = service.getRemoteConnections().iterator();
283                        while (it.hasNext()) {
284                            Connection conn = (Connection) it.next();
285                            if (!conn.isOpen()) {
286                                it.remove();
287                                service.notifyListeners(conn);
288                            }
289                        }
290                        
291                        //build list to discard (don't discard while iterating!)
292                        /*ArrayList toDiscard = new ArrayList();
293                        for (int i = 0; i < service.getRemoteConnections().size(); i++) {
294                            Connection conn = (Connection) service.getRemoteConnections().get(i);
295                            if (!conn.isOpen()) {
296                                toDiscard.add(conn);
297                            }
298                        }
299                        for (int i = 0; i < toDiscard.size(); i++) {
300                            service.discardConnection((Connection) toDiscard.get(i));
301                        }*/
302                    }
303                }
304            }
305    
306        }
307    
308    }