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}