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