001/**
002The 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. 
004You may obtain a copy of the License at http://www.mozilla.org/MPL/ 
005Software distributed under the License is distributed on an "AS IS" basis, 
006WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the 
007specific language governing rights and limitations under the License. 
008
009The Original Code is "TwoPortService.java".  Description: 
010"A TCP/IP-based HL7 Service that uses separate ports for inbound and outbound messages." 
011
012The Initial Developer of the Original Code is University Health Network. Copyright (C) 
0132001.  All Rights Reserved. 
014
015Contributor(s): ______________________________________. 
016
017Alternatively, the contents of this file may be used under the terms of the 
018GNU General Public License (the  �GPL�), in which case the provisions of the GPL are 
019applicable instead of those above.  If you wish to allow use of your version of this 
020file only under the terms of the GPL and not to allow others to use your version 
021of this file under the MPL, indicate your decision by deleting  the provisions above 
022and replace  them with the notice and other provisions required by the GPL License.  
023If you do not delete the provisions above, a recipient may use your version of 
024this file under either the MPL or the GPL. 
025 */
026
027package ca.uhn.hl7v2.app;
028
029import ca.uhn.hl7v2.DefaultHapiContext;
030import ca.uhn.hl7v2.HapiContext;
031import ca.uhn.hl7v2.app.AcceptorThread.AcceptedSocket;
032import ca.uhn.hl7v2.concurrent.DefaultExecutorService;
033import ca.uhn.hl7v2.llp.LLPException;
034import ca.uhn.hl7v2.llp.LowerLayerProtocol;
035import ca.uhn.hl7v2.llp.MinLowerLayerProtocol;
036import ca.uhn.hl7v2.parser.Parser;
037import ca.uhn.hl7v2.parser.PipeParser;
038import ca.uhn.hl7v2.util.SocketFactory;
039import org.slf4j.Logger;
040import org.slf4j.LoggerFactory;
041
042import java.io.File;
043import java.io.IOException;
044import java.net.Socket;
045import java.util.HashMap;
046import java.util.Map;
047import java.util.concurrent.BlockingQueue;
048import java.util.concurrent.ExecutorService;
049import java.util.concurrent.LinkedBlockingQueue;
050import java.util.concurrent.TimeUnit;
051
052/**
053 * A TCP/IP-based HL7 Service that uses separate ports for inbound and outbound
054 * messages. A connection is only activated when the same remote host connects
055 * to both the inbound and outbound ports.
056 * 
057 * @author Bryan Tripp
058 */
059public class TwoPortService extends HL7Service {
060
061        private static final Logger log = LoggerFactory
062                        .getLogger(TwoPortService.class);
063
064        private final Map<String, AcceptedSocket> waitingForSecondSocket = new HashMap<>();
065        private final int inboundPort;
066        private final int outboundPort;
067        private final boolean tls;
068        private final BlockingQueue<AcceptedSocket> queue;
069        private AcceptorThread inboundAcceptor, outboundAcceptor;
070        private final HapiContext hapiContext;
071
072        public TwoPortService(int inboundPort, int outboundPort) {
073                this(new PipeParser(), new MinLowerLayerProtocol(), inboundPort,
074                                outboundPort, false);
075        }
076
077        public TwoPortService(int inboundPort, int outboundPort, boolean tls) {
078                this(new PipeParser(), new MinLowerLayerProtocol(), inboundPort,
079                                outboundPort, tls);
080        }
081
082        /** Creates a new instance of TwoPortService */
083        public TwoPortService(Parser parser, LowerLayerProtocol llp,
084                        int inboundPort, int outboundPort, boolean tls) {
085                this(parser, llp, inboundPort, outboundPort, tls,
086                                DefaultExecutorService.getDefaultService());
087        }
088
089        /** Creates a new instance of TwoPortService */
090        public TwoPortService(HapiContext hapiContext, 
091                        int inboundPort, int outboundPort, boolean tls) {
092                super(hapiContext);
093                this.hapiContext = hapiContext;
094                this.queue = new LinkedBlockingQueue<>();
095                this.inboundPort = inboundPort;
096                this.outboundPort = outboundPort;
097                this.tls = tls;
098                
099                if (inboundPort == outboundPort) {
100                        throw new IllegalArgumentException("Inbound port and outbound port can not be the same");
101                }
102                if (inboundPort < 1) {
103                        throw new IllegalArgumentException("Invalid inbound port");
104                }
105                if (outboundPort < 1) {
106                        throw new IllegalArgumentException("Invalid outbound port");
107                }
108                
109        }
110
111        /** Creates a new instance of TwoPortService */
112        public TwoPortService(Parser parser, LowerLayerProtocol llp,
113                        int inboundPort, int outboundPort, boolean tls,
114                        ExecutorService executorService) {
115                super(parser, llp, executorService);
116                this.hapiContext = new DefaultHapiContext();
117                this.queue = new LinkedBlockingQueue<>();
118                this.inboundPort = inboundPort;
119                this.outboundPort = outboundPort;
120                this.tls = tls;
121        }
122
123        /**
124         * Launches two threads that concurrently listen on the inboundPort and
125         * outboundPort.
126         * 
127         * @see ca.uhn.hl7v2.app.HL7Service#afterStartup()
128         */
129        @Override
130        protected void afterStartup() {
131                try {
132                        super.afterStartup();
133                        inboundAcceptor = createAcceptThread(inboundPort);
134                        outboundAcceptor = createAcceptThread(outboundPort);
135                        inboundAcceptor.start();
136                        outboundAcceptor.start();
137                        log.info("TwoPortService running on ports {} and {}", inboundPort,
138                                        outboundPort);
139                } catch (Exception e) {
140                        log.error("Could not run TwoPortService on ports {} and {}",
141                                        inboundPort, outboundPort);
142                        throw new RuntimeException(e);
143                }
144        }
145
146        /**
147         * Terminate the two acceptor threads
148         * 
149         * @see ca.uhn.hl7v2.app.HL7Service#afterTermination()
150         */
151        @Override
152        protected void afterTermination() {
153                super.afterTermination();
154                inboundAcceptor.stop();
155                outboundAcceptor.stop();
156        }
157
158        /**
159         * Polls for accepted sockets
160         */
161        protected void handle() {
162                if (inboundAcceptor.getServiceExitedWithException() != null) {
163                        setServiceExitedWithException(inboundAcceptor.getServiceExitedWithException());
164                }
165                if (outboundAcceptor.getServiceExitedWithException() != null) {
166                        setServiceExitedWithException(outboundAcceptor.getServiceExitedWithException());
167                }
168                
169                try {
170                        ActiveConnection conn = acceptConnection(queue.poll(2, TimeUnit.SECONDS));
171                        if (conn != null) {
172                                log.info("Accepted connection from "
173                                                + conn.getRemoteAddress().getHostAddress());
174                                newConnection(conn);
175                        }
176                } catch (Exception e) {
177                        log.error("Error while accepting connections: ", e);
178                }
179        }
180
181        /**
182         * Helper method that checks whether the newSocket completes a two-port
183         * connection or not. If yes, the {@link ActiveConnection} object is created and
184         * returned.
185         */
186        private ActiveConnection acceptConnection(AcceptedSocket newSocket)
187                        throws LLPException, IOException {
188                ActiveConnection conn = null;
189                if (newSocket != null) {
190                        String address = newSocket.socket.getInetAddress().getHostAddress();
191                        AcceptedSocket otherSocket = waitingForSecondSocket.remove(address);
192                        if (otherSocket != null && otherSocket.origin != newSocket.origin) {
193                                log.debug("Socket {} completes a two-port connection",
194                                                newSocket.socket);
195                                Socket in = getInboundSocket(newSocket, otherSocket);
196                                Socket out = getOutboundSocket(newSocket, otherSocket);
197                                conn = new ActiveConnection(getParser(), getLlp(), in, out,
198                                                getExecutorService());
199                        } else {
200                                log.debug(
201                                                "Registered {} Still waiting for second socket for two-port connection",
202                                                newSocket.socket);
203                                waitingForSecondSocket.put(address, newSocket);
204                        }
205                }
206                return conn;
207        }
208
209        private Socket getInboundSocket(AcceptedSocket socket1,
210                        AcceptedSocket socket2) {
211                return socket1.origin == inboundAcceptor ? socket1.socket
212                                : socket2.socket;
213        }
214
215        private Socket getOutboundSocket(AcceptedSocket socket1,
216                        AcceptedSocket socket2) {
217                return socket1.origin == outboundAcceptor ? socket1.socket
218                                : socket2.socket;
219        }
220
221        protected AcceptorThread createAcceptThread(int port) {
222                SocketFactory ss = this.hapiContext.getSocketFactory();
223                return new AcceptorThread(port, tls, getExecutorService(), queue, ss);
224        }
225
226        /**
227         * Run server from command line. Inbound and outbound port numbers should be
228         * provided as arguments, and a file containing a list of Applications to
229         * use can also be specified as an optional argument (as per
230         * <code>super.loadApplicationsFromFile(...)</code>). Uses the default
231         * LowerLayerProtocol.
232         */
233        public static void main(String[] args) {
234                if (args.length < 2 || args.length > 3) {
235                        System.out
236                                        .println("Usage: ca.uhn.hl7v2.app.TwoPortService inbound_port outbound_port [application_spec_file_name]");
237                        System.exit(1);
238                }
239
240                int inPort = 0;
241                int outPort = 0;
242                try {
243                        inPort = Integer.parseInt(args[0]);
244                        outPort = Integer.parseInt(args[1]);
245                } catch (NumberFormatException e) {
246                        System.err.println("One of the given ports (" + args[0] + " or "
247                                        + args[1] + ") is not an integer.");
248                        System.exit(1);
249                }
250
251                File appFile = null;
252                if (args.length == 3) {
253                        appFile = new File(args[2]);
254                }
255
256                try {
257                        TwoPortService server = new TwoPortService(inPort, outPort);
258                        if (appFile != null)
259                                server.loadApplicationsFromFile(appFile);
260                        server.start();
261                } catch (Exception e) {
262                        e.printStackTrace();
263                }
264
265        }
266
267}