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 "TwoPortService.java". Description: 010 "A TCP/IP-based HL7 Service that uses separate ports for inbound and outbound messages." 011 012 The Initial Developer of the Original Code is University Health Network. Copyright (C) 013 2001. All Rights Reserved. 014 015 Contributor(s): ______________________________________. 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 027 package ca.uhn.hl7v2.app; 028 029 import java.io.File; 030 import java.io.IOException; 031 import java.io.InterruptedIOException; 032 import java.net.ServerSocket; 033 import java.net.Socket; 034 import java.net.SocketException; 035 import java.util.Vector; 036 037 import ca.uhn.hl7v2.llp.LLPException; 038 import ca.uhn.hl7v2.llp.LowerLayerProtocol; 039 import ca.uhn.hl7v2.parser.Parser; 040 import ca.uhn.hl7v2.parser.PipeParser; 041 import ca.uhn.log.HapiLog; 042 import ca.uhn.log.HapiLogFactory; 043 044 /** 045 * A TCP/IP-based HL7 Service that uses separate ports for inbound and outbound messages. 046 * A connection is only activated when the same remote host connects to both the 047 * inbound and outbound ports. 048 * @author Bryan Tripp 049 */ 050 public class TwoPortService extends HL7Service { 051 052 private static final HapiLog log = HapiLogFactory.getHapiLog(TwoPortService.class); 053 054 private Vector inSockets; //Vector because it's synchronized 055 private Vector outSockets; 056 private int inboundPort; 057 private int outboundPort; 058 059 /** Creates a new instance of TwoPortService */ 060 public TwoPortService(Parser parser, LowerLayerProtocol llp, int inboundPort, int outboundPort) { 061 super(parser, llp); 062 inSockets = new Vector(20); 063 outSockets = new Vector(20); 064 this.inboundPort = inboundPort; 065 this.outboundPort = outboundPort; 066 } 067 068 /** 069 * Initially sets up server sockets and starts separate threads to accept connections 070 * on them. Then loops, calling this.accept() super.newConnection(). 071 */ 072 public void run() { 073 try { 074 AcceptThread inAccept = new AcceptThread(inboundPort, inSockets); 075 AcceptThread outAccept = new AcceptThread(outboundPort, outSockets); 076 Thread inThread = new Thread(inAccept); 077 Thread outThread = new Thread(outAccept); 078 inThread.start(); 079 outThread.start(); 080 log.info("TwoPortService running on ports " + inboundPort + " and " + outboundPort); 081 082 while (keepRunning()) { 083 Connection conn = accept(3000); 084 if (conn != null) { 085 newConnection(conn); 086 log.info("Accepted connection from " + conn.getRemoteAddress().getHostAddress()); 087 } 088 } 089 090 inAccept.stop(); 091 outAccept.stop(); 092 } 093 catch (Exception e) { 094 log.error("Error while accepting connections: ", e); 095 } 096 } 097 098 /** 099 * Returns a Connection based on an inbound and outbound connection pair from 100 * the same remote host. This is done by looping through all the connections 101 * trying to match the host addresses of all posible inbound and outbound 102 * pairs. When a matching pair is found, both sockets are removed from the 103 * pending sockets lists, so there should normally be a very small number of 104 * sockets to search through. This method will return null if the specified 105 * number of milliseconds has passed, otherwise will wait until a single remote 106 * host has connected to both the inbound and outbound ports. 107 */ 108 private Connection accept(long timeoutMillis) throws LLPException, IOException { 109 long startTime = System.currentTimeMillis(); 110 Connection conn = null; 111 while (conn == null && System.currentTimeMillis() < startTime + timeoutMillis) { 112 int i = 0; 113 while (conn == null && i < inSockets.size()) { 114 Socket in = (Socket) inSockets.get(i); 115 int j = 0; 116 while (conn == null && j < outSockets.size()) { 117 Socket out = (Socket) outSockets.get(j); 118 if (out.getInetAddress().getHostAddress().equals(in.getInetAddress().getHostAddress())) { 119 conn = new Connection(parser, llp, in, out); 120 inSockets.remove(i); 121 outSockets.remove(j); 122 } 123 j++; 124 } 125 i++; 126 } 127 try { 128 Thread.sleep(10); 129 } 130 catch (InterruptedException e) { 131 } 132 } 133 return conn; 134 } 135 136 /** 137 * A Runnable that accepts connections on a ServerSocket and adds them to 138 * a Vector, so that they can be matched later. After stop() is called, the 139 * ServerSocket is closed. 140 */ 141 private class AcceptThread implements Runnable { 142 143 private ServerSocket ss; 144 private Vector sockets; 145 private boolean keepRunning = true; 146 147 public AcceptThread(int port, Vector sockets) throws IOException, SocketException { 148 ss = new ServerSocket(port); 149 ss.setSoTimeout(3000); 150 this.sockets = sockets; 151 } 152 153 public void run() { 154 try { 155 while (keepRunning) { 156 try { 157 Socket s = ss.accept(); 158 sockets.add(s); 159 } 160 catch (InterruptedIOException e) { /* OK - just timed out */ 161 } 162 } 163 ss.close(); 164 } 165 catch (Exception e) { 166 log.error("Problem running connection accept thread", e); 167 } 168 } 169 170 public void stop() { 171 keepRunning = false; 172 } 173 } 174 175 /** 176 * Run server from command line. Inbound and outbound port numbers should be provided as arguments, 177 * and a file containing a list of Applications to use can also be specified 178 * as an optional argument (as per <code>super.loadApplicationsFromFile(...)</code>). 179 * Uses the default LowerLayerProtocol. 180 */ 181 public static void main(String args[]) { 182 if (args.length < 2 || args.length > 3) { 183 System.out.println( 184 "Usage: ca.uhn.hl7v2.app.TwoPortService inbound_port outbound_port [application_spec_file_name]"); 185 System.exit(1); 186 } 187 188 int inPort = 0; 189 int outPort = 0; 190 try { 191 inPort = Integer.parseInt(args[0]); 192 outPort = Integer.parseInt(args[1]); 193 } 194 catch (NumberFormatException e) { 195 System.err.println("One of the given ports (" + args[0] + " or " + args[1] + ") is not an integer."); 196 System.exit(1); 197 } 198 199 File appFile = null; 200 if (args.length == 3) { 201 appFile = new File(args[2]); 202 } 203 204 try { 205 TwoPortService server = new TwoPortService(new PipeParser(), LowerLayerProtocol.makeLLP(), inPort, outPort); 206 if (appFile != null) 207 server.loadApplicationsFromFile(appFile); 208 server.start(); 209 } 210 catch (Exception e) { 211 e.printStackTrace(); 212 } 213 214 } 215 216 }