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