J. Duke | 319a3b9 | 2007-12-01 00:00:00 +0000 | [diff] [blame^] | 1 | /* |
| 2 | * Copyright 2002-2005 Sun Microsystems, Inc. All Rights Reserved. |
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| 4 | * |
| 5 | * This code is free software; you can redistribute it and/or modify it |
| 6 | * under the terms of the GNU General Public License version 2 only, as |
| 7 | * published by the Free Software Foundation. Sun designates this |
| 8 | * particular file as subject to the "Classpath" exception as provided |
| 9 | * by Sun in the LICENSE file that accompanied this code. |
| 10 | * |
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT |
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| 14 | * version 2 for more details (a copy is included in the LICENSE file that |
| 15 | * accompanied this code). |
| 16 | * |
| 17 | * You should have received a copy of the GNU General Public License version |
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, |
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| 20 | * |
| 21 | * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, |
| 22 | * CA 95054 USA or visit www.sun.com if you need additional information or |
| 23 | * have any questions. |
| 24 | */ |
| 25 | |
| 26 | package com.sun.jndi.ldap; |
| 27 | |
| 28 | import java.util.Arrays; // JDK 1.2 |
| 29 | import java.io.OutputStream; |
| 30 | import javax.naming.ldap.Control; |
| 31 | import java.lang.reflect.Method; |
| 32 | import javax.net.SocketFactory; |
| 33 | |
| 34 | /** |
| 35 | * Represents identity information about an anonymous LDAP connection. |
| 36 | * This base class contains the following information: |
| 37 | * - protocol version number |
| 38 | * - server's hostname (case-insensitive) |
| 39 | * - server's port number |
| 40 | * - prototype type (plain or ssl) |
| 41 | * - controls to be sent with the LDAP bind request |
| 42 | * |
| 43 | * All other identity classes must be a subclass of ClientId. |
| 44 | * Identity subclasses would add more distinguishing information, depending |
| 45 | * on the type of authentication that the connection is to have. |
| 46 | * |
| 47 | * The equals() and hashCode() methods of this class and its subclasses are |
| 48 | * important because they are used to determine whether two requests for |
| 49 | * the same connection are identical, and thus whether the same connection |
| 50 | * may be shared. This is especially important for authenticated connections |
| 51 | * because a mistake would result in a serious security violation. |
| 52 | * |
| 53 | * @author Rosanna Lee |
| 54 | */ |
| 55 | class ClientId { |
| 56 | final private int version; |
| 57 | final private String hostname; |
| 58 | final private int port; |
| 59 | final private String protocol; |
| 60 | final private Control[] bindCtls; |
| 61 | final private OutputStream trace; |
| 62 | final private String socketFactory; |
| 63 | final private int myHash; |
| 64 | final private int ctlHash; |
| 65 | |
| 66 | private SocketFactory factory = null; |
| 67 | private Method sockComparator = null; |
| 68 | private boolean isDefaultSockFactory = false; |
| 69 | final public static boolean debug = false; |
| 70 | |
| 71 | ClientId(int version, String hostname, int port, String protocol, |
| 72 | Control[] bindCtls, OutputStream trace, String socketFactory) { |
| 73 | this.version = version; |
| 74 | this.hostname = hostname.toLowerCase(); // ignore case |
| 75 | this.port = port; |
| 76 | this.protocol = protocol; |
| 77 | this.bindCtls = (bindCtls != null ? (Control[]) bindCtls.clone() : null); |
| 78 | this.trace = trace; |
| 79 | // |
| 80 | // Needed for custom socket factory pooling |
| 81 | // |
| 82 | this.socketFactory = socketFactory; |
| 83 | if ((socketFactory != null) && |
| 84 | !socketFactory.equals(LdapCtx.DEFAULT_SSL_FACTORY)) { |
| 85 | try { |
| 86 | Class socketFactoryClass = Obj.helper.loadClass(socketFactory); |
| 87 | Class objClass = Class.forName("java.lang.Object"); |
| 88 | this.sockComparator = socketFactoryClass.getMethod( |
| 89 | "compare", new Class[]{objClass, objClass}); |
| 90 | Method getDefault = |
| 91 | socketFactoryClass.getMethod("getDefault", new Class[]{}); |
| 92 | this.factory = (SocketFactory) getDefault.invoke(null, new Object[]{}); |
| 93 | } catch (Exception e) { |
| 94 | // Ignore it here, the same exceptions are/will be handled by |
| 95 | // LdapPoolManager and Connection classes. |
| 96 | if (debug) { |
| 97 | System.out.println("ClientId received an exception"); |
| 98 | e.printStackTrace(); |
| 99 | } |
| 100 | } |
| 101 | } else { |
| 102 | isDefaultSockFactory = true; |
| 103 | } |
| 104 | |
| 105 | // The SocketFactory field is not used in the myHash |
| 106 | // computation as there is no right way to compute the hash code |
| 107 | // for this field. There is no harm in skipping it from the hash |
| 108 | // computation |
| 109 | myHash = version + port |
| 110 | + (trace != null ? trace.hashCode() : 0) |
| 111 | + (this.hostname != null ? this.hostname.hashCode() : 0) |
| 112 | + (protocol != null ? protocol.hashCode() : 0) |
| 113 | + (ctlHash=hashCodeControls(bindCtls)); |
| 114 | } |
| 115 | |
| 116 | public boolean equals(Object obj) { |
| 117 | if (!(obj instanceof ClientId)) { |
| 118 | return false; |
| 119 | } |
| 120 | |
| 121 | ClientId other = (ClientId)obj; |
| 122 | |
| 123 | return myHash == other.myHash |
| 124 | && version == other.version |
| 125 | && port == other.port |
| 126 | && trace == other.trace |
| 127 | && (hostname == other.hostname // null OK |
| 128 | || (hostname != null && hostname.equals(other.hostname))) |
| 129 | && (protocol == other.protocol // null OK |
| 130 | || (protocol != null && protocol.equals(other.protocol))) |
| 131 | && ctlHash == other.ctlHash |
| 132 | && (equalsControls(bindCtls, other.bindCtls)) |
| 133 | && (equalsSockFactory(other)); |
| 134 | } |
| 135 | |
| 136 | public int hashCode() { |
| 137 | return myHash; |
| 138 | } |
| 139 | |
| 140 | private static int hashCodeControls(Control[] c) { |
| 141 | if (c == null) { |
| 142 | return 0; |
| 143 | } |
| 144 | |
| 145 | int code = 0; |
| 146 | for (int i = 0; i < c.length; i++) { |
| 147 | code = code * 31 + c[i].getID().hashCode(); |
| 148 | } |
| 149 | return code; |
| 150 | } |
| 151 | |
| 152 | private static boolean equalsControls(Control[] a, Control[] b) { |
| 153 | if (a == b) { |
| 154 | return true; // both null or same |
| 155 | } |
| 156 | if (a == null || b == null) { |
| 157 | return false; // one is non-null |
| 158 | } |
| 159 | if (a.length != b.length) { |
| 160 | return false; |
| 161 | } |
| 162 | |
| 163 | for (int i = 0; i < a.length; i++) { |
| 164 | if (!a[i].getID().equals(b[i].getID()) |
| 165 | || a[i].isCritical() != b[i].isCritical() |
| 166 | || !Arrays.equals(a[i].getEncodedValue(), |
| 167 | b[i].getEncodedValue())) { |
| 168 | return false; |
| 169 | } |
| 170 | } |
| 171 | return true; |
| 172 | } |
| 173 | |
| 174 | private boolean equalsSockFactory(ClientId other) { |
| 175 | if (this.isDefaultSockFactory && other.isDefaultSockFactory) { |
| 176 | return true; |
| 177 | } |
| 178 | else if (!other.isDefaultSockFactory) { |
| 179 | return invokeComparator(other, this); |
| 180 | } else { |
| 181 | return invokeComparator(this, other); |
| 182 | } |
| 183 | } |
| 184 | |
| 185 | // delegate the comparison work to the SocketFactory class |
| 186 | // as there is no enough information here, to do the comparison |
| 187 | private boolean invokeComparator(ClientId c1, ClientId c2) { |
| 188 | Object ret; |
| 189 | try { |
| 190 | ret = (c1.sockComparator).invoke( |
| 191 | c1.factory, c1.socketFactory, c2.socketFactory); |
| 192 | } catch(Exception e) { |
| 193 | if (debug) { |
| 194 | System.out.println("ClientId received an exception"); |
| 195 | e.printStackTrace(); |
| 196 | } |
| 197 | // Failed to invoke the comparator; flag unequality |
| 198 | return false; |
| 199 | } |
| 200 | if (((Integer) ret) == 0) { |
| 201 | return true; |
| 202 | } |
| 203 | return false; |
| 204 | } |
| 205 | |
| 206 | private static String toStringControls(Control[] ctls) { |
| 207 | if (ctls == null) { |
| 208 | return ""; |
| 209 | } |
| 210 | StringBuffer str = new StringBuffer(); |
| 211 | for (int i = 0; i < ctls.length; i++) { |
| 212 | str.append(ctls[i].getID()); |
| 213 | str.append(' '); |
| 214 | } |
| 215 | return str.toString(); |
| 216 | } |
| 217 | |
| 218 | public String toString() { |
| 219 | return (hostname + ":" + port + ":" + |
| 220 | (protocol != null ? protocol : "") + ":" + |
| 221 | toStringControls(bindCtls) + ":" + |
| 222 | socketFactory); |
| 223 | } |
| 224 | } |