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 	 trigger_event 	 application_class</p> 204 * <p>message_type 	 trigger_event 	 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 	 * 	 org.yourorganization.ADTProcessor<br> 211 * ORU 	 R01 	 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 }