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}