0001: /*
0002: * Copyright 1999-2005 Sun Microsystems, Inc. All Rights Reserved.
0003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004: *
0005: * This code is free software; you can redistribute it and/or modify it
0006: * under the terms of the GNU General Public License version 2 only, as
0007: * published by the Free Software Foundation. Sun designates this
0008: * particular file as subject to the "Classpath" exception as provided
0009: * by Sun in the LICENSE file that accompanied this code.
0010: *
0011: * This code is distributed in the hope that it will be useful, but WITHOUT
0012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014: * version 2 for more details (a copy is included in the LICENSE file that
0015: * accompanied this code).
0016: *
0017: * You should have received a copy of the GNU General Public License version
0018: * 2 along with this work; if not, write to the Free Software Foundation,
0019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020: *
0021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022: * CA 95054 USA or visit www.sun.com if you need additional information or
0023: * have any questions.
0024: */
0025:
0026: package com.sun.jndi.ldap;
0027:
0028: import javax.naming.*;
0029: import javax.naming.directory.*;
0030: import javax.naming.spi.*;
0031: import javax.naming.event.*;
0032: import javax.naming.ldap.*;
0033: import javax.naming.ldap.LdapName;
0034: import javax.naming.ldap.Rdn;
0035:
0036: import java.util.Vector;
0037: import java.util.Hashtable;
0038: import java.util.List;
0039: import java.util.StringTokenizer;
0040: import java.util.Enumeration;
0041:
0042: import java.io.IOException;
0043: import java.io.OutputStream;
0044:
0045: import com.sun.jndi.toolkit.ctx.*;
0046: import com.sun.jndi.toolkit.dir.HierMemDirCtx;
0047: import com.sun.jndi.toolkit.dir.SearchFilter;
0048: import com.sun.jndi.ldap.ext.StartTlsResponseImpl;
0049:
0050: /**
0051: * The LDAP context implementation.
0052: *
0053: * Implementation is not thread-safe. Caller must sync as per JNDI spec.
0054: * Members that are used directly or indirectly by internal worker threads
0055: * (Connection, EventQueue, NamingEventNotifier) must be thread-safe.
0056: * Connection - calls LdapClient.processUnsolicited(), which in turn calls
0057: * LdapCtx.convertControls() and LdapCtx.fireUnsolicited().
0058: * convertControls() - no sync; reads envprops and 'this'
0059: * fireUnsolicited() - sync on eventSupport for all references to 'unsolicited'
0060: * (even those in other methods); don't sync on LdapCtx in case caller
0061: * is already sync'ing on it - this would prevent Unsol events from firing
0062: * and the Connection thread to block (thus preventing any other data
0063: * from being read from the connection)
0064: * References to 'eventSupport' need not be sync'ed because these
0065: * methods can only be called after eventSupport has been set first
0066: * (via addNamingListener()).
0067: * EventQueue - no direct or indirect calls to LdapCtx
0068: * NamingEventNotifier - calls newInstance() to get instance for run() to use;
0069: * no sync needed for methods invoked on new instance;
0070: *
0071: * LdapAttribute links to LdapCtx in order to process getAttributeDefinition()
0072: * and getAttributeSyntaxDefinition() calls. It invokes LdapCtx.getSchema(),
0073: * which uses schemaTrees (a Hashtable - already sync). Potential conflict
0074: * of duplicating construction of tree for same subschemasubentry
0075: * but no inconsistency problems.
0076: *
0077: * NamingEnumerations link to LdapCtx for the following:
0078: * 1. increment/decrement enum count so that ctx doesn't close the
0079: * underlying connection
0080: * 2. LdapClient handle to get next batch of results
0081: * 3. Sets LdapCtx's response controls
0082: * 4. Process return code
0083: * 5. For narrowing response controls (using ctx's factories)
0084: * Since processing of NamingEnumeration by client is treated the same as method
0085: * invocation on LdapCtx, caller is responsible for locking.
0086: *
0087: * @author Vincent Ryan
0088: * @author Rosanna Lee
0089: */
0090:
0091: final public class LdapCtx extends ComponentDirContext implements
0092: EventDirContext, LdapContext {
0093:
0094: /*
0095: * Used to store arguments to the search method.
0096: */
0097: final static class SearchArgs {
0098: Name name;
0099: String filter;
0100: SearchControls cons;
0101: String[] reqAttrs; // those attributes originally requested
0102:
0103: SearchArgs(Name name, String filter, SearchControls cons,
0104: String[] ra) {
0105: this .name = name;
0106: this .filter = filter;
0107: this .cons = cons;
0108: this .reqAttrs = ra;
0109: }
0110: }
0111:
0112: private static final boolean debug = false;
0113:
0114: private static final boolean HARD_CLOSE = true;
0115: private static final boolean SOFT_CLOSE = false;
0116:
0117: // ----------------- Constants -----------------
0118:
0119: public static final int DEFAULT_PORT = 389;
0120: public static final int DEFAULT_SSL_PORT = 636;
0121: public static final String DEFAULT_HOST = "localhost";
0122:
0123: private static final boolean DEFAULT_DELETE_RDN = true;
0124: private static final boolean DEFAULT_TYPES_ONLY = false;
0125: private static final int DEFAULT_DEREF_ALIASES = 3; // always deref
0126: private static final int DEFAULT_LDAP_VERSION = LdapClient.LDAP_VERSION3_VERSION2;
0127: private static final int DEFAULT_BATCH_SIZE = 1;
0128: private static final int DEFAULT_REFERRAL_MODE = LdapClient.LDAP_REF_IGNORE;
0129: private static final char DEFAULT_REF_SEPARATOR = '#';
0130:
0131: // Used by LdapPoolManager
0132: static final String DEFAULT_SSL_FACTORY = "javax.net.ssl.SSLSocketFactory"; // use Sun's SSL
0133: private static final int DEFAULT_REFERRAL_LIMIT = 10;
0134: private static final String STARTTLS_REQ_OID = "1.3.6.1.4.1.1466.20037";
0135:
0136: // schema operational and user attributes
0137: private static final String[] SCHEMA_ATTRIBUTES = {
0138: "objectClasses", "attributeTypes", "matchingRules",
0139: "ldapSyntaxes" };
0140:
0141: // --------------- Environment property names ----------
0142:
0143: // LDAP protocol version: "2", "3"
0144: private static final String VERSION = "java.naming.ldap.version";
0145:
0146: // Binary-valued attributes. Space separated string of attribute names.
0147: private static final String BINARY_ATTRIBUTES = "java.naming.ldap.attributes.binary";
0148:
0149: // Delete old RDN during modifyDN: "true", "false"
0150: private static final String DELETE_RDN = "java.naming.ldap.deleteRDN";
0151:
0152: // De-reference aliases: "never", "searching", "finding", "always"
0153: private static final String DEREF_ALIASES = "java.naming.ldap.derefAliases";
0154:
0155: // Return only attribute types (no values)
0156: private static final String TYPES_ONLY = "java.naming.ldap.typesOnly";
0157:
0158: // Separator character for encoding Reference's RefAddrs; default is '#'
0159: private static final String REF_SEPARATOR = "java.naming.ldap.ref.separator";
0160:
0161: // Socket factory
0162: private static final String SOCKET_FACTORY = "java.naming.ldap.factory.socket";
0163:
0164: // Bind Controls (used by LdapReferralException)
0165: static final String BIND_CONTROLS = "java.naming.ldap.control.connect";
0166:
0167: private static final String REFERRAL_LIMIT = "java.naming.ldap.referral.limit";
0168:
0169: // trace BER (java.io.OutputStream)
0170: private static final String TRACE_BER = "com.sun.jndi.ldap.trace.ber";
0171:
0172: // Get around Netscape Schema Bugs
0173: private static final String NETSCAPE_SCHEMA_BUG = "com.sun.jndi.ldap.netscape.schemaBugs";
0174: // deprecated
0175: private static final String OLD_NETSCAPE_SCHEMA_BUG = "com.sun.naming.netscape.schemaBugs"; // for backward compatability
0176:
0177: // Timeout for socket connect
0178: private static final String CONNECT_TIMEOUT = "com.sun.jndi.ldap.connect.timeout";
0179:
0180: // Timeout for reading responses
0181: private static final String READ_TIMEOUT = "com.sun.jndi.ldap.read.timeout";
0182:
0183: // Environment property for connection pooling
0184: private static final String ENABLE_POOL = "com.sun.jndi.ldap.connect.pool";
0185:
0186: // Environment property for the domain name (derived from this context's DN)
0187: private static final String DOMAIN_NAME = "com.sun.jndi.ldap.domainname";
0188:
0189: // ----------------- Fields that don't change -----------------------
0190: private static final NameParser parser = new LdapNameParser();
0191:
0192: // controls that Provider needs
0193: private static final ControlFactory myResponseControlFactory = new DefaultResponseControlFactory();
0194: private static final Control manageReferralControl = new ManageReferralControl(
0195: false);
0196:
0197: private static final HierMemDirCtx EMPTY_SCHEMA = new HierMemDirCtx();
0198: static {
0199: EMPTY_SCHEMA.setReadOnly(new SchemaViolationException(
0200: "Cannot update schema object"));
0201: }
0202:
0203: // ------------ Package private instance variables ----------------
0204: // Cannot be private; used by enums
0205:
0206: // ------- Inherited by derived context instances
0207:
0208: int port_number; // port number of server
0209: String hostname = null; // host name of server (no brackets
0210: // for IPv6 literals)
0211: LdapClient clnt = null; // connection handle
0212: Hashtable envprops = null; // environment properties of context
0213: int handleReferrals = DEFAULT_REFERRAL_MODE; // how referral is handled
0214: boolean hasLdapsScheme = false; // true if the context was created
0215: // using an LDAPS URL.
0216:
0217: // ------- Not inherited by derived context instances
0218:
0219: String currentDN; // DN of this context
0220: Name currentParsedDN; // DN of this context
0221: Vector respCtls = null; // Response controls read
0222: Control[] reqCtls = null; // Controls to be sent with each request
0223:
0224: // ------------- Private instance variables ------------------------
0225:
0226: // ------- Inherited by derived context instances
0227:
0228: private OutputStream trace = null; // output stream for BER debug output
0229: private boolean netscapeSchemaBug = false; // workaround
0230: private Control[] bindCtls = null; // Controls to be sent with LDAP "bind"
0231: private int referralHopLimit = DEFAULT_REFERRAL_LIMIT; // max referral
0232: private Hashtable schemaTrees = null; // schema root of this context
0233: private int batchSize = DEFAULT_BATCH_SIZE; // batch size for search results
0234: private boolean deleteRDN = DEFAULT_DELETE_RDN; // delete the old RDN when modifying DN
0235: private boolean typesOnly = DEFAULT_TYPES_ONLY; // return attribute types (no values)
0236: private int derefAliases = DEFAULT_DEREF_ALIASES;// de-reference alias entries during searching
0237: private char addrEncodingSeparator = DEFAULT_REF_SEPARATOR; // encoding RefAddr
0238:
0239: private Hashtable binaryAttrs = null; // attr values returned as byte[]
0240: private int connectTimeout = -1; // no timeout value
0241: private int readTimeout = -1; // no timeout value
0242: private boolean useSsl = false; // true if SSL protocol is active
0243: private boolean useDefaultPortNumber = false; // no port number was supplied
0244:
0245: // ------- Not inherited by derived context instances
0246:
0247: // True if this context was created by another LdapCtx.
0248: private boolean parentIsLdapCtx = false; // see composeName()
0249:
0250: private int hopCount = 1; // current referral hop count
0251: private String url = null; // URL of context; see getURL()
0252: private EventSupport eventSupport; // Event support helper for this ctx
0253: private boolean unsolicited = false; // if there unsolicited listeners
0254: private boolean sharable = true; // can share connection with other ctx
0255:
0256: // -------------- Constructors -----------------------------------
0257:
0258: public LdapCtx(String dn, String host, int port_number,
0259: Hashtable props, boolean useSsl) throws NamingException {
0260:
0261: this .useSsl = this .hasLdapsScheme = useSsl;
0262:
0263: if (props != null) {
0264: envprops = (Hashtable) props.clone();
0265:
0266: // SSL env prop overrides the useSsl argument
0267: if ("ssl".equals(envprops.get(Context.SECURITY_PROTOCOL))) {
0268: this .useSsl = true;
0269: }
0270:
0271: // %%% These are only examined when the context is created
0272: // %%% because they are only for debugging or workaround purposes.
0273: trace = (OutputStream) envprops.get(TRACE_BER);
0274:
0275: if (props.get(NETSCAPE_SCHEMA_BUG) != null
0276: || props.get(OLD_NETSCAPE_SCHEMA_BUG) != null) {
0277: netscapeSchemaBug = true;
0278: }
0279: }
0280:
0281: currentDN = (dn != null) ? dn : "";
0282: currentParsedDN = parser.parse(currentDN);
0283:
0284: hostname = (host != null && host.length() > 0) ? host
0285: : DEFAULT_HOST;
0286: if (hostname.charAt(0) == '[') {
0287: hostname = hostname.substring(1, hostname.length() - 1);
0288: }
0289:
0290: if (port_number > 0) {
0291: this .port_number = port_number;
0292: } else {
0293: this .port_number = this .useSsl ? DEFAULT_SSL_PORT
0294: : DEFAULT_PORT;
0295: this .useDefaultPortNumber = true;
0296: }
0297:
0298: schemaTrees = new Hashtable(11, 0.75f);
0299: initEnv();
0300: connect(false);
0301: }
0302:
0303: LdapCtx(LdapCtx existing, String newDN) throws NamingException {
0304: useSsl = existing.useSsl;
0305: hasLdapsScheme = existing.hasLdapsScheme;
0306: useDefaultPortNumber = existing.useDefaultPortNumber;
0307:
0308: hostname = existing.hostname;
0309: port_number = existing.port_number;
0310: currentDN = newDN;
0311: if (existing.currentDN == currentDN) {
0312: currentParsedDN = existing.currentParsedDN;
0313: } else {
0314: currentParsedDN = parser.parse(currentDN);
0315: }
0316:
0317: envprops = existing.envprops;
0318: schemaTrees = existing.schemaTrees;
0319:
0320: clnt = existing.clnt;
0321: clnt.incRefCount();
0322:
0323: parentIsLdapCtx = ((newDN == null || newDN
0324: .equals(existing.currentDN)) ? existing.parentIsLdapCtx
0325: : true);
0326:
0327: // inherit these debugging/workaround flags
0328: trace = existing.trace;
0329: netscapeSchemaBug = existing.netscapeSchemaBug;
0330:
0331: initEnv();
0332: }
0333:
0334: public LdapContext newInstance(Control[] reqCtls)
0335: throws NamingException {
0336:
0337: LdapContext clone = new LdapCtx(this , currentDN);
0338:
0339: // Connection controls are inherited from environment
0340:
0341: // Set clone's request controls
0342: // setRequestControls() will clone reqCtls
0343: clone.setRequestControls(reqCtls);
0344: return clone;
0345: }
0346:
0347: // --------------- Namespace Updates ---------------------
0348: // -- bind/rebind/unbind
0349: // -- rename
0350: // -- createSubcontext/destroySubcontext
0351:
0352: protected void c_bind(Name name, Object obj, Continuation cont)
0353: throws NamingException {
0354: c_bind(name, obj, null, cont);
0355: }
0356:
0357: /*
0358: * attrs == null
0359: * if obj is DirContext, attrs = obj.getAttributes()
0360: * if attrs == null && obj == null
0361: * disallow (cannot determine objectclass to use)
0362: * if obj == null
0363: * just create entry using attrs
0364: * else
0365: * objAttrs = create attributes for representing obj
0366: * attrs += objAttrs
0367: * create entry using attrs
0368: */
0369: protected void c_bind(Name name, Object obj, Attributes attrs,
0370: Continuation cont) throws NamingException {
0371:
0372: cont.setError(this , name);
0373:
0374: Attributes inputAttrs = attrs; // Attributes supplied by caller
0375: try {
0376: ensureOpen();
0377:
0378: if (obj == null) {
0379: if (attrs == null) {
0380: throw new IllegalArgumentException(
0381: "cannot bind null object with no attributes");
0382: }
0383: } else {
0384: attrs = Obj.determineBindAttrs(addrEncodingSeparator,
0385: obj, attrs, false, name, this , envprops); // not cloned
0386: }
0387:
0388: String newDN = fullyQualifiedName(name);
0389: attrs = addRdnAttributes(newDN, attrs, inputAttrs != attrs);
0390: LdapEntry entry = new LdapEntry(newDN, attrs);
0391:
0392: LdapResult answer = clnt.add(entry, reqCtls);
0393: respCtls = answer.resControls; // retrieve response controls
0394:
0395: if (answer.status != LdapClient.LDAP_SUCCESS) {
0396: processReturnCode(answer, name);
0397: }
0398:
0399: } catch (LdapReferralException e) {
0400: if (handleReferrals == LdapClient.LDAP_REF_THROW)
0401: throw cont.fillInException(e);
0402:
0403: // process the referrals sequentially
0404: while (true) {
0405:
0406: LdapReferralContext refCtx = (LdapReferralContext) e
0407: .getReferralContext(envprops, bindCtls);
0408:
0409: // repeat the original operation at the new context
0410: try {
0411:
0412: refCtx.bind(name, obj, inputAttrs);
0413: return;
0414:
0415: } catch (LdapReferralException re) {
0416: e = re;
0417: continue;
0418:
0419: } finally {
0420: // Make sure we close referral context
0421: refCtx.close();
0422: }
0423: }
0424:
0425: } catch (IOException e) {
0426: NamingException e2 = new CommunicationException(e
0427: .getMessage());
0428: e2.setRootCause(e);
0429: throw cont.fillInException(e2);
0430:
0431: } catch (NamingException e) {
0432: throw cont.fillInException(e);
0433: }
0434: }
0435:
0436: protected void c_rebind(Name name, Object obj, Continuation cont)
0437: throws NamingException {
0438: c_rebind(name, obj, null, cont);
0439: }
0440:
0441: /*
0442: * attrs == null
0443: * if obj is DirContext, attrs = obj.getAttributes().
0444: * if attrs == null
0445: * leave any existing attributes alone
0446: * (set attrs = {objectclass=top} if object doesn't exist)
0447: * else
0448: * replace all existing attributes with attrs
0449: * if obj == null
0450: * just create entry using attrs
0451: * else
0452: * objAttrs = create attributes for representing obj
0453: * attrs += objAttrs
0454: * create entry using attrs
0455: */
0456: protected void c_rebind(Name name, Object obj, Attributes attrs,
0457: Continuation cont) throws NamingException {
0458:
0459: cont.setError(this , name);
0460:
0461: Attributes inputAttrs = attrs;
0462:
0463: try {
0464: Attributes origAttrs = null;
0465:
0466: // Check if name is bound
0467: try {
0468: origAttrs = c_getAttributes(name, null, cont);
0469: } catch (NameNotFoundException e) {
0470: }
0471:
0472: // Name not bound, just add it
0473: if (origAttrs == null) {
0474: c_bind(name, obj, attrs, cont);
0475: return;
0476: }
0477:
0478: // there's an object there already, need to figure out
0479: // what to do about its attributes
0480:
0481: if (attrs == null && obj instanceof DirContext) {
0482: attrs = ((DirContext) obj).getAttributes("");
0483: }
0484: Attributes keepAttrs = (Attributes) origAttrs.clone();
0485:
0486: if (attrs == null) {
0487: // we're not changing any attrs, leave old attributes alone
0488:
0489: // Remove Java-related object classes from objectclass attribute
0490: Attribute origObjectClass = origAttrs
0491: .get(Obj.JAVA_ATTRIBUTES[Obj.OBJECT_CLASS]);
0492:
0493: if (origObjectClass != null) {
0494: // clone so that keepAttrs is not affected
0495: origObjectClass = (Attribute) origObjectClass
0496: .clone();
0497: for (int i = 0; i < Obj.JAVA_OBJECT_CLASSES.length; i++) {
0498: origObjectClass
0499: .remove(Obj.JAVA_OBJECT_CLASSES_LOWER[i]);
0500: origObjectClass
0501: .remove(Obj.JAVA_OBJECT_CLASSES[i]);
0502: }
0503: // update;
0504: origAttrs.put(origObjectClass);
0505: }
0506:
0507: // remove all Java-related attributes except objectclass
0508: for (int i = 1; i < Obj.JAVA_ATTRIBUTES.length; i++) {
0509: origAttrs.remove(Obj.JAVA_ATTRIBUTES[i]);
0510: }
0511:
0512: attrs = origAttrs;
0513: }
0514: if (obj != null) {
0515: attrs = Obj.determineBindAttrs(addrEncodingSeparator,
0516: obj, attrs, inputAttrs != attrs, name, this ,
0517: envprops);
0518: }
0519:
0520: String newDN = fullyQualifiedName(name);
0521: // remove entry
0522: LdapResult answer = clnt.delete(newDN, reqCtls);
0523: respCtls = answer.resControls; // retrieve response controls
0524:
0525: if (answer.status != LdapClient.LDAP_SUCCESS) {
0526: processReturnCode(answer, name);
0527: return;
0528: }
0529:
0530: Exception addEx = null;
0531: try {
0532: attrs = addRdnAttributes(newDN, attrs,
0533: inputAttrs != attrs);
0534:
0535: // add it back using updated attrs
0536: LdapEntry entry = new LdapEntry(newDN, attrs);
0537: answer = clnt.add(entry, reqCtls);
0538: if (answer.resControls != null) {
0539: respCtls = appendVector(respCtls,
0540: answer.resControls);
0541: }
0542: } catch (NamingException ae) {
0543: addEx = ae;
0544: } catch (IOException ae) {
0545: addEx = ae;
0546: }
0547:
0548: if ((addEx != null && !(addEx instanceof LdapReferralException))
0549: || answer.status != LdapClient.LDAP_SUCCESS) {
0550: // Attempt to restore old entry
0551: LdapResult answer2 = clnt.add(new LdapEntry(newDN,
0552: keepAttrs), reqCtls);
0553: if (answer2.resControls != null) {
0554: respCtls = appendVector(respCtls,
0555: answer2.resControls);
0556: }
0557:
0558: if (addEx == null) {
0559: processReturnCode(answer, name);
0560: }
0561: }
0562:
0563: // Rethrow exception
0564: if (addEx instanceof NamingException) {
0565: throw (NamingException) addEx;
0566: } else if (addEx instanceof IOException) {
0567: throw (IOException) addEx;
0568: }
0569:
0570: } catch (LdapReferralException e) {
0571: if (handleReferrals == LdapClient.LDAP_REF_THROW)
0572: throw cont.fillInException(e);
0573:
0574: // process the referrals sequentially
0575: while (true) {
0576:
0577: LdapReferralContext refCtx = (LdapReferralContext) e
0578: .getReferralContext(envprops, bindCtls);
0579:
0580: // repeat the original operation at the new context
0581: try {
0582:
0583: refCtx.rebind(name, obj, inputAttrs);
0584: return;
0585:
0586: } catch (LdapReferralException re) {
0587: e = re;
0588: continue;
0589:
0590: } finally {
0591: // Make sure we close referral context
0592: refCtx.close();
0593: }
0594: }
0595:
0596: } catch (IOException e) {
0597: NamingException e2 = new CommunicationException(e
0598: .getMessage());
0599: e2.setRootCause(e);
0600: throw cont.fillInException(e2);
0601:
0602: } catch (NamingException e) {
0603: throw cont.fillInException(e);
0604: }
0605: }
0606:
0607: protected void c_unbind(Name name, Continuation cont)
0608: throws NamingException {
0609: cont.setError(this , name);
0610:
0611: try {
0612: ensureOpen();
0613:
0614: String fname = fullyQualifiedName(name);
0615: LdapResult answer = clnt.delete(fname, reqCtls);
0616: respCtls = answer.resControls; // retrieve response controls
0617:
0618: adjustDeleteStatus(fname, answer);
0619:
0620: if (answer.status != LdapClient.LDAP_SUCCESS) {
0621: processReturnCode(answer, name);
0622: }
0623:
0624: } catch (LdapReferralException e) {
0625: if (handleReferrals == LdapClient.LDAP_REF_THROW)
0626: throw cont.fillInException(e);
0627:
0628: // process the referrals sequentially
0629: while (true) {
0630:
0631: LdapReferralContext refCtx = (LdapReferralContext) e
0632: .getReferralContext(envprops, bindCtls);
0633:
0634: // repeat the original operation at the new context
0635: try {
0636:
0637: refCtx.unbind(name);
0638: return;
0639:
0640: } catch (LdapReferralException re) {
0641: e = re;
0642: continue;
0643:
0644: } finally {
0645: // Make sure we close referral context
0646: refCtx.close();
0647: }
0648: }
0649:
0650: } catch (IOException e) {
0651: NamingException e2 = new CommunicationException(e
0652: .getMessage());
0653: e2.setRootCause(e);
0654: throw cont.fillInException(e2);
0655:
0656: } catch (NamingException e) {
0657: throw cont.fillInException(e);
0658: }
0659: }
0660:
0661: protected void c_rename(Name oldName, Name newName,
0662: Continuation cont) throws NamingException {
0663: Name oldParsed, newParsed;
0664: Name oldParent, newParent;
0665: String newRDN = null;
0666: String newSuperior = null;
0667:
0668: // assert (oldName instanceOf CompositeName);
0669:
0670: cont.setError(this , oldName);
0671:
0672: try {
0673: ensureOpen();
0674:
0675: // permit oldName to be empty (for processing referral contexts)
0676: if (oldName.isEmpty()) {
0677: oldParent = parser.parse("");
0678: } else {
0679: oldParsed = parser.parse(oldName.get(0)); // extract DN & parse
0680: oldParent = oldParsed.getPrefix(oldParsed.size() - 1);
0681: }
0682:
0683: if (newName instanceof CompositeName) {
0684: newParsed = parser.parse(newName.get(0)); // extract DN & parse
0685: } else {
0686: newParsed = newName; // CompoundName/LdapName is already parsed
0687: }
0688: newParent = newParsed.getPrefix(newParsed.size() - 1);
0689:
0690: if (!oldParent.equals(newParent)) {
0691: if (!clnt.isLdapv3) {
0692: throw new InvalidNameException(
0693: "LDAPv2 doesn't support changing "
0694: + "the parent as a result of a rename");
0695: } else {
0696: newSuperior = fullyQualifiedName(newParent
0697: .toString());
0698: }
0699: }
0700:
0701: newRDN = newParsed.get(newParsed.size() - 1);
0702:
0703: LdapResult answer = clnt.moddn(fullyQualifiedName(oldName),
0704: newRDN, deleteRDN, newSuperior, reqCtls);
0705: respCtls = answer.resControls; // retrieve response controls
0706:
0707: if (answer.status != LdapClient.LDAP_SUCCESS) {
0708: processReturnCode(answer, oldName);
0709: }
0710:
0711: } catch (LdapReferralException e) {
0712:
0713: // Record the new RDN (for use after the referral is followed).
0714: e.setNewRdn(newRDN);
0715:
0716: // Cannot continue when a referral has been received and a
0717: // newSuperior name was supplied (because the newSuperior is
0718: // relative to a naming context BEFORE the referral is followed).
0719: if (newSuperior != null) {
0720: PartialResultException pre = new PartialResultException(
0721: "Cannot continue referral processing when newSuperior is "
0722: + "nonempty: " + newSuperior);
0723: pre.setRootCause(cont.fillInException(e));
0724: throw cont.fillInException(pre);
0725: }
0726:
0727: if (handleReferrals == LdapClient.LDAP_REF_THROW)
0728: throw cont.fillInException(e);
0729:
0730: // process the referrals sequentially
0731: while (true) {
0732:
0733: LdapReferralContext refCtx = (LdapReferralContext) e
0734: .getReferralContext(envprops, bindCtls);
0735:
0736: // repeat the original operation at the new context
0737: try {
0738:
0739: refCtx.rename(oldName, newName);
0740: return;
0741:
0742: } catch (LdapReferralException re) {
0743: e = re;
0744: continue;
0745:
0746: } finally {
0747: // Make sure we close referral context
0748: refCtx.close();
0749: }
0750: }
0751:
0752: } catch (IOException e) {
0753: NamingException e2 = new CommunicationException(e
0754: .getMessage());
0755: e2.setRootCause(e);
0756: throw cont.fillInException(e2);
0757:
0758: } catch (NamingException e) {
0759: throw cont.fillInException(e);
0760: }
0761: }
0762:
0763: protected Context c_createSubcontext(Name name, Continuation cont)
0764: throws NamingException {
0765: return c_createSubcontext(name, null, cont);
0766: }
0767:
0768: protected DirContext c_createSubcontext(Name name,
0769: Attributes attrs, Continuation cont) throws NamingException {
0770: cont.setError(this , name);
0771:
0772: Attributes inputAttrs = attrs;
0773: try {
0774: ensureOpen();
0775: if (attrs == null) {
0776: // add structural objectclass; name needs to have "cn"
0777: Attribute oc = new BasicAttribute(
0778: Obj.JAVA_ATTRIBUTES[Obj.OBJECT_CLASS],
0779: Obj.JAVA_OBJECT_CLASSES[Obj.STRUCTURAL]);
0780: oc.add("top");
0781: attrs = new BasicAttributes(true); // case ignore
0782: attrs.put(oc);
0783: }
0784: String newDN = fullyQualifiedName(name);
0785: attrs = addRdnAttributes(newDN, attrs, inputAttrs != attrs);
0786:
0787: LdapEntry entry = new LdapEntry(newDN, attrs);
0788:
0789: LdapResult answer = clnt.add(entry, reqCtls);
0790: respCtls = answer.resControls; // retrieve response controls
0791:
0792: if (answer.status != LdapClient.LDAP_SUCCESS) {
0793: processReturnCode(answer, name);
0794: return null;
0795: }
0796:
0797: // creation successful, get back live object
0798: return new LdapCtx(this , newDN);
0799:
0800: } catch (LdapReferralException e) {
0801: if (handleReferrals == LdapClient.LDAP_REF_THROW)
0802: throw cont.fillInException(e);
0803:
0804: // process the referrals sequentially
0805: while (true) {
0806:
0807: LdapReferralContext refCtx = (LdapReferralContext) e
0808: .getReferralContext(envprops, bindCtls);
0809:
0810: // repeat the original operation at the new context
0811: try {
0812:
0813: return refCtx.createSubcontext(name, inputAttrs);
0814:
0815: } catch (LdapReferralException re) {
0816: e = re;
0817: continue;
0818:
0819: } finally {
0820: // Make sure we close referral context
0821: refCtx.close();
0822: }
0823: }
0824:
0825: } catch (IOException e) {
0826: NamingException e2 = new CommunicationException(e
0827: .getMessage());
0828: e2.setRootCause(e);
0829: throw cont.fillInException(e2);
0830:
0831: } catch (NamingException e) {
0832: throw cont.fillInException(e);
0833: }
0834: }
0835:
0836: protected void c_destroySubcontext(Name name, Continuation cont)
0837: throws NamingException {
0838: cont.setError(this , name);
0839:
0840: try {
0841: ensureOpen();
0842:
0843: String fname = fullyQualifiedName(name);
0844: LdapResult answer = clnt.delete(fname, reqCtls);
0845: respCtls = answer.resControls; // retrieve response controls
0846:
0847: adjustDeleteStatus(fname, answer);
0848:
0849: if (answer.status != LdapClient.LDAP_SUCCESS) {
0850: processReturnCode(answer, name);
0851: }
0852:
0853: } catch (LdapReferralException e) {
0854: if (handleReferrals == LdapClient.LDAP_REF_THROW)
0855: throw cont.fillInException(e);
0856:
0857: // process the referrals sequentially
0858: while (true) {
0859:
0860: LdapReferralContext refCtx = (LdapReferralContext) e
0861: .getReferralContext(envprops, bindCtls);
0862:
0863: // repeat the original operation at the new context
0864: try {
0865:
0866: refCtx.destroySubcontext(name);
0867: return;
0868: } catch (LdapReferralException re) {
0869: e = re;
0870: continue;
0871: } finally {
0872: // Make sure we close referral context
0873: refCtx.close();
0874: }
0875: }
0876: } catch (IOException e) {
0877: NamingException e2 = new CommunicationException(e
0878: .getMessage());
0879: e2.setRootCause(e);
0880: throw cont.fillInException(e2);
0881: } catch (NamingException e) {
0882: throw cont.fillInException(e);
0883: }
0884: }
0885:
0886: /**
0887: * Adds attributes from RDN to attrs if not already present.
0888: * Note that if attrs already contains an attribute by the same name,
0889: * or if the distinguished name is empty, then leave attrs unchanged.
0890: *
0891: * @param dn The non-null DN of the entry to add
0892: * @param attrs The non-null attributes of entry to add
0893: * @param directUpdate Whether attrs can be updated directly
0894: * @returns Non-null attributes with attributes from the RDN added
0895: */
0896: private static Attributes addRdnAttributes(String dn,
0897: Attributes attrs, boolean directUpdate)
0898: throws NamingException {
0899:
0900: // Handle the empty name
0901: if (dn.equals("")) {
0902: return attrs;
0903: }
0904:
0905: // Parse string name into list of RDNs
0906: //List<Rdn> rdnList = (new LdapName(dn)).rdns();
0907: List rdnList = (new LdapName(dn)).getRdns();
0908:
0909: // Get leaf RDN
0910: //Rdn rdn = rdnList.get(rdnList.size() - 1);
0911: Rdn rdn = (Rdn) rdnList.get(rdnList.size() - 1);
0912: Attributes nameAttrs = rdn.toAttributes();
0913:
0914: // Add attributes of RDN to attrs if not already there
0915: NamingEnumeration enum_ = nameAttrs.getAll();
0916: Attribute nameAttr;
0917: while (enum_.hasMore()) {
0918: nameAttr = (Attribute) enum_.next();
0919:
0920: // If attrs already has the attribute, don't change or add to it
0921: if (attrs.get(nameAttr.getID()) == null) {
0922:
0923: /**
0924: * When attrs.isCaseIgnored() is false, attrs.get() will
0925: * return null when the case mis-matches for otherwise
0926: * equal attrIDs.
0927: * As the attrIDs' case is irrelevant for LDAP, ignore
0928: * the case of attrIDs even when attrs.isCaseIgnored() is
0929: * false. This is done by explicitly comparing the elements in
0930: * the enumeration of IDs with their case ignored.
0931: */
0932: if (!attrs.isCaseIgnored()
0933: && containsIgnoreCase(attrs.getIDs(), nameAttr
0934: .getID())) {
0935: continue;
0936: }
0937:
0938: if (!directUpdate) {
0939: attrs = (Attributes) attrs.clone();
0940: directUpdate = true;
0941: }
0942: attrs.put(nameAttr);
0943: }
0944: }
0945:
0946: return attrs;
0947: }
0948:
0949: private static boolean containsIgnoreCase(
0950: NamingEnumeration enumStr, String str)
0951: throws NamingException {
0952: String strEntry;
0953:
0954: while (enumStr.hasMore()) {
0955: strEntry = (String) enumStr.next();
0956: if (strEntry.equalsIgnoreCase(str)) {
0957: return true;
0958: }
0959: }
0960: return false;
0961: }
0962:
0963: private void adjustDeleteStatus(String fname, LdapResult answer) {
0964: if (answer.status == LdapClient.LDAP_NO_SUCH_OBJECT
0965: && answer.matchedDN != null) {
0966: try {
0967: // %%% RL: are there any implications for referrals?
0968:
0969: Name orig = parser.parse(fname);
0970: Name matched = parser.parse(answer.matchedDN);
0971: if ((orig.size() - matched.size()) == 1)
0972: answer.status = LdapClient.LDAP_SUCCESS;
0973: } catch (NamingException e) {
0974: }
0975: }
0976: }
0977:
0978: /*
0979: * Append the the second Vector onto the first Vector
0980: * (v2 must be non-null)
0981: */
0982: private static Vector appendVector(Vector v1, Vector v2) {
0983: if (v1 == null) {
0984: v1 = v2;
0985: } else {
0986: for (int i = 0; i < v2.size(); i++) {
0987: v1.addElement(v2.elementAt(i));
0988: }
0989: }
0990: return v1;
0991: }
0992:
0993: // ------------- Lookups and Browsing -------------------------
0994: // lookup/lookupLink
0995: // list/listBindings
0996:
0997: protected Object c_lookupLink(Name name, Continuation cont)
0998: throws NamingException {
0999: return c_lookup(name, cont);
1000: }
1001:
1002: protected Object c_lookup(Name name, Continuation cont)
1003: throws NamingException {
1004: cont.setError(this , name);
1005: Object obj = null;
1006: Attributes attrs;
1007:
1008: try {
1009: SearchControls cons = new SearchControls();
1010: cons.setSearchScope(SearchControls.OBJECT_SCOPE);
1011: cons.setReturningAttributes(null); // ask for all attributes
1012: cons.setReturningObjFlag(true); // need values to construct obj
1013:
1014: LdapResult answer = doSearchOnce(name, "(objectClass=*)",
1015: cons, true);
1016: respCtls = answer.resControls; // retrieve response controls
1017:
1018: // should get back 1 SearchResponse and 1 SearchResult
1019:
1020: if (answer.status != LdapClient.LDAP_SUCCESS) {
1021: processReturnCode(answer, name);
1022: }
1023:
1024: if (answer.entries == null || answer.entries.size() != 1) {
1025: // found it but got no attributes
1026: attrs = new BasicAttributes(LdapClient.caseIgnore);
1027: } else {
1028: LdapEntry entry = (LdapEntry) answer.entries
1029: .elementAt(0);
1030: attrs = entry.attributes;
1031:
1032: Vector entryCtls = entry.respCtls; // retrieve entry controls
1033: if (entryCtls != null) {
1034: appendVector(respCtls, entryCtls); // concatenate controls
1035: }
1036: }
1037:
1038: if (attrs.get(Obj.JAVA_ATTRIBUTES[Obj.CLASSNAME]) != null) {
1039: // serialized object or object reference
1040: obj = Obj.decodeObject(attrs);
1041: }
1042: if (obj == null) {
1043: obj = new LdapCtx(this , fullyQualifiedName(name));
1044: }
1045: } catch (LdapReferralException e) {
1046: if (handleReferrals == LdapClient.LDAP_REF_THROW)
1047: throw cont.fillInException(e);
1048:
1049: // process the referrals sequentially
1050: while (true) {
1051:
1052: LdapReferralContext refCtx = (LdapReferralContext) e
1053: .getReferralContext(envprops, bindCtls);
1054: // repeat the original operation at the new context
1055: try {
1056:
1057: return refCtx.lookup(name);
1058:
1059: } catch (LdapReferralException re) {
1060: e = re;
1061: continue;
1062:
1063: } finally {
1064: // Make sure we close referral context
1065: refCtx.close();
1066: }
1067: }
1068:
1069: } catch (NamingException e) {
1070: throw cont.fillInException(e);
1071: }
1072:
1073: try {
1074: return DirectoryManager.getObjectInstance(obj, name, this ,
1075: envprops, attrs);
1076:
1077: } catch (NamingException e) {
1078: throw cont.fillInException(e);
1079:
1080: } catch (Exception e) {
1081: NamingException e2 = new NamingException(
1082: "problem generating object using object factory");
1083: e2.setRootCause(e);
1084: throw cont.fillInException(e2);
1085: }
1086: }
1087:
1088: protected NamingEnumeration c_list(Name name, Continuation cont)
1089: throws NamingException {
1090: SearchControls cons = new SearchControls();
1091: String[] classAttrs = new String[2];
1092:
1093: classAttrs[0] = Obj.JAVA_ATTRIBUTES[Obj.OBJECT_CLASS];
1094: classAttrs[1] = Obj.JAVA_ATTRIBUTES[Obj.CLASSNAME];
1095: cons.setReturningAttributes(classAttrs);
1096:
1097: // set this flag to override the typesOnly flag
1098: cons.setReturningObjFlag(true);
1099:
1100: cont.setError(this , name);
1101:
1102: LdapResult answer = null;
1103:
1104: try {
1105: answer = doSearch(name, "(objectClass=*)", cons, true, true);
1106:
1107: // list result may contain continuation references
1108: if ((answer.status != LdapClient.LDAP_SUCCESS)
1109: || (answer.referrals != null)) {
1110: processReturnCode(answer, name);
1111: }
1112:
1113: return new LdapNamingEnumeration(this , answer, name, cont);
1114:
1115: } catch (LdapReferralException e) {
1116: if (handleReferrals == LdapClient.LDAP_REF_THROW)
1117: throw cont.fillInException(e);
1118:
1119: // process the referrals sequentially
1120: while (true) {
1121:
1122: LdapReferralContext refCtx = (LdapReferralContext) e
1123: .getReferralContext(envprops, bindCtls);
1124:
1125: // repeat the original operation at the new context
1126: try {
1127:
1128: return refCtx.list(name);
1129:
1130: } catch (LdapReferralException re) {
1131: e = re;
1132: continue;
1133:
1134: } finally {
1135: // Make sure we close referral context
1136: refCtx.close();
1137: }
1138: }
1139:
1140: } catch (LimitExceededException e) {
1141: LdapNamingEnumeration res = new LdapNamingEnumeration(this ,
1142: answer, name, cont);
1143:
1144: res.setNamingException((LimitExceededException) cont
1145: .fillInException(e));
1146: return res;
1147:
1148: } catch (PartialResultException e) {
1149: LdapNamingEnumeration res = new LdapNamingEnumeration(this ,
1150: answer, name, cont);
1151:
1152: res.setNamingException((PartialResultException) cont
1153: .fillInException(e));
1154: return res;
1155:
1156: } catch (NamingException e) {
1157: throw cont.fillInException(e);
1158: }
1159: }
1160:
1161: protected NamingEnumeration c_listBindings(Name name,
1162: Continuation cont) throws NamingException {
1163:
1164: SearchControls cons = new SearchControls();
1165: cons.setReturningAttributes(null); // ask for all attributes
1166: cons.setReturningObjFlag(true); // need values to construct obj
1167:
1168: cont.setError(this , name);
1169:
1170: LdapResult answer = null;
1171:
1172: try {
1173: answer = doSearch(name, "(objectClass=*)", cons, true, true);
1174:
1175: // listBindings result may contain continuation references
1176: if ((answer.status != LdapClient.LDAP_SUCCESS)
1177: || (answer.referrals != null)) {
1178: processReturnCode(answer, name);
1179: }
1180:
1181: return new LdapBindingEnumeration(this , answer, name, cont);
1182:
1183: } catch (LdapReferralException e) {
1184: if (handleReferrals == LdapClient.LDAP_REF_THROW)
1185: throw cont.fillInException(e);
1186:
1187: // process the referrals sequentially
1188: while (true) {
1189:
1190: LdapReferralContext refCtx = (LdapReferralContext) e
1191: .getReferralContext(envprops, bindCtls);
1192:
1193: // repeat the original operation at the new context
1194: try {
1195:
1196: return refCtx.listBindings(name);
1197:
1198: } catch (LdapReferralException re) {
1199: e = re;
1200: continue;
1201:
1202: } finally {
1203: // Make sure we close referral context
1204: refCtx.close();
1205: }
1206: }
1207: } catch (LimitExceededException e) {
1208: LdapBindingEnumeration res = new LdapBindingEnumeration(
1209: this , answer, name, cont);
1210:
1211: res.setNamingException((LimitExceededException) cont
1212: .fillInException(e));
1213: return res;
1214:
1215: } catch (PartialResultException e) {
1216: LdapBindingEnumeration res = new LdapBindingEnumeration(
1217: this , answer, name, cont);
1218:
1219: res.setNamingException((PartialResultException) cont
1220: .fillInException(e));
1221: return res;
1222:
1223: } catch (NamingException e) {
1224: throw cont.fillInException(e);
1225: }
1226: }
1227:
1228: // --------------- Name-related Methods -----------------------
1229: // -- getNameParser/getNameInNamespace/composeName
1230:
1231: protected NameParser c_getNameParser(Name name, Continuation cont)
1232: throws NamingException {
1233: // ignore name, always return same parser
1234: cont.setSuccess();
1235: return parser;
1236: }
1237:
1238: public String getNameInNamespace() {
1239: return currentDN;
1240: }
1241:
1242: public Name composeName(Name name, Name prefix)
1243: throws NamingException {
1244: Name result;
1245:
1246: // Handle compound names. A pair of LdapNames is an easy case.
1247: if ((name instanceof LdapName) && (prefix instanceof LdapName)) {
1248: result = (Name) (prefix.clone());
1249: result.addAll(name);
1250: return new CompositeName().add(result.toString());
1251: }
1252: if (!(name instanceof CompositeName)) {
1253: name = new CompositeName().add(name.toString());
1254: }
1255: if (!(prefix instanceof CompositeName)) {
1256: prefix = new CompositeName().add(prefix.toString());
1257: }
1258:
1259: int prefixLast = prefix.size() - 1;
1260:
1261: if (name.isEmpty() || prefix.isEmpty()
1262: || name.get(0).equals("")
1263: || prefix.get(prefixLast).equals("")) {
1264: return super .composeName(name, prefix);
1265: }
1266:
1267: result = (Name) (prefix.clone());
1268: result.addAll(name);
1269:
1270: if (parentIsLdapCtx) {
1271: String ldapComp = concatNames(result.get(prefixLast + 1),
1272: result.get(prefixLast));
1273: result.remove(prefixLast + 1);
1274: result.remove(prefixLast);
1275: result.add(prefixLast, ldapComp);
1276: }
1277: return result;
1278: }
1279:
1280: private String fullyQualifiedName(Name rel) {
1281: return rel.isEmpty() ? currentDN : fullyQualifiedName(rel
1282: .get(0));
1283: }
1284:
1285: private String fullyQualifiedName(String rel) {
1286: return (concatNames(rel, currentDN));
1287: }
1288:
1289: // used by LdapSearchEnumeration
1290: private static String concatNames(String lesser, String greater) {
1291: if (lesser == null || lesser.equals("")) {
1292: return greater;
1293: } else if (greater == null || greater.equals("")) {
1294: return lesser;
1295: } else {
1296: return (lesser + "," + greater);
1297: }
1298: }
1299:
1300: // --------------- Reading and Updating Attributes
1301: // getAttributes/modifyAttributes
1302:
1303: protected Attributes c_getAttributes(Name name, String[] attrIds,
1304: Continuation cont) throws NamingException {
1305: cont.setError(this , name);
1306:
1307: SearchControls cons = new SearchControls();
1308: cons.setSearchScope(SearchControls.OBJECT_SCOPE);
1309: cons.setReturningAttributes(attrIds);
1310:
1311: try {
1312: LdapResult answer = doSearchOnce(name, "(objectClass=*)",
1313: cons, true);
1314: respCtls = answer.resControls; // retrieve response controls
1315:
1316: if (answer.status != LdapClient.LDAP_SUCCESS) {
1317: processReturnCode(answer, name);
1318: }
1319:
1320: if (answer.entries == null || answer.entries.size() != 1) {
1321: return new BasicAttributes(LdapClient.caseIgnore);
1322: }
1323:
1324: // get attributes from result
1325: LdapEntry entry = (LdapEntry) answer.entries.elementAt(0);
1326:
1327: Vector entryCtls = entry.respCtls; // retrieve entry controls
1328: if (entryCtls != null) {
1329: appendVector(respCtls, entryCtls); // concatenate controls
1330: }
1331:
1332: // do this so attributes can find their schema
1333: setParents(entry.attributes, (Name) name.clone());
1334:
1335: return (entry.attributes);
1336:
1337: } catch (LdapReferralException e) {
1338: if (handleReferrals == LdapClient.LDAP_REF_THROW)
1339: throw cont.fillInException(e);
1340:
1341: // process the referrals sequentially
1342: while (true) {
1343:
1344: LdapReferralContext refCtx = (LdapReferralContext) e
1345: .getReferralContext(envprops, bindCtls);
1346:
1347: // repeat the original operation at the new context
1348: try {
1349:
1350: return refCtx.getAttributes(name, attrIds);
1351:
1352: } catch (LdapReferralException re) {
1353: e = re;
1354: continue;
1355:
1356: } finally {
1357: // Make sure we close referral context
1358: refCtx.close();
1359: }
1360: }
1361:
1362: } catch (NamingException e) {
1363: throw cont.fillInException(e);
1364: }
1365: }
1366:
1367: protected void c_modifyAttributes(Name name, int mod_op,
1368: Attributes attrs, Continuation cont) throws NamingException {
1369:
1370: cont.setError(this , name);
1371:
1372: try {
1373: ensureOpen();
1374:
1375: if (attrs == null || attrs.size() == 0) {
1376: return; // nothing to do
1377: }
1378: String newDN = fullyQualifiedName(name);
1379: int jmod_op = convertToLdapModCode(mod_op);
1380:
1381: // construct mod list
1382: int[] jmods = new int[attrs.size()];
1383: Attribute[] jattrs = new Attribute[attrs.size()];
1384:
1385: NamingEnumeration ae = attrs.getAll();
1386: for (int i = 0; i < jmods.length && ae.hasMore(); i++) {
1387: jmods[i] = jmod_op;
1388: jattrs[i] = (Attribute) ae.next();
1389: }
1390:
1391: LdapResult answer = clnt.modify(newDN, jmods, jattrs,
1392: reqCtls);
1393: respCtls = answer.resControls; // retrieve response controls
1394:
1395: if (answer.status != LdapClient.LDAP_SUCCESS) {
1396: processReturnCode(answer, name);
1397: return;
1398: }
1399:
1400: } catch (LdapReferralException e) {
1401: if (handleReferrals == LdapClient.LDAP_REF_THROW)
1402: throw cont.fillInException(e);
1403:
1404: // process the referrals sequentially
1405: while (true) {
1406:
1407: LdapReferralContext refCtx = (LdapReferralContext) e
1408: .getReferralContext(envprops, bindCtls);
1409:
1410: // repeat the original operation at the new context
1411: try {
1412:
1413: refCtx.modifyAttributes(name, mod_op, attrs);
1414: return;
1415:
1416: } catch (LdapReferralException re) {
1417: e = re;
1418: continue;
1419:
1420: } finally {
1421: // Make sure we close referral context
1422: refCtx.close();
1423: }
1424: }
1425:
1426: } catch (IOException e) {
1427: NamingException e2 = new CommunicationException(e
1428: .getMessage());
1429: e2.setRootCause(e);
1430: throw cont.fillInException(e2);
1431:
1432: } catch (NamingException e) {
1433: throw cont.fillInException(e);
1434: }
1435: }
1436:
1437: protected void c_modifyAttributes(Name name,
1438: ModificationItem[] mods, Continuation cont)
1439: throws NamingException {
1440: cont.setError(this , name);
1441:
1442: try {
1443: ensureOpen();
1444:
1445: if (mods == null || mods.length == 0) {
1446: return; // nothing to do
1447: }
1448: String newDN = fullyQualifiedName(name);
1449:
1450: // construct mod list
1451: int[] jmods = new int[mods.length];
1452: Attribute[] jattrs = new Attribute[mods.length];
1453: ModificationItem mod;
1454: for (int i = 0; i < jmods.length; i++) {
1455: mod = mods[i];
1456: jmods[i] = convertToLdapModCode(mod.getModificationOp());
1457: jattrs[i] = mod.getAttribute();
1458: }
1459:
1460: LdapResult answer = clnt.modify(newDN, jmods, jattrs,
1461: reqCtls);
1462: respCtls = answer.resControls; // retrieve response controls
1463:
1464: if (answer.status != LdapClient.LDAP_SUCCESS) {
1465: processReturnCode(answer, name);
1466: }
1467:
1468: } catch (LdapReferralException e) {
1469: if (handleReferrals == LdapClient.LDAP_REF_THROW)
1470: throw cont.fillInException(e);
1471:
1472: // process the referrals sequentially
1473: while (true) {
1474:
1475: LdapReferralContext refCtx = (LdapReferralContext) e
1476: .getReferralContext(envprops, bindCtls);
1477:
1478: // repeat the original operation at the new context
1479: try {
1480:
1481: refCtx.modifyAttributes(name, mods);
1482: return;
1483:
1484: } catch (LdapReferralException re) {
1485: e = re;
1486: continue;
1487:
1488: } finally {
1489: // Make sure we close referral context
1490: refCtx.close();
1491: }
1492: }
1493:
1494: } catch (IOException e) {
1495: NamingException e2 = new CommunicationException(e
1496: .getMessage());
1497: e2.setRootCause(e);
1498: throw cont.fillInException(e2);
1499:
1500: } catch (NamingException e) {
1501: throw cont.fillInException(e);
1502: }
1503: }
1504:
1505: private static int convertToLdapModCode(int mod_op) {
1506: switch (mod_op) {
1507: case DirContext.ADD_ATTRIBUTE:
1508: return (LdapClient.ADD);
1509:
1510: case DirContext.REPLACE_ATTRIBUTE:
1511: return (LdapClient.REPLACE);
1512:
1513: case DirContext.REMOVE_ATTRIBUTE:
1514: return (LdapClient.DELETE);
1515:
1516: default:
1517: throw new IllegalArgumentException(
1518: "Invalid modification code");
1519: }
1520: }
1521:
1522: // ------------------- Schema -----------------------
1523:
1524: protected DirContext c_getSchema(Name name, Continuation cont)
1525: throws NamingException {
1526: cont.setError(this , name);
1527: try {
1528: return getSchemaTree(name);
1529:
1530: } catch (NamingException e) {
1531: throw cont.fillInException(e);
1532: }
1533: }
1534:
1535: protected DirContext c_getSchemaClassDefinition(Name name,
1536: Continuation cont) throws NamingException {
1537: cont.setError(this , name);
1538:
1539: try {
1540: // retrieve the objectClass attribute from LDAP
1541: Attribute objectClassAttr = c_getAttributes(name,
1542: new String[] { "objectclass" }, cont).get(
1543: "objectclass");
1544: if (objectClassAttr == null || objectClassAttr.size() == 0) {
1545: return EMPTY_SCHEMA;
1546: }
1547:
1548: // retrieve the root of the ObjectClass schema tree
1549: Context ocSchema = (Context) c_getSchema(name, cont)
1550: .lookup(
1551: LdapSchemaParser.OBJECTCLASS_DEFINITION_NAME);
1552:
1553: // create a context to hold the schema objects representing the object
1554: // classes
1555: HierMemDirCtx objectClassCtx = new HierMemDirCtx();
1556: DirContext objectClassDef;
1557: String objectClassName;
1558: for (Enumeration objectClasses = objectClassAttr.getAll(); objectClasses
1559: .hasMoreElements();) {
1560: objectClassName = (String) objectClasses.nextElement();
1561: // %%% Should we fail if not found, or just continue?
1562: objectClassDef = (DirContext) ocSchema
1563: .lookup(objectClassName);
1564: objectClassCtx.bind(objectClassName, objectClassDef);
1565: }
1566:
1567: // Make context read-only
1568: objectClassCtx.setReadOnly(new SchemaViolationException(
1569: "Cannot update schema object"));
1570: return (DirContext) objectClassCtx;
1571:
1572: } catch (NamingException e) {
1573: throw cont.fillInException(e);
1574: }
1575: }
1576:
1577: /*
1578: * getSchemaTree first looks to see if we have already built a
1579: * schema tree for the given entry. If not, it builds a new one and
1580: * stores it in our private hash table
1581: */
1582: private DirContext getSchemaTree(Name name) throws NamingException {
1583: String subschemasubentry = getSchemaEntry(name, true);
1584:
1585: DirContext schemaTree = (DirContext) schemaTrees
1586: .get(subschemasubentry);
1587:
1588: if (schemaTree == null) {
1589: if (debug) {
1590: System.err.println("LdapCtx: building new schema tree "
1591: + this );
1592: }
1593: schemaTree = buildSchemaTree(subschemasubentry);
1594: schemaTrees.put(subschemasubentry, schemaTree);
1595: }
1596:
1597: return schemaTree;
1598: }
1599:
1600: /*
1601: * buildSchemaTree builds the schema tree corresponding to the
1602: * given subschemasubentree
1603: */
1604: private DirContext buildSchemaTree(String subschemasubentry)
1605: throws NamingException {
1606:
1607: // get the schema entry itself
1608: // DO ask for return object here because we need it to
1609: // create context. Since asking for all attrs, we won't
1610: // be transmitting any specific attrIDs (like Java-specific ones).
1611: SearchControls constraints = new SearchControls(
1612: SearchControls.OBJECT_SCOPE, 0, 0, /* count and time limits */
1613: SCHEMA_ATTRIBUTES /* return schema attrs */,
1614: true /* return obj */, false /*deref link */);
1615:
1616: Name sse = (new CompositeName()).add(subschemasubentry);
1617: NamingEnumeration results = searchAux(sse,
1618: "(objectClass=subschema)", constraints, false, true,
1619: new Continuation());
1620:
1621: if (!results.hasMore()) {
1622: throw new OperationNotSupportedException(
1623: "Cannot get read subschemasubentry: "
1624: + subschemasubentry);
1625: }
1626: SearchResult result = (SearchResult) results.next();
1627: results.close();
1628:
1629: Object obj = result.getObject();
1630: if (!(obj instanceof LdapCtx)) {
1631: throw new NamingException(
1632: "Cannot get schema object as DirContext: "
1633: + subschemasubentry);
1634: }
1635:
1636: return LdapSchemaCtx.createSchemaTree(envprops,
1637: subschemasubentry, (LdapCtx) obj /* schema entry */,
1638: result.getAttributes() /* schema attributes */,
1639: netscapeSchemaBug);
1640: }
1641:
1642: /*
1643: * getSchemaEntree returns the DN of the subschemasubentree for the
1644: * given entree. It first looks to see if the given entry has
1645: * a subschema different from that of the root DIT (by looking for
1646: * a "subschemasubentry" attribute). If it doesn't find one, it returns
1647: * the one for the root of the DIT (by looking for the root's
1648: * "subschemasubentry" attribute).
1649: *
1650: * This function is called regardless of the server's version, since
1651: * an administrator may have setup the server to support client schema
1652: * queries. If this function trys a serarch on a v2 server that
1653: * doesn't support schema, one of these two things will happen:
1654: * 1) It will get an exception when querying the root DSE
1655: * 2) It will not find a subschemasubentry on the root DSE
1656: * If either of these things occur and the server is not v3, we
1657: * throw OperationNotSupported.
1658: *
1659: * the relative flag tells whether the given name is relative to this
1660: * context.
1661: */
1662: private String getSchemaEntry(Name name, boolean relative)
1663: throws NamingException {
1664:
1665: // Asks for operational attribute "subschemasubentry"
1666: SearchControls constraints = new SearchControls(
1667: SearchControls.OBJECT_SCOPE,
1668: 0,
1669: 0, /* count and time limits */
1670: new String[] { "subschemasubentry" } /* attr to return */,
1671: false /* returning obj */, false /* deref link */);
1672:
1673: NamingEnumeration results;
1674: try {
1675: results = searchAux(name, "objectclass=*", constraints,
1676: relative, true, new Continuation());
1677:
1678: } catch (NamingException ne) {
1679: if (!clnt.isLdapv3 && currentDN.length() == 0
1680: && name.isEmpty()) {
1681: // we got an error looking for a root entry on an ldapv2
1682: // server. The server must not support schema.
1683: throw new OperationNotSupportedException(
1684: "Cannot get schema information from server");
1685: } else {
1686: throw ne;
1687: }
1688: }
1689:
1690: if (!results.hasMoreElements()) {
1691: throw new ConfigurationException(
1692: "Requesting schema of nonexistent entry: " + name);
1693: }
1694:
1695: SearchResult result = (SearchResult) results.next();
1696: results.close();
1697:
1698: Attribute schemaEntryAttr = result.getAttributes().get(
1699: "subschemasubentry");
1700: //System.err.println("schema entry attrs: " + schemaEntryAttr);
1701:
1702: if (schemaEntryAttr == null || schemaEntryAttr.size() < 0) {
1703: if (currentDN.length() == 0 && name.isEmpty()) {
1704: // the server doesn't have a subschemasubentry in its root DSE.
1705: // therefore, it doesn't support schema.
1706: throw new OperationNotSupportedException(
1707: "Cannot read subschemasubentry of root DSE");
1708: } else {
1709: return getSchemaEntry(new CompositeName(), false);
1710: }
1711: }
1712:
1713: return (String) (schemaEntryAttr.get()); // return schema entry name
1714: }
1715:
1716: // package-private; used by search enum.
1717: // Set attributes to point to this context in case some one
1718: // asked for their schema
1719: void setParents(Attributes attrs, Name name) throws NamingException {
1720: NamingEnumeration ae = attrs.getAll();
1721: while (ae.hasMore()) {
1722: ((LdapAttribute) ae.next()).setParent(this , name);
1723: }
1724: }
1725:
1726: /*
1727: * Returns the URL associated with this context; used by LdapAttribute
1728: * after deserialization to get pointer to this context.
1729: */
1730: String getURL() {
1731: if (url == null) {
1732: url = LdapURL.toUrlString(hostname, port_number, currentDN,
1733: hasLdapsScheme);
1734: }
1735:
1736: return url;
1737: }
1738:
1739: // --------------------- Searches -----------------------------
1740: protected NamingEnumeration c_search(Name name,
1741: Attributes matchingAttributes, Continuation cont)
1742: throws NamingException {
1743: return c_search(name, matchingAttributes, null, cont);
1744: }
1745:
1746: protected NamingEnumeration c_search(Name name,
1747: Attributes matchingAttributes, String[] attributesToReturn,
1748: Continuation cont) throws NamingException {
1749: SearchControls cons = new SearchControls();
1750: cons.setReturningAttributes(attributesToReturn);
1751: String filter;
1752: try {
1753: filter = SearchFilter.format(matchingAttributes);
1754: } catch (NamingException e) {
1755: cont.setError(this , name);
1756: throw cont.fillInException(e);
1757: }
1758: return c_search(name, filter, cons, cont);
1759: }
1760:
1761: protected NamingEnumeration c_search(Name name, String filter,
1762: SearchControls cons, Continuation cont)
1763: throws NamingException {
1764: return searchAux(name, filter, cloneSearchControls(cons), true,
1765: true, cont);
1766: }
1767:
1768: protected NamingEnumeration c_search(Name name, String filterExpr,
1769: Object[] filterArgs, SearchControls cons, Continuation cont)
1770: throws NamingException {
1771: String strfilter;
1772: try {
1773: strfilter = SearchFilter.format(filterExpr, filterArgs);
1774: } catch (NamingException e) {
1775: cont.setError(this , name);
1776: throw cont.fillInException(e);
1777: }
1778: return c_search(name, strfilter, cons, cont);
1779: }
1780:
1781: // Used by NamingNotifier
1782: NamingEnumeration searchAux(Name name, String filter,
1783: SearchControls cons, boolean relative,
1784: boolean waitForReply, Continuation cont)
1785: throws NamingException {
1786:
1787: LdapResult answer = null;
1788: String[] tokens = new String[2]; // stores ldap compare op. values
1789: String[] reqAttrs; // remember what was asked
1790:
1791: if (cons == null) {
1792: cons = new SearchControls();
1793: }
1794: reqAttrs = cons.getReturningAttributes();
1795:
1796: // if objects are requested then request the Java attributes too
1797: // so that the objects can be constructed
1798: if (cons.getReturningObjFlag()) {
1799: if (reqAttrs != null) {
1800:
1801: // check for presence of "*" (user attributes wildcard)
1802: boolean hasWildcard = false;
1803: for (int i = reqAttrs.length - 1; i >= 0; i--) {
1804: if (reqAttrs[i].equals("*")) {
1805: hasWildcard = true;
1806: break;
1807: }
1808: }
1809: if (!hasWildcard) {
1810: String[] totalAttrs = new String[reqAttrs.length
1811: + Obj.JAVA_ATTRIBUTES.length];
1812: System.arraycopy(reqAttrs, 0, totalAttrs, 0,
1813: reqAttrs.length);
1814: System.arraycopy(Obj.JAVA_ATTRIBUTES, 0,
1815: totalAttrs, reqAttrs.length,
1816: Obj.JAVA_ATTRIBUTES.length);
1817:
1818: cons.setReturningAttributes(totalAttrs);
1819: }
1820: }
1821: }
1822:
1823: LdapCtx.SearchArgs args = new LdapCtx.SearchArgs(name, filter,
1824: cons, reqAttrs);
1825:
1826: cont.setError(this , name);
1827: try {
1828: // see if this can be done as a compare, otherwise do a search
1829: if (searchToCompare(filter, cons, tokens)) {
1830: //System.err.println("compare triggered");
1831: answer = compare(name, tokens[0], tokens[1]);
1832: if (!(answer
1833: .compareToSearchResult(fullyQualifiedName(name)))) {
1834: processReturnCode(answer, name);
1835: }
1836: } else {
1837: answer = doSearch(name, filter, cons, relative,
1838: waitForReply);
1839: // search result may contain referrals
1840: processReturnCode(answer, name);
1841: }
1842: return new LdapSearchEnumeration(this , answer,
1843: fullyQualifiedName(name), args, cont);
1844:
1845: } catch (LdapReferralException e) {
1846: if (handleReferrals == LdapClient.LDAP_REF_THROW)
1847: throw cont.fillInException(e);
1848:
1849: // process the referrals sequentially
1850: while (true) {
1851:
1852: LdapReferralContext refCtx = (LdapReferralContext) e
1853: .getReferralContext(envprops, bindCtls);
1854:
1855: // repeat the original operation at the new context
1856: try {
1857:
1858: return refCtx.search(name, filter, cons);
1859:
1860: } catch (LdapReferralException re) {
1861: e = re;
1862: continue;
1863:
1864: } finally {
1865: // Make sure we close referral context
1866: refCtx.close();
1867: }
1868: }
1869:
1870: } catch (LimitExceededException e) {
1871: LdapSearchEnumeration res = new LdapSearchEnumeration(this ,
1872: answer, fullyQualifiedName(name), args, cont);
1873: res.setNamingException(e);
1874: return res;
1875:
1876: } catch (PartialResultException e) {
1877: LdapSearchEnumeration res = new LdapSearchEnumeration(this ,
1878: answer, fullyQualifiedName(name), args, cont);
1879:
1880: res.setNamingException(e);
1881: return res;
1882:
1883: } catch (IOException e) {
1884: NamingException e2 = new CommunicationException(e
1885: .getMessage());
1886: e2.setRootCause(e);
1887: throw cont.fillInException(e2);
1888:
1889: } catch (NamingException e) {
1890: throw cont.fillInException(e);
1891: }
1892: }
1893:
1894: LdapResult getSearchReply(LdapClient eClnt, LdapResult res)
1895: throws NamingException {
1896: // ensureOpen() won't work here because
1897: // session was associated with previous connection
1898:
1899: // %%% RL: we can actually allow the enumeration to continue
1900: // using the old handle but other weird things might happen
1901: // when we hit a referral
1902: if (clnt != eClnt) {
1903: throw new CommunicationException(
1904: "Context's connection changed; unable to continue enumeration");
1905: }
1906:
1907: try {
1908: return eClnt.getSearchReply(batchSize, res, binaryAttrs);
1909: } catch (IOException e) {
1910: NamingException e2 = new CommunicationException(e
1911: .getMessage());
1912: e2.setRootCause(e);
1913: throw e2;
1914: }
1915: }
1916:
1917: // Perform a search. Expect 1 SearchResultEntry and the SearchResultDone.
1918: private LdapResult doSearchOnce(Name name, String filter,
1919: SearchControls cons, boolean relative)
1920: throws NamingException {
1921:
1922: int savedBatchSize = batchSize;
1923: batchSize = 2; // 2 protocol elements
1924:
1925: LdapResult answer = doSearch(name, filter, cons, relative, true);
1926:
1927: batchSize = savedBatchSize;
1928: return answer;
1929: }
1930:
1931: private LdapResult doSearch(Name name, String filter,
1932: SearchControls cons, boolean relative,
1933: boolean waitFirstReply) throws NamingException {
1934: ensureOpen();
1935: try {
1936: int scope;
1937:
1938: switch (cons.getSearchScope()) {
1939: case SearchControls.OBJECT_SCOPE:
1940: scope = LdapClient.SCOPE_BASE_OBJECT;
1941: break;
1942: default:
1943: case SearchControls.ONELEVEL_SCOPE:
1944: scope = LdapClient.SCOPE_ONE_LEVEL;
1945: break;
1946: case SearchControls.SUBTREE_SCOPE:
1947: scope = LdapClient.SCOPE_SUBTREE;
1948: break;
1949: }
1950:
1951: // If cons.getReturningObjFlag() then caller should already
1952: // have make sure to request the appropriate attrs
1953:
1954: String[] retattrs = cons.getReturningAttributes();
1955: if (retattrs != null && retattrs.length == 0) {
1956: // Ldap treats null and empty array the same
1957: // need to replace with single element array
1958: retattrs = new String[1];
1959: retattrs[0] = "1.1";
1960: }
1961:
1962: String nm = (relative ? fullyQualifiedName(name) : (name
1963: .isEmpty() ? "" : name.get(0)));
1964:
1965: // JNDI unit is milliseconds, LDAP unit is seconds.
1966: // Zero means no limit.
1967: int msecLimit = cons.getTimeLimit();
1968: int secLimit = 0;
1969:
1970: if (msecLimit > 0) {
1971: secLimit = (msecLimit / 1000) + 1;
1972: }
1973:
1974: LdapResult answer = clnt.search(nm, scope, derefAliases,
1975: (int) cons.getCountLimit(), secLimit, cons
1976: .getReturningObjFlag() ? false : typesOnly,
1977: retattrs, filter, batchSize, reqCtls, binaryAttrs,
1978: waitFirstReply);
1979: respCtls = answer.resControls; // retrieve response controls
1980: return answer;
1981:
1982: } catch (IOException e) {
1983: NamingException e2 = new CommunicationException(e
1984: .getMessage());
1985: e2.setRootCause(e);
1986: throw e2;
1987: }
1988: }
1989:
1990: /*
1991: * Certain simple JNDI searches are automatically converted to
1992: * LDAP compare operations by the LDAP service provider. A search
1993: * is converted to a compare iff:
1994: *
1995: * - the scope is set to OBJECT_SCOPE
1996: * - the filter string contains a simple assertion: "<type>=<value>"
1997: * - the returning attributes list is present but empty
1998: */
1999:
2000: // returns true if a search can be caried out as a compare, and sets
2001: // tokens[0] and tokens[1] to the type and value respectively.
2002: // e.g. filter "cn=Jon Ruiz" becomes, type "cn" and value "Jon Ruiz"
2003: // This function uses the documents JNDI Compare example as a model
2004: // for when to turn a search into a compare.
2005: private static boolean searchToCompare(String filter,
2006: SearchControls cons, String tokens[]) {
2007:
2008: // if scope is not object-scope, it's really a search
2009: if (cons.getSearchScope() != SearchControls.OBJECT_SCOPE) {
2010: return false;
2011: }
2012:
2013: // if attributes are to be returned, it's really a search
2014: String[] attrs = cons.getReturningAttributes();
2015: if (attrs == null || attrs.length != 0) {
2016: return false;
2017: }
2018:
2019: // if the filter not a simple assertion, it's really a search
2020: if (!filterToAssertion(filter, tokens)) {
2021: return false;
2022: }
2023:
2024: // it can be converted to a compare
2025: return true;
2026: }
2027:
2028: // If the supplied filter is a simple assertion i.e. "<type>=<value>"
2029: // (enclosing parentheses are permitted) then
2030: // filterToAssertion will return true and pass the type and value as
2031: // the first and second elements of tokens respectively.
2032: // precondition: tokens[] must be initialized and be at least of size 2.
2033:
2034: private static boolean filterToAssertion(String filter,
2035: String tokens[]) {
2036:
2037: // find the left and right half of the assertion
2038: StringTokenizer assertionTokenizer = new StringTokenizer(
2039: filter, "=");
2040:
2041: if (assertionTokenizer.countTokens() != 2) {
2042: return false;
2043: }
2044:
2045: tokens[0] = assertionTokenizer.nextToken();
2046: tokens[1] = assertionTokenizer.nextToken();
2047:
2048: // make sure the value does not contain a wildcard
2049: if (tokens[1].indexOf('*') != -1) {
2050: return false;
2051: }
2052:
2053: // test for enclosing parenthesis
2054: boolean hasParens = false;
2055: int len = tokens[1].length();
2056:
2057: if ((tokens[0].charAt(0) == '(')
2058: && (tokens[1].charAt(len - 1) == ')')) {
2059: hasParens = true;
2060:
2061: } else if ((tokens[0].charAt(0) == '(')
2062: || (tokens[1].charAt(len - 1) == ')')) {
2063: return false; // unbalanced
2064: }
2065:
2066: // make sure the left and right half are not expresions themselves
2067: StringTokenizer illegalCharsTokenizer = new StringTokenizer(
2068: tokens[0], "()&|!=~><*", true);
2069:
2070: if (illegalCharsTokenizer.countTokens() != (hasParens ? 2 : 1)) {
2071: return false;
2072: }
2073:
2074: illegalCharsTokenizer = new StringTokenizer(tokens[1],
2075: "()&|!=~><*", true);
2076:
2077: if (illegalCharsTokenizer.countTokens() != (hasParens ? 2 : 1)) {
2078: return false;
2079: }
2080:
2081: // strip off enclosing parenthesis, if present
2082: if (hasParens) {
2083: tokens[0] = tokens[0].substring(1);
2084: tokens[1] = tokens[1].substring(0, len - 1);
2085: }
2086:
2087: return true;
2088: }
2089:
2090: private LdapResult compare(Name name, String type, String value)
2091: throws IOException, NamingException {
2092:
2093: ensureOpen();
2094: String nm = fullyQualifiedName(name);
2095:
2096: LdapResult answer = clnt.compare(nm, type, value, reqCtls);
2097: respCtls = answer.resControls; // retrieve response controls
2098:
2099: return answer;
2100: }
2101:
2102: private static SearchControls cloneSearchControls(
2103: SearchControls cons) {
2104: if (cons == null) {
2105: return null;
2106: }
2107: String[] retAttrs = cons.getReturningAttributes();
2108: if (retAttrs != null) {
2109: String[] attrs = new String[retAttrs.length];
2110: System.arraycopy(retAttrs, 0, attrs, 0, retAttrs.length);
2111: retAttrs = attrs;
2112: }
2113: return new SearchControls(cons.getSearchScope(), cons
2114: .getCountLimit(), cons.getTimeLimit(), retAttrs, cons
2115: .getReturningObjFlag(), cons.getDerefLinkFlag());
2116: }
2117:
2118: // -------------- Environment Properties ------------------
2119:
2120: /**
2121: * Override with noncloning version.
2122: */
2123: protected Hashtable p_getEnvironment() {
2124: return envprops;
2125: }
2126:
2127: public Hashtable getEnvironment() throws NamingException {
2128: return (envprops == null ? new Hashtable(5, 0.75f)
2129: : (Hashtable) envprops.clone());
2130: }
2131:
2132: public Object removeFromEnvironment(String propName)
2133: throws NamingException {
2134:
2135: // not there; just return
2136: if (envprops == null || envprops.get(propName) == null) {
2137: return null;
2138: }
2139:
2140: if (propName.equals(REF_SEPARATOR)) {
2141: addrEncodingSeparator = DEFAULT_REF_SEPARATOR;
2142: } else if (propName.equals(TYPES_ONLY)) {
2143: typesOnly = DEFAULT_TYPES_ONLY;
2144: } else if (propName.equals(DELETE_RDN)) {
2145: deleteRDN = DEFAULT_DELETE_RDN;
2146: } else if (propName.equals(DEREF_ALIASES)) {
2147: derefAliases = DEFAULT_DEREF_ALIASES;
2148: } else if (propName.equals(Context.BATCHSIZE)) {
2149: batchSize = DEFAULT_BATCH_SIZE;
2150: } else if (propName.equals(REFERRAL_LIMIT)) {
2151: referralHopLimit = DEFAULT_REFERRAL_LIMIT;
2152: } else if (propName.equals(Context.REFERRAL)) {
2153: setReferralMode(null, true);
2154: } else if (propName.equals(BINARY_ATTRIBUTES)) {
2155: setBinaryAttributes(null);
2156: } else if (propName.equals(CONNECT_TIMEOUT)) {
2157: connectTimeout = -1;
2158: } else if (propName.equals(READ_TIMEOUT)) {
2159: readTimeout = -1;
2160:
2161: // The following properties affect the connection
2162:
2163: } else if (propName.equals(Context.SECURITY_PROTOCOL)) {
2164: closeConnection(SOFT_CLOSE);
2165: // De-activate SSL and reset the context's url and port number
2166: if (useSsl && !hasLdapsScheme) {
2167: useSsl = false;
2168: url = null;
2169: if (useDefaultPortNumber) {
2170: port_number = DEFAULT_PORT;
2171: }
2172: }
2173: } else if (propName.equals(VERSION)
2174: || propName.equals(SOCKET_FACTORY)) {
2175: closeConnection(SOFT_CLOSE);
2176: } else if (propName.equals(Context.SECURITY_AUTHENTICATION)
2177: || propName.equals(Context.SECURITY_PRINCIPAL)
2178: || propName.equals(Context.SECURITY_CREDENTIALS)) {
2179: sharable = false;
2180: }
2181:
2182: // Update environment; reconnection will use new props
2183: envprops = (Hashtable) envprops.clone();
2184: return envprops.remove(propName);
2185: }
2186:
2187: public Object addToEnvironment(String propName, Object propVal)
2188: throws NamingException {
2189:
2190: // If adding null, call remove
2191: if (propVal == null) {
2192: return removeFromEnvironment(propName);
2193: }
2194:
2195: if (propName.equals(REF_SEPARATOR)) {
2196: setRefSeparator((String) propVal);
2197: } else if (propName.equals(TYPES_ONLY)) {
2198: setTypesOnly((String) propVal);
2199: } else if (propName.equals(DELETE_RDN)) {
2200: setDeleteRDN((String) propVal);
2201: } else if (propName.equals(DEREF_ALIASES)) {
2202: setDerefAliases((String) propVal);
2203: } else if (propName.equals(Context.BATCHSIZE)) {
2204: setBatchSize((String) propVal);
2205: } else if (propName.equals(REFERRAL_LIMIT)) {
2206: setReferralLimit((String) propVal);
2207: } else if (propName.equals(Context.REFERRAL)) {
2208: setReferralMode((String) propVal, true);
2209: } else if (propName.equals(BINARY_ATTRIBUTES)) {
2210: setBinaryAttributes((String) propVal);
2211: } else if (propName.equals(CONNECT_TIMEOUT)) {
2212: setConnectTimeout((String) propVal);
2213: } else if (propName.equals(READ_TIMEOUT)) {
2214: setReadTimeout((String) propVal);
2215: // The following properties affect the connection
2216:
2217: } else if (propName.equals(Context.SECURITY_PROTOCOL)) {
2218: closeConnection(SOFT_CLOSE);
2219: // Activate SSL and reset the context's url and port number
2220: if ("ssl".equals(propVal)) {
2221: useSsl = true;
2222: url = null;
2223: if (useDefaultPortNumber) {
2224: port_number = DEFAULT_SSL_PORT;
2225: }
2226: }
2227: } else if (propName.equals(VERSION)
2228: || propName.equals(SOCKET_FACTORY)) {
2229: closeConnection(SOFT_CLOSE);
2230: } else if (propName.equals(Context.SECURITY_AUTHENTICATION)
2231: || propName.equals(Context.SECURITY_PRINCIPAL)
2232: || propName.equals(Context.SECURITY_CREDENTIALS)) {
2233: sharable = false;
2234: }
2235:
2236: // Update environment; reconnection will use new props
2237: envprops = (envprops == null ? new Hashtable(5, 0.75f)
2238: : (Hashtable) envprops.clone());
2239: return envprops.put(propName, propVal);
2240: }
2241:
2242: /**
2243: * Sets the URL that created the context in the java.naming.provider.url
2244: * property.
2245: */
2246: void setProviderUrl(String providerUrl) { // called by LdapCtxFactory
2247: if (envprops != null) {
2248: envprops.put(Context.PROVIDER_URL, providerUrl);
2249: }
2250: }
2251:
2252: /**
2253: * Sets the domain name for the context in the com.sun.jndi.ldap.domainname
2254: * property.
2255: * Used for hostname verification by Start TLS
2256: */
2257: void setDomainName(String domainName) { // called by LdapCtxFactory
2258: if (envprops != null) {
2259: envprops.put(DOMAIN_NAME, domainName);
2260: }
2261: }
2262:
2263: private void initEnv() throws NamingException {
2264: if (envprops == null) {
2265: // Make sure that referrals are to their default
2266: setReferralMode(null, false);
2267: return;
2268: }
2269:
2270: // Set batch size
2271: setBatchSize((String) envprops.get(Context.BATCHSIZE));
2272:
2273: // Set separator used for encoding RefAddr
2274: setRefSeparator((String) envprops.get(REF_SEPARATOR));
2275:
2276: // Set whether RDN is removed when renaming object
2277: setDeleteRDN((String) envprops.get(DELETE_RDN));
2278:
2279: // Set whether types are returned only
2280: setTypesOnly((String) envprops.get(TYPES_ONLY));
2281:
2282: // Set how aliases are dereferenced
2283: setDerefAliases((String) envprops.get(DEREF_ALIASES));
2284:
2285: // Set the limit on referral chains
2286: setReferralLimit((String) envprops.get(REFERRAL_LIMIT));
2287:
2288: setBinaryAttributes((String) envprops.get(BINARY_ATTRIBUTES));
2289:
2290: bindCtls = cloneControls((Control[]) envprops
2291: .get(BIND_CONTROLS));
2292:
2293: // set referral handling
2294: setReferralMode((String) envprops.get(Context.REFERRAL), false);
2295:
2296: // Set the connect timeout
2297: setConnectTimeout((String) envprops.get(CONNECT_TIMEOUT));
2298:
2299: // Set the read timeout
2300: setReadTimeout((String) envprops.get(READ_TIMEOUT));
2301:
2302: // When connection is created, it will use these and other
2303: // properties from the environment
2304: }
2305:
2306: private void setDeleteRDN(String deleteRDNProp) {
2307: if ((deleteRDNProp != null)
2308: && (deleteRDNProp.equalsIgnoreCase("false"))) {
2309: deleteRDN = false;
2310: } else {
2311: deleteRDN = DEFAULT_DELETE_RDN;
2312: }
2313: }
2314:
2315: private void setTypesOnly(String typesOnlyProp) {
2316: if ((typesOnlyProp != null)
2317: && (typesOnlyProp.equalsIgnoreCase("true"))) {
2318: typesOnly = true;
2319: } else {
2320: typesOnly = DEFAULT_TYPES_ONLY;
2321: }
2322: }
2323:
2324: /**
2325: * Sets the batch size of this context;
2326: */
2327: private void setBatchSize(String batchSizeProp) {
2328: // set batchsize
2329: if (batchSizeProp != null) {
2330: batchSize = Integer.parseInt(batchSizeProp);
2331: } else {
2332: batchSize = DEFAULT_BATCH_SIZE;
2333: }
2334: }
2335:
2336: /**
2337: * Sets the referral mode of this context to 'follow', 'throw' or 'ignore'.
2338: * If referral mode is 'ignore' then activate the manageReferral control.
2339: */
2340: private void setReferralMode(String ref, boolean update) {
2341: // First determine the referral mode
2342: if (ref != null) {
2343: if (ref.equals("follow")) {
2344: handleReferrals = LdapClient.LDAP_REF_FOLLOW;
2345: } else if (ref.equals("throw")) {
2346: handleReferrals = LdapClient.LDAP_REF_THROW;
2347: } else if (ref.equals("ignore")) {
2348: handleReferrals = LdapClient.LDAP_REF_IGNORE;
2349: } else {
2350: throw new IllegalArgumentException("Illegal value for "
2351: + Context.REFERRAL + " property.");
2352: }
2353: } else {
2354: handleReferrals = DEFAULT_REFERRAL_MODE;
2355: }
2356:
2357: if (handleReferrals == LdapClient.LDAP_REF_IGNORE) {
2358: // If ignoring referrals, add manageReferralControl
2359: reqCtls = addControl(reqCtls, manageReferralControl);
2360:
2361: } else if (update) {
2362:
2363: // If we're update an existing context, remove the control
2364: reqCtls = removeControl(reqCtls, manageReferralControl);
2365:
2366: } // else, leave alone; need not update
2367: }
2368:
2369: /**
2370: * Set whether aliases are derefereced during resolution and searches.
2371: */
2372: private void setDerefAliases(String deref) {
2373: if (deref != null) {
2374: if (deref.equals("never")) {
2375: derefAliases = 0; // never de-reference aliases
2376: } else if (deref.equals("searching")) {
2377: derefAliases = 1; // de-reference aliases during searching
2378: } else if (deref.equals("finding")) {
2379: derefAliases = 2; // de-reference during name resolution
2380: } else if (deref.equals("always")) {
2381: derefAliases = 3; // always de-reference aliases
2382: } else {
2383: throw new IllegalArgumentException("Illegal value for "
2384: + DEREF_ALIASES + " property.");
2385: }
2386: } else {
2387: derefAliases = DEFAULT_DEREF_ALIASES;
2388: }
2389: }
2390:
2391: private void setRefSeparator(String sepStr) throws NamingException {
2392: if (sepStr != null && sepStr.length() > 0) {
2393: addrEncodingSeparator = sepStr.charAt(0);
2394: } else {
2395: addrEncodingSeparator = DEFAULT_REF_SEPARATOR;
2396: }
2397: }
2398:
2399: /**
2400: * Sets the limit on referral chains
2401: */
2402: private void setReferralLimit(String referralLimitProp) {
2403: // set referral limit
2404: if (referralLimitProp != null) {
2405: referralHopLimit = Integer.parseInt(referralLimitProp);
2406:
2407: // a zero setting indicates no limit
2408: if (referralHopLimit == 0)
2409: referralHopLimit = Integer.MAX_VALUE;
2410: } else {
2411: referralHopLimit = DEFAULT_REFERRAL_LIMIT;
2412: }
2413: }
2414:
2415: // For counting referral hops
2416: void setHopCount(int hopCount) {
2417: this .hopCount = hopCount;
2418: }
2419:
2420: /**
2421: * Sets the connect timeout value
2422: */
2423: private void setConnectTimeout(String connectTimeoutProp) {
2424: if (connectTimeoutProp != null) {
2425: connectTimeout = Integer.parseInt(connectTimeoutProp);
2426: } else {
2427: connectTimeout = -1;
2428: }
2429: }
2430:
2431: /**
2432: * Sets the read timeout value
2433: */
2434: private void setReadTimeout(String readTimeoutProp) {
2435: if (readTimeoutProp != null) {
2436: readTimeout = Integer.parseInt(readTimeoutProp);
2437: } else {
2438: readTimeout = -1;
2439: }
2440: }
2441:
2442: /*
2443: * Extract URLs from a string. The format of the string is:
2444: *
2445: * <urlstring > ::= "Referral:" <ldapurls>
2446: * <ldapurls> ::= <separator> <ldapurl> | <ldapurls>
2447: * <separator> ::= ASCII linefeed character (0x0a)
2448: * <ldapurl> ::= LDAP URL format (RFC 1959)
2449: */
2450: private static Vector extractURLs(String refString) {
2451:
2452: int separator = 0;
2453: int urlCount = 0;
2454:
2455: // count the number of URLs
2456: while ((separator = refString.indexOf('\n', separator)) >= 0) {
2457: separator++;
2458: urlCount++;
2459: }
2460:
2461: Vector referrals = new Vector(urlCount);
2462: int iURL;
2463: int i = 0;
2464:
2465: separator = refString.indexOf('\n');
2466: iURL = separator + 1;
2467: while ((separator = refString.indexOf('\n', iURL)) >= 0) {
2468: referrals.addElement(refString.substring(iURL, separator));
2469: iURL = separator + 1;
2470: }
2471: referrals.addElement(refString.substring(iURL));
2472:
2473: return referrals;
2474: }
2475:
2476: /*
2477: * Argument is a space-separated list of attribute IDs
2478: * Converts attribute IDs to lowercase before adding to built-in list.
2479: */
2480: private void setBinaryAttributes(String attrIds) {
2481: if (attrIds == null) {
2482: binaryAttrs = null;
2483: } else {
2484: binaryAttrs = new Hashtable(11, 0.75f);
2485: StringTokenizer tokens = new StringTokenizer(attrIds
2486: .toLowerCase(), " ");
2487:
2488: while (tokens.hasMoreTokens()) {
2489: binaryAttrs.put(tokens.nextToken(), Boolean.TRUE);
2490: }
2491: }
2492: }
2493:
2494: // ----------------- Connection ---------------------
2495:
2496: protected void finalize() {
2497: try {
2498: close();
2499: } catch (NamingException e) {
2500: // ignore failures
2501: }
2502: }
2503:
2504: synchronized public void close() throws NamingException {
2505: if (debug) {
2506: System.err.println("LdapCtx: close() called " + this );
2507: (new Throwable()).printStackTrace();
2508: }
2509:
2510: // Event (normal and unsolicited)
2511: if (eventSupport != null) {
2512: eventSupport.cleanup(); // idempotent
2513: removeUnsolicited();
2514: }
2515:
2516: // Enumerations that are keeping the connection alive
2517: if (enumCount > 0) {
2518: if (debug)
2519: System.err.println("LdapCtx: close deferred");
2520: closeRequested = true;
2521: return;
2522: }
2523: closeConnection(SOFT_CLOSE);
2524:
2525: // %%%: RL: There is no need to set these to null, as they're just
2526: // variables whose contents and references will automatically
2527: // be cleaned up when they're no longer referenced.
2528: // Also, setting these to null creates problems for the attribute
2529: // schema-related methods, which need these to work.
2530: /*
2531: schemaTrees = null;
2532: envprops = null;
2533: */
2534: }
2535:
2536: public void reconnect(Control[] connCtls) throws NamingException {
2537: // Update environment
2538: envprops = (envprops == null ? new Hashtable(5, 0.75f)
2539: : (Hashtable) envprops.clone());
2540:
2541: if (connCtls == null) {
2542: envprops.remove(BIND_CONTROLS);
2543: bindCtls = null;
2544: } else {
2545: envprops.put(BIND_CONTROLS,
2546: bindCtls = cloneControls(connCtls));
2547: }
2548:
2549: sharable = false; // can't share with existing contexts
2550: ensureOpen(); // open or reauthenticated
2551: }
2552:
2553: private void ensureOpen() throws NamingException {
2554: ensureOpen(false);
2555: }
2556:
2557: private void ensureOpen(boolean startTLS) throws NamingException {
2558:
2559: try {
2560: if (clnt == null) {
2561: if (debug) {
2562: System.err.println("LdapCtx: Reconnecting " + this );
2563: }
2564:
2565: // reset the cache before a new connection is established
2566: schemaTrees = new Hashtable(11, 0.75f);
2567: connect(startTLS);
2568:
2569: } else if (!sharable || startTLS) {
2570:
2571: synchronized (clnt) {
2572: if (!clnt.isLdapv3 || clnt.referenceCount > 1
2573: || clnt.usingSaslStreams()) {
2574: closeConnection(SOFT_CLOSE);
2575: }
2576: }
2577: // reset the cache before a new connection is established
2578: schemaTrees = new Hashtable(11, 0.75f);
2579: connect(startTLS);
2580: }
2581:
2582: } finally {
2583: sharable = true; // connection is now either new or single-use
2584: // OK for others to start sharing again
2585: }
2586: }
2587:
2588: private void connect(boolean startTLS) throws NamingException {
2589: if (debug) {
2590: System.err.println("LdapCtx: Connecting " + this );
2591: }
2592:
2593: String user = null; // authenticating user
2594: Object passwd = null; // password for authenticating user
2595: String secProtocol = null; // security protocol (e.g. "ssl")
2596: String socketFactory = null; // socket factory
2597: String authMechanism = null; // authentication mechanism
2598: String ver = null;
2599: int ldapVersion; // LDAP protocol version
2600: boolean usePool = false; // enable connection pooling
2601:
2602: if (envprops != null) {
2603: user = (String) envprops.get(Context.SECURITY_PRINCIPAL);
2604: passwd = envprops.get(Context.SECURITY_CREDENTIALS);
2605: ver = (String) envprops.get(VERSION);
2606: secProtocol = useSsl ? "ssl" : (String) envprops
2607: .get(Context.SECURITY_PROTOCOL);
2608: socketFactory = (String) envprops.get(SOCKET_FACTORY);
2609: authMechanism = (String) envprops
2610: .get(Context.SECURITY_AUTHENTICATION);
2611:
2612: usePool = "true".equalsIgnoreCase((String) envprops
2613: .get(ENABLE_POOL));
2614: }
2615:
2616: if (socketFactory == null) {
2617: socketFactory = "ssl".equals(secProtocol) ? DEFAULT_SSL_FACTORY
2618: : null;
2619: }
2620:
2621: if (authMechanism == null) {
2622: authMechanism = (user == null) ? "none" : "simple";
2623: }
2624:
2625: try {
2626: boolean initial = (clnt == null);
2627:
2628: if (initial) {
2629: ldapVersion = (ver != null) ? Integer.parseInt(ver)
2630: : DEFAULT_LDAP_VERSION;
2631:
2632: clnt = LdapClient.getInstance(
2633: usePool, // Whether to use connection pooling
2634:
2635: // Required for LdapClient constructor
2636: hostname, port_number, socketFactory,
2637: connectTimeout, readTimeout, trace,
2638:
2639: // Required for basic client identity
2640: ldapVersion, authMechanism, bindCtls,
2641: secProtocol,
2642:
2643: // Required for simple client identity
2644: user, passwd,
2645:
2646: // Required for SASL client identity
2647: envprops);
2648:
2649: /**
2650: * Pooled connections are preauthenticated;
2651: * newly created ones are not.
2652: */
2653: if (clnt.authenticateCalled()) {
2654: return;
2655: }
2656:
2657: } else if (sharable && startTLS) {
2658: return; // no authentication required
2659:
2660: } else {
2661: // reauthenticating over existing connection;
2662: // only v3 supports this
2663: ldapVersion = LdapClient.LDAP_VERSION3;
2664: }
2665:
2666: LdapResult answer = clnt.authenticate(initial, user,
2667: passwd, ldapVersion, authMechanism, bindCtls,
2668: envprops);
2669:
2670: respCtls = answer.resControls; // retrieve (bind) response controls
2671:
2672: if (answer.status != LdapClient.LDAP_SUCCESS) {
2673: if (initial) {
2674: closeConnection(HARD_CLOSE); // hard close
2675: }
2676: processReturnCode(answer);
2677: }
2678:
2679: } catch (LdapReferralException e) {
2680: if (handleReferrals == LdapClient.LDAP_REF_THROW)
2681: throw e;
2682:
2683: String referral;
2684: LdapURL url;
2685: NamingException saved_ex = null;
2686:
2687: // Process the referrals sequentially (top level) and
2688: // recursively (per referral)
2689: while (true) {
2690:
2691: if ((referral = e.getNextReferral()) == null) {
2692: // No more referrals to follow
2693:
2694: if (saved_ex != null) {
2695: throw (NamingException) (saved_ex
2696: .fillInStackTrace());
2697: } else {
2698: // No saved exception, something must have gone wrong
2699: throw new NamingException(
2700: "Internal error processing referral during connection");
2701: }
2702: }
2703:
2704: // Use host/port number from referral
2705: url = new LdapURL(referral);
2706: hostname = url.getHost();
2707: if ((hostname != null) && (hostname.charAt(0) == '[')) {
2708: hostname = hostname.substring(1,
2709: hostname.length() - 1);
2710: }
2711: port_number = url.getPort();
2712:
2713: // Try to connect again using new host/port number
2714: try {
2715: connect(startTLS);
2716: break;
2717:
2718: } catch (NamingException ne) {
2719: saved_ex = ne;
2720: continue; // follow another referral
2721: }
2722: }
2723: }
2724: }
2725:
2726: private void closeConnection(boolean hardclose) {
2727: removeUnsolicited(); // idempotent
2728:
2729: if (clnt != null) {
2730: if (debug) {
2731: System.err.println("LdapCtx: calling clnt.close() "
2732: + this );
2733: }
2734: clnt.close(reqCtls, hardclose);
2735: clnt = null;
2736: }
2737: }
2738:
2739: // Used by Enum classes to track whether it still needs context
2740: private int enumCount = 0;
2741: private boolean closeRequested = false;
2742:
2743: synchronized void incEnumCount() {
2744: ++enumCount;
2745: if (debug)
2746: System.err.println("LdapCtx: " + this + " enum inc: "
2747: + enumCount);
2748: }
2749:
2750: synchronized void decEnumCount() {
2751: --enumCount;
2752: if (debug)
2753: System.err.println("LdapCtx: " + this + " enum dec: "
2754: + enumCount);
2755:
2756: if (enumCount == 0 && closeRequested) {
2757: try {
2758: close();
2759: } catch (NamingException e) {
2760: // ignore failures
2761: }
2762: }
2763: }
2764:
2765: // ------------ Return code and Error messages -----------------------
2766:
2767: protected void processReturnCode(LdapResult answer)
2768: throws NamingException {
2769: processReturnCode(answer, null, this , null, envprops, null);
2770: }
2771:
2772: void processReturnCode(LdapResult answer, Name remainName)
2773: throws NamingException {
2774: processReturnCode(answer, (new CompositeName()).add(currentDN),
2775: this , remainName, envprops,
2776: fullyQualifiedName(remainName));
2777: }
2778:
2779: protected void processReturnCode(LdapResult res, Name resolvedName,
2780: Object resolvedObj, Name remainName, Hashtable envprops,
2781: String fullDN) throws NamingException {
2782:
2783: String msg = LdapClient.getErrorMessage(res.status,
2784: res.errorMessage);
2785: NamingException e;
2786: LdapReferralException r = null;
2787:
2788: switch (res.status) {
2789:
2790: case LdapClient.LDAP_SUCCESS:
2791:
2792: // handle Search continuation references
2793: if (res.referrals != null) {
2794:
2795: msg = "Unprocessed Continuation Reference(s)";
2796:
2797: if (handleReferrals == LdapClient.LDAP_REF_IGNORE) {
2798: e = new PartialResultException(msg);
2799: break;
2800: }
2801:
2802: // handle multiple sets of URLs
2803: int contRefCount = res.referrals.size();
2804: LdapReferralException head = null;
2805: LdapReferralException ptr = null;
2806:
2807: msg = "Continuation Reference";
2808:
2809: // make a chain of LdapReferralExceptions
2810: for (int i = 0; i < contRefCount; i++) {
2811:
2812: r = new LdapReferralException(resolvedName,
2813: resolvedObj, remainName, msg, envprops,
2814: fullDN, handleReferrals, reqCtls);
2815: r.setReferralInfo((Vector) res.referrals
2816: .elementAt(i), true);
2817:
2818: if (hopCount > 1) {
2819: r.setHopCount(hopCount);
2820: }
2821:
2822: if (head == null) {
2823: head = ptr = r;
2824: } else {
2825: ptr.nextReferralEx = r; // append ex. to end of chain
2826: ptr = r;
2827: }
2828: }
2829: res.referrals = null; // reset
2830:
2831: if (res.refEx == null) {
2832: res.refEx = head;
2833:
2834: } else {
2835: ptr = res.refEx;
2836:
2837: while (ptr.nextReferralEx != null) {
2838: ptr = ptr.nextReferralEx;
2839: }
2840: ptr.nextReferralEx = head;
2841: }
2842:
2843: // check the hop limit
2844: if (hopCount > referralHopLimit) {
2845: NamingException lee = new LimitExceededException(
2846: "Referral limit exceeded");
2847: lee.setRootCause(r);
2848: throw lee;
2849: }
2850: }
2851: return;
2852:
2853: case LdapClient.LDAP_REFERRAL:
2854:
2855: if (handleReferrals == LdapClient.LDAP_REF_IGNORE) {
2856: e = new PartialResultException(msg);
2857: break;
2858: }
2859:
2860: r = new LdapReferralException(resolvedName, resolvedObj,
2861: remainName, msg, envprops, fullDN, handleReferrals,
2862: reqCtls);
2863: // only one set of URLs is present
2864: r.setReferralInfo((Vector) res.referrals.elementAt(0),
2865: false);
2866:
2867: if (hopCount > 1) {
2868: r.setHopCount(hopCount);
2869: }
2870:
2871: // check the hop limit
2872: if (hopCount > referralHopLimit) {
2873: NamingException lee = new LimitExceededException(
2874: "Referral limit exceeded");
2875: lee.setRootCause(r);
2876: e = lee;
2877:
2878: } else {
2879: e = r;
2880: }
2881: break;
2882:
2883: /*
2884: * Handle SLAPD-style referrals.
2885: *
2886: * Referrals received during name resolution should be followed
2887: * until one succeeds - the target entry is located. An exception
2888: * is thrown now to handle these.
2889: *
2890: * Referrals received during a search operation point to unexplored
2891: * parts of the directory and each should be followed. An exception
2892: * is thrown later (during results enumeration) to handle these.
2893: */
2894:
2895: case LdapClient.LDAP_PARTIAL_RESULTS:
2896:
2897: if (handleReferrals == LdapClient.LDAP_REF_IGNORE) {
2898: e = new PartialResultException(msg);
2899: break;
2900: }
2901:
2902: // extract SLAPD-style referrals from errorMessage
2903: if ((res.errorMessage != null)
2904: && (!res.errorMessage.equals(""))) {
2905: res.referrals = extractURLs(res.errorMessage);
2906: } else {
2907: e = new PartialResultException(msg);
2908: break;
2909: }
2910:
2911: // build exception
2912: r = new LdapReferralException(resolvedName, resolvedObj,
2913: remainName, msg, envprops, fullDN, handleReferrals,
2914: reqCtls);
2915:
2916: if (hopCount > 1) {
2917: r.setHopCount(hopCount);
2918: }
2919: /*
2920: * %%%
2921: * SLAPD-style referrals received during name resolution
2922: * cannot be distinguished from those received during a
2923: * search operation. Since both must be handled differently
2924: * the following rule is applied:
2925: *
2926: * If 1 referral and 0 entries is received then
2927: * assume name resolution has not yet completed.
2928: */
2929: if (((res.entries == null) || (res.entries.size() == 0))
2930: && (res.referrals.size() == 1)) {
2931:
2932: r.setReferralInfo((Vector) res.referrals, false);
2933:
2934: // check the hop limit
2935: if (hopCount > referralHopLimit) {
2936: NamingException lee = new LimitExceededException(
2937: "Referral limit exceeded");
2938: lee.setRootCause(r);
2939: e = lee;
2940:
2941: } else {
2942: e = r;
2943: }
2944:
2945: } else {
2946: r.setReferralInfo(res.referrals, true);
2947: res.refEx = r;
2948: return;
2949: }
2950: break;
2951:
2952: case LdapClient.LDAP_INVALID_DN_SYNTAX:
2953: case LdapClient.LDAP_NAMING_VIOLATION:
2954:
2955: if (remainName != null) {
2956: e = new InvalidNameException(remainName.toString()
2957: + ": " + msg);
2958: } else {
2959: e = new InvalidNameException(msg);
2960: }
2961: break;
2962:
2963: default:
2964: e = mapErrorCode(res.status, res.errorMessage);
2965: break;
2966: }
2967: e.setResolvedName(resolvedName);
2968: e.setResolvedObj(resolvedObj);
2969: e.setRemainingName(remainName);
2970: throw e;
2971: }
2972:
2973: /**
2974: * Maps an LDAP error code to an appropriate NamingException.
2975: * %%% public; used by controls
2976: *
2977: * @param errorCode numeric LDAP error code
2978: * @param errorMessage textual description of the LDAP error. May be null.
2979: *
2980: * @return A NamingException or null if the error code indicates success.
2981: */
2982: public static NamingException mapErrorCode(int errorCode,
2983: String errorMessage) {
2984:
2985: if (errorCode == LdapClient.LDAP_SUCCESS)
2986: return null;
2987:
2988: NamingException e = null;
2989: String message = LdapClient.getErrorMessage(errorCode,
2990: errorMessage);
2991:
2992: switch (errorCode) {
2993:
2994: case LdapClient.LDAP_ALIAS_DEREFERENCING_PROBLEM:
2995: e = new NamingException(message);
2996: break;
2997:
2998: case LdapClient.LDAP_ALIAS_PROBLEM:
2999: e = new NamingException(message);
3000: break;
3001:
3002: case LdapClient.LDAP_ATTRIBUTE_OR_VALUE_EXISTS:
3003: e = new AttributeInUseException(message);
3004: break;
3005:
3006: case LdapClient.LDAP_AUTH_METHOD_NOT_SUPPORTED:
3007: case LdapClient.LDAP_CONFIDENTIALITY_REQUIRED:
3008: case LdapClient.LDAP_STRONG_AUTH_REQUIRED:
3009: case LdapClient.LDAP_INAPPROPRIATE_AUTHENTICATION:
3010: e = new AuthenticationNotSupportedException(message);
3011: break;
3012:
3013: case LdapClient.LDAP_ENTRY_ALREADY_EXISTS:
3014: e = new NameAlreadyBoundException(message);
3015: break;
3016:
3017: case LdapClient.LDAP_INVALID_CREDENTIALS:
3018: case LdapClient.LDAP_SASL_BIND_IN_PROGRESS:
3019: e = new AuthenticationException(message);
3020: break;
3021:
3022: case LdapClient.LDAP_INAPPROPRIATE_MATCHING:
3023: e = new InvalidSearchFilterException(message);
3024: break;
3025:
3026: case LdapClient.LDAP_INSUFFICIENT_ACCESS_RIGHTS:
3027: e = new NoPermissionException(message);
3028: break;
3029:
3030: case LdapClient.LDAP_INVALID_ATTRIBUTE_SYNTAX:
3031: case LdapClient.LDAP_CONSTRAINT_VIOLATION:
3032: e = new InvalidAttributeValueException(message);
3033: break;
3034:
3035: case LdapClient.LDAP_LOOP_DETECT:
3036: e = new NamingException(message);
3037: break;
3038:
3039: case LdapClient.LDAP_NO_SUCH_ATTRIBUTE:
3040: e = new NoSuchAttributeException(message);
3041: break;
3042:
3043: case LdapClient.LDAP_NO_SUCH_OBJECT:
3044: e = new NameNotFoundException(message);
3045: break;
3046:
3047: case LdapClient.LDAP_OBJECT_CLASS_MODS_PROHIBITED:
3048: case LdapClient.LDAP_OBJECT_CLASS_VIOLATION:
3049: case LdapClient.LDAP_NOT_ALLOWED_ON_RDN:
3050: e = new SchemaViolationException(message);
3051: break;
3052:
3053: case LdapClient.LDAP_NOT_ALLOWED_ON_NON_LEAF:
3054: e = new ContextNotEmptyException(message);
3055: break;
3056:
3057: case LdapClient.LDAP_OPERATIONS_ERROR:
3058: // %%% need new exception ?
3059: e = new NamingException(message);
3060: break;
3061:
3062: case LdapClient.LDAP_OTHER:
3063: e = new NamingException(message);
3064: break;
3065:
3066: case LdapClient.LDAP_PROTOCOL_ERROR:
3067: e = new CommunicationException(message);
3068: break;
3069:
3070: case LdapClient.LDAP_SIZE_LIMIT_EXCEEDED:
3071: e = new SizeLimitExceededException(message);
3072: break;
3073:
3074: case LdapClient.LDAP_TIME_LIMIT_EXCEEDED:
3075: e = new TimeLimitExceededException(message);
3076: break;
3077:
3078: case LdapClient.LDAP_UNAVAILABLE_CRITICAL_EXTENSION:
3079: e = new OperationNotSupportedException(message);
3080: break;
3081:
3082: case LdapClient.LDAP_UNAVAILABLE:
3083: case LdapClient.LDAP_BUSY:
3084: e = new ServiceUnavailableException(message);
3085: break;
3086:
3087: case LdapClient.LDAP_UNDEFINED_ATTRIBUTE_TYPE:
3088: e = new InvalidAttributeIdentifierException(message);
3089: break;
3090:
3091: case LdapClient.LDAP_UNWILLING_TO_PERFORM:
3092: e = new OperationNotSupportedException(message);
3093: break;
3094:
3095: case LdapClient.LDAP_COMPARE_FALSE:
3096: case LdapClient.LDAP_COMPARE_TRUE:
3097: case LdapClient.LDAP_IS_LEAF:
3098: // these are really not exceptions and this code probably
3099: // never gets executed
3100: e = new NamingException(message);
3101: break;
3102:
3103: case LdapClient.LDAP_ADMIN_LIMIT_EXCEEDED:
3104: e = new LimitExceededException(message);
3105: break;
3106:
3107: case LdapClient.LDAP_REFERRAL:
3108: e = new NamingException(message);
3109: break;
3110:
3111: case LdapClient.LDAP_PARTIAL_RESULTS:
3112: e = new NamingException(message);
3113: break;
3114:
3115: case LdapClient.LDAP_INVALID_DN_SYNTAX:
3116: case LdapClient.LDAP_NAMING_VIOLATION:
3117: e = new InvalidNameException(message);
3118: break;
3119:
3120: default:
3121: e = new NamingException(message);
3122: break;
3123: }
3124:
3125: return e;
3126: }
3127:
3128: // ----------------- Extensions and Controls -------------------
3129:
3130: public ExtendedResponse extendedOperation(ExtendedRequest request)
3131: throws NamingException {
3132:
3133: boolean startTLS = (request.getID().equals(STARTTLS_REQ_OID));
3134: ensureOpen(startTLS);
3135:
3136: try {
3137:
3138: LdapResult answer = clnt.extendedOp(request.getID(),
3139: request.getEncodedValue(), reqCtls, startTLS);
3140: respCtls = answer.resControls; // retrieve response controls
3141:
3142: if (answer.status != LdapClient.LDAP_SUCCESS) {
3143: processReturnCode(answer, new CompositeName());
3144: }
3145: // %%% verify request.getID() == answer.extensionId
3146:
3147: int len = (answer.extensionValue == null) ? 0
3148: : answer.extensionValue.length;
3149:
3150: ExtendedResponse er = request.createExtendedResponse(
3151: answer.extensionId, answer.extensionValue, 0, len);
3152:
3153: if (er instanceof StartTlsResponseImpl) {
3154: // Pass the connection handle to StartTlsResponseImpl
3155: String domainName = (String) (envprops != null ? envprops
3156: .get(DOMAIN_NAME)
3157: : null);
3158: ((StartTlsResponseImpl) er).setConnection(clnt.conn,
3159: domainName);
3160: }
3161: return er;
3162:
3163: } catch (LdapReferralException e) {
3164:
3165: if (handleReferrals == LdapClient.LDAP_REF_THROW)
3166: throw e;
3167:
3168: // process the referrals sequentially
3169: while (true) {
3170:
3171: LdapReferralContext refCtx = (LdapReferralContext) e
3172: .getReferralContext(envprops, bindCtls);
3173:
3174: // repeat the original operation at the new context
3175: try {
3176:
3177: return refCtx.extendedOperation(request);
3178:
3179: } catch (LdapReferralException re) {
3180: e = re;
3181: continue;
3182:
3183: } finally {
3184: // Make sure we close referral context
3185: refCtx.close();
3186: }
3187: }
3188:
3189: } catch (IOException e) {
3190: NamingException e2 = new CommunicationException(e
3191: .getMessage());
3192: e2.setRootCause(e);
3193: throw e2;
3194: }
3195: }
3196:
3197: public void setRequestControls(Control[] reqCtls)
3198: throws NamingException {
3199: if (handleReferrals == LdapClient.LDAP_REF_IGNORE) {
3200: this .reqCtls = addControl(reqCtls, manageReferralControl);
3201: } else {
3202: this .reqCtls = cloneControls(reqCtls);
3203: }
3204: }
3205:
3206: public Control[] getRequestControls() throws NamingException {
3207: return cloneControls(reqCtls);
3208: }
3209:
3210: public Control[] getConnectControls() throws NamingException {
3211: return cloneControls(bindCtls);
3212: }
3213:
3214: public Control[] getResponseControls() throws NamingException {
3215: return (respCtls != null) ? convertControls(respCtls) : null;
3216: }
3217:
3218: /**
3219: * Narrow controls using own default factory and ControlFactory.
3220: * @param ctls A non-null Vector
3221: */
3222: Control[] convertControls(Vector ctls) throws NamingException {
3223: int count = ctls.size();
3224:
3225: if (count == 0) {
3226: return null;
3227: }
3228:
3229: Control[] controls = new Control[count];
3230:
3231: for (int i = 0; i < count; i++) {
3232: // Try own factory first
3233: controls[i] = myResponseControlFactory
3234: .getControlInstance((Control) ctls.elementAt(i));
3235:
3236: // Try assigned factories if own produced null
3237: if (controls[i] == null) {
3238: controls[i] = ControlFactory.getControlInstance(
3239: (Control) ctls.elementAt(i), this , envprops);
3240: }
3241: }
3242: return controls;
3243: }
3244:
3245: private static Control[] addControl(Control[] prevCtls,
3246: Control addition) {
3247: if (prevCtls == null) {
3248: return new Control[] { addition };
3249: }
3250:
3251: // Find it
3252: int found = findControl(prevCtls, addition);
3253: if (found != -1) {
3254: return prevCtls; // no need to do it again
3255: }
3256:
3257: Control[] newCtls = new Control[prevCtls.length + 1];
3258: System.arraycopy(prevCtls, 0, newCtls, 0, prevCtls.length);
3259: newCtls[prevCtls.length] = addition;
3260: return newCtls;
3261: }
3262:
3263: private static int findControl(Control[] ctls, Control target) {
3264: for (int i = 0; i < ctls.length; i++) {
3265: if (ctls[i] == target) {
3266: return i;
3267: }
3268: }
3269: return -1;
3270: }
3271:
3272: private static Control[] removeControl(Control[] prevCtls,
3273: Control target) {
3274: if (prevCtls == null) {
3275: return null;
3276: }
3277:
3278: // Find it
3279: int found = findControl(prevCtls, target);
3280: if (found == -1) {
3281: return prevCtls; // not there
3282: }
3283:
3284: // Remove it
3285: Control[] newCtls = new Control[prevCtls.length - 1];
3286: System.arraycopy(prevCtls, 0, newCtls, 0, found);
3287: System.arraycopy(prevCtls, found + 1, newCtls, found,
3288: prevCtls.length - found - 1);
3289: return newCtls;
3290: }
3291:
3292: private static Control[] cloneControls(Control[] ctls) {
3293: if (ctls == null) {
3294: return null;
3295: }
3296: Control[] copiedCtls = new Control[ctls.length];
3297: System.arraycopy(ctls, 0, copiedCtls, 0, ctls.length);
3298: return copiedCtls;
3299: }
3300:
3301: // -------------------- Events ------------------------
3302: /*
3303: * Access to eventSupport need not be synchronized even though the
3304: * Connection thread can access it asynchronously. It is
3305: * impossible for a race condition to occur because
3306: * eventSupport.addNamingListener() must have been called before
3307: * the Connection thread can call back to this ctx.
3308: */
3309: public void addNamingListener(Name nm, int scope, NamingListener l)
3310: throws NamingException {
3311: addNamingListener(getTargetName(nm), scope, l);
3312: }
3313:
3314: public void addNamingListener(String nm, int scope, NamingListener l)
3315: throws NamingException {
3316: if (eventSupport == null)
3317: eventSupport = new EventSupport(this );
3318: eventSupport.addNamingListener(getTargetName(new CompositeName(
3319: nm)), scope, l);
3320:
3321: // If first time asking for unsol
3322: if (l instanceof UnsolicitedNotificationListener
3323: && !unsolicited) {
3324: addUnsolicited();
3325: }
3326: }
3327:
3328: public void removeNamingListener(NamingListener l)
3329: throws NamingException {
3330: if (eventSupport == null)
3331: return; // no activity before, so just return
3332:
3333: eventSupport.removeNamingListener(l);
3334:
3335: // If removing an Unsol listener and it is the last one, let clnt know
3336: if (l instanceof UnsolicitedNotificationListener
3337: && !eventSupport.hasUnsolicited()) {
3338: removeUnsolicited();
3339: }
3340: }
3341:
3342: public void addNamingListener(String nm, String filter,
3343: SearchControls ctls, NamingListener l)
3344: throws NamingException {
3345: if (eventSupport == null)
3346: eventSupport = new EventSupport(this );
3347: eventSupport.addNamingListener(getTargetName(new CompositeName(
3348: nm)), filter, cloneSearchControls(ctls), l);
3349:
3350: // If first time asking for unsol
3351: if (l instanceof UnsolicitedNotificationListener
3352: && !unsolicited) {
3353: addUnsolicited();
3354: }
3355: }
3356:
3357: public void addNamingListener(Name nm, String filter,
3358: SearchControls ctls, NamingListener l)
3359: throws NamingException {
3360: addNamingListener(getTargetName(nm), filter, ctls, l);
3361: }
3362:
3363: public void addNamingListener(Name nm, String filter,
3364: Object[] filterArgs, SearchControls ctls, NamingListener l)
3365: throws NamingException {
3366: addNamingListener(getTargetName(nm), filter, filterArgs, ctls,
3367: l);
3368: }
3369:
3370: public void addNamingListener(String nm, String filterExpr,
3371: Object[] filterArgs, SearchControls ctls, NamingListener l)
3372: throws NamingException {
3373: String strfilter = SearchFilter.format(filterExpr, filterArgs);
3374: addNamingListener(getTargetName(new CompositeName(nm)),
3375: strfilter, ctls, l);
3376: }
3377:
3378: public boolean targetMustExist() {
3379: return true;
3380: }
3381:
3382: /**
3383: * Retrieves the target name for which the listener is registering.
3384: * If nm is a CompositeName, use its first and only component. It
3385: * cannot have more than one components because a target be outside of
3386: * this namespace. If nm is not a CompositeName, then treat it as a
3387: * compound name.
3388: * @param nm The non-null target name.
3389: */
3390: private static String getTargetName(Name nm) throws NamingException {
3391: if (nm instanceof CompositeName) {
3392: if (nm.size() > 1) {
3393: throw new InvalidNameException(
3394: "Target cannot span multiple namespaces: " + nm);
3395: } else if (nm.size() == 0) {
3396: return "";
3397: } else {
3398: return nm.get(0);
3399: }
3400: } else {
3401: // treat as compound name
3402: return nm.toString();
3403: }
3404: }
3405:
3406: // ------------------ Unsolicited Notification ---------------
3407: // package private methods for handling unsolicited notification
3408:
3409: /**
3410: * Registers this context with the underlying LdapClient.
3411: * When the underlying LdapClient receives an unsolicited notification,
3412: * it will invoke LdapCtx.fireUnsolicited() so that this context
3413: * can (using EventSupport) notified any registered listeners.
3414: * This method is called by EventSupport when an unsolicited listener
3415: * first registers with this context (should be called just once).
3416: * @see #removeUnsolicited
3417: * @see #fireUnsolicited
3418: */
3419: private void addUnsolicited() throws NamingException {
3420: if (debug) {
3421: System.out.println("LdapCtx.addUnsolicited: " + this );
3422: }
3423:
3424: // addNamingListener must have created EventSupport already
3425: ensureOpen();
3426: synchronized (eventSupport) {
3427: clnt.addUnsolicited(this );
3428: unsolicited = true;
3429: }
3430: }
3431:
3432: /**
3433: * Removes this context from registering interest in unsolicited
3434: * notifications from the underlying LdapClient. This method is called
3435: * under any one of the following conditions:
3436: * <ul>
3437: * <li>All unsolicited listeners have been removed. (see removingNamingListener)
3438: * <li>This context is closed.
3439: * <li>This context's underlying LdapClient changes.
3440: *</ul>
3441: * After this method has been called, this context will not pass
3442: * on any events related to unsolicited notifications to EventSupport and
3443: * and its listeners.
3444: */
3445:
3446: private void removeUnsolicited() {
3447: if (debug) {
3448: System.out.println("LdapCtx.removeUnsolicited: "
3449: + unsolicited);
3450: }
3451: if (eventSupport == null) {
3452: return;
3453: }
3454:
3455: // addNamingListener must have created EventSupport already
3456: synchronized (eventSupport) {
3457: if (unsolicited && clnt != null) {
3458: clnt.removeUnsolicited(this );
3459: }
3460: unsolicited = false;
3461: }
3462: }
3463:
3464: /**
3465: * Uses EventSupport to fire an event related to an unsolicited notification.
3466: * Called by LdapClient when LdapClient receives an unsolicited notification.
3467: */
3468: void fireUnsolicited(Object obj) {
3469: if (debug) {
3470: System.out.println("LdapCtx.fireUnsolicited: " + obj);
3471: }
3472: // addNamingListener must have created EventSupport already
3473: synchronized (eventSupport) {
3474: if (unsolicited) {
3475: eventSupport.fireUnsolicited(obj);
3476:
3477: if (obj instanceof NamingException) {
3478: unsolicited = false;
3479: // No need to notify clnt because clnt is the
3480: // only one that can fire a NamingException to
3481: // unsol listeners and it will handle its own cleanup
3482: }
3483: }
3484: }
3485: }
3486: }
|