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