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.BufferedReader;
031 import java.io.File;
032 import java.io.FileReader;
033 import java.io.IOException;
034 import java.net.ServerSocket;
035 import java.util.ArrayList;
036 import java.util.Iterator;
037 import java.util.List;
038 import java.util.NoSuchElementException;
039 import java.util.StringTokenizer;
040 import java.util.concurrent.ExecutorService;
041
042 import org.slf4j.Logger;
043 import org.slf4j.LoggerFactory;
044
045 import ca.uhn.hl7v2.HL7Exception;
046 import ca.uhn.hl7v2.concurrent.DefaultExecutorService;
047 import ca.uhn.hl7v2.concurrent.Service;
048 import ca.uhn.hl7v2.llp.LowerLayerProtocol;
049 import 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 */
064 public 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 }