View Javadoc
1   /**
2    * The contents of this file are subject to the Mozilla Public License Version 1.1
3    * (the "License"); you may not use this file except in compliance with the License.
4    * You may obtain a copy of the License at http://www.mozilla.org/MPL/
5    * Software distributed under the License is distributed on an "AS IS" basis,
6    * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
7    * specific language governing rights and limitations under the License.
8    *
9    * The Original Code is "SimpleServer.java".  Description:
10   * "A simple TCP/IP-based HL7 server."
11   *
12   * The Initial Developer of the Original Code is University Health Network. Copyright (C)
13   * 2002.  All Rights Reserved.
14   *
15   * Contributor(s): Kyle Buza
16   *
17   * Alternatively, the contents of this file may be used under the terms of the
18   * GNU General Public License (the  �GPL�), in which case the provisions of the GPL are
19   * applicable instead of those above.  If you wish to allow use of your version of this
20   * file only under the terms of the GPL and not to allow others to use your version
21   * of this file under the MPL, indicate your decision by deleting  the provisions above
22   * and replace  them with the notice and other provisions required by the GPL License.
23   * If you do not delete the provisions above, a recipient may use your version of
24   * this file under either the MPL or the GPL.
25   */
26  
27  package ca.uhn.hl7v2.app;
28  
29  import java.io.File;
30  import java.util.concurrent.BlockingQueue;
31  import java.util.concurrent.ExecutorService;
32  import java.util.concurrent.LinkedBlockingQueue;
33  import java.util.concurrent.TimeUnit;
34  
35  import ca.uhn.hl7v2.util.StandardSocketFactory;
36  import org.slf4j.Logger;
37  import org.slf4j.LoggerFactory;
38  
39  import ca.uhn.hl7v2.DefaultHapiContext;
40  import ca.uhn.hl7v2.HapiContext;
41  import ca.uhn.hl7v2.app.AcceptorThread.AcceptedSocket;
42  import ca.uhn.hl7v2.concurrent.DefaultExecutorService;
43  import ca.uhn.hl7v2.llp.LowerLayerProtocol;
44  import ca.uhn.hl7v2.llp.MinLowerLayerProtocol;
45  import ca.uhn.hl7v2.parser.Parser;
46  import ca.uhn.hl7v2.parser.PipeParser;
47  import ca.uhn.hl7v2.util.SocketFactory;
48  
49  /**
50   * <p>
51   * A simple TCP/IP-based HL7 server. This server listens for connections on a
52   * particular port, and creates a ConnectionManager for each incoming
53   * connection.
54   * </p>
55   * <p>
56   * A single SimpleServer can only service requests that use a single class of
57   * LowerLayerProtocol (specified at construction time).
58   * </p>
59   * <p>
60   * The ConnectionManager uses a {@link PipeParser} of the version specified in
61   * the constructor
62   * </p>
63   * <p>
64   * ConnectionManagers currently only support original mode processing.
65   * </p>
66   * <p>
67   * The ConnectionManager routes messages to various {@link Application}s based
68   * on message type. From the HL7 perspective, an {@link Application} is
69   * something that does something with a message.
70   * </p>
71   * 
72   * @author Bryan Tripp
73   * @author Christian Ohr
74   */
75  public class SimpleServer extends HL7Service {
76  
77  	/**
78  	 * Socket timeout for simple server
79  	 */
80  	public static final int SO_TIMEOUT = StandardSocketFactory.DEFAULT_ACCEPTED_SOCKET_TIMEOUT;
81  
82  	private static final Logger log = LoggerFactory.getLogger(SimpleServer.class);
83  	
84  	private final int port;
85  	private final boolean tls;
86  	private final BlockingQueue<AcceptedSocket> queue;
87  	private AcceptorThread acceptor;
88  	private final HapiContext hapiContext;
89  	private boolean acceptAllMsg = false;
90  
91  	/**
92  	 * Creates a new instance of SimpleServer that listens on the given port,
93  	 * using the {@link MinLowerLayerProtocol} and a standard {@link PipeParser}.
94  	 */
95  	public SimpleServer(int port) {
96  		this(port, new MinLowerLayerProtocol(), new PipeParser(), false);
97  	}
98  	
99  	/**
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 }