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 "Connection.java". Description: 010"A TCP/IP connection to a remote HL7 server." 011 012The Initial Developer of the Original Code is University Health Network. Copyright (C) 0132002. 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 java.io.IOException; 030import java.net.InetAddress; 031import java.net.Socket; 032import java.util.ArrayList; 033import java.util.Iterator; 034import java.util.List; 035import java.util.concurrent.ExecutorService; 036import java.util.concurrent.Future; 037import java.util.concurrent.TimeUnit; 038 039import javax.net.ssl.SSLSocket; 040 041import org.slf4j.Logger; 042import org.slf4j.LoggerFactory; 043 044import ca.uhn.hl7v2.concurrent.BlockingMap; 045import ca.uhn.hl7v2.concurrent.BlockingHashMap; 046import ca.uhn.hl7v2.concurrent.DefaultExecutorService; 047import ca.uhn.hl7v2.llp.HL7Writer; 048import ca.uhn.hl7v2.llp.LLPException; 049import ca.uhn.hl7v2.llp.LowerLayerProtocol; 050import ca.uhn.hl7v2.parser.Parser; 051 052/** 053 * A TCP/IP connection to a remote HL7 server. 054 * 055 * @author Bryan Tripp 056 */ 057public class Connection { 058 059 private static final Logger log = LoggerFactory.getLogger(Connection.class); 060 061 private Initiator initiator; 062 private Responder responder; 063 private List<Socket> sockets; 064 private HL7Writer ackWriter; 065 private HL7Writer sendWriter; 066 private Parser parser; 067 private BlockingMap<String, String> responses; 068 private List<Receiver> receivers; 069 private boolean open = true; 070 private ExecutorService executorService; 071 boolean outbound; 072 073 /** 074 * Creates a new instance of Connection, with inbound and outbound 075 * communication on a single port. 076 */ 077 public Connection(Parser parser, LowerLayerProtocol llp, 078 Socket bidirectional) throws LLPException, IOException { 079 this(parser, llp, bidirectional, DefaultExecutorService 080 .getDefaultService()); 081 } 082 083 public Connection(Parser parser, LowerLayerProtocol llp, 084 Socket bidirectional, ExecutorService executorService) 085 throws LLPException, IOException { 086 init(parser, executorService); 087 ackWriter = llp.getWriter(bidirectional.getOutputStream()); 088 sendWriter = ackWriter; 089 this.executorService = executorService; 090 sockets.add(bidirectional); 091 receivers.add(new Receiver(this, llp.getReader(bidirectional 092 .getInputStream()))); 093 this.initiator = new Initiator(this); 094 } 095 096 /** 097 * Creates a new instance of Connection, with inbound communication on one 098 * port and outbound on another. 099 */ 100 public Connection(Parser parser, LowerLayerProtocol llp, Socket inbound, 101 Socket outbound) throws LLPException, IOException { 102 this(parser, llp, inbound, outbound, DefaultExecutorService 103 .getDefaultService()); 104 } 105 106 /** 107 * Creates a new instance of Connection, with inbound communication on one 108 * port and outbound on another. 109 */ 110 public Connection(Parser parser, LowerLayerProtocol llp, Socket inbound, 111 Socket outbound, ExecutorService executorService) 112 throws LLPException, IOException { 113 init(parser, executorService); 114 ackWriter = llp.getWriter(inbound.getOutputStream()); 115 sendWriter = llp.getWriter(outbound.getOutputStream()); 116 sockets.add(outbound); // always add outbound first ... see getRemoteAddress() 117 sockets.add(inbound); 118 119 receivers.add(new Receiver(this, 120 llp.getReader(inbound.getInputStream()))); 121 receivers.add(new Receiver(this, llp.getReader(outbound 122 .getInputStream()))); 123 this.initiator = new Initiator(this); 124 } 125 126 /** Common initialization tasks */ 127 private void init(Parser parser, ExecutorService executorService) 128 throws LLPException { 129 this.parser = parser; 130 this.executorService = executorService; 131 sockets = new ArrayList<Socket>(); 132 responses = new BlockingHashMap<String, String>(executorService); 133 receivers = new ArrayList<Receiver>(2); 134 responder = new Responder(parser); 135 } 136 137 /** 138 * Start the receiver thread(s) 139 */ 140 public void activate() { 141 if (receivers != null) { 142 for (Receiver receiver : receivers) { 143 receiver.start(); 144 } 145 } 146 } 147 148 public ExecutorService getExecutorService() { 149 return executorService; 150 } 151 152 /** 153 * Returns the address of the remote host to which this Connection is 154 * connected. If separate inbound and outbound sockets are used, the address 155 * of the outbound socket is returned (the addresses should normally be the 156 * same, but this isn't checked). 157 */ 158 public InetAddress getRemoteAddress() { 159 Socket s = sockets.get(0); 160 return s.getInetAddress(); 161 } 162 163 /** 164 * Returns the remote port on the remote host to which this Connection is 165 * connected. If separate inbound and outbound sockets are used, the port of 166 * the outbound socket is returned. 167 */ 168 public int getRemotePort() { 169 Socket s = sockets.get(0); 170 return s.getPort(); 171 } 172 173 /** Returns the Initiator associated with this connection */ 174 public Initiator getInitiator() { 175 return this.initiator; 176 } 177 178 /** Returns the Responder associated with this connection */ 179 public Responder getResponder() { 180 return this.responder; 181 } 182 183 public boolean isSecure() { 184 if (isOpen() && sockets.size() > 0) { 185 return (sockets.get(0) instanceof SSLSocket); 186 } else { 187 throw new IllegalStateException( 188 "Can't determine status on closed socket"); 189 } 190 } 191 192 /** 193 * Returns the HL7Writer through which unsolicited outbound messages should 194 * be sent. 195 */ 196 protected HL7Writer getSendWriter() { 197 return this.sendWriter; 198 } 199 200 /** 201 * Returns the HL7Writer through which responses to inbound messages should 202 * be sent. 203 */ 204 protected HL7Writer getAckWriter() { 205 return this.ackWriter; 206 } 207 208 public Parser getParser() { 209 return this.parser; 210 } 211 212 public String toString() { 213 StringBuffer buf = new StringBuffer(); 214 buf.append(getRemoteAddress().getHostName()); 215 buf.append(":"); 216 for (Iterator<Socket> iter = sockets.iterator(); iter.hasNext();) { 217 Socket socket = iter.next(); 218 buf.append(socket.getPort()); 219 if (iter.hasNext()) 220 buf.append(","); 221 } 222 return buf.toString(); 223 } 224 225 /** 226 * Reserves a future incoming message by ack ID. When the incoming message 227 * with the given ack ID arrives, the message will be returned. 228 */ 229 protected Future<String> waitForResponse(final String messageID, 230 long timeout) throws InterruptedException { 231 return responses.asyncPoll(messageID, timeout, TimeUnit.MILLISECONDS); 232 } 233 234 /** 235 * Given the ack ID (MSA-2) of a message, notifies a waiting consumer thread 236 * about a received response. 237 */ 238 protected boolean isRecipientWaiting(String ackID, String message) { 239 return responses.give(ackID, message); 240 } 241 242 /** Stops running Receiver threads and closes open sockets */ 243 public void close() { 244 // Mark all running receiver threads to be stopped 245 for (Receiver receiver : receivers) { 246 if (receiver.isRunning()) 247 receiver.stop(); 248 } 249 // Forces open sockets to be closed. This causes the Receiver threads to 250 // eventually terminate 251 for (Socket socket : sockets) { 252 try { 253 if (!socket.isClosed()) 254 socket.close(); 255 } catch (Exception e) { 256 log.error("Error while stopping threads and closing sockets", e); 257 } 258 } 259 260 open = false; 261 } 262 263 public boolean isOpen() { 264 return open; 265 } 266 267}