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    }