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 "ConnectionHub.java". Description: 010"Provides access to shared HL7 Connections" 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 java.util.Collections; 030import java.util.Map; 031import java.util.Set; 032import java.util.concurrent.ConcurrentHashMap; 033import java.util.concurrent.ConcurrentMap; 034import java.util.concurrent.ExecutorService; 035 036import org.slf4j.Logger; 037import org.slf4j.LoggerFactory; 038 039import ca.uhn.hl7v2.DefaultHapiContext; 040import ca.uhn.hl7v2.HL7Exception; 041import ca.uhn.hl7v2.HapiContext; 042import ca.uhn.hl7v2.HapiContextSupport; 043import ca.uhn.hl7v2.concurrent.DefaultExecutorService; 044import ca.uhn.hl7v2.llp.LowerLayerProtocol; 045import ca.uhn.hl7v2.parser.Parser; 046import ca.uhn.hl7v2.util.ReflectionUtil; 047import ca.uhn.hl7v2.util.SocketFactory; 048 049/** 050 * <p> 051 * Provides access to shared HL7 Connections. The ConnectionHub has at most one connection to any 052 * given address at any time. 053 * </p> 054 * <p> 055 * <b>Synchronization Note:</b> This class should be safe to use in a multithreaded environment. A 056 * synchronization mutex is maintained for any given target host and port, so that if two threads 057 * are trying to connect to two separate destinations neither will block, but if two threads are 058 * trying to connect to the same destination, one will block until the other has finished trying. 059 * Use caution if this class is to be used in an environment where a very large (over 1000) number 060 * of target host/port destinations will be accessed at the same time. 061 * </p> 062 * 063 * @author Bryan Tripp 064 */ 065public class ConnectionHub extends HapiContextSupport { 066 067 private static volatile ConnectionHub instance = null; 068 private static final Logger log = LoggerFactory.getLogger(ConnectionHub.class); 069 /** 070 * Set a system property with this key to a string containing an integer larger than the default 071 * ("1000") if you need to connect to a very large number of targets at the same time in a 072 * multithreaded environment. 073 */ 074 public static final String MAX_CONCURRENT_TARGETS = ConnectionHub.class.getName() + ".maxSize"; 075 private final ConcurrentMap<String, String> connectionMutexes = new ConcurrentHashMap<String, String>(); 076 private final CountingMap<ConnectionData, Connection> connections; 077 078 /** Creates a new instance of ConnectionHub */ 079 private ConnectionHub(ExecutorService executorService) { 080 this(new DefaultHapiContext(executorService)); 081 } 082 083 private ConnectionHub(HapiContext context) { 084 super(context); 085 connections = new CountingMap<ConnectionData, Connection>() { 086 087 @Override 088 protected void dispose(Connection connection) { 089 connection.close(); 090 } 091 092 @Override 093 protected Connection open(ConnectionData connectionData) throws Exception { 094 return ConnectionFactory 095 .open(connectionData, getHapiContext().getExecutorService()); 096 } 097 098 }; 099 } 100 101 public Set<? extends ConnectionData> allConnections() { 102 return connections.keySet(); 103 } 104 105 /** 106 * @since 2.0 107 */ 108 public Connection attach(ConnectionData data) throws HL7Exception { 109 try { 110 Connection conn = null; 111 // Disallow establishing same connection targets concurrently 112 connectionMutexes.putIfAbsent(data.toString(), data.toString()); 113 String mutex = connectionMutexes.get(data.toString()); 114 synchronized (mutex) { 115 discardConnectionIfStale(connections.get(data)); 116 // Create connection or increase counter 117 conn = connections.put(data); 118 } 119 return conn; 120 } catch (Exception e) { 121 log.debug("Failed to attach", e); 122 throw new HL7Exception("Cannot open connection to " + data.getHost() + ":" 123 + data.getPort() + "/" + data.getPort2(), e); 124 } 125 } 126 127 /** 128 * Returns a Connection to the given address, opening this Connection if necessary. The given 129 * Parser will only be used if a new Connection is opened, so there is no guarantee that the 130 * Connection returned will be using the Parser you provide. If you need explicit access to the 131 * Parser the Connection is using, call <code>Connection.getParser()</code>. 132 * 133 * @since 2.1 134 */ 135 public Connection attach(String host, int port, boolean tls) throws HL7Exception { 136 return attach(new ConnectionData(host, port, 0, getHapiContext().getGenericParser(), 137 getHapiContext().getLowerLayerProtocol(), tls, getHapiContext() 138 .getSocketFactory())); 139 } 140 141 /** 142 * @since 2.1 143 */ 144 public Connection attach(String host, int outboundPort, int inboundPort, boolean tls) throws HL7Exception { 145 return attach(new ConnectionData(host, outboundPort, inboundPort, getHapiContext() 146 .getGenericParser(), getHapiContext().getLowerLayerProtocol(), tls, 147 getHapiContext().getSocketFactory())); 148 } 149 150 /** 151 * @since 2.0 152 */ 153 public Connection attach(String host, int outboundPort, int inboundPort, Parser parser, 154 Class<? extends LowerLayerProtocol> llpClass) throws HL7Exception { 155 return attach(host, outboundPort, inboundPort, parser, llpClass, false); 156 } 157 158 /** 159 * @since 2.0 160 */ 161 public Connection attach(String host, int outboundPort, int inboundPort, Parser parser, 162 Class<? extends LowerLayerProtocol> llpClass, boolean tls) throws HL7Exception { 163 LowerLayerProtocol llp = ReflectionUtil.instantiate(llpClass); 164 return attach(host, outboundPort, inboundPort, parser, llp, tls); 165 } 166 167 /** 168 * @since 2.0 169 */ 170 public Connection attach(String host, int outboundPort, int inboundPort, Parser parser, 171 LowerLayerProtocol llp, boolean tls) throws HL7Exception { 172 return attach(new ConnectionData(host, outboundPort, inboundPort, parser, llp, tls, null)); 173 } 174 175 /** 176 * @since 2.1 177 */ 178 public Connection attach(String host, int outboundPort, int inboundPort, Parser parser, LowerLayerProtocol llp, boolean tls, SocketFactory socketFactory) throws HL7Exception { 179 return attach(new ConnectionData(host, outboundPort, inboundPort, parser, llp, tls, socketFactory)); 180 } 181 182 /** 183 * @since 2.1 184 */ 185 public Connection attach(String host, int port, Parser parser, LowerLayerProtocol llp, boolean tls, SocketFactory socketFactory) throws HL7Exception { 186 return attach(new ConnectionData(host, port, 0, parser, llp, tls, socketFactory)); 187 } 188 189 /** 190 * @since 2.1 191 */ 192 public Connection attach(DefaultHapiContext hapiContext, String host, int port, boolean tls) throws HL7Exception { 193 return attach(new ConnectionData(host, port, 0, hapiContext.getGenericParser(), hapiContext.getLowerLayerProtocol(), tls, hapiContext.getSocketFactory())); 194 } 195 196 /** 197 * @since 2.1 198 */ 199 public Connection attach(DefaultHapiContext hapiContext, String host, int outboundPort, int inboundPort, boolean tls) throws HL7Exception { 200 return attach(new ConnectionData(host, outboundPort, inboundPort, hapiContext.getGenericParser(), hapiContext.getLowerLayerProtocol(), tls, hapiContext.getSocketFactory())); 201 } 202 203 /** 204 * @since 1.2 205 */ 206 public Connection attach(String host, int port, Parser parser, 207 Class<? extends LowerLayerProtocol> llpClass) throws HL7Exception { 208 return attach(host, port, parser, llpClass, false); 209 } 210 211 /** 212 * @since 2.0 213 */ 214 public Connection attach(String host, int port, Parser parser, 215 Class<? extends LowerLayerProtocol> llpClass, boolean tls) throws HL7Exception { 216 return attach(host, port, 0, parser, llpClass, tls); 217 } 218 219 /** 220 * @since 2.0 221 */ 222 public Connection attach(String host, int port, Parser parser, LowerLayerProtocol llp) 223 throws HL7Exception { 224 return attach(host, port, 0, parser, llp, false); 225 } 226 227 228 /** 229 * @since 2.0 230 */ 231 public Connection attach(String host, int port, Parser parser, LowerLayerProtocol llp, 232 boolean tls) throws HL7Exception { 233 return attach(host, port, 0, parser, llp, tls); 234 } 235 236 /** 237 * Informs the ConnectionHub that you are done with the given Connection - if no other code is 238 * using it, it will be closed, so you should not attempt to use a Connection after detaching 239 * from it. If the connection is not enlisted, this method does nothing. 240 */ 241 public void detach(Connection c) { 242 ConnectionData cd = connections.find(c); 243 if (cd != null) 244 connections.remove(cd); 245 } 246 247 /** 248 * Closes and discards the given Connection so that it can not be returned in subsequent calls 249 * to attach(). This method is to be used when there is a problem with a Connection, e.g. socket 250 * connection closed by remote host. 251 */ 252 public void discard(Connection c) { 253 ConnectionData cd = connections.find(c); 254 if (cd != null) 255 connections.removeAllOf(cd); 256 } 257 258 public void discardAll() { 259 for (ConnectionData cd : allConnections()) { 260 connections.removeAllOf(cd); 261 } 262 } 263 264 private void discardConnectionIfStale(Connection conn) { 265 if (conn != null && !conn.isOpen()) { 266 log.info("Discarding connection which appears to be closed. Remote addr: {}", 267 conn.getRemoteAddress()); 268 discard(conn); 269 conn = null; 270 } 271 } 272 273 public Connection getKnownConnection(ConnectionData key) { 274 return connections.get(key); 275 } 276 277 public boolean isOpen(ConnectionData key) { 278 return getKnownConnection(key).isOpen(); 279 } 280 281 /** 282 * Returns the singleton instance of ConnectionHub 283 * 284 * @deprecated Use {@link HapiContext#getConnectionHub()} to get an instance of ConnectionHub. 285 * See <a href="http://hl7api.sourceforge.net/xref/ca/uhn/hl7v2/examples/SendAndReceiveAMessage.html">this example page</a> for an example of how to use ConnectionHub. 286 */ 287 public static ConnectionHub getInstance() { 288 return getInstance(DefaultExecutorService.getDefaultService()); 289 } 290 291 /** 292 * Returns the singleton instance of ConnectionHub. 293 * 294 * @deprecated Use {@link HapiContext#getConnectionHub()} to get an instance of ConnectionHub. 295 * See <a href="http://hl7api.sourceforge.net/xref/ca/uhn/hl7v2/examples/SendAndReceiveAMessage.html">this example page</a> for an example of how to use ConnectionHub. 296 */ 297 public synchronized static ConnectionHub getInstance(ExecutorService service) { 298 if (instance == null || service.isShutdown()) { 299 instance = new ConnectionHub(service); 300 } 301 return instance; 302 } 303 304 /** 305 * Returns the singleton instance of ConnectionHub. 306 * 307 * @deprecated Use {@link HapiContext#getConnectionHub()} to get an instance of ConnectionHub. 308 * See <a href="http://hl7api.sourceforge.net/xref/ca/uhn/hl7v2/examples/SendAndReceiveAMessage.html">this example page</a> for an example of how to use ConnectionHub. 309 */ 310 public static ConnectionHub getInstance(HapiContext context) { 311 if (instance == null || context.getExecutorService().isShutdown()) { 312 instance = new ConnectionHub(context); 313 } 314 return instance; 315 } 316 317 /** 318 * <p> 319 * Returns a new (non-singleton) instance of the ConnectionHub which uses the given executor 320 * service. 321 * </p> 322 * <p> 323 * See <a href="http://hl7api.sourceforge.net/xref/ca/uhn/hl7v2/examples/SendAndReceiveAMessage.html">this example page</a> 324 * for an example of how to use ConnectionHub. 325 * </p> 326 */ 327 public synchronized static ConnectionHub getNewInstance(HapiContext context) { 328 return new ConnectionHub(context); 329 } 330 331 /** 332 * @deprecated default executor service is shut down automatically 333 */ 334 public static void shutdown() { 335 ConnectionHub hub = getInstance(); 336 if (DefaultExecutorService.isDefaultService(hub.getHapiContext().getExecutorService())) { 337 hub.getHapiContext().getExecutorService().shutdown(); 338 instance = null; 339 } 340 } 341 342 /** 343 * Helper class that implements a map that increases/decreases a counter when an entry is 344 * added/removed. It is furthermore intended that an entry's value is derived from its key. 345 * 346 * @param <K> key class 347 * @param <D> managed value class 348 */ 349 private abstract class CountingMap<K, D> { 350 private Map<K, Count> content; 351 352 public CountingMap() { 353 super(); 354 content = new ConcurrentHashMap<K, Count>(); 355 } 356 357 protected abstract void dispose(D value); 358 359 public K find(D value) { 360 for (Map.Entry<K, Count> entry : content.entrySet()) { 361 if (entry.getValue().getValue().equals(value)) { 362 return entry.getKey(); 363 } 364 } 365 return null; 366 } 367 368 public D get(K key) { 369 return content.containsKey(key) ? content.get(key).getValue() : null; 370 } 371 372 public Set<K> keySet() { 373 return Collections.unmodifiableSet(content.keySet()); 374 } 375 376 protected abstract D open(K key) throws Exception; 377 378 /** 379 * If the key exists, the counter is increased. Otherwise, a value is created, and the 380 * key/value pair is added to the map. 381 */ 382 public D put(K key) throws Exception { 383 if (content.containsKey(key)) { 384 return content.put(key, content.get(key).increase()).getValue(); 385 } else { 386 Count c = new Count(open(key)); 387 content.put(key, c); 388 return c.getValue(); 389 } 390 } 391 392 /** 393 * If the counter of the key/value is greater than one, the counter is decreased. Otherwise, 394 * the entry is removed and the value is cleaned up. 395 */ 396 public D remove(K key) { 397 Count pair = content.get(key); 398 if (pair == null) 399 return null; 400 if (pair.isLast()) { 401 return removeAllOf(key); 402 } 403 return content.put(key, content.get(key).decrease()).getValue(); 404 } 405 406 /** 407 * The key/value entry is removed and the value is cleaned up. 408 */ 409 public D removeAllOf(K key) { 410 D removed = content.remove(key).value; 411 dispose(removed); 412 return removed; 413 } 414 415 private class Count { 416 private int count; 417 private D value; 418 419 public Count(D value) { 420 this(value, 1); 421 } 422 423 private Count(D value, int number) { 424 this.value = value; 425 this.count = number; 426 } 427 428 Count decrease() { 429 return !isLast() ? new Count(value, count - 1) : null; 430 } 431 432 public D getValue() { 433 return value; 434 } 435 436 Count increase() { 437 return new Count(value, count + 1); 438 } 439 440 boolean isLast() { 441 return count == 1; 442 } 443 444 } 445 446 } 447 448}