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 "ConnectionHub.java". Description: 010 "Provides access to shared HL7 Connections" 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.IOException; 030 import java.net.Socket; 031 import java.net.UnknownHostException; 032 import java.util.HashMap; 033 import java.util.Iterator; 034 035 import ca.uhn.hl7v2.HL7Exception; 036 import ca.uhn.hl7v2.llp.LLPException; 037 import ca.uhn.hl7v2.llp.LowerLayerProtocol; 038 import ca.uhn.hl7v2.parser.Parser; 039 import ca.uhn.log.HapiLog; 040 import ca.uhn.log.HapiLogFactory; 041 042 /** 043 * Provides access to shared HL7 Connections. The ConnectionHub 044 * has at most one connection to any given address at any time. 045 * @author Bryan Tripp 046 */ 047 public class ConnectionHub { 048 049 private static final HapiLog log = HapiLogFactory.getHapiLog(ConnectionHub.class); 050 051 private static ConnectionHub instance = null; 052 private HashMap connections; 053 private HashMap sockets; 054 private HashMap numRefs; 055 056 /** Creates a new instance of ConnectionHub */ 057 private ConnectionHub() { 058 connections = new HashMap(20); 059 sockets = new HashMap(20); 060 numRefs = new HashMap(20); 061 } 062 063 /** Returns the singleton instance of ConnectionHub */ 064 public synchronized static ConnectionHub getInstance() { 065 if (instance == null) { 066 instance = new ConnectionHub(); 067 } 068 return instance; 069 } 070 071 /** 072 * Returns a Connection to the given address, opening this 073 * Connection if necessary. The given Parser will only be used if a new Connection 074 * is opened, so there is no guarantee that the Connection returnd will be using the 075 * Parser you provide. If you need explicit access to the Parser the Connection 076 * is using, call <code>Connection.getParser()</code>. 077 */ 078 public Connection attach(String host, int port, Parser parser, Class<? extends LowerLayerProtocol> llpClass) throws HL7Exception { 079 Connection conn = getExisting(host, port, parser.getClass(), llpClass); 080 if (conn == null) { 081 try { 082 //Parser p = (Parser) parserClass.newInstance(); 083 LowerLayerProtocol llp = llpClass.newInstance(); 084 conn = connect(host, port, parser, llp); 085 } catch (ClassCastException e) { 086 //Log.tryToLog(cce, "Problem opening new connection to " + host + " port " + port); 087 throw new HL7Exception( "ClassCastException - need a LowerLayerProtocol class to get an Inititator", 088 HL7Exception.APPLICATION_INTERNAL_ERROR, 089 e); 090 } catch (Exception e) { 091 //Log.tryToLog(e, "Problem opening new connection to " + host + " port " + port); 092 throw new HL7Exception( "Can't connect to " + host + " port " + port + ": " + e.getClass().getName() + ": " + e.getMessage(), 093 HL7Exception.APPLICATION_INTERNAL_ERROR, 094 e); 095 } 096 } 097 incrementRefs(conn); 098 return conn; 099 } 100 101 /** Returns an existing connection if one exists, null otherwise */ 102 private Connection getExisting(String host, int port, Class parserClass, Class llpClass) { 103 Connection existing = null; 104 Object o = connections.get(makeHashKey(host, port, parserClass, llpClass)); 105 if (o != null) existing = (Connection) o; 106 return existing; 107 } 108 109 /** 110 * Opens a connection to the given address, and stores it in the 111 * connections Hash. 112 */ 113 private Connection connect(String host, int port, Parser parser, LowerLayerProtocol llp) throws UnknownHostException, IOException, LLPException { 114 Socket s = new Socket(host, port); 115 Connection i = new Connection(parser, llp, s); 116 connections.put(makeHashKey(host, port, parser.getClass(), llp.getClass()), i); 117 sockets.put(makeHashKey(host, port, parser.getClass(), llp.getClass()), s); 118 return i; 119 } 120 121 /** 122 * Informs the ConnectionHub that you are done with the given Connection - 123 * if no other code is using it, it will be closed, so you should not 124 * attempt to use a Connection after detaching from it. 125 */ 126 public void detach(Connection c) { 127 int refs = decrementRefs(c); 128 if (refs == 0) { 129 close(c); 130 } 131 } 132 133 /** 134 * Closes and discards the given Concection so that it can not be returned 135 * in subsequent calls to attach(). This method is to be used when there 136 * is a problem with a Connection, e.g. socket connection closed by remote host. 137 */ 138 public void discard(Connection c) { 139 close(c); 140 } 141 142 /** Closes the given connection & removes from hash - to be called when there are 0 references to it */ 143 private void close(Connection c) { 144 c.close(); 145 146 //remove from "connections" 147 Iterator keys = connections.keySet().iterator(); 148 boolean removed = false; 149 while (keys.hasNext() && !removed) { 150 Object key = keys.next(); 151 Object val = connections.get(key); 152 if (val.hashCode() == c.hashCode()) { 153 connections.remove(key); 154 numRefs.remove(new Integer(c.hashCode())); 155 removed = true; 156 } 157 } 158 } 159 160 /** 161 * This should be called to indicate that a new party is using the 162 * given Connection. 163 * @returns the number of times this Connection is referenced 164 */ 165 private int incrementRefs(Connection c) { 166 return updateRefs(c, 1); 167 } 168 169 /** 170 * This should be called to indicate that some party is ceasing use of the 171 * given Connection. 172 * @returns the number of times this Connection is referenced 173 */ 174 private int decrementRefs(Connection c) { 175 return updateRefs(c, -1); 176 } 177 178 /** Updates the number of references to i - used by incrementRefs and decrementRefs */ 179 private int updateRefs(Connection c, int change) { 180 Integer hashCode = new Integer(c.hashCode()); 181 Object o = numRefs.get(hashCode); 182 int existingRefs = 0; 183 if (o != null) { 184 existingRefs = ((Integer)o).intValue(); 185 } 186 Integer newRefs = new Integer(existingRefs + change); 187 numRefs.put(hashCode, newRefs); 188 return newRefs.intValue(); 189 } 190 191 /** 192 * Creates a consistent hash key using a host (recommend use IP address, not host name), 193 * port number, and the class names of a Parser and LowerLayerProtocol. In other words, 194 * allows us to store and retrieve remote connections using a hash map. 195 */ 196 private static String makeHashKey(String IP, int port, Class parserClass, Class llpClass) { 197 StringBuffer key = new StringBuffer(); 198 key.append(IP); 199 key.append(":"); 200 key.append(port); 201 key.append(":"); 202 key.append(parserClass.getName()); 203 key.append(":"); 204 key.append(llpClass.getName()); 205 return key.toString(); 206 } 207 }