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 "SimpleServer.java". Description: 010 * "A simple TCP/IP-based HL7 server." 011 * 012 * The Initial Developer of the Original Code is University Health Network. Copyright (C) 013 * 2002. 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 027package ca.uhn.hl7v2.app; 028 029import java.io.File; 030import java.util.concurrent.BlockingQueue; 031import java.util.concurrent.ExecutorService; 032import java.util.concurrent.LinkedBlockingQueue; 033import java.util.concurrent.TimeUnit; 034 035import ca.uhn.hl7v2.util.StandardSocketFactory; 036import org.slf4j.Logger; 037import org.slf4j.LoggerFactory; 038 039import ca.uhn.hl7v2.DefaultHapiContext; 040import ca.uhn.hl7v2.HapiContext; 041import ca.uhn.hl7v2.app.AcceptorThread.AcceptedSocket; 042import ca.uhn.hl7v2.concurrent.DefaultExecutorService; 043import ca.uhn.hl7v2.llp.LowerLayerProtocol; 044import ca.uhn.hl7v2.llp.MinLowerLayerProtocol; 045import ca.uhn.hl7v2.parser.Parser; 046import ca.uhn.hl7v2.parser.PipeParser; 047import ca.uhn.hl7v2.util.SocketFactory; 048 049/** 050 * <p> 051 * A simple TCP/IP-based HL7 server. This server listens for connections on a 052 * particular port, and creates a ConnectionManager for each incoming 053 * connection. 054 * </p> 055 * <p> 056 * A single SimpleServer can only service requests that use a single class of 057 * LowerLayerProtocol (specified at construction time). 058 * </p> 059 * <p> 060 * The ConnectionManager uses a {@link PipeParser} of the version specified in 061 * the constructor 062 * </p> 063 * <p> 064 * ConnectionManagers currently only support original mode processing. 065 * </p> 066 * <p> 067 * The ConnectionManager routes messages to various {@link Application}s based 068 * on message type. From the HL7 perspective, an {@link Application} is 069 * something that does something with a message. 070 * </p> 071 * 072 * @author Bryan Tripp 073 * @author Christian Ohr 074 */ 075public class SimpleServer extends HL7Service { 076 077 /** 078 * Socket timeout for simple server 079 */ 080 public static final int SO_TIMEOUT = StandardSocketFactory.DEFAULT_ACCEPTED_SOCKET_TIMEOUT; 081 082 private static final Logger log = LoggerFactory.getLogger(SimpleServer.class); 083 084 private final int port; 085 private final boolean tls; 086 private final BlockingQueue<AcceptedSocket> queue; 087 private AcceptorThread acceptor; 088 private final HapiContext hapiContext; 089 private boolean acceptAllMsg = false; 090 091 /** 092 * Creates a new instance of SimpleServer that listens on the given port, 093 * using the {@link MinLowerLayerProtocol} and a standard {@link PipeParser}. 094 */ 095 public SimpleServer(int port) { 096 this(port, new MinLowerLayerProtocol(), new PipeParser(), false); 097 } 098 099 /** 100 * Creates a new instance of SimpleServer that listens on the given port, 101 * using the {@link MinLowerLayerProtocol} and a standard {@link PipeParser}. 102 */ 103 public SimpleServer(int port, boolean tls) { 104 this(port, new MinLowerLayerProtocol(), new PipeParser(), tls); 105 } 106 107 /** 108 * Creates a new instance of SimpleServer that listens on the given port. 109 */ 110 public SimpleServer(int port, LowerLayerProtocol llp, Parser parser) { 111 this(port, llp, parser, false); 112 } 113 114 /** 115 * Creates a new instance of SimpleServer that listens on the given port. 116 */ 117 public SimpleServer(int port, LowerLayerProtocol llp, Parser parser, boolean tls) { 118 this(port, llp, parser, tls, DefaultExecutorService.getDefaultService()); 119 } 120 121 /** 122 * Creates a new instance of SimpleServer using a custom {link 123 * {@link ExecutorService}. This {@link ExecutorService} instance will 124 * <i>not</i> be shut down after the server stops! 125 */ 126 public SimpleServer(int port, LowerLayerProtocol llp, Parser parser, boolean tls, 127 ExecutorService executorService) { 128 super(parser, llp, executorService); 129 this.port = port; 130 this.tls = tls; 131 this.hapiContext = new DefaultHapiContext(); 132 this.queue = new LinkedBlockingQueue<>(100); 133 } 134 135 /** 136 * Creates a new instance of SimpleServer that listens on a given server socket. 137 * SimpleServer will bind the socket when it is started, so the server socket 138 * must not already be bound. 139 * 140 * @since 2.1 141 * @throws IllegalStateException If serverSocket is already bound 142 */ 143 public SimpleServer(HapiContext hapiContext, int port, boolean tls) { 144 super(hapiContext); 145 this.hapiContext = hapiContext; 146 this.port = port; 147 this.tls = tls; 148 this.queue = new LinkedBlockingQueue<>(100); 149 } 150 151 /** 152 * Creates a new instance of SimpleServer that listens on a given server socket 153 * and will pass all messages to the responders, even if the message control id 154 * is not known to the server. 155 * SimpleServer will bind the socket when it is started, so the server socket 156 * must not already be bound. 157 * 158 * @since 2.4 159 * @throws IllegalStateException If serverSocket is already bound 160 */ 161 public SimpleServer(HapiContext hapiContext, int port, boolean tls, boolean acceptAll) { 162 this(hapiContext, port, tls); 163 acceptAllMsg = acceptAll; 164 } 165 166 /** 167 * Prepare server by initializing the server socket 168 * 169 * @see ca.uhn.hl7v2.app.HL7Service#afterStartup() 170 */ 171 @Override 172 protected void afterStartup() { 173 try { 174 super.afterStartup(); 175 log.info("Starting SimpleServer running on port {}", port); 176 SocketFactory ss = this.hapiContext.getSocketFactory(); 177 acceptor = new AcceptorThread(port, tls, getExecutorService(), queue, ss); 178 acceptor.start(); 179 } catch (Exception e) { 180 log.error("Failed starting SimpleServer on port {}", port); 181 throw new RuntimeException(e); 182 } 183 } 184 185 /** 186 * Loop that waits for a connection and starts a ConnectionManager when it 187 * gets one. 188 */ 189 @Override 190 protected void handle() { 191 if (acceptor.getServiceExitedWithException() != null) { 192 setServiceExitedWithException(acceptor.getServiceExitedWithException()); 193 } 194 195 try { 196 // Wait some period of time for connections 197 AcceptedSocket newSocket = queue.poll(500, TimeUnit.MILLISECONDS); 198 if (newSocket != null) { 199 log.info("Accepted connection from {}:{} on local port {}", 200 newSocket.socket.getInetAddress().getHostAddress(), newSocket.socket.getPort(), port); 201 ActiveConnection c = new ActiveConnection(getParser(), getLlp(), newSocket.socket, 202 getExecutorService(), acceptAllMsg); 203 newConnection(c); 204 } 205 } catch (InterruptedException ie) { 206 // just timed out 207 } catch (Exception e) { 208 log.error("Error while accepting connections: ", e); 209 } 210 } 211 212 /** 213 * Close down socket 214 */ 215 @Override 216 protected void afterTermination() { 217 super.afterTermination(); 218 // use stopAndWait (instead of stop) to ensure port is released when this function returns, 219 // so that components using this server class can reuse the port without having to add 220 // logic to wait for port to be released 221 acceptor.stopAndWait(); 222 } 223 224 /** 225 * Run server from command line. Port number should be passed as an 226 * argument, and a file containing a list of Applications to use can also be 227 * specified as an optional argument (as per 228 * <code>loadApplicationsFromFile(...)</code>). Uses the default 229 * LowerLayerProtocol. 230 */ 231 public static void main(String[] args) { 232 if (args.length < 1 || args.length > 2) { 233 System.out 234 .println("Usage: ca.uhn.hl7v2.app.SimpleServer port_num [application_spec_file_name]"); 235 System.exit(1); 236 } 237 238 int port = 0; 239 try { 240 port = Integer.parseInt(args[0]); 241 } catch (NumberFormatException e) { 242 System.err.println("The given port (" + args[0] 243 + ") is not an integer."); 244 System.exit(1); 245 } 246 247 File appFile = null; 248 if (args.length == 2) { 249 appFile = new File(args[1]); 250 } 251 252 try { 253 SimpleServer server = new SimpleServer(port); 254 if (appFile != null) 255 server.loadApplicationsFromFile(appFile); 256 server.start(); 257 } catch (Exception e) { 258 e.printStackTrace(); 259 } 260 261 } 262 263}