/*
* Copyright (c) 2002, 2005, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.jndi.ldap;
import java.util.Arrays; // JDK 1.2
import java.io.OutputStream;
import javax.naming.ldap.Control;
import java.lang.reflect.Method;
import javax.net.SocketFactory;
Represents identity information about an anonymous LDAP connection.
This base class contains the following information:
- protocol version number
- server's hostname (case-insensitive)
- server's port number
- prototype type (plain or ssl)
- controls to be sent with the LDAP bind request
All other identity classes must be a subclass of ClientId.
Identity subclasses would add more distinguishing information, depending
on the type of authentication that the connection is to have.
The equals() and hashCode() methods of this class and its subclasses are
important because they are used to determine whether two requests for
the same connection are identical, and thus whether the same connection
may be shared. This is especially important for authenticated connections
because a mistake would result in a serious security violation.
Author: Rosanna Lee
/**
* Represents identity information about an anonymous LDAP connection.
* This base class contains the following information:
* - protocol version number
* - server's hostname (case-insensitive)
* - server's port number
* - prototype type (plain or ssl)
* - controls to be sent with the LDAP bind request
*
* All other identity classes must be a subclass of ClientId.
* Identity subclasses would add more distinguishing information, depending
* on the type of authentication that the connection is to have.
*
* The equals() and hashCode() methods of this class and its subclasses are
* important because they are used to determine whether two requests for
* the same connection are identical, and thus whether the same connection
* may be shared. This is especially important for authenticated connections
* because a mistake would result in a serious security violation.
*
* @author Rosanna Lee
*/
class ClientId {
final private int version;
final private String hostname;
final private int port;
final private String protocol;
final private Control[] bindCtls;
final private OutputStream trace;
final private String socketFactory;
final private int myHash;
final private int ctlHash;
private SocketFactory factory = null;
private Method sockComparator = null;
private boolean isDefaultSockFactory = false;
final public static boolean debug = false;
ClientId(int version, String hostname, int port, String protocol,
Control[] bindCtls, OutputStream trace, String socketFactory) {
this.version = version;
this.hostname = hostname.toLowerCase(); // ignore case
this.port = port;
this.protocol = protocol;
this.bindCtls = (bindCtls != null ? (Control[]) bindCtls.clone() : null);
this.trace = trace;
//
// Needed for custom socket factory pooling
//
this.socketFactory = socketFactory;
if ((socketFactory != null) &&
!socketFactory.equals(LdapCtx.DEFAULT_SSL_FACTORY)) {
try {
Class socketFactoryClass = Obj.helper.loadClass(socketFactory);
Class objClass = Class.forName("java.lang.Object");
this.sockComparator = socketFactoryClass.getMethod(
"compare", new Class[]{objClass, objClass});
Method getDefault =
socketFactoryClass.getMethod("getDefault", new Class[]{});
this.factory = (SocketFactory) getDefault.invoke(null, new Object[]{});
} catch (Exception e) {
// Ignore it here, the same exceptions are/will be handled by
// LdapPoolManager and Connection classes.
if (debug) {
System.out.println("ClientId received an exception");
e.printStackTrace();
}
}
} else {
isDefaultSockFactory = true;
}
// The SocketFactory field is not used in the myHash
// computation as there is no right way to compute the hash code
// for this field. There is no harm in skipping it from the hash
// computation
myHash = version + port
+ (trace != null ? trace.hashCode() : 0)
+ (this.hostname != null ? this.hostname.hashCode() : 0)
+ (protocol != null ? protocol.hashCode() : 0)
+ (ctlHash=hashCodeControls(bindCtls));
}
public boolean equals(Object obj) {
if (!(obj instanceof ClientId)) {
return false;
}
ClientId other = (ClientId)obj;
return myHash == other.myHash
&& version == other.version
&& port == other.port
&& trace == other.trace
&& (hostname == other.hostname // null OK
|| (hostname != null && hostname.equals(other.hostname)))
&& (protocol == other.protocol // null OK
|| (protocol != null && protocol.equals(other.protocol)))
&& ctlHash == other.ctlHash
&& (equalsControls(bindCtls, other.bindCtls))
&& (equalsSockFactory(other));
}
public int hashCode() {
return myHash;
}
private static int hashCodeControls(Control[] c) {
if (c == null) {
return 0;
}
int code = 0;
for (int i = 0; i < c.length; i++) {
code = code * 31 + c[i].getID().hashCode();
}
return code;
}
private static boolean equalsControls(Control[] a, Control[] b) {
if (a == b) {
return true; // both null or same
}
if (a == null || b == null) {
return false; // one is non-null
}
if (a.length != b.length) {
return false;
}
for (int i = 0; i < a.length; i++) {
if (!a[i].getID().equals(b[i].getID())
|| a[i].isCritical() != b[i].isCritical()
|| !Arrays.equals(a[i].getEncodedValue(),
b[i].getEncodedValue())) {
return false;
}
}
return true;
}
private boolean equalsSockFactory(ClientId other) {
if (this.isDefaultSockFactory && other.isDefaultSockFactory) {
return true;
}
else if (!other.isDefaultSockFactory) {
return invokeComparator(other, this);
} else {
return invokeComparator(this, other);
}
}
// delegate the comparison work to the SocketFactory class
// as there is no enough information here, to do the comparison
private boolean invokeComparator(ClientId c1, ClientId c2) {
Object ret;
try {
ret = (c1.sockComparator).invoke(
c1.factory, c1.socketFactory, c2.socketFactory);
} catch(Exception e) {
if (debug) {
System.out.println("ClientId received an exception");
e.printStackTrace();
}
// Failed to invoke the comparator; flag unequality
return false;
}
if (((Integer) ret) == 0) {
return true;
}
return false;
}
private static String toStringControls(Control[] ctls) {
if (ctls == null) {
return "";
}
StringBuffer str = new StringBuffer();
for (int i = 0; i < ctls.length; i++) {
str.append(ctls[i].getID());
str.append(' ');
}
return str.toString();
}
public String toString() {
return (hostname + ":" + port + ":" +
(protocol != null ? protocol : "") + ":" +
toStringControls(bindCtls) + ":" +
socketFactory);
}
}