1 /*
   2  * Copyright (c) 1999, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.jndi.ldap;
  27 
  28 import javax.naming.*;
  29 import javax.naming.directory.*;
  30 import javax.naming.spi.*;
  31 import javax.naming.event.*;
  32 import javax.naming.ldap.*;
  33 import javax.naming.ldap.LdapName;
  34 import javax.naming.ldap.Rdn;
  35 
  36 import java.util.Locale;
  37 import java.util.Vector;
  38 import java.util.Hashtable;
  39 import java.util.List;
  40 import java.util.StringTokenizer;
  41 import java.util.Enumeration;
  42 
  43 import java.io.IOException;
  44 import java.io.OutputStream;
  45 
  46 import com.sun.jndi.toolkit.ctx.*;

  47 import com.sun.jndi.toolkit.dir.HierMemDirCtx;
  48 import com.sun.jndi.toolkit.dir.SearchFilter;
  49 import com.sun.jndi.ldap.ext.StartTlsResponseImpl;
  50 
  51 /**
  52  * The LDAP context implementation.
  53  *
  54  * Implementation is not thread-safe. Caller must sync as per JNDI spec.
  55  * Members that are used directly or indirectly by internal worker threads
  56  * (Connection, EventQueue, NamingEventNotifier) must be thread-safe.
  57  * Connection - calls LdapClient.processUnsolicited(), which in turn calls
  58  *   LdapCtx.convertControls() and LdapCtx.fireUnsolicited().
  59  *   convertControls() - no sync; reads envprops and 'this'
  60  *   fireUnsolicited() - sync on eventSupport for all references to 'unsolicited'
  61  *      (even those in other methods);  don't sync on LdapCtx in case caller
  62  *      is already sync'ing on it - this would prevent Unsol events from firing
  63  *      and the Connection thread to block (thus preventing any other data
  64  *      from being read from the connection)
  65  *      References to 'eventSupport' need not be sync'ed because these
  66  *      methods can only be called after eventSupport has been set first
  67  *      (via addNamingListener()).
  68  * EventQueue - no direct or indirect calls to LdapCtx
  69  * NamingEventNotifier - calls newInstance() to get instance for run() to use;
  70  *      no sync needed for methods invoked on new instance;
  71  *
  72  * LdapAttribute links to LdapCtx in order to process getAttributeDefinition()
  73  * and getAttributeSyntaxDefinition() calls. It invokes LdapCtx.getSchema(),
  74  * which uses schemaTrees (a Hashtable - already sync). Potential conflict
  75  * of duplicating construction of tree for same subschemasubentry
  76  * but no inconsistency problems.
  77  *
  78  * NamingEnumerations link to LdapCtx for the following:
  79  * 1. increment/decrement enum count so that ctx doesn't close the
  80  *    underlying connection
  81  * 2. LdapClient handle to get next batch of results
  82  * 3. Sets LdapCtx's response controls
  83  * 4. Process return code
  84  * 5. For narrowing response controls (using ctx's factories)
  85  * Since processing of NamingEnumeration by client is treated the same as method
  86  * invocation on LdapCtx, caller is responsible for locking.
  87  *
  88  * @author Vincent Ryan
  89  * @author Rosanna Lee
  90  */
  91 
  92 final public class LdapCtx extends ComponentDirContext
  93     implements EventDirContext, LdapContext {
  94 
  95     /*
  96      * Used to store arguments to the search method.
  97      */
  98     final static class SearchArgs {
  99         Name name;
 100         String filter;
 101         SearchControls cons;
 102         String[] reqAttrs; // those attributes originally requested
 103 
 104         SearchArgs(Name name, String filter, SearchControls cons, String[] ra) {
 105             this.name = name;
 106             this.filter = filter;
 107             this.cons = cons;
 108             this.reqAttrs = ra;
 109         }
 110     }
 111 
 112     private static final boolean debug = false;
 113 
 114     private static final boolean HARD_CLOSE = true;
 115     private static final boolean SOFT_CLOSE = false;
 116 
 117     // -----------------  Constants  -----------------
 118 
 119     public static final int DEFAULT_PORT = 389;
 120     public static final int DEFAULT_SSL_PORT = 636;
 121     public static final String DEFAULT_HOST = "localhost";
 122 
 123     private static final boolean DEFAULT_DELETE_RDN = true;
 124     private static final boolean DEFAULT_TYPES_ONLY = false;
 125     private static final int DEFAULT_DEREF_ALIASES = 3; // always deref
 126     private static final int DEFAULT_LDAP_VERSION = LdapClient.LDAP_VERSION3_VERSION2;
 127     private static final int DEFAULT_BATCH_SIZE = 1;
 128     private static final int DEFAULT_REFERRAL_MODE = LdapClient.LDAP_REF_IGNORE;
 129     private static final char DEFAULT_REF_SEPARATOR = '#';
 130 
 131         // Used by LdapPoolManager
 132     static final String DEFAULT_SSL_FACTORY =
 133         "javax.net.ssl.SSLSocketFactory";       // use Sun's SSL
 134     private static final int DEFAULT_REFERRAL_LIMIT = 10;
 135     private static final String STARTTLS_REQ_OID = "1.3.6.1.4.1.1466.20037";
 136 
 137     // schema operational and user attributes
 138     private static final String[] SCHEMA_ATTRIBUTES =
 139         { "objectClasses", "attributeTypes", "matchingRules", "ldapSyntaxes" };
 140 
 141     // --------------- Environment property names ----------
 142 
 143     // LDAP protocol version: "2", "3"
 144     private static final String VERSION = "java.naming.ldap.version";
 145 
 146     // Binary-valued attributes. Space separated string of attribute names.
 147     private static final String BINARY_ATTRIBUTES =
 148                                         "java.naming.ldap.attributes.binary";
 149 
 150     // Delete old RDN during modifyDN: "true", "false"
 151     private static final String DELETE_RDN = "java.naming.ldap.deleteRDN";
 152 
 153     // De-reference aliases: "never", "searching", "finding", "always"
 154     private static final String DEREF_ALIASES = "java.naming.ldap.derefAliases";
 155 
 156     // Return only attribute types (no values)
 157     private static final String TYPES_ONLY = "java.naming.ldap.typesOnly";
 158 
 159     // Separator character for encoding Reference's RefAddrs; default is '#'
 160     private static final String REF_SEPARATOR = "java.naming.ldap.ref.separator";
 161 
 162     // Socket factory
 163     private static final String SOCKET_FACTORY = "java.naming.ldap.factory.socket";
 164 
 165     // Bind Controls (used by LdapReferralException)
 166     static final String BIND_CONTROLS = "java.naming.ldap.control.connect";
 167 
 168     private static final String REFERRAL_LIMIT =
 169         "java.naming.ldap.referral.limit";
 170 
 171     // trace BER (java.io.OutputStream)
 172     private static final String TRACE_BER = "com.sun.jndi.ldap.trace.ber";
 173 
 174     // Get around Netscape Schema Bugs
 175     private static final String NETSCAPE_SCHEMA_BUG =
 176         "com.sun.jndi.ldap.netscape.schemaBugs";
 177     // deprecated
 178     private static final String OLD_NETSCAPE_SCHEMA_BUG =
 179         "com.sun.naming.netscape.schemaBugs";   // for backward compatibility
 180 
 181     // Timeout for socket connect
 182     private static final String CONNECT_TIMEOUT =
 183         "com.sun.jndi.ldap.connect.timeout";
 184 
 185      // Timeout for reading responses
 186     private static final String READ_TIMEOUT =
 187         "com.sun.jndi.ldap.read.timeout";
 188 
 189     // Environment property for connection pooling
 190     private static final String ENABLE_POOL = "com.sun.jndi.ldap.connect.pool";
 191 
 192     // Environment property for the domain name (derived from this context's DN)
 193     private static final String DOMAIN_NAME = "com.sun.jndi.ldap.domainname";
 194 
 195     // Block until the first search reply is received
 196     private static final String WAIT_FOR_REPLY =
 197         "com.sun.jndi.ldap.search.waitForReply";
 198 
 199     // Size of the queue of unprocessed search replies
 200     private static final String REPLY_QUEUE_SIZE =
 201         "com.sun.jndi.ldap.search.replyQueueSize";
 202 
 203     // ----------------- Fields that don't change -----------------------
 204     private static final NameParser parser = new LdapNameParser();
 205 
 206     // controls that Provider needs
 207     private static final ControlFactory myResponseControlFactory =
 208         new DefaultResponseControlFactory();
 209     private static final Control manageReferralControl =
 210         new ManageReferralControl(false);
 211 
 212     private static final HierMemDirCtx EMPTY_SCHEMA = new HierMemDirCtx();
 213     static {
 214         EMPTY_SCHEMA.setReadOnly(
 215             new SchemaViolationException("Cannot update schema object"));
 216     }
 217 
 218     // ------------ Package private instance variables ----------------
 219     // Cannot be private; used by enums
 220 
 221         // ------- Inherited by derived context instances
 222 
 223     int port_number;                    // port number of server
 224     String hostname = null;             // host name of server (no brackets
 225                                         //   for IPv6 literals)
 226     LdapClient clnt = null;             // connection handle
 227     private boolean reconnect = false;  // indicates that re-connect requested
 228     Hashtable<String, java.lang.Object> envprops = null; // environment properties of context
 229     int handleReferrals = DEFAULT_REFERRAL_MODE; // how referral is handled
 230     boolean hasLdapsScheme = false;     // true if the context was created
 231                                         //  using an LDAPS URL.
 232 
 233         // ------- Not inherited by derived context instances
 234 
 235     String currentDN;                   // DN of this context
 236     Name currentParsedDN;               // DN of this context
 237     Vector<Control> respCtls = null;    // Response controls read
 238     Control[] reqCtls = null;           // Controls to be sent with each request
 239 
 240 
 241     // ------------- Private instance variables ------------------------
 242 
 243         // ------- Inherited by derived context instances
 244 
 245     private OutputStream trace = null;  // output stream for BER debug output
 246     private boolean netscapeSchemaBug = false;       // workaround
 247     private Control[] bindCtls = null;  // Controls to be sent with LDAP "bind"
 248     private int referralHopLimit = DEFAULT_REFERRAL_LIMIT;  // max referral
 249     private Hashtable<String, DirContext> schemaTrees = null; // schema root of this context
 250     private int batchSize = DEFAULT_BATCH_SIZE;      // batch size for search results
 251     private boolean deleteRDN = DEFAULT_DELETE_RDN;  // delete the old RDN when modifying DN
 252     private boolean typesOnly = DEFAULT_TYPES_ONLY;  // return attribute types (no values)
 253     private int derefAliases = DEFAULT_DEREF_ALIASES;// de-reference alias entries during searching
 254     private char addrEncodingSeparator = DEFAULT_REF_SEPARATOR;  // encoding RefAddr
 255 
 256     private Hashtable<String, Boolean> binaryAttrs = null; // attr values returned as byte[]
 257     private int connectTimeout = -1;         // no timeout value
 258     private int readTimeout = -1;            // no timeout value
 259     private boolean waitForReply = true;     // wait for search response
 260     private int replyQueueSize  = -1;        // unlimited queue size
 261     private boolean useSsl = false;          // true if SSL protocol is active
 262     private boolean useDefaultPortNumber = false; // no port number was supplied
 263 
 264         // ------- Not inherited by derived context instances
 265 
 266     // True if this context was created by another LdapCtx.
 267     private boolean parentIsLdapCtx = false; // see composeName()
 268 
 269     private int hopCount = 1;                // current referral hop count
 270     private String url = null;               // URL of context; see getURL()
 271     private EventSupport eventSupport;       // Event support helper for this ctx
 272     private boolean unsolicited = false;     // if there unsolicited listeners
 273     private boolean sharable = true;         // can share connection with other ctx
 274 
 275     // -------------- Constructors  -----------------------------------
 276 
 277     @SuppressWarnings("unchecked")
 278     public LdapCtx(String dn, String host, int port_number,
 279             Hashtable<?,?> props,
 280             boolean useSsl) throws NamingException {
 281 
 282         this.useSsl = this.hasLdapsScheme = useSsl;
 283 
 284         if (props != null) {
 285             envprops = (Hashtable<String, java.lang.Object>) props.clone();
 286 
 287             // SSL env prop overrides the useSsl argument
 288             if ("ssl".equals(envprops.get(Context.SECURITY_PROTOCOL))) {
 289                 this.useSsl = true;
 290             }
 291 
 292             // %%% These are only examined when the context is created
 293             // %%% because they are only for debugging or workaround purposes.
 294             trace = (OutputStream)envprops.get(TRACE_BER);
 295 
 296             if (props.get(NETSCAPE_SCHEMA_BUG) != null ||
 297                 props.get(OLD_NETSCAPE_SCHEMA_BUG) != null) {
 298                 netscapeSchemaBug = true;
 299             }
 300         }
 301 
 302         currentDN = (dn != null) ? dn : "";
 303         currentParsedDN = parser.parse(currentDN);
 304 
 305         hostname = (host != null && host.length() > 0) ? host : DEFAULT_HOST;
 306         if (hostname.charAt(0) == '[') {
 307             hostname = hostname.substring(1, hostname.length() - 1);
 308         }
 309 
 310         if (port_number > 0) {
 311             this.port_number = port_number;
 312         } else {
 313             this.port_number = this.useSsl ? DEFAULT_SSL_PORT : DEFAULT_PORT;
 314             this.useDefaultPortNumber = true;
 315         }
 316 
 317         schemaTrees = new Hashtable<>(11, 0.75f);
 318         initEnv();
 319         try {
 320             connect(false);
 321         } catch (NamingException e) {
 322             try {
 323                 close();
 324             } catch (Exception e2) {
 325                 // Nothing
 326             }
 327             throw e;
 328         }
 329     }
 330 
 331     LdapCtx(LdapCtx existing, String newDN) throws NamingException {
 332         useSsl = existing.useSsl;
 333         hasLdapsScheme = existing.hasLdapsScheme;
 334         useDefaultPortNumber = existing.useDefaultPortNumber;
 335 
 336         hostname = existing.hostname;
 337         port_number = existing.port_number;
 338         currentDN = newDN;
 339         if (existing.currentDN == currentDN) {
 340             currentParsedDN = existing.currentParsedDN;
 341         } else {
 342             currentParsedDN = parser.parse(currentDN);
 343         }
 344 
 345         envprops = existing.envprops;
 346         schemaTrees = existing.schemaTrees;
 347 
 348         clnt = existing.clnt;
 349         clnt.incRefCount();
 350 
 351         parentIsLdapCtx = ((newDN == null || newDN.equals(existing.currentDN))
 352                            ? existing.parentIsLdapCtx
 353                            : true);
 354 
 355         // inherit these debugging/workaround flags
 356         trace = existing.trace;
 357         netscapeSchemaBug = existing.netscapeSchemaBug;
 358 
 359         initEnv();
 360     }
 361 
 362     public LdapContext newInstance(Control[] reqCtls) throws NamingException {
 363 
 364         LdapContext clone = new LdapCtx(this, currentDN);
 365 
 366         // Connection controls are inherited from environment
 367 
 368         // Set clone's request controls
 369         // setRequestControls() will clone reqCtls
 370         clone.setRequestControls(reqCtls);
 371         return clone;
 372     }
 373 
 374     // --------------- Namespace Updates ---------------------
 375     // -- bind/rebind/unbind
 376     // -- rename
 377     // -- createSubcontext/destroySubcontext
 378 
 379     protected void c_bind(Name name, Object obj, Continuation cont)
 380             throws NamingException {
 381         c_bind(name, obj, null, cont);
 382     }
 383 
 384     /*
 385      * attrs == null
 386      *      if obj is DirContext, attrs = obj.getAttributes()
 387      * if attrs == null && obj == null
 388      *      disallow (cannot determine objectclass to use)
 389      * if obj == null
 390      *      just create entry using attrs
 391      * else
 392      *      objAttrs = create attributes for representing obj
 393      *      attrs += objAttrs
 394      *      create entry using attrs
 395      */
 396     protected void c_bind(Name name, Object obj, Attributes attrs,
 397                           Continuation cont)
 398             throws NamingException {
 399 
 400         cont.setError(this, name);
 401 
 402         Attributes inputAttrs = attrs; // Attributes supplied by caller
 403         try {
 404             ensureOpen();
 405 
 406             if (obj == null) {
 407                 if (attrs == null) {
 408                     throw new IllegalArgumentException(
 409                         "cannot bind null object with no attributes");
 410                 }
 411             } else {
 412                 attrs = Obj.determineBindAttrs(addrEncodingSeparator, obj, attrs,
 413                     false, name, this, envprops); // not cloned
 414             }
 415 
 416             String newDN = fullyQualifiedName(name);
 417             attrs = addRdnAttributes(newDN, attrs, inputAttrs != attrs);
 418             LdapEntry entry = new LdapEntry(newDN, attrs);
 419 
 420             LdapResult answer = clnt.add(entry, reqCtls);
 421             respCtls = answer.resControls; // retrieve response controls
 422 
 423             if (answer.status != LdapClient.LDAP_SUCCESS) {
 424                 processReturnCode(answer, name);
 425             }
 426 
 427         } catch (LdapReferralException e) {
 428             if (handleReferrals == LdapClient.LDAP_REF_THROW)
 429                 throw cont.fillInException(e);
 430 
 431             // process the referrals sequentially
 432             while (true) {
 433 
 434                 LdapReferralContext refCtx =
 435                     (LdapReferralContext)e.getReferralContext(envprops, bindCtls);
 436 
 437                 // repeat the original operation at the new context
 438                 try {
 439 
 440                     refCtx.bind(name, obj, inputAttrs);
 441                     return;
 442 
 443                 } catch (LdapReferralException re) {
 444                     e = re;
 445                     continue;
 446 
 447                 } finally {
 448                     // Make sure we close referral context
 449                     refCtx.close();
 450                 }
 451             }
 452 
 453         } catch (IOException e) {
 454             NamingException e2 = new CommunicationException(e.getMessage());
 455             e2.setRootCause(e);
 456             throw cont.fillInException(e2);
 457 
 458         } catch (NamingException e) {
 459             throw cont.fillInException(e);
 460         }
 461     }
 462 
 463     protected void c_rebind(Name name, Object obj, Continuation cont)
 464             throws NamingException {
 465         c_rebind(name, obj, null, cont);
 466     }
 467 
 468 
 469     /*
 470      * attrs == null
 471      *    if obj is DirContext, attrs = obj.getAttributes().
 472      * if attrs == null
 473      *    leave any existing attributes alone
 474      *    (set attrs = {objectclass=top} if object doesn't exist)
 475      * else
 476      *    replace all existing attributes with attrs
 477      * if obj == null
 478      *      just create entry using attrs
 479      * else
 480      *      objAttrs = create attributes for representing obj
 481      *      attrs += objAttrs
 482      *      create entry using attrs
 483      */
 484     protected void c_rebind(Name name, Object obj, Attributes attrs,
 485         Continuation cont) throws NamingException {
 486 
 487         cont.setError(this, name);
 488 
 489         Attributes inputAttrs = attrs;
 490 
 491         try {
 492             Attributes origAttrs = null;
 493 
 494             // Check if name is bound
 495             try {
 496                 origAttrs = c_getAttributes(name, null, cont);
 497             } catch (NameNotFoundException e) {}
 498 
 499             // Name not bound, just add it
 500             if (origAttrs == null) {
 501                 c_bind(name, obj, attrs, cont);
 502                 return;
 503             }
 504 
 505             // there's an object there already, need to figure out
 506             // what to do about its attributes
 507 
 508             if (attrs == null && obj instanceof DirContext) {
 509                 attrs = ((DirContext)obj).getAttributes("");
 510             }
 511             Attributes keepAttrs = (Attributes)origAttrs.clone();
 512 
 513             if (attrs == null) {
 514                 // we're not changing any attrs, leave old attributes alone
 515 
 516                 // Remove Java-related object classes from objectclass attribute
 517                 Attribute origObjectClass =
 518                     origAttrs.get(Obj.JAVA_ATTRIBUTES[Obj.OBJECT_CLASS]);
 519 
 520                 if (origObjectClass != null) {
 521                     // clone so that keepAttrs is not affected
 522                     origObjectClass = (Attribute)origObjectClass.clone();
 523                     for (int i = 0; i < Obj.JAVA_OBJECT_CLASSES.length; i++) {
 524                         origObjectClass.remove(Obj.JAVA_OBJECT_CLASSES_LOWER[i]);
 525                         origObjectClass.remove(Obj.JAVA_OBJECT_CLASSES[i]);
 526                     }
 527                     // update;
 528                     origAttrs.put(origObjectClass);
 529                 }
 530 
 531                 // remove all Java-related attributes except objectclass
 532                 for (int i = 1; i < Obj.JAVA_ATTRIBUTES.length; i++) {
 533                     origAttrs.remove(Obj.JAVA_ATTRIBUTES[i]);
 534                 }
 535 
 536                 attrs = origAttrs;
 537             }
 538             if (obj != null) {
 539                 attrs =
 540                     Obj.determineBindAttrs(addrEncodingSeparator, obj, attrs,
 541                         inputAttrs != attrs, name, this, envprops);
 542             }
 543 
 544             String newDN = fullyQualifiedName(name);
 545             // remove entry
 546             LdapResult answer = clnt.delete(newDN, reqCtls);
 547             respCtls = answer.resControls; // retrieve response controls
 548 
 549             if (answer.status != LdapClient.LDAP_SUCCESS) {
 550                 processReturnCode(answer, name);
 551                 return;
 552             }
 553 
 554             Exception addEx = null;
 555             try {
 556                 attrs = addRdnAttributes(newDN, attrs, inputAttrs != attrs);
 557 
 558                 // add it back using updated attrs
 559                 LdapEntry entry = new LdapEntry(newDN, attrs);
 560                 answer = clnt.add(entry, reqCtls);
 561                 if (answer.resControls != null) {
 562                     respCtls = appendVector(respCtls, answer.resControls);
 563                 }
 564             } catch (NamingException | IOException ae) {
 565                 addEx = ae;
 566             }
 567 
 568             if ((addEx != null && !(addEx instanceof LdapReferralException)) ||
 569                 answer.status != LdapClient.LDAP_SUCCESS) {
 570                 // Attempt to restore old entry
 571                 LdapResult answer2 =
 572                     clnt.add(new LdapEntry(newDN, keepAttrs), reqCtls);
 573                 if (answer2.resControls != null) {
 574                     respCtls = appendVector(respCtls, answer2.resControls);
 575                 }
 576 
 577                 if (addEx == null) {
 578                     processReturnCode(answer, name);
 579                 }
 580             }
 581 
 582             // Rethrow exception
 583             if (addEx instanceof NamingException) {
 584                 throw (NamingException)addEx;
 585             } else if (addEx instanceof IOException) {
 586                 throw (IOException)addEx;
 587             }
 588 
 589         } catch (LdapReferralException e) {
 590             if (handleReferrals == LdapClient.LDAP_REF_THROW)
 591                 throw cont.fillInException(e);
 592 
 593             // process the referrals sequentially
 594             while (true) {
 595 
 596                 LdapReferralContext refCtx =
 597                     (LdapReferralContext)e.getReferralContext(envprops, bindCtls);
 598 
 599                 // repeat the original operation at the new context
 600                 try {
 601 
 602                     refCtx.rebind(name, obj, inputAttrs);
 603                     return;
 604 
 605                 } catch (LdapReferralException re) {
 606                     e = re;
 607                     continue;
 608 
 609                 } finally {
 610                     // Make sure we close referral context
 611                     refCtx.close();
 612                 }
 613             }
 614 
 615         } catch (IOException e) {
 616             NamingException e2 = new CommunicationException(e.getMessage());
 617             e2.setRootCause(e);
 618             throw cont.fillInException(e2);
 619 
 620         } catch (NamingException e) {
 621             throw cont.fillInException(e);
 622         }
 623     }
 624 
 625     protected void c_unbind(Name name, Continuation cont)
 626             throws NamingException {
 627         cont.setError(this, name);
 628 
 629         try {
 630             ensureOpen();
 631 
 632             String fname = fullyQualifiedName(name);
 633             LdapResult answer = clnt.delete(fname, reqCtls);
 634             respCtls = answer.resControls; // retrieve response controls
 635 
 636             adjustDeleteStatus(fname, answer);
 637 
 638             if (answer.status != LdapClient.LDAP_SUCCESS) {
 639                 processReturnCode(answer, name);
 640             }
 641 
 642         } catch (LdapReferralException e) {
 643             if (handleReferrals == LdapClient.LDAP_REF_THROW)
 644                 throw cont.fillInException(e);
 645 
 646             // process the referrals sequentially
 647             while (true) {
 648 
 649                 LdapReferralContext refCtx =
 650                     (LdapReferralContext)e.getReferralContext(envprops, bindCtls);
 651 
 652                 // repeat the original operation at the new context
 653                 try {
 654 
 655                     refCtx.unbind(name);
 656                     return;
 657 
 658                 } catch (LdapReferralException re) {
 659                     e = re;
 660                     continue;
 661 
 662                 } finally {
 663                     // Make sure we close referral context
 664                     refCtx.close();
 665                 }
 666             }
 667 
 668         } catch (IOException e) {
 669             NamingException e2 = new CommunicationException(e.getMessage());
 670             e2.setRootCause(e);
 671             throw cont.fillInException(e2);
 672 
 673         } catch (NamingException e) {
 674             throw cont.fillInException(e);
 675         }
 676     }
 677 
 678     protected void c_rename(Name oldName, Name newName, Continuation cont)
 679             throws NamingException
 680     {
 681         Name oldParsed, newParsed;
 682         Name oldParent, newParent;
 683         String newRDN = null;
 684         String newSuperior = null;
 685 
 686         // assert (oldName instanceOf CompositeName);
 687 
 688         cont.setError(this, oldName);
 689 
 690         try {
 691             ensureOpen();
 692 
 693             // permit oldName to be empty (for processing referral contexts)
 694             if (oldName.isEmpty()) {
 695                 oldParent = parser.parse("");
 696             } else {
 697                 oldParsed = parser.parse(oldName.get(0)); // extract DN & parse
 698                 oldParent = oldParsed.getPrefix(oldParsed.size() - 1);
 699             }
 700 
 701             if (newName instanceof CompositeName) {
 702                 newParsed = parser.parse(newName.get(0)); // extract DN & parse
 703             } else {
 704                 newParsed = newName; // CompoundName/LdapName is already parsed
 705             }
 706             newParent = newParsed.getPrefix(newParsed.size() - 1);
 707 
 708             if(!oldParent.equals(newParent)) {
 709                 if (!clnt.isLdapv3) {
 710                     throw new InvalidNameException(
 711                                   "LDAPv2 doesn't support changing " +
 712                                   "the parent as a result of a rename");
 713                 } else {
 714                     newSuperior = fullyQualifiedName(newParent.toString());
 715                 }
 716             }
 717 
 718             newRDN = newParsed.get(newParsed.size() - 1);
 719 
 720             LdapResult answer = clnt.moddn(fullyQualifiedName(oldName),
 721                                     newRDN,
 722                                     deleteRDN,
 723                                     newSuperior,
 724                                     reqCtls);
 725             respCtls = answer.resControls; // retrieve response controls
 726 
 727             if (answer.status != LdapClient.LDAP_SUCCESS) {
 728                 processReturnCode(answer, oldName);
 729             }
 730 
 731         } catch (LdapReferralException e) {
 732 
 733             // Record the new RDN (for use after the referral is followed).
 734             e.setNewRdn(newRDN);
 735 
 736             // Cannot continue when a referral has been received and a
 737             // newSuperior name was supplied (because the newSuperior is
 738             // relative to a naming context BEFORE the referral is followed).
 739             if (newSuperior != null) {
 740                 PartialResultException pre = new PartialResultException(
 741                     "Cannot continue referral processing when newSuperior is " +
 742                     "nonempty: " + newSuperior);
 743                 pre.setRootCause(cont.fillInException(e));
 744                 throw cont.fillInException(pre);
 745             }
 746 
 747             if (handleReferrals == LdapClient.LDAP_REF_THROW)
 748                 throw cont.fillInException(e);
 749 
 750             // process the referrals sequentially
 751             while (true) {
 752 
 753                 LdapReferralContext refCtx =
 754                     (LdapReferralContext)e.getReferralContext(envprops, bindCtls);
 755 
 756                 // repeat the original operation at the new context
 757                 try {
 758 
 759                     refCtx.rename(oldName, newName);
 760                     return;
 761 
 762                 } catch (LdapReferralException re) {
 763                     e = re;
 764                     continue;
 765 
 766                 } finally {
 767                     // Make sure we close referral context
 768                     refCtx.close();
 769                 }
 770             }
 771 
 772         } catch (IOException e) {
 773             NamingException e2 = new CommunicationException(e.getMessage());
 774             e2.setRootCause(e);
 775             throw cont.fillInException(e2);
 776 
 777         } catch (NamingException e) {
 778             throw cont.fillInException(e);
 779         }
 780     }
 781 
 782     protected Context c_createSubcontext(Name name, Continuation cont)
 783             throws NamingException {
 784         return c_createSubcontext(name, null, cont);
 785     }
 786 
 787     protected DirContext c_createSubcontext(Name name, Attributes attrs,
 788                                             Continuation cont)
 789             throws NamingException {
 790         cont.setError(this, name);
 791 
 792         Attributes inputAttrs = attrs;
 793         try {
 794             ensureOpen();
 795             if (attrs == null) {
 796                   // add structural objectclass; name needs to have "cn"
 797                   Attribute oc = new BasicAttribute(
 798                       Obj.JAVA_ATTRIBUTES[Obj.OBJECT_CLASS],
 799                       Obj.JAVA_OBJECT_CLASSES[Obj.STRUCTURAL]);
 800                   oc.add("top");
 801                   attrs = new BasicAttributes(true); // case ignore
 802                   attrs.put(oc);
 803             }
 804             String newDN = fullyQualifiedName(name);
 805             attrs = addRdnAttributes(newDN, attrs, inputAttrs != attrs);
 806 
 807             LdapEntry entry = new LdapEntry(newDN, attrs);
 808 
 809             LdapResult answer = clnt.add(entry, reqCtls);
 810             respCtls = answer.resControls; // retrieve response controls
 811 
 812             if (answer.status != LdapClient.LDAP_SUCCESS) {
 813                 processReturnCode(answer, name);
 814                 return null;
 815             }
 816 
 817             // creation successful, get back live object
 818             return new LdapCtx(this, newDN);
 819 
 820         } catch (LdapReferralException e) {
 821             if (handleReferrals == LdapClient.LDAP_REF_THROW)
 822                 throw cont.fillInException(e);
 823 
 824             // process the referrals sequentially
 825             while (true) {
 826 
 827                 LdapReferralContext refCtx =
 828                     (LdapReferralContext)e.getReferralContext(envprops, bindCtls);
 829 
 830                 // repeat the original operation at the new context
 831                 try {
 832 
 833                     return refCtx.createSubcontext(name, inputAttrs);
 834 
 835                 } catch (LdapReferralException re) {
 836                     e = re;
 837                     continue;
 838 
 839                 } finally {
 840                     // Make sure we close referral context
 841                     refCtx.close();
 842                 }
 843             }
 844 
 845         } catch (IOException e) {
 846             NamingException e2 = new CommunicationException(e.getMessage());
 847             e2.setRootCause(e);
 848             throw cont.fillInException(e2);
 849 
 850         } catch (NamingException e) {
 851             throw cont.fillInException(e);
 852         }
 853     }
 854 
 855     protected void c_destroySubcontext(Name name, Continuation cont)
 856         throws NamingException {
 857         cont.setError(this, name);
 858 
 859         try {
 860             ensureOpen();
 861 
 862             String fname = fullyQualifiedName(name);
 863             LdapResult answer = clnt.delete(fname, reqCtls);
 864             respCtls = answer.resControls; // retrieve response controls
 865 
 866             adjustDeleteStatus(fname, answer);
 867 
 868             if (answer.status != LdapClient.LDAP_SUCCESS) {
 869                 processReturnCode(answer, name);
 870             }
 871 
 872         } catch (LdapReferralException e) {
 873             if (handleReferrals == LdapClient.LDAP_REF_THROW)
 874                 throw cont.fillInException(e);
 875 
 876             // process the referrals sequentially
 877             while (true) {
 878 
 879                 LdapReferralContext refCtx =
 880                     (LdapReferralContext)e.getReferralContext(envprops, bindCtls);
 881 
 882                 // repeat the original operation at the new context
 883                 try {
 884 
 885                     refCtx.destroySubcontext(name);
 886                     return;
 887                 } catch (LdapReferralException re) {
 888                     e = re;
 889                     continue;
 890                 } finally {
 891                     // Make sure we close referral context
 892                     refCtx.close();
 893                 }
 894             }
 895         } catch (IOException e) {
 896             NamingException e2 = new CommunicationException(e.getMessage());
 897             e2.setRootCause(e);
 898             throw cont.fillInException(e2);
 899         } catch (NamingException e) {
 900             throw cont.fillInException(e);
 901         }
 902     }
 903 
 904     /**
 905      * Adds attributes from RDN to attrs if not already present.
 906      * Note that if attrs already contains an attribute by the same name,
 907      * or if the distinguished name is empty, then leave attrs unchanged.
 908      *
 909      * @param dn The non-null DN of the entry to add
 910      * @param attrs The non-null attributes of entry to add
 911      * @param directUpdate Whether attrs can be updated directly
 912      * @return Non-null attributes with attributes from the RDN added
 913      */
 914     private static Attributes addRdnAttributes(String dn, Attributes attrs,
 915         boolean directUpdate) throws NamingException {
 916 
 917             // Handle the empty name
 918             if (dn.equals("")) {
 919                 return attrs;
 920             }
 921 
 922             // Parse string name into list of RDNs
 923             List<Rdn> rdnList = (new LdapName(dn)).getRdns();
 924 
 925             // Get leaf RDN
 926             Rdn rdn = rdnList.get(rdnList.size() - 1);
 927             Attributes nameAttrs = rdn.toAttributes();
 928 
 929             // Add attributes of RDN to attrs if not already there
 930             NamingEnumeration<? extends Attribute> enum_ = nameAttrs.getAll();
 931             Attribute nameAttr;
 932             while (enum_.hasMore()) {
 933                 nameAttr = enum_.next();
 934 
 935                 // If attrs already has the attribute, don't change or add to it
 936                 if (attrs.get(nameAttr.getID()) ==  null) {
 937 
 938                     /**
 939                      * When attrs.isCaseIgnored() is false, attrs.get() will
 940                      * return null when the case mis-matches for otherwise
 941                      * equal attrIDs.
 942                      * As the attrIDs' case is irrelevant for LDAP, ignore
 943                      * the case of attrIDs even when attrs.isCaseIgnored() is
 944                      * false. This is done by explicitly comparing the elements in
 945                      * the enumeration of IDs with their case ignored.
 946                      */
 947                     if (!attrs.isCaseIgnored() &&
 948                             containsIgnoreCase(attrs.getIDs(), nameAttr.getID())) {
 949                         continue;
 950                     }
 951 
 952                     if (!directUpdate) {
 953                         attrs = (Attributes)attrs.clone();
 954                         directUpdate = true;
 955                     }
 956                     attrs.put(nameAttr);
 957                 }
 958             }
 959 
 960             return attrs;
 961     }
 962 
 963 
 964     private static boolean containsIgnoreCase(NamingEnumeration<String> enumStr,
 965                                 String str) throws NamingException {
 966         String strEntry;
 967 
 968         while (enumStr.hasMore()) {
 969              strEntry = enumStr.next();
 970              if (strEntry.equalsIgnoreCase(str)) {
 971                 return true;
 972              }
 973         }
 974         return false;
 975     }
 976 
 977 
 978     private void adjustDeleteStatus(String fname, LdapResult answer) {
 979         if (answer.status == LdapClient.LDAP_NO_SUCH_OBJECT &&
 980             answer.matchedDN != null) {
 981             try {
 982                 // %%% RL: are there any implications for referrals?
 983 
 984                 Name orig = parser.parse(fname);
 985                 Name matched = parser.parse(answer.matchedDN);
 986                 if ((orig.size() - matched.size()) == 1)
 987                     answer.status = LdapClient.LDAP_SUCCESS;
 988             } catch (NamingException e) {}
 989         }
 990     }
 991 
 992     /*
 993      * Append the second Vector onto the first Vector
 994      * (v2 must be non-null)
 995      */
 996     private static <T> Vector<T> appendVector(Vector<T> v1, Vector<T> v2) {
 997         if (v1 == null) {
 998             v1 = v2;
 999         } else {
1000             for (int i = 0; i < v2.size(); i++) {
1001                 v1.addElement(v2.elementAt(i));
1002             }
1003         }
1004         return v1;
1005     }
1006 
1007     // ------------- Lookups and Browsing -------------------------
1008     // lookup/lookupLink
1009     // list/listBindings
1010 
1011     protected Object c_lookupLink(Name name, Continuation cont)
1012             throws NamingException {
1013         return c_lookup(name, cont);
1014     }
1015 
1016     protected Object c_lookup(Name name, Continuation cont)
1017             throws NamingException {
1018         cont.setError(this, name);
1019         Object obj = null;
1020         Attributes attrs;
1021 
1022         try {
1023             SearchControls cons = new SearchControls();
1024             cons.setSearchScope(SearchControls.OBJECT_SCOPE);
1025             cons.setReturningAttributes(null); // ask for all attributes
1026             cons.setReturningObjFlag(true); // need values to construct obj
1027 
1028             LdapResult answer = doSearchOnce(name, "(objectClass=*)", cons, true);
1029             respCtls = answer.resControls; // retrieve response controls
1030 
1031             // should get back 1 SearchResponse and 1 SearchResult
1032 
1033             if (answer.status != LdapClient.LDAP_SUCCESS) {
1034                 processReturnCode(answer, name);
1035             }
1036 
1037             if (answer.entries == null || answer.entries.size() != 1) {
1038                 // found it but got no attributes
1039                 attrs = new BasicAttributes(LdapClient.caseIgnore);
1040             } else {
1041                 LdapEntry entry = answer.entries.elementAt(0);
1042                 attrs = entry.attributes;
1043 
1044                 Vector<Control> entryCtls = entry.respCtls; // retrieve entry controls
1045                 if (entryCtls != null) {
1046                     appendVector(respCtls, entryCtls); // concatenate controls
1047                 }
1048             }
1049 
1050             if (attrs.get(Obj.JAVA_ATTRIBUTES[Obj.CLASSNAME]) != null) {
1051                 // serialized object or object reference
1052                 obj = Obj.decodeObject(attrs);
1053             }
1054             if (obj == null) {
1055                 obj = new LdapCtx(this, fullyQualifiedName(name));
1056             }
1057         } catch (LdapReferralException e) {
1058             if (handleReferrals == LdapClient.LDAP_REF_THROW)
1059                 throw cont.fillInException(e);
1060 
1061             // process the referrals sequentially
1062             while (true) {
1063 
1064                 LdapReferralContext refCtx =
1065                     (LdapReferralContext)e.getReferralContext(envprops, bindCtls);
1066                 // repeat the original operation at the new context
1067                 try {
1068 
1069                     return refCtx.lookup(name);
1070 
1071                 } catch (LdapReferralException re) {
1072                     e = re;
1073                     continue;
1074 
1075                 } finally {
1076                     // Make sure we close referral context
1077                     refCtx.close();
1078                 }
1079             }
1080 
1081         } catch (NamingException e) {
1082             throw cont.fillInException(e);
1083         }
1084 
1085         try {
1086             return DirectoryManager.getObjectInstance(obj, name,
1087                 this, envprops, attrs);
1088 
1089         } catch (NamingException e) {
1090             throw cont.fillInException(e);
1091 
1092         } catch (Exception e) {
1093             NamingException e2 = new NamingException(
1094                     "problem generating object using object factory");
1095             e2.setRootCause(e);
1096             throw cont.fillInException(e2);
1097         }
1098     }
1099 
1100     protected NamingEnumeration<NameClassPair> c_list(Name name, Continuation cont)
1101             throws NamingException {
1102         SearchControls cons = new SearchControls();
1103         String[] classAttrs = new String[2];
1104 
1105         classAttrs[0] = Obj.JAVA_ATTRIBUTES[Obj.OBJECT_CLASS];
1106         classAttrs[1] = Obj.JAVA_ATTRIBUTES[Obj.CLASSNAME];
1107         cons.setReturningAttributes(classAttrs);
1108 
1109         // set this flag to override the typesOnly flag
1110         cons.setReturningObjFlag(true);
1111 
1112         cont.setError(this, name);
1113 
1114         LdapResult answer = null;
1115 
1116         try {
1117             answer = doSearch(name, "(objectClass=*)", cons, true, true);
1118 
1119             // list result may contain continuation references
1120             if ((answer.status != LdapClient.LDAP_SUCCESS) ||
1121                 (answer.referrals != null)) {
1122                 processReturnCode(answer, name);
1123             }
1124 
1125             return new LdapNamingEnumeration(this, answer, name, cont);
1126 
1127         } catch (LdapReferralException e) {
1128             if (handleReferrals == LdapClient.LDAP_REF_THROW)
1129                 throw cont.fillInException(e);
1130 
1131             // process the referrals sequentially
1132             while (true) {
1133 
1134                 LdapReferralContext refCtx =
1135                     (LdapReferralContext)e.getReferralContext(envprops, bindCtls);
1136 
1137                 // repeat the original operation at the new context
1138                 try {
1139 
1140                     return refCtx.list(name);
1141 
1142                 } catch (LdapReferralException re) {
1143                     e = re;
1144                     continue;
1145 
1146                 } finally {
1147                     // Make sure we close referral context
1148                     refCtx.close();
1149                 }
1150             }
1151 
1152         } catch (LimitExceededException e) {
1153             LdapNamingEnumeration res =
1154                 new LdapNamingEnumeration(this, answer, name, cont);
1155 
1156             res.setNamingException(
1157                     (LimitExceededException)cont.fillInException(e));
1158             return res;
1159 
1160         } catch (PartialResultException e) {
1161             LdapNamingEnumeration res =
1162                 new LdapNamingEnumeration(this, answer, name, cont);
1163 
1164             res.setNamingException(
1165                     (PartialResultException)cont.fillInException(e));
1166             return res;
1167 
1168         } catch (NamingException e) {
1169             throw cont.fillInException(e);
1170         }
1171     }
1172 
1173     protected NamingEnumeration<Binding> c_listBindings(Name name, Continuation cont)
1174             throws NamingException {
1175 
1176         SearchControls cons = new SearchControls();
1177         cons.setReturningAttributes(null); // ask for all attributes
1178         cons.setReturningObjFlag(true); // need values to construct obj
1179 
1180         cont.setError(this, name);
1181 
1182         LdapResult answer = null;
1183 
1184         try {
1185             answer = doSearch(name, "(objectClass=*)", cons, true, true);
1186 
1187             // listBindings result may contain continuation references
1188             if ((answer.status != LdapClient.LDAP_SUCCESS) ||
1189                 (answer.referrals != null)) {
1190                 processReturnCode(answer, name);
1191             }
1192 
1193             return new LdapBindingEnumeration(this, answer, name, cont);
1194 
1195         } catch (LdapReferralException e) {
1196             if (handleReferrals == LdapClient.LDAP_REF_THROW)
1197                 throw cont.fillInException(e);
1198 
1199             // process the referrals sequentially
1200             while (true) {
1201                 @SuppressWarnings("unchecked")
1202                 LdapReferralContext refCtx =
1203                     (LdapReferralContext)e.getReferralContext(envprops, bindCtls);
1204 
1205                 // repeat the original operation at the new context
1206                 try {
1207 
1208                     return refCtx.listBindings(name);
1209 
1210                 } catch (LdapReferralException re) {
1211                     e = re;
1212                     continue;
1213 
1214                 } finally {
1215                     // Make sure we close referral context
1216                     refCtx.close();
1217                 }
1218             }
1219         } catch (LimitExceededException e) {
1220             LdapBindingEnumeration res =
1221                 new LdapBindingEnumeration(this, answer, name, cont);
1222 
1223             res.setNamingException(cont.fillInException(e));
1224             return res;
1225 
1226         } catch (PartialResultException e) {
1227             LdapBindingEnumeration res =
1228                 new LdapBindingEnumeration(this, answer, name, cont);
1229 
1230             res.setNamingException(cont.fillInException(e));
1231             return res;
1232 
1233         } catch (NamingException e) {
1234             throw cont.fillInException(e);
1235         }
1236     }
1237 
1238     // --------------- Name-related Methods -----------------------
1239     // -- getNameParser/getNameInNamespace/composeName
1240 
1241     protected NameParser c_getNameParser(Name name, Continuation cont)
1242             throws NamingException
1243     {
1244         // ignore name, always return same parser
1245         cont.setSuccess();
1246         return parser;
1247     }
1248 
1249     public String getNameInNamespace() {
1250         return currentDN;
1251     }
1252 
1253     public Name composeName(Name name, Name prefix)
1254         throws NamingException
1255     {
1256         Name result;
1257 
1258         // Handle compound names.  A pair of LdapNames is an easy case.
1259         if ((name instanceof LdapName) && (prefix instanceof LdapName)) {
1260             result = (Name)(prefix.clone());
1261             result.addAll(name);
1262             return new CompositeName().add(result.toString());
1263         }
1264         if (!(name instanceof CompositeName)) {
1265             name = new CompositeName().add(name.toString());
1266         }
1267         if (!(prefix instanceof CompositeName)) {
1268             prefix = new CompositeName().add(prefix.toString());
1269         }
1270 
1271         int prefixLast = prefix.size() - 1;
1272 
1273         if (name.isEmpty() || prefix.isEmpty() ||
1274                 name.get(0).equals("") || prefix.get(prefixLast).equals("")) {
1275             return super.composeName(name, prefix);
1276         }
1277 
1278         result = (Name)(prefix.clone());
1279         result.addAll(name);
1280 
1281         if (parentIsLdapCtx) {
1282             String ldapComp = concatNames(result.get(prefixLast + 1),
1283                                           result.get(prefixLast));
1284             result.remove(prefixLast + 1);
1285             result.remove(prefixLast);
1286             result.add(prefixLast, ldapComp);
1287         }
1288         return result;
1289     }
1290 
1291     private String fullyQualifiedName(Name rel) {
1292         return rel.isEmpty()
1293                 ? currentDN
1294                 : fullyQualifiedName(rel.get(0));
1295     }
1296 
1297     private String fullyQualifiedName(String rel) {
1298         return (concatNames(rel, currentDN));
1299     }
1300 
1301     // used by LdapSearchEnumeration
1302     private static String concatNames(String lesser, String greater) {
1303         if (lesser == null || lesser.equals("")) {
1304             return greater;
1305         } else if (greater == null || greater.equals("")) {
1306             return lesser;
1307         } else {
1308             return (lesser + "," + greater);
1309         }
1310     }
1311 
1312    // --------------- Reading and Updating Attributes
1313    // getAttributes/modifyAttributes
1314 
1315     protected Attributes c_getAttributes(Name name, String[] attrIds,
1316                                       Continuation cont)
1317             throws NamingException {
1318         cont.setError(this, name);
1319 
1320         SearchControls cons = new SearchControls();
1321         cons.setSearchScope(SearchControls.OBJECT_SCOPE);
1322         cons.setReturningAttributes(attrIds);
1323 
1324         try {
1325             LdapResult answer =
1326                 doSearchOnce(name, "(objectClass=*)", cons, true);
1327             respCtls = answer.resControls; // retrieve response controls
1328 
1329             if (answer.status != LdapClient.LDAP_SUCCESS) {
1330                 processReturnCode(answer, name);
1331             }
1332 
1333             if (answer.entries == null || answer.entries.size() != 1) {
1334                 return new BasicAttributes(LdapClient.caseIgnore);
1335             }
1336 
1337             // get attributes from result
1338             LdapEntry entry = answer.entries.elementAt(0);
1339 
1340             Vector<Control> entryCtls = entry.respCtls; // retrieve entry controls
1341             if (entryCtls != null) {
1342                 appendVector(respCtls, entryCtls); // concatenate controls
1343             }
1344 
1345             // do this so attributes can find their schema
1346             setParents(entry.attributes, (Name) name.clone());
1347 
1348             return (entry.attributes);
1349 
1350         } catch (LdapReferralException e) {
1351             if (handleReferrals == LdapClient.LDAP_REF_THROW)
1352                 throw cont.fillInException(e);
1353 
1354             // process the referrals sequentially
1355             while (true) {
1356 
1357                 LdapReferralContext refCtx =
1358                     (LdapReferralContext)e.getReferralContext(envprops, bindCtls);
1359 
1360                 // repeat the original operation at the new context
1361                 try {
1362 
1363                     return refCtx.getAttributes(name, attrIds);
1364 
1365                 } catch (LdapReferralException re) {
1366                     e = re;
1367                     continue;
1368 
1369                 } finally {
1370                     // Make sure we close referral context
1371                     refCtx.close();
1372                 }
1373             }
1374 
1375         } catch (NamingException e) {
1376             throw cont.fillInException(e);
1377         }
1378     }
1379 
1380     protected void c_modifyAttributes(Name name, int mod_op, Attributes attrs,
1381                                       Continuation cont)
1382             throws NamingException {
1383 
1384         cont.setError(this, name);
1385 
1386         try {
1387             ensureOpen();
1388 
1389             if (attrs == null || attrs.size() == 0) {
1390                 return; // nothing to do
1391             }
1392             String newDN = fullyQualifiedName(name);
1393             int jmod_op = convertToLdapModCode(mod_op);
1394 
1395             // construct mod list
1396             int[] jmods = new int[attrs.size()];
1397             Attribute[] jattrs = new Attribute[attrs.size()];
1398 
1399             NamingEnumeration<? extends Attribute> ae = attrs.getAll();
1400             for(int i = 0; i < jmods.length && ae.hasMore(); i++) {
1401                 jmods[i] = jmod_op;
1402                 jattrs[i] = ae.next();
1403             }
1404 
1405             LdapResult answer = clnt.modify(newDN, jmods, jattrs, reqCtls);
1406             respCtls = answer.resControls; // retrieve response controls
1407 
1408             if (answer.status != LdapClient.LDAP_SUCCESS) {
1409                 processReturnCode(answer, name);
1410                 return;
1411             }
1412 
1413         } catch (LdapReferralException e) {
1414             if (handleReferrals == LdapClient.LDAP_REF_THROW)
1415                 throw cont.fillInException(e);
1416 
1417             // process the referrals sequentially
1418             while (true) {
1419 
1420                 LdapReferralContext refCtx =
1421                     (LdapReferralContext)e.getReferralContext(envprops, bindCtls);
1422 
1423                 // repeat the original operation at the new context
1424                 try {
1425 
1426                     refCtx.modifyAttributes(name, mod_op, attrs);
1427                     return;
1428 
1429                 } catch (LdapReferralException re) {
1430                     e = re;
1431                     continue;
1432 
1433                 } finally {
1434                     // Make sure we close referral context
1435                     refCtx.close();
1436                 }
1437             }
1438 
1439         } catch (IOException e) {
1440             NamingException e2 = new CommunicationException(e.getMessage());
1441             e2.setRootCause(e);
1442             throw cont.fillInException(e2);
1443 
1444         } catch (NamingException e) {
1445             throw cont.fillInException(e);
1446         }
1447     }
1448 
1449     protected void c_modifyAttributes(Name name, ModificationItem[] mods,
1450                                       Continuation cont)
1451             throws NamingException {
1452         cont.setError(this, name);
1453 
1454         try {
1455             ensureOpen();
1456 
1457             if (mods == null || mods.length == 0) {
1458                 return; // nothing to do
1459             }
1460             String newDN = fullyQualifiedName(name);
1461 
1462             // construct mod list
1463             int[] jmods = new int[mods.length];
1464             Attribute[] jattrs = new Attribute[mods.length];
1465             ModificationItem mod;
1466             for (int i = 0; i < jmods.length; i++) {
1467                 mod = mods[i];
1468                 jmods[i] = convertToLdapModCode(mod.getModificationOp());
1469                 jattrs[i] = mod.getAttribute();
1470             }
1471 
1472             LdapResult answer = clnt.modify(newDN, jmods, jattrs, reqCtls);
1473             respCtls = answer.resControls; // retrieve response controls
1474 
1475             if (answer.status != LdapClient.LDAP_SUCCESS) {
1476                 processReturnCode(answer, name);
1477             }
1478 
1479         } catch (LdapReferralException e) {
1480             if (handleReferrals == LdapClient.LDAP_REF_THROW)
1481                 throw cont.fillInException(e);
1482 
1483             // process the referrals sequentially
1484             while (true) {
1485 
1486                 LdapReferralContext refCtx =
1487                     (LdapReferralContext)e.getReferralContext(envprops, bindCtls);
1488 
1489                 // repeat the original operation at the new context
1490                 try {
1491 
1492                     refCtx.modifyAttributes(name, mods);
1493                     return;
1494 
1495                 } catch (LdapReferralException re) {
1496                     e = re;
1497                     continue;
1498 
1499                 } finally {
1500                     // Make sure we close referral context
1501                     refCtx.close();
1502                 }
1503             }
1504 
1505         } catch (IOException e) {
1506             NamingException e2 = new CommunicationException(e.getMessage());
1507             e2.setRootCause(e);
1508             throw cont.fillInException(e2);
1509 
1510         } catch (NamingException e) {
1511             throw cont.fillInException(e);
1512         }
1513     }
1514 
1515     private static int convertToLdapModCode(int mod_op) {
1516         switch (mod_op) {
1517         case DirContext.ADD_ATTRIBUTE:
1518             return(LdapClient.ADD);
1519 
1520         case DirContext.REPLACE_ATTRIBUTE:
1521             return (LdapClient.REPLACE);
1522 
1523         case DirContext.REMOVE_ATTRIBUTE:
1524             return (LdapClient.DELETE);
1525 
1526         default:
1527             throw new IllegalArgumentException("Invalid modification code");
1528         }
1529     }
1530 
1531    // ------------------- Schema -----------------------
1532 
1533     protected DirContext c_getSchema(Name name, Continuation cont)
1534             throws NamingException {
1535         cont.setError(this, name);
1536         try {
1537             return getSchemaTree(name);
1538 
1539         } catch (NamingException e) {
1540             throw cont.fillInException(e);
1541         }
1542     }
1543 
1544     protected DirContext c_getSchemaClassDefinition(Name name,
1545                                                     Continuation cont)
1546             throws NamingException {
1547         cont.setError(this, name);
1548 
1549         try {
1550             // retrieve the objectClass attribute from LDAP
1551             Attribute objectClassAttr = c_getAttributes(name,
1552                 new String[]{"objectclass"}, cont).get("objectclass");
1553             if (objectClassAttr == null || objectClassAttr.size() == 0) {
1554                 return EMPTY_SCHEMA;
1555             }
1556 
1557             // retrieve the root of the ObjectClass schema tree
1558             Context ocSchema = (Context) c_getSchema(name, cont).lookup(
1559                 LdapSchemaParser.OBJECTCLASS_DEFINITION_NAME);
1560 
1561             // create a context to hold the schema objects representing the object
1562             // classes
1563             HierMemDirCtx objectClassCtx = new HierMemDirCtx();
1564             DirContext objectClassDef;
1565             String objectClassName;
1566             for (Enumeration<?> objectClasses = objectClassAttr.getAll();
1567                 objectClasses.hasMoreElements(); ) {
1568                 objectClassName = (String)objectClasses.nextElement();
1569                 // %%% Should we fail if not found, or just continue?
1570                 objectClassDef = (DirContext)ocSchema.lookup(objectClassName);
1571                 objectClassCtx.bind(objectClassName, objectClassDef);
1572             }
1573 
1574             // Make context read-only
1575             objectClassCtx.setReadOnly(
1576                 new SchemaViolationException("Cannot update schema object"));
1577             return (DirContext)objectClassCtx;
1578 
1579         } catch (NamingException e) {
1580             throw cont.fillInException(e);
1581         }
1582     }
1583 
1584     /*
1585      * getSchemaTree first looks to see if we have already built a
1586      * schema tree for the given entry. If not, it builds a new one and
1587      * stores it in our private hash table
1588      */
1589     private DirContext getSchemaTree(Name name) throws NamingException {
1590         String subschemasubentry = getSchemaEntry(name, true);
1591 
1592         DirContext schemaTree = schemaTrees.get(subschemasubentry);
1593 
1594         if(schemaTree==null) {
1595             if(debug){System.err.println("LdapCtx: building new schema tree " + this);}
1596             schemaTree = buildSchemaTree(subschemasubentry);
1597             schemaTrees.put(subschemasubentry, schemaTree);
1598         }
1599 
1600         return schemaTree;
1601     }
1602 
1603     /*
1604      * buildSchemaTree builds the schema tree corresponding to the
1605      * given subschemasubentree
1606      */
1607     private DirContext buildSchemaTree(String subschemasubentry)
1608         throws NamingException {
1609 
1610         // get the schema entry itself
1611         // DO ask for return object here because we need it to
1612         // create context. Since asking for all attrs, we won't
1613         // be transmitting any specific attrIDs (like Java-specific ones).
1614         SearchControls constraints = new
1615             SearchControls(SearchControls.OBJECT_SCOPE,
1616                 0, 0, /* count and time limits */
1617                 SCHEMA_ATTRIBUTES /* return schema attrs */,
1618                 true /* return obj */,
1619                 false /*deref link */ );
1620 
1621         Name sse = (new CompositeName()).add(subschemasubentry);
1622         NamingEnumeration<SearchResult> results =
1623             searchAux(sse, "(objectClass=subschema)", constraints,
1624             false, true, new Continuation());
1625 
1626         if(!results.hasMore()) {
1627             throw new OperationNotSupportedException(
1628                 "Cannot get read subschemasubentry: " + subschemasubentry);
1629         }
1630         SearchResult result = results.next();
1631         results.close();
1632 
1633         Object obj = result.getObject();
1634         if(!(obj instanceof LdapCtx)) {
1635             throw new NamingException(
1636                 "Cannot get schema object as DirContext: " + subschemasubentry);
1637         }
1638 
1639         return LdapSchemaCtx.createSchemaTree(envprops, subschemasubentry,
1640             (LdapCtx)obj /* schema entry */,
1641             result.getAttributes() /* schema attributes */,
1642             netscapeSchemaBug);
1643    }
1644 
1645     /*
1646      * getSchemaEntree returns the DN of the subschemasubentree for the
1647      * given entree. It first looks to see if the given entry has
1648      * a subschema different from that of the root DIT (by looking for
1649      * a "subschemasubentry" attribute). If it doesn't find one, it returns
1650      * the one for the root of the DIT (by looking for the root's
1651      * "subschemasubentry" attribute).
1652      *
1653      * This function is called regardless of the server's version, since
1654      * an administrator may have setup the server to support client schema
1655      * queries. If this function tries a search on a v2 server that
1656      * doesn't support schema, one of these two things will happen:
1657      * 1) It will get an exception when querying the root DSE
1658      * 2) It will not find a subschemasubentry on the root DSE
1659      * If either of these things occur and the server is not v3, we
1660      * throw OperationNotSupported.
1661      *
1662      * the relative flag tells whether the given name is relative to this
1663      * context.
1664      */
1665     private String getSchemaEntry(Name name, boolean relative)
1666         throws NamingException {
1667 
1668         // Asks for operational attribute "subschemasubentry"
1669         SearchControls constraints = new SearchControls(SearchControls.OBJECT_SCOPE,
1670             0, 0, /* count and time limits */
1671             new String[]{"subschemasubentry"} /* attr to return */,
1672             false /* returning obj */,
1673             false /* deref link */);
1674 
1675         NamingEnumeration<SearchResult> results;
1676         try {
1677             results = searchAux(name, "objectclass=*", constraints, relative,
1678                 true, new Continuation());
1679 
1680         } catch (NamingException ne) {
1681             if (!clnt.isLdapv3 && currentDN.length() == 0 && name.isEmpty()) {
1682                 // we got an error looking for a root entry on an ldapv2
1683                 // server. The server must not support schema.
1684                 throw new OperationNotSupportedException(
1685                     "Cannot get schema information from server");
1686             } else {
1687                 throw ne;
1688             }
1689         }
1690 
1691         if (!results.hasMoreElements()) {
1692             throw new ConfigurationException(
1693                 "Requesting schema of nonexistent entry: " + name);
1694         }
1695 
1696         SearchResult result = results.next();
1697         results.close();
1698 
1699         Attribute schemaEntryAttr =
1700             result.getAttributes().get("subschemasubentry");
1701         //System.err.println("schema entry attrs: " + schemaEntryAttr);
1702 
1703         if (schemaEntryAttr == null || schemaEntryAttr.size() < 0) {
1704             if (currentDN.length() == 0 && name.isEmpty()) {
1705                 // the server doesn't have a subschemasubentry in its root DSE.
1706                 // therefore, it doesn't support schema.
1707                 throw new OperationNotSupportedException(
1708                     "Cannot read subschemasubentry of root DSE");
1709             } else {
1710                 return getSchemaEntry(new CompositeName(), false);
1711             }
1712         }
1713 
1714         return (String)(schemaEntryAttr.get()); // return schema entry name
1715     }
1716 
1717     // package-private; used by search enum.
1718     // Set attributes to point to this context in case some one
1719     // asked for their schema
1720     void setParents(Attributes attrs, Name name) throws NamingException {
1721         NamingEnumeration<? extends Attribute> ae = attrs.getAll();
1722         while(ae.hasMore()) {
1723             ((LdapAttribute) ae.next()).setParent(this, name);
1724         }
1725     }
1726 
1727     /*
1728      * Returns the URL associated with this context; used by LdapAttribute
1729      * after deserialization to get pointer to this context.
1730      */
1731     String getURL() {
1732         if (url == null) {
1733             url = LdapURL.toUrlString(hostname, port_number, currentDN,
1734                 hasLdapsScheme);
1735         }
1736 
1737         return url;
1738     }
1739 
1740    // --------------------- Searches -----------------------------
1741     protected NamingEnumeration<SearchResult> c_search(Name name,
1742                                          Attributes matchingAttributes,
1743                                          Continuation cont)
1744             throws NamingException {
1745         return c_search(name, matchingAttributes, null, cont);
1746     }
1747 
1748     protected NamingEnumeration<SearchResult> c_search(Name name,
1749                                          Attributes matchingAttributes,
1750                                          String[] attributesToReturn,
1751                                          Continuation cont)
1752             throws NamingException {
1753         SearchControls cons = new SearchControls();
1754         cons.setReturningAttributes(attributesToReturn);
1755         String filter;
1756         try {
1757             filter = SearchFilter.format(matchingAttributes);
1758         } catch (NamingException e) {
1759             cont.setError(this, name);
1760             throw cont.fillInException(e);
1761         }
1762         return c_search(name, filter, cons, cont);
1763     }
1764 
1765     protected NamingEnumeration<SearchResult> c_search(Name name,
1766                                          String filter,
1767                                          SearchControls cons,
1768                                          Continuation cont)
1769             throws NamingException {
1770         return searchAux(name, filter, cloneSearchControls(cons), true,
1771                  waitForReply, cont);
1772     }
1773 
1774     protected NamingEnumeration<SearchResult> c_search(Name name,
1775                                          String filterExpr,
1776                                          Object[] filterArgs,
1777                                          SearchControls cons,
1778                                          Continuation cont)
1779             throws NamingException {
1780         String strfilter;
1781         try {
1782             strfilter = SearchFilter.format(filterExpr, filterArgs);
1783         } catch (NamingException e) {
1784             cont.setError(this, name);
1785             throw cont.fillInException(e);
1786         }
1787         return c_search(name, strfilter, cons, cont);
1788     }
1789 
1790         // Used by NamingNotifier
1791     NamingEnumeration<SearchResult> searchAux(Name name,
1792         String filter,
1793         SearchControls cons,
1794         boolean relative,
1795         boolean waitForReply, Continuation cont) throws NamingException {
1796 
1797         LdapResult answer = null;
1798         String[] tokens = new String[2];    // stores ldap compare op. values
1799         String[] reqAttrs;                  // remember what was asked
1800 
1801         if (cons == null) {
1802             cons = new SearchControls();
1803         }
1804         reqAttrs = cons.getReturningAttributes();
1805 
1806         // if objects are requested then request the Java attributes too
1807         // so that the objects can be constructed
1808         if (cons.getReturningObjFlag()) {
1809             if (reqAttrs != null) {
1810 
1811                 // check for presence of "*" (user attributes wildcard)
1812                 boolean hasWildcard = false;
1813                 for (int i = reqAttrs.length - 1; i >= 0; i--) {
1814                     if (reqAttrs[i].equals("*")) {
1815                         hasWildcard = true;
1816                         break;
1817                     }
1818                 }
1819                 if (! hasWildcard) {
1820                     String[] totalAttrs =
1821                         new String[reqAttrs.length +Obj.JAVA_ATTRIBUTES.length];
1822                     System.arraycopy(reqAttrs, 0, totalAttrs, 0,
1823                         reqAttrs.length);
1824                     System.arraycopy(Obj.JAVA_ATTRIBUTES, 0, totalAttrs,
1825                         reqAttrs.length, Obj.JAVA_ATTRIBUTES.length);
1826 
1827                     cons.setReturningAttributes(totalAttrs);
1828                 }
1829             }
1830         }
1831 
1832         LdapCtx.SearchArgs args =
1833             new LdapCtx.SearchArgs(name, filter, cons, reqAttrs);
1834 
1835         cont.setError(this, name);
1836         try {
1837             // see if this can be done as a compare, otherwise do a search
1838             if (searchToCompare(filter, cons, tokens)){
1839                 //System.err.println("compare triggered");
1840                 answer = compare(name, tokens[0], tokens[1]);
1841                 if (! (answer.compareToSearchResult(fullyQualifiedName(name)))){
1842                     processReturnCode(answer, name);
1843                 }
1844             } else {
1845                 answer = doSearch(name, filter, cons, relative, waitForReply);
1846                 // search result may contain referrals
1847                 processReturnCode(answer, name);
1848             }
1849             return new LdapSearchEnumeration(this, answer,
1850                                              fullyQualifiedName(name),
1851                                              args, cont);
1852 
1853         } catch (LdapReferralException e) {
1854             if (handleReferrals == LdapClient.LDAP_REF_THROW)
1855                 throw cont.fillInException(e);
1856 
1857             // process the referrals sequentially
1858             while (true) {
1859 
1860                 @SuppressWarnings("unchecked")
1861                 LdapReferralContext refCtx = (LdapReferralContext)
1862                         e.getReferralContext(envprops, bindCtls);
1863 
1864                 // repeat the original operation at the new context
1865                 try {
1866 
1867                     return refCtx.search(name, filter, cons);
1868 
1869                 } catch (LdapReferralException re) {
1870                     e = re;
1871                     continue;
1872 
1873                 } finally {
1874                     // Make sure we close referral context
1875                     refCtx.close();
1876                 }
1877             }
1878 
1879         } catch (LimitExceededException e) {
1880             LdapSearchEnumeration res =
1881                 new LdapSearchEnumeration(this, answer, fullyQualifiedName(name),
1882                                           args, cont);
1883             res.setNamingException(e);
1884             return res;
1885 
1886         } catch (PartialResultException e) {
1887             LdapSearchEnumeration res =
1888                 new LdapSearchEnumeration(this, answer, fullyQualifiedName(name),
1889                                           args, cont);
1890 
1891             res.setNamingException(e);
1892             return res;
1893 
1894         } catch (IOException e) {
1895             NamingException e2 = new CommunicationException(e.getMessage());
1896             e2.setRootCause(e);
1897             throw cont.fillInException(e2);
1898 
1899         } catch (NamingException e) {
1900             throw cont.fillInException(e);
1901         }
1902     }
1903 
1904 
1905     LdapResult getSearchReply(LdapClient eClnt, LdapResult res)
1906             throws NamingException {
1907         // ensureOpen() won't work here because
1908         // session was associated with previous connection
1909 
1910         // %%% RL: we can actually allow the enumeration to continue
1911         // using the old handle but other weird things might happen
1912         // when we hit a referral
1913         if (clnt != eClnt) {
1914             throw new CommunicationException(
1915                 "Context's connection changed; unable to continue enumeration");
1916         }
1917 
1918         try {
1919             return eClnt.getSearchReply(batchSize, res, binaryAttrs);
1920         } catch (IOException e) {
1921             NamingException e2 = new CommunicationException(e.getMessage());
1922             e2.setRootCause(e);
1923             throw e2;
1924         }
1925     }
1926 
1927     // Perform a search. Expect 1 SearchResultEntry and the SearchResultDone.
1928     private LdapResult doSearchOnce(Name name, String filter,
1929         SearchControls cons, boolean relative) throws NamingException {
1930 
1931         int savedBatchSize = batchSize;
1932         batchSize = 2; // 2 protocol elements
1933 
1934         LdapResult answer = doSearch(name, filter, cons, relative, true);
1935 
1936         batchSize = savedBatchSize;
1937         return answer;
1938     }
1939 
1940     private LdapResult doSearch(Name name, String filter, SearchControls cons,
1941         boolean relative, boolean waitForReply) throws NamingException {
1942             ensureOpen();
1943             try {
1944                 int scope;
1945 
1946                 switch (cons.getSearchScope()) {
1947                 case SearchControls.OBJECT_SCOPE:
1948                     scope = LdapClient.SCOPE_BASE_OBJECT;
1949                     break;
1950                 default:
1951                 case SearchControls.ONELEVEL_SCOPE:
1952                     scope = LdapClient.SCOPE_ONE_LEVEL;
1953                     break;
1954                 case SearchControls.SUBTREE_SCOPE:
1955                     scope = LdapClient.SCOPE_SUBTREE;
1956                     break;
1957                 }
1958 
1959                 // If cons.getReturningObjFlag() then caller should already
1960                 // have make sure to request the appropriate attrs
1961 
1962                 String[] retattrs = cons.getReturningAttributes();
1963                 if (retattrs != null && retattrs.length == 0) {
1964                     // Ldap treats null and empty array the same
1965                     // need to replace with single element array
1966                     retattrs = new String[1];
1967                     retattrs[0] = "1.1";
1968                 }
1969 
1970                 String nm = (relative
1971                              ? fullyQualifiedName(name)
1972                              : (name.isEmpty()
1973                                 ? ""
1974                                 : name.get(0)));
1975 
1976                 // JNDI unit is milliseconds, LDAP unit is seconds.
1977                 // Zero means no limit.
1978                 int msecLimit = cons.getTimeLimit();
1979                 int secLimit = 0;
1980 
1981                 if (msecLimit > 0) {
1982                     secLimit = (msecLimit / 1000) + 1;
1983                 }
1984 
1985                 LdapResult answer =
1986                     clnt.search(nm,
1987                         scope,
1988                         derefAliases,
1989                         (int)cons.getCountLimit(),
1990                         secLimit,
1991                         cons.getReturningObjFlag() ? false : typesOnly,
1992                         retattrs,
1993                         filter,
1994                         batchSize,
1995                         reqCtls,
1996                         binaryAttrs,
1997                         waitForReply,
1998                         replyQueueSize);
1999                 respCtls = answer.resControls; // retrieve response controls
2000                 return answer;
2001 
2002             } catch (IOException e) {
2003                 NamingException e2 = new CommunicationException(e.getMessage());
2004                 e2.setRootCause(e);
2005                 throw e2;
2006             }
2007     }
2008 
2009 
2010     /*
2011      * Certain simple JNDI searches are automatically converted to
2012      * LDAP compare operations by the LDAP service provider. A search
2013      * is converted to a compare iff:
2014      *
2015      *    - the scope is set to OBJECT_SCOPE
2016      *    - the filter string contains a simple assertion: "<type>=<value>"
2017      *    - the returning attributes list is present but empty
2018      */
2019 
2020     // returns true if a search can be carried out as a compare, and sets
2021     // tokens[0] and tokens[1] to the type and value respectively.
2022     // e.g. filter "cn=Jon Ruiz" becomes, type "cn" and value "Jon Ruiz"
2023     // This function uses the documents JNDI Compare example as a model
2024     // for when to turn a search into a compare.
2025 
2026     private static boolean searchToCompare(
2027                                     String filter,
2028                                     SearchControls cons,
2029                                     String tokens[]) {
2030 
2031         // if scope is not object-scope, it's really a search
2032         if (cons.getSearchScope() != SearchControls.OBJECT_SCOPE) {
2033             return false;
2034         }
2035 
2036         // if attributes are to be returned, it's really a search
2037         String[] attrs = cons.getReturningAttributes();
2038         if (attrs == null || attrs.length != 0) {
2039             return false;
2040         }
2041 
2042         // if the filter not a simple assertion, it's really a search
2043         if (! filterToAssertion(filter, tokens)) {
2044             return false;
2045         }
2046 
2047         // it can be converted to a compare
2048         return true;
2049     }
2050 
2051     // If the supplied filter is a simple assertion i.e. "<type>=<value>"
2052     // (enclosing parentheses are permitted) then
2053     // filterToAssertion will return true and pass the type and value as
2054     // the first and second elements of tokens respectively.
2055     // precondition: tokens[] must be initialized and be at least of size 2.
2056 
2057     private static boolean filterToAssertion(String filter, String tokens[]) {
2058 
2059         // find the left and right half of the assertion
2060         StringTokenizer assertionTokenizer = new StringTokenizer(filter, "=");
2061 
2062         if (assertionTokenizer.countTokens() != 2) {
2063             return false;
2064         }
2065 
2066         tokens[0] = assertionTokenizer.nextToken();
2067         tokens[1] = assertionTokenizer.nextToken();
2068 
2069         // make sure the value does not contain a wildcard
2070         if (tokens[1].indexOf('*') != -1) {
2071             return false;
2072         }
2073 
2074         // test for enclosing parenthesis
2075         boolean hasParens = false;
2076         int len = tokens[1].length();
2077 
2078         if ((tokens[0].charAt(0) == '(') &&
2079             (tokens[1].charAt(len - 1) == ')')) {
2080             hasParens = true;
2081 
2082         } else if ((tokens[0].charAt(0) == '(') ||
2083             (tokens[1].charAt(len - 1) == ')')) {
2084             return false; // unbalanced
2085         }
2086 
2087         // make sure the left and right half are not expressions themselves
2088         StringTokenizer illegalCharsTokenizer =
2089             new StringTokenizer(tokens[0], "()&|!=~><*", true);
2090 
2091         if (illegalCharsTokenizer.countTokens() != (hasParens ? 2 : 1)) {
2092             return false;
2093         }
2094 
2095         illegalCharsTokenizer =
2096             new StringTokenizer(tokens[1], "()&|!=~><*", true);
2097 
2098         if (illegalCharsTokenizer.countTokens() != (hasParens ? 2 : 1)) {
2099             return false;
2100         }
2101 
2102         // strip off enclosing parenthesis, if present
2103         if (hasParens) {
2104             tokens[0] = tokens[0].substring(1);
2105             tokens[1] = tokens[1].substring(0, len - 1);
2106         }
2107 
2108         return true;
2109     }
2110 
2111     private LdapResult compare(Name name, String type, String value)
2112         throws IOException, NamingException {
2113 
2114         ensureOpen();
2115         String nm = fullyQualifiedName(name);
2116 
2117         LdapResult answer = clnt.compare(nm, type, value, reqCtls);
2118         respCtls = answer.resControls; // retrieve response controls
2119 
2120         return answer;
2121     }
2122 
2123     private static SearchControls cloneSearchControls(SearchControls cons) {
2124         if (cons == null) {
2125             return null;
2126         }
2127         String[] retAttrs = cons.getReturningAttributes();
2128         if (retAttrs != null) {
2129             String[] attrs = new String[retAttrs.length];
2130             System.arraycopy(retAttrs, 0, attrs, 0, retAttrs.length);
2131             retAttrs = attrs;
2132         }
2133         return new SearchControls(cons.getSearchScope(),
2134                                   cons.getCountLimit(),
2135                                   cons.getTimeLimit(),
2136                                   retAttrs,
2137                                   cons.getReturningObjFlag(),
2138                                   cons.getDerefLinkFlag());
2139     }
2140 
2141    // -------------- Environment Properties ------------------
2142 
2143     /**
2144      * Override with noncloning version.
2145      */
2146     protected Hashtable<String, Object> p_getEnvironment() {
2147         return envprops;
2148     }
2149 
2150     @SuppressWarnings("unchecked") // clone()
2151     public Hashtable<String, Object> getEnvironment() throws NamingException {
2152         return (envprops == null
2153                 ? new Hashtable<String, Object>(5, 0.75f)
2154                 : (Hashtable<String, Object>)envprops.clone());
2155     }
2156 
2157     @SuppressWarnings("unchecked") // clone()
2158     public Object removeFromEnvironment(String propName)
2159         throws NamingException {
2160 
2161         // not there; just return
2162         if (envprops == null || envprops.get(propName) == null) {
2163             return null;
2164         }
2165         switch (propName) {
2166             case REF_SEPARATOR:
2167                 addrEncodingSeparator = DEFAULT_REF_SEPARATOR;
2168                 break;
2169             case TYPES_ONLY:
2170                 typesOnly = DEFAULT_TYPES_ONLY;
2171                 break;
2172             case DELETE_RDN:
2173                 deleteRDN = DEFAULT_DELETE_RDN;
2174                 break;
2175             case DEREF_ALIASES:
2176                 derefAliases = DEFAULT_DEREF_ALIASES;
2177                 break;
2178             case Context.BATCHSIZE:
2179                 batchSize = DEFAULT_BATCH_SIZE;
2180                 break;
2181             case REFERRAL_LIMIT:
2182                 referralHopLimit = DEFAULT_REFERRAL_LIMIT;
2183                 break;
2184             case Context.REFERRAL:
2185                 setReferralMode(null, true);
2186                 break;
2187             case BINARY_ATTRIBUTES:
2188                 setBinaryAttributes(null);
2189                 break;
2190             case CONNECT_TIMEOUT:
2191                 connectTimeout = -1;
2192                 break;
2193             case READ_TIMEOUT:
2194                 readTimeout = -1;
2195                 break;
2196             case WAIT_FOR_REPLY:
2197                 waitForReply = true;
2198                 break;
2199             case REPLY_QUEUE_SIZE:
2200                 replyQueueSize = -1;
2201                 break;
2202 
2203             // The following properties affect the connection
2204 
2205             case Context.SECURITY_PROTOCOL:
2206                 closeConnection(SOFT_CLOSE);
2207                 // De-activate SSL and reset the context's url and port number
2208                 if (useSsl && !hasLdapsScheme) {
2209                     useSsl = false;
2210                     url = null;
2211                     if (useDefaultPortNumber) {
2212                         port_number = DEFAULT_PORT;
2213                     }
2214                 }
2215                 break;
2216             case VERSION:
2217             case SOCKET_FACTORY:
2218                 closeConnection(SOFT_CLOSE);
2219                 break;
2220             case Context.SECURITY_AUTHENTICATION:
2221             case Context.SECURITY_PRINCIPAL:
2222             case Context.SECURITY_CREDENTIALS:
2223                 sharable = false;
2224                 break;
2225         }
2226 
2227         // Update environment; reconnection will use new props
2228         envprops = (Hashtable<String, Object>)envprops.clone();
2229         return envprops.remove(propName);
2230     }
2231 
2232     @SuppressWarnings("unchecked") // clone()
2233     public Object addToEnvironment(String propName, Object propVal)
2234         throws NamingException {
2235 
2236             // If adding null, call remove
2237             if (propVal == null) {
2238                 return removeFromEnvironment(propName);
2239             }
2240             switch (propName) {
2241                 case REF_SEPARATOR:
2242                     setRefSeparator((String)propVal);
2243                     break;
2244                 case TYPES_ONLY:
2245                     setTypesOnly((String)propVal);
2246                     break;
2247                 case DELETE_RDN:
2248                     setDeleteRDN((String)propVal);
2249                     break;
2250                 case DEREF_ALIASES:
2251                     setDerefAliases((String)propVal);
2252                     break;
2253                 case Context.BATCHSIZE:
2254                     setBatchSize((String)propVal);
2255                     break;
2256                 case REFERRAL_LIMIT:
2257                     setReferralLimit((String)propVal);
2258                     break;
2259                 case Context.REFERRAL:
2260                     setReferralMode((String)propVal, true);
2261                     break;
2262                 case BINARY_ATTRIBUTES:
2263                     setBinaryAttributes((String)propVal);
2264                     break;
2265                 case CONNECT_TIMEOUT:
2266                     setConnectTimeout((String)propVal);
2267                     break;
2268                 case READ_TIMEOUT:
2269                     setReadTimeout((String)propVal);
2270                     break;
2271                 case WAIT_FOR_REPLY:
2272                     setWaitForReply((String)propVal);
2273                     break;
2274                 case REPLY_QUEUE_SIZE:
2275                     setReplyQueueSize((String)propVal);
2276                     break;
2277 
2278             // The following properties affect the connection
2279 
2280                 case Context.SECURITY_PROTOCOL:
2281                     closeConnection(SOFT_CLOSE);
2282                     // Activate SSL and reset the context's url and port number
2283                     if ("ssl".equals(propVal)) {
2284                         useSsl = true;
2285                         url = null;
2286                         if (useDefaultPortNumber) {
2287                             port_number = DEFAULT_SSL_PORT;
2288                         }
2289                     }
2290                     break;
2291                 case VERSION:
2292                 case SOCKET_FACTORY:
2293                     closeConnection(SOFT_CLOSE);
2294                     break;
2295                 case Context.SECURITY_AUTHENTICATION:
2296                 case Context.SECURITY_PRINCIPAL:
2297                 case Context.SECURITY_CREDENTIALS:
2298                     sharable = false;
2299                     break;
2300             }
2301 
2302             // Update environment; reconnection will use new props
2303             envprops = (envprops == null
2304                 ? new Hashtable<String, Object>(5, 0.75f)
2305                 : (Hashtable<String, Object>)envprops.clone());
2306             return envprops.put(propName, propVal);
2307     }
2308 
2309     /**
2310      * Sets the URL that created the context in the java.naming.provider.url
2311      * property.
2312      */
2313     void setProviderUrl(String providerUrl) { // called by LdapCtxFactory
2314         if (envprops != null) {
2315             envprops.put(Context.PROVIDER_URL, providerUrl);
2316         }
2317     }
2318 
2319     /**
2320      * Sets the domain name for the context in the com.sun.jndi.ldap.domainname
2321      * property.
2322      * Used for hostname verification by Start TLS
2323      */
2324     void setDomainName(String domainName) { // called by LdapCtxFactory
2325         if (envprops != null) {
2326             envprops.put(DOMAIN_NAME, domainName);
2327         }
2328     }
2329 
2330     private void initEnv() throws NamingException {
2331         if (envprops == null) {
2332             // Make sure that referrals are to their default
2333             setReferralMode(null, false);
2334             return;
2335         }
2336 
2337         // Set batch size
2338         setBatchSize((String)envprops.get(Context.BATCHSIZE));
2339 
2340         // Set separator used for encoding RefAddr
2341         setRefSeparator((String)envprops.get(REF_SEPARATOR));
2342 
2343         // Set whether RDN is removed when renaming object
2344         setDeleteRDN((String)envprops.get(DELETE_RDN));
2345 
2346         // Set whether types are returned only
2347         setTypesOnly((String)envprops.get(TYPES_ONLY));
2348 
2349         // Set how aliases are dereferenced
2350         setDerefAliases((String)envprops.get(DEREF_ALIASES));
2351 
2352         // Set the limit on referral chains
2353         setReferralLimit((String)envprops.get(REFERRAL_LIMIT));
2354 
2355         setBinaryAttributes((String)envprops.get(BINARY_ATTRIBUTES));
2356 
2357         bindCtls = cloneControls((Control[]) envprops.get(BIND_CONTROLS));
2358 
2359         // set referral handling
2360         setReferralMode((String)envprops.get(Context.REFERRAL), false);
2361 
2362         // Set the connect timeout
2363         setConnectTimeout((String)envprops.get(CONNECT_TIMEOUT));
2364 
2365         // Set the read timeout
2366         setReadTimeout((String)envprops.get(READ_TIMEOUT));
2367 
2368         // Set the flag that controls whether to block until the first reply
2369         // is received
2370         setWaitForReply((String)envprops.get(WAIT_FOR_REPLY));
2371 
2372         // Set the size of the queue of unprocessed search replies
2373         setReplyQueueSize((String)envprops.get(REPLY_QUEUE_SIZE));
2374 
2375         // When connection is created, it will use these and other
2376         // properties from the environment
2377     }
2378 
2379     private void setDeleteRDN(String deleteRDNProp) {
2380         if ((deleteRDNProp != null) &&
2381             (deleteRDNProp.equalsIgnoreCase("false"))) {
2382             deleteRDN = false;
2383         } else {
2384             deleteRDN = DEFAULT_DELETE_RDN;
2385         }
2386     }
2387 
2388     private void setTypesOnly(String typesOnlyProp) {
2389         if ((typesOnlyProp != null) &&
2390             (typesOnlyProp.equalsIgnoreCase("true"))) {
2391             typesOnly = true;
2392         } else {
2393             typesOnly = DEFAULT_TYPES_ONLY;
2394         }
2395     }
2396 
2397     /**
2398      * Sets the batch size of this context;
2399      */
2400     private void setBatchSize(String batchSizeProp) {
2401         // set batchsize
2402         if (batchSizeProp != null) {
2403             batchSize = Integer.parseInt(batchSizeProp);
2404         } else {
2405             batchSize = DEFAULT_BATCH_SIZE;
2406         }
2407     }
2408 
2409     /**
2410      * Sets the referral mode of this context to 'follow', 'throw' or 'ignore'.
2411      * If referral mode is 'ignore' then activate the manageReferral control.
2412      */
2413     private void setReferralMode(String ref, boolean update) {
2414         // First determine the referral mode
2415         if (ref != null) {
2416             switch (ref) {
2417                 case "follow-scheme":
2418                     handleReferrals = LdapClient.LDAP_REF_FOLLOW_SCHEME;
2419                     break;
2420                 case "follow":
2421                     handleReferrals = LdapClient.LDAP_REF_FOLLOW;
2422                     break;
2423                 case "throw":
2424                     handleReferrals = LdapClient.LDAP_REF_THROW;
2425                     break;
2426                 case "ignore":
2427                     handleReferrals = LdapClient.LDAP_REF_IGNORE;
2428                     break;
2429                 default:
2430                     throw new IllegalArgumentException(
2431                         "Illegal value for " + Context.REFERRAL + " property.");
2432             }
2433         } else {
2434             handleReferrals = DEFAULT_REFERRAL_MODE;
2435         }
2436 
2437         if (handleReferrals == LdapClient.LDAP_REF_IGNORE) {
2438             // If ignoring referrals, add manageReferralControl
2439             reqCtls = addControl(reqCtls, manageReferralControl);
2440 
2441         } else if (update) {
2442 
2443             // If we're update an existing context, remove the control
2444             reqCtls = removeControl(reqCtls, manageReferralControl);
2445 
2446         } // else, leave alone; need not update
2447     }
2448 
2449     /**
2450      * Set whether aliases are dereferenced during resolution and searches.
2451      */
2452     private void setDerefAliases(String deref) {
2453         if (deref != null) {
2454             switch (deref) {
2455                 case "never":
2456                     derefAliases = 0; // never de-reference aliases
2457                     break;
2458                 case "searching":
2459                     derefAliases = 1; // de-reference aliases during searching
2460                     break;
2461                 case "finding":
2462                     derefAliases = 2; // de-reference during name resolution
2463                     break;
2464                 case "always":
2465                     derefAliases = 3; // always de-reference aliases
2466                     break;
2467                 default:
2468                     throw new IllegalArgumentException("Illegal value for " +
2469                         DEREF_ALIASES + " property.");
2470             }
2471         } else {
2472             derefAliases = DEFAULT_DEREF_ALIASES;
2473         }
2474     }
2475 
2476     private void setRefSeparator(String sepStr) throws NamingException {
2477         if (sepStr != null && sepStr.length() > 0) {
2478             addrEncodingSeparator = sepStr.charAt(0);
2479         } else {
2480             addrEncodingSeparator = DEFAULT_REF_SEPARATOR;
2481         }
2482     }
2483 
2484     /**
2485      * Sets the limit on referral chains
2486      */
2487     private void setReferralLimit(String referralLimitProp) {
2488         // set referral limit
2489         if (referralLimitProp != null) {
2490             referralHopLimit = Integer.parseInt(referralLimitProp);
2491 
2492             // a zero setting indicates no limit
2493             if (referralHopLimit == 0)
2494                 referralHopLimit = Integer.MAX_VALUE;
2495         } else {
2496             referralHopLimit = DEFAULT_REFERRAL_LIMIT;
2497         }
2498     }
2499 
2500     // For counting referral hops
2501     void setHopCount(int hopCount) {
2502         this.hopCount = hopCount;
2503     }
2504 
2505     /**
2506      * Sets the connect timeout value
2507      */
2508     private void setConnectTimeout(String connectTimeoutProp) {
2509         if (connectTimeoutProp != null) {
2510             connectTimeout = Integer.parseInt(connectTimeoutProp);
2511         } else {
2512             connectTimeout = -1;
2513         }
2514     }
2515 
2516     /**
2517      * Sets the size of the queue of unprocessed search replies
2518      */
2519     private void setReplyQueueSize(String replyQueueSizeProp) {
2520         if (replyQueueSizeProp != null) {
2521            replyQueueSize = Integer.parseInt(replyQueueSizeProp);
2522             // disallow an empty queue
2523             if (replyQueueSize <= 0) {
2524                 replyQueueSize = -1;    // unlimited
2525             }
2526         } else {
2527             replyQueueSize = -1;        // unlimited
2528         }
2529     }
2530 
2531     /**
2532      * Sets the flag that controls whether to block until the first search
2533      * reply is received
2534      */
2535     private void setWaitForReply(String waitForReplyProp) {
2536         if (waitForReplyProp != null &&
2537             (waitForReplyProp.equalsIgnoreCase("false"))) {
2538             waitForReply = false;
2539         } else {
2540             waitForReply = true;
2541         }
2542     }
2543 
2544     /**
2545      * Sets the read timeout value
2546      */
2547     private void setReadTimeout(String readTimeoutProp) {
2548         if (readTimeoutProp != null) {
2549            readTimeout = Integer.parseInt(readTimeoutProp);
2550         } else {
2551             readTimeout = -1;
2552         }
2553     }
2554 
2555     /*
2556      * Extract URLs from a string. The format of the string is:
2557      *
2558      *     <urlstring > ::= "Referral:" <ldapurls>
2559      *     <ldapurls>   ::= <separator> <ldapurl> | <ldapurls>
2560      *     <separator>  ::= ASCII linefeed character (0x0a)
2561      *     <ldapurl>    ::= LDAP URL format (RFC 1959)
2562      *
2563      * Returns a Vector of single-String Vectors.
2564      */
2565     private static Vector<Vector<String>> extractURLs(String refString) {
2566 
2567         int separator = 0;
2568         int urlCount = 0;
2569 
2570         // count the number of URLs
2571         while ((separator = refString.indexOf('\n', separator)) >= 0) {
2572             separator++;
2573             urlCount++;
2574         }
2575 
2576         Vector<Vector<String>> referrals = new Vector<>(urlCount);
2577         int iURL;
2578         int i = 0;
2579 
2580         separator = refString.indexOf('\n');
2581         iURL = separator + 1;
2582         while ((separator = refString.indexOf('\n', iURL)) >= 0) {
2583             Vector<String> referral = new Vector<>(1);
2584             referral.addElement(refString.substring(iURL, separator));
2585             referrals.addElement(referral);
2586             iURL = separator + 1;
2587         }
2588         Vector<String> referral = new Vector<>(1);
2589         referral.addElement(refString.substring(iURL));
2590         referrals.addElement(referral);
2591 
2592         return referrals;
2593     }
2594 
2595     /*
2596      * Argument is a space-separated list of attribute IDs
2597      * Converts attribute IDs to lowercase before adding to built-in list.
2598      */
2599     private void setBinaryAttributes(String attrIds) {
2600         if (attrIds == null) {
2601             binaryAttrs = null;
2602         } else {
2603             binaryAttrs = new Hashtable<>(11, 0.75f);
2604             StringTokenizer tokens =
2605                 new StringTokenizer(attrIds.toLowerCase(Locale.ENGLISH), " ");
2606 
2607             while (tokens.hasMoreTokens()) {
2608                 binaryAttrs.put(tokens.nextToken(), Boolean.TRUE);
2609             }
2610         }
2611     }
2612 
2613    // ----------------- Connection  ---------------------
2614 
2615     @SuppressWarnings("deprecation")
2616     protected void finalize() {
2617         try {
2618             close();
2619         } catch (NamingException e) {
2620             // ignore failures
2621         }
2622     }
2623 
2624     synchronized public void close() throws NamingException {
2625         if (debug) {
2626             System.err.println("LdapCtx: close() called " + this);
2627             (new Throwable()).printStackTrace();
2628         }
2629 
2630         // Event (normal and unsolicited)
2631         if (eventSupport != null) {
2632             eventSupport.cleanup(); // idempotent
2633             removeUnsolicited();
2634         }
2635 
2636         // Enumerations that are keeping the connection alive
2637         if (enumCount > 0) {
2638             if (debug)
2639                 System.err.println("LdapCtx: close deferred");
2640             closeRequested = true;
2641             return;
2642         }
2643         closeConnection(SOFT_CLOSE);
2644 
2645 // %%%: RL: There is no need to set these to null, as they're just
2646 // variables whose contents and references will automatically
2647 // be cleaned up when they're no longer referenced.
2648 // Also, setting these to null creates problems for the attribute
2649 // schema-related methods, which need these to work.
2650 /*
2651         schemaTrees = null;
2652         envprops = null;
2653 */
2654     }
2655 
2656     @SuppressWarnings("unchecked") // clone()
2657     public void reconnect(Control[] connCtls) throws NamingException {
2658         // Update environment
2659         envprops = (envprops == null
2660                 ? new Hashtable<String, Object>(5, 0.75f)
2661                 : (Hashtable<String, Object>)envprops.clone());
2662 
2663         if (connCtls == null) {
2664             envprops.remove(BIND_CONTROLS);
2665             bindCtls = null;
2666         } else {
2667             envprops.put(BIND_CONTROLS, bindCtls = cloneControls(connCtls));
2668         }
2669 
2670         sharable = false;  // can't share with existing contexts
2671         reconnect = true;
2672         ensureOpen();      // open or reauthenticated
2673     }
2674 
2675     private void ensureOpen() throws NamingException {
2676         ensureOpen(false);
2677     }
2678 
2679     private void ensureOpen(boolean startTLS) throws NamingException {
2680 
2681         try {
2682             if (clnt == null) {
2683                 if (debug) {
2684                     System.err.println("LdapCtx: Reconnecting " + this);
2685                 }
2686 
2687                 // reset the cache before a new connection is established
2688                 schemaTrees = new Hashtable<>(11, 0.75f);
2689                 connect(startTLS);
2690 
2691             } else if (!sharable || startTLS) {
2692 
2693                 synchronized (clnt) {
2694                     if (!clnt.isLdapv3
2695                         || clnt.referenceCount > 1
2696                         || clnt.usingSaslStreams()) {
2697                         closeConnection(SOFT_CLOSE);
2698                     }
2699                 }
2700                 // reset the cache before a new connection is established
2701                 schemaTrees = new Hashtable<>(11, 0.75f);
2702                 connect(startTLS);
2703             }
2704 
2705         } finally {
2706             sharable = true;   // connection is now either new or single-use
2707                                // OK for others to start sharing again
2708         }
2709     }
2710 
2711     private void connect(boolean startTLS) throws NamingException {
2712         if (debug) { System.err.println("LdapCtx: Connecting " + this); }
2713 
2714         String user = null;             // authenticating user
2715         Object passwd = null;           // password for authenticating user
2716         String secProtocol = null;      // security protocol (e.g. "ssl")
2717         String socketFactory = null;    // socket factory
2718         String authMechanism = null;    // authentication mechanism
2719         String ver = null;
2720         int ldapVersion;                // LDAP protocol version
2721         boolean usePool = false;        // enable connection pooling
2722 
2723         if (envprops != null) {
2724             user = (String)envprops.get(Context.SECURITY_PRINCIPAL);
2725             passwd = envprops.get(Context.SECURITY_CREDENTIALS);
2726             ver = (String)envprops.get(VERSION);
2727             secProtocol =
2728                useSsl ? "ssl" : (String)envprops.get(Context.SECURITY_PROTOCOL);
2729             socketFactory = (String)envprops.get(SOCKET_FACTORY);
2730             authMechanism =
2731                 (String)envprops.get(Context.SECURITY_AUTHENTICATION);
2732 
2733             usePool = "true".equalsIgnoreCase((String)envprops.get(ENABLE_POOL));
2734         }
2735 
2736         if (socketFactory == null) {
2737             socketFactory =
2738                 "ssl".equals(secProtocol) ? DEFAULT_SSL_FACTORY : null;
2739         }
2740 
2741         if (authMechanism == null) {
2742             authMechanism = (user == null) ? "none" : "simple";
2743         }
2744 
2745         try {
2746             boolean initial = (clnt == null);
2747 
2748             if (initial || reconnect) {
2749                 ldapVersion = (ver != null) ? Integer.parseInt(ver) :
2750                     DEFAULT_LDAP_VERSION;
2751 
2752                 clnt = LdapClient.getInstance(
2753                     usePool, // Whether to use connection pooling
2754 
2755                     // Required for LdapClient constructor
2756                     hostname,
2757                     port_number,
2758                     socketFactory,
2759                     connectTimeout,
2760                     readTimeout,
2761                     trace,
2762 
2763                     // Required for basic client identity
2764                     ldapVersion,
2765                     authMechanism,
2766                     bindCtls,
2767                     secProtocol,
2768 
2769                     // Required for simple client identity
2770                     user,
2771                     passwd,
2772 
2773                     // Required for SASL client identity
2774                     envprops);
2775 
2776                 reconnect = false;
2777 
2778                 /**
2779                  * Pooled connections are preauthenticated;
2780                  * newly created ones are not.
2781                  */
2782                 if (clnt.authenticateCalled()) {
2783                     return;
2784                 }
2785 
2786             } else if (sharable && startTLS) {
2787                 return; // no authentication required
2788 
2789             } else {
2790                 // reauthenticating over existing connection;
2791                 // only v3 supports this
2792                 ldapVersion = LdapClient.LDAP_VERSION3;
2793             }
2794 
2795             LdapResult answer = clnt.authenticate(initial,
2796                 user, passwd, ldapVersion, authMechanism, bindCtls, envprops);
2797 
2798             respCtls = answer.resControls; // retrieve (bind) response controls
2799 
2800             if (answer.status != LdapClient.LDAP_SUCCESS) {
2801                 if (initial) {
2802                     closeConnection(HARD_CLOSE);  // hard close
2803                 }
2804                 processReturnCode(answer);
2805             }
2806 
2807         } catch (LdapReferralException e) {
2808             if (handleReferrals == LdapClient.LDAP_REF_THROW)
2809                 throw e;
2810 
2811             String referral;
2812             LdapURL url;
2813             NamingException saved_ex = null;
2814 
2815             // Process the referrals sequentially (top level) and
2816             // recursively (per referral)
2817             while (true) {
2818 
2819                 if ((referral = e.getNextReferral()) == null) {
2820                     // No more referrals to follow
2821 
2822                     if (saved_ex != null) {
2823                         throw (NamingException)(saved_ex.fillInStackTrace());
2824                     } else {
2825                         // No saved exception, something must have gone wrong
2826                         throw new NamingException(
2827                         "Internal error processing referral during connection");
2828                     }
2829                 }
2830 
2831                 // Use host/port number from referral
2832                 url = new LdapURL(referral);
2833                 hostname = url.getHost();
2834                 if ((hostname != null) && (hostname.charAt(0) == '[')) {
2835                     hostname = hostname.substring(1, hostname.length() - 1);
2836                 }
2837                 port_number = url.getPort();
2838 
2839                 // Try to connect again using new host/port number
2840                 try {
2841                     connect(startTLS);
2842                     break;
2843 
2844                 } catch (NamingException ne) {
2845                     saved_ex = ne;
2846                     continue; // follow another referral
2847                 }
2848             }
2849         }
2850     }
2851 
2852     private void closeConnection(boolean hardclose) {
2853         removeUnsolicited();            // idempotent
2854 
2855         if (clnt != null) {
2856             if (debug) {
2857                 System.err.println("LdapCtx: calling clnt.close() " + this);
2858             }
2859             clnt.close(reqCtls, hardclose);
2860             clnt = null;
2861         }
2862     }
2863 
2864     // Used by Enum classes to track whether it still needs context
2865     private int enumCount = 0;
2866     private boolean closeRequested = false;
2867 
2868     synchronized void incEnumCount() {
2869         ++enumCount;
2870         if (debug) System.err.println("LdapCtx: " + this + " enum inc: " + enumCount);
2871     }
2872 
2873     synchronized void decEnumCount() {
2874         --enumCount;
2875         if (debug) System.err.println("LdapCtx: " + this + " enum dec: " + enumCount);
2876 
2877         if (enumCount == 0 && closeRequested) {
2878             try {
2879                 close();
2880             } catch (NamingException e) {
2881                 // ignore failures
2882             }
2883         }
2884     }
2885 
2886 
2887    // ------------ Return code and Error messages  -----------------------
2888 
2889     protected void processReturnCode(LdapResult answer) throws NamingException {
2890         processReturnCode(answer, null, this, null, envprops, null);
2891     }
2892 
2893     void processReturnCode(LdapResult answer, Name remainName)
2894     throws NamingException {
2895         processReturnCode(answer,
2896                           (new CompositeName()).add(currentDN),
2897                           this,
2898                           remainName,
2899                           envprops,
2900                           fullyQualifiedName(remainName));
2901     }
2902 
2903     protected void processReturnCode(LdapResult res, Name resolvedName,
2904         Object resolvedObj, Name remainName, Hashtable<?,?> envprops, String fullDN)
2905     throws NamingException {
2906 
2907         String msg = LdapClient.getErrorMessage(res.status, res.errorMessage);
2908         NamingException e;
2909         LdapReferralException r = null;
2910 
2911         switch (res.status) {
2912 
2913         case LdapClient.LDAP_SUCCESS:
2914 
2915             // handle Search continuation references
2916             if (res.referrals != null) {
2917 
2918                 msg = "Unprocessed Continuation Reference(s)";
2919 
2920                 if (handleReferrals == LdapClient.LDAP_REF_IGNORE) {
2921                     e = new PartialResultException(msg);
2922                     break;
2923                 }
2924 
2925                 // handle multiple sets of URLs
2926                 int contRefCount = res.referrals.size();
2927                 LdapReferralException head = null;
2928                 LdapReferralException ptr = null;
2929 
2930                 msg = "Continuation Reference";
2931 
2932                 // make a chain of LdapReferralExceptions
2933                 for (int i = 0; i < contRefCount; i++) {
2934 
2935                     r = new LdapReferralException(resolvedName, resolvedObj,
2936                         remainName, msg, envprops, fullDN, handleReferrals,
2937                         reqCtls);
2938                     r.setReferralInfo(res.referrals.elementAt(i), true);
2939 
2940                     if (hopCount > 1) {
2941                         r.setHopCount(hopCount);
2942                     }
2943 
2944                     if (head == null) {
2945                         head = ptr = r;
2946                     } else {
2947                         ptr.nextReferralEx = r; // append ex. to end of chain
2948                         ptr = r;
2949                     }
2950                 }
2951                 res.referrals = null;  // reset
2952 
2953                 if (res.refEx == null) {
2954                     res.refEx = head;
2955 
2956                 } else {
2957                     ptr = res.refEx;
2958 
2959                     while (ptr.nextReferralEx != null) {
2960                         ptr = ptr.nextReferralEx;
2961                     }
2962                     ptr.nextReferralEx = head;
2963                 }
2964 
2965                 // check the hop limit
2966                 if (hopCount > referralHopLimit) {
2967                     NamingException lee =
2968                         new LimitExceededException("Referral limit exceeded");
2969                     lee.setRootCause(r);
2970                     throw lee;
2971                 }
2972             }
2973             return;
2974 
2975         case LdapClient.LDAP_REFERRAL:
2976 
2977             if (handleReferrals == LdapClient.LDAP_REF_IGNORE) {
2978                 e = new PartialResultException(msg);
2979                 break;
2980             }
2981 
2982             r = new LdapReferralException(resolvedName, resolvedObj, remainName,
2983                 msg, envprops, fullDN, handleReferrals, reqCtls);
2984             // only one set of URLs is present
2985             Vector<String> refs;
2986             if (res.referrals == null) {
2987                 refs = null;
2988             } else if (handleReferrals == LdapClient.LDAP_REF_FOLLOW_SCHEME) {
2989                 refs = new Vector<>();
2990                 for (String s : res.referrals.elementAt(0)) {
2991                     if (s.startsWith("ldap:")) {
2992                         refs.add(s);
2993                     }
2994                 }
2995                 if (refs.isEmpty()) {
2996                     refs = null;
2997                 }
2998             } else {
2999                 refs = res.referrals.elementAt(0);
3000             }
3001             r.setReferralInfo(refs, false);
3002 
3003             if (hopCount > 1) {
3004                 r.setHopCount(hopCount);
3005             }
3006 
3007             // check the hop limit
3008             if (hopCount > referralHopLimit) {
3009                 NamingException lee =
3010                     new LimitExceededException("Referral limit exceeded");
3011                 lee.setRootCause(r);
3012                 e = lee;
3013 
3014             } else {
3015                 e = r;
3016             }
3017             break;
3018 
3019         /*
3020          * Handle SLAPD-style referrals.
3021          *
3022          * Referrals received during name resolution should be followed
3023          * until one succeeds - the target entry is located. An exception
3024          * is thrown now to handle these.
3025          *
3026          * Referrals received during a search operation point to unexplored
3027          * parts of the directory and each should be followed. An exception
3028          * is thrown later (during results enumeration) to handle these.
3029          */
3030 
3031         case LdapClient.LDAP_PARTIAL_RESULTS:
3032 
3033             if (handleReferrals == LdapClient.LDAP_REF_IGNORE) {
3034                 e = new PartialResultException(msg);
3035                 break;
3036             }
3037 
3038             // extract SLAPD-style referrals from errorMessage
3039             if ((res.errorMessage != null) && (!res.errorMessage.equals(""))) {
3040                 res.referrals = extractURLs(res.errorMessage);
3041             } else {
3042                 e = new PartialResultException(msg);
3043                 break;
3044             }
3045 
3046             // build exception
3047             r = new LdapReferralException(resolvedName,
3048                 resolvedObj,
3049                 remainName,
3050                 msg,
3051                 envprops,
3052                 fullDN,
3053                 handleReferrals,
3054                 reqCtls);
3055 
3056             if (hopCount > 1) {
3057                 r.setHopCount(hopCount);
3058             }
3059             /*
3060              * %%%
3061              * SLAPD-style referrals received during name resolution
3062              * cannot be distinguished from those received during a
3063              * search operation. Since both must be handled differently
3064              * the following rule is applied:
3065              *
3066              *     If 1 referral and 0 entries is received then
3067              *     assume name resolution has not yet completed.
3068              */
3069             if (((res.entries == null) || (res.entries.isEmpty())) &&
3070                 ((res.referrals != null) && (res.referrals.size() == 1))) {
3071 
3072                 r.setReferralInfo(res.referrals, false);
3073 
3074                 // check the hop limit
3075                 if (hopCount > referralHopLimit) {
3076                     NamingException lee =
3077                         new LimitExceededException("Referral limit exceeded");
3078                     lee.setRootCause(r);
3079                     e = lee;
3080 
3081                 } else {
3082                     e = r;
3083                 }
3084 
3085             } else {
3086                 r.setReferralInfo(res.referrals, true);
3087                 res.refEx = r;
3088                 return;
3089             }
3090             break;
3091 
3092         case LdapClient.LDAP_INVALID_DN_SYNTAX:
3093         case LdapClient.LDAP_NAMING_VIOLATION:
3094 
3095             if (remainName != null) {
3096                 e = new
3097                     InvalidNameException(remainName.toString() + ": " + msg);
3098             } else {
3099                 e = new InvalidNameException(msg);
3100             }
3101             break;
3102 
3103         default:
3104             e = mapErrorCode(res.status, res.errorMessage);
3105             break;
3106         }
3107         e.setResolvedName(resolvedName);
3108         e.setResolvedObj(resolvedObj);
3109         e.setRemainingName(remainName);
3110         throw e;
3111     }
3112 
3113     /**
3114      * Maps an LDAP error code to an appropriate NamingException.
3115      * %%% public; used by controls
3116      *
3117      * @param errorCode numeric LDAP error code
3118      * @param errorMessage textual description of the LDAP error. May be null.
3119      *
3120      * @return A NamingException or null if the error code indicates success.
3121      */
3122     public static NamingException mapErrorCode(int errorCode,
3123         String errorMessage) {
3124 
3125         if (errorCode == LdapClient.LDAP_SUCCESS)
3126             return null;
3127 
3128         NamingException e = null;
3129         String message = LdapClient.getErrorMessage(errorCode, errorMessage);
3130 
3131         switch (errorCode) {
3132 
3133         case LdapClient.LDAP_ALIAS_DEREFERENCING_PROBLEM:
3134             e = new NamingException(message);
3135             break;
3136 
3137         case LdapClient.LDAP_ALIAS_PROBLEM:
3138             e = new NamingException(message);
3139             break;
3140 
3141         case LdapClient.LDAP_ATTRIBUTE_OR_VALUE_EXISTS:
3142             e = new AttributeInUseException(message);
3143             break;
3144 
3145         case LdapClient.LDAP_AUTH_METHOD_NOT_SUPPORTED:
3146         case LdapClient.LDAP_CONFIDENTIALITY_REQUIRED:
3147         case LdapClient.LDAP_STRONG_AUTH_REQUIRED:
3148         case LdapClient.LDAP_INAPPROPRIATE_AUTHENTICATION:
3149             e = new AuthenticationNotSupportedException(message);
3150             break;
3151 
3152         case LdapClient.LDAP_ENTRY_ALREADY_EXISTS:
3153             e = new NameAlreadyBoundException(message);
3154             break;
3155 
3156         case LdapClient.LDAP_INVALID_CREDENTIALS:
3157         case LdapClient.LDAP_SASL_BIND_IN_PROGRESS:
3158             e = new AuthenticationException(message);
3159             break;
3160 
3161         case LdapClient.LDAP_INAPPROPRIATE_MATCHING:
3162             e = new InvalidSearchFilterException(message);
3163             break;
3164 
3165         case LdapClient.LDAP_INSUFFICIENT_ACCESS_RIGHTS:
3166             e = new NoPermissionException(message);
3167             break;
3168 
3169         case LdapClient.LDAP_INVALID_ATTRIBUTE_SYNTAX:
3170         case LdapClient.LDAP_CONSTRAINT_VIOLATION:
3171             e =  new InvalidAttributeValueException(message);
3172             break;
3173 
3174         case LdapClient.LDAP_LOOP_DETECT:
3175             e = new NamingException(message);
3176             break;
3177 
3178         case LdapClient.LDAP_NO_SUCH_ATTRIBUTE:
3179             e = new NoSuchAttributeException(message);
3180             break;
3181 
3182         case LdapClient.LDAP_NO_SUCH_OBJECT:
3183             e = new NameNotFoundException(message);
3184             break;
3185 
3186         case LdapClient.LDAP_OBJECT_CLASS_MODS_PROHIBITED:
3187         case LdapClient.LDAP_OBJECT_CLASS_VIOLATION:
3188         case LdapClient.LDAP_NOT_ALLOWED_ON_RDN:
3189             e = new SchemaViolationException(message);
3190             break;
3191 
3192         case LdapClient.LDAP_NOT_ALLOWED_ON_NON_LEAF:
3193             e = new ContextNotEmptyException(message);
3194             break;
3195 
3196         case LdapClient.LDAP_OPERATIONS_ERROR:
3197             // %%% need new exception ?
3198             e = new NamingException(message);
3199             break;
3200 
3201         case LdapClient.LDAP_OTHER:
3202             e = new NamingException(message);
3203             break;
3204 
3205         case LdapClient.LDAP_PROTOCOL_ERROR:
3206             e = new CommunicationException(message);
3207             break;
3208 
3209         case LdapClient.LDAP_SIZE_LIMIT_EXCEEDED:
3210             e = new SizeLimitExceededException(message);
3211             break;
3212 
3213         case LdapClient.LDAP_TIME_LIMIT_EXCEEDED:
3214             e = new TimeLimitExceededException(message);
3215             break;
3216 
3217         case LdapClient.LDAP_UNAVAILABLE_CRITICAL_EXTENSION:
3218             e = new OperationNotSupportedException(message);
3219             break;
3220 
3221         case LdapClient.LDAP_UNAVAILABLE:
3222         case LdapClient.LDAP_BUSY:
3223             e = new ServiceUnavailableException(message);
3224             break;
3225 
3226         case LdapClient.LDAP_UNDEFINED_ATTRIBUTE_TYPE:
3227             e = new InvalidAttributeIdentifierException(message);
3228             break;
3229 
3230         case LdapClient.LDAP_UNWILLING_TO_PERFORM:
3231             e = new OperationNotSupportedException(message);
3232             break;
3233 
3234         case LdapClient.LDAP_COMPARE_FALSE:
3235         case LdapClient.LDAP_COMPARE_TRUE:
3236         case LdapClient.LDAP_IS_LEAF:
3237             // these are really not exceptions and this code probably
3238             // never gets executed
3239             e = new NamingException(message);
3240             break;
3241 
3242         case LdapClient.LDAP_ADMIN_LIMIT_EXCEEDED:
3243             e = new LimitExceededException(message);
3244             break;
3245 
3246         case LdapClient.LDAP_REFERRAL:
3247             e = new NamingException(message);
3248             break;
3249 
3250         case LdapClient.LDAP_PARTIAL_RESULTS:
3251             e = new NamingException(message);
3252             break;
3253 
3254         case LdapClient.LDAP_INVALID_DN_SYNTAX:
3255         case LdapClient.LDAP_NAMING_VIOLATION:
3256             e = new InvalidNameException(message);
3257             break;
3258 
3259         default:
3260             e = new NamingException(message);
3261             break;
3262         }
3263 
3264         return e;
3265     }
3266 
3267     // ----------------- Extensions and Controls -------------------
3268 
3269     public ExtendedResponse extendedOperation(ExtendedRequest request)
3270         throws NamingException {
3271 
3272         boolean startTLS = (request.getID().equals(STARTTLS_REQ_OID));
3273         ensureOpen(startTLS);
3274 
3275         try {
3276 
3277             LdapResult answer =
3278                 clnt.extendedOp(request.getID(), request.getEncodedValue(),
3279                                 reqCtls, startTLS);
3280             respCtls = answer.resControls; // retrieve response controls
3281 
3282             if (answer.status != LdapClient.LDAP_SUCCESS) {
3283                 processReturnCode(answer, new CompositeName());
3284             }
3285             // %%% verify request.getID() == answer.extensionId
3286 
3287             int len = (answer.extensionValue == null) ?
3288                         0 :
3289                         answer.extensionValue.length;
3290 
3291             ExtendedResponse er =
3292                 request.createExtendedResponse(answer.extensionId,
3293                     answer.extensionValue, 0, len);
3294 
3295             if (er instanceof StartTlsResponseImpl) {
3296                 // Pass the connection handle to StartTlsResponseImpl
3297                 String domainName = (String)
3298                     (envprops != null ? envprops.get(DOMAIN_NAME) : null);
3299                 ((StartTlsResponseImpl)er).setConnection(clnt.conn, domainName);
3300             }
3301             return er;
3302 
3303         } catch (LdapReferralException e) {
3304 
3305             if (handleReferrals == LdapClient.LDAP_REF_THROW)
3306                 throw e;
3307 
3308             // process the referrals sequentially
3309             while (true) {
3310 
3311                 LdapReferralContext refCtx =
3312                     (LdapReferralContext)e.getReferralContext(envprops, bindCtls);
3313 
3314                 // repeat the original operation at the new context
3315                 try {
3316 
3317                     return refCtx.extendedOperation(request);
3318 
3319                 } catch (LdapReferralException re) {
3320                     e = re;
3321                     continue;
3322 
3323                 } finally {
3324                     // Make sure we close referral context
3325                     refCtx.close();
3326                 }
3327             }
3328 
3329         } catch (IOException e) {
3330             NamingException e2 = new CommunicationException(e.getMessage());
3331             e2.setRootCause(e);
3332             throw e2;
3333         }
3334     }
3335 
3336     public void setRequestControls(Control[] reqCtls) throws NamingException {
3337         if (handleReferrals == LdapClient.LDAP_REF_IGNORE) {
3338             this.reqCtls = addControl(reqCtls, manageReferralControl);
3339         } else {
3340             this.reqCtls = cloneControls(reqCtls);
3341         }
3342     }
3343 
3344     public Control[] getRequestControls() throws NamingException {
3345         return cloneControls(reqCtls);
3346     }
3347 
3348     public Control[] getConnectControls() throws NamingException {
3349         return cloneControls(bindCtls);
3350     }
3351 
3352     public Control[] getResponseControls() throws NamingException {
3353         return (respCtls != null)? convertControls(respCtls) : null;
3354     }
3355 
3356     /**
3357      * Narrow controls using own default factory and ControlFactory.
3358      * @param ctls A non-null Vector<Control>
3359      */
3360     Control[] convertControls(Vector<Control> ctls) throws NamingException {
3361         int count = ctls.size();
3362 
3363         if (count == 0) {
3364             return null;
3365         }
3366 
3367         Control[] controls = new Control[count];
3368 
3369         for (int i = 0; i < count; i++) {
3370             // Try own factory first
3371             controls[i] = myResponseControlFactory.getControlInstance(
3372                 ctls.elementAt(i));
3373 
3374             // Try assigned factories if own produced null
3375             if (controls[i] == null) {
3376                 controls[i] = ControlFactory.getControlInstance(
3377                 ctls.elementAt(i), this, envprops);
3378             }
3379         }
3380         return controls;
3381     }
3382 
3383     private static Control[] addControl(Control[] prevCtls, Control addition) {
3384         if (prevCtls == null) {
3385             return new Control[]{addition};
3386         }
3387 
3388         // Find it
3389         int found = findControl(prevCtls, addition);
3390         if (found != -1) {
3391             return prevCtls;  // no need to do it again
3392         }
3393 
3394         Control[] newCtls = new Control[prevCtls.length+1];
3395         System.arraycopy(prevCtls, 0, newCtls, 0, prevCtls.length);
3396         newCtls[prevCtls.length] = addition;
3397         return newCtls;
3398     }
3399 
3400     private static int findControl(Control[] ctls, Control target) {
3401         for (int i = 0; i < ctls.length; i++) {
3402             if (ctls[i] == target) {
3403                 return i;
3404             }
3405         }
3406         return -1;
3407     }
3408 
3409     private static Control[] removeControl(Control[] prevCtls, Control target) {
3410         if (prevCtls == null) {
3411             return null;
3412         }
3413 
3414         // Find it
3415         int found = findControl(prevCtls, target);
3416         if (found == -1) {
3417             return prevCtls;  // not there
3418         }
3419 
3420         // Remove it
3421         Control[] newCtls = new Control[prevCtls.length-1];
3422         System.arraycopy(prevCtls, 0, newCtls, 0, found);
3423         System.arraycopy(prevCtls, found+1, newCtls, found,
3424             prevCtls.length-found-1);
3425         return newCtls;
3426     }
3427 
3428     private static Control[] cloneControls(Control[] ctls) {
3429         if (ctls == null) {
3430             return null;
3431         }
3432         Control[] copiedCtls = new Control[ctls.length];
3433         System.arraycopy(ctls, 0, copiedCtls, 0, ctls.length);
3434         return copiedCtls;
3435     }
3436 
3437     // -------------------- Events ------------------------
3438     /*
3439      * Access to eventSupport need not be synchronized even though the
3440      * Connection thread can access it asynchronously. It is
3441      * impossible for a race condition to occur because
3442      * eventSupport.addNamingListener() must have been called before
3443      * the Connection thread can call back to this ctx.
3444      */
3445     public void addNamingListener(Name nm, int scope, NamingListener l)
3446         throws NamingException {
3447             addNamingListener(getTargetName(nm), scope, l);
3448     }
3449 
3450     public void addNamingListener(String nm, int scope, NamingListener l)
3451         throws NamingException {
3452             if (eventSupport == null)
3453                 eventSupport = new EventSupport(this);
3454             eventSupport.addNamingListener(getTargetName(new CompositeName(nm)),
3455                 scope, l);
3456 
3457             // If first time asking for unsol
3458             if (l instanceof UnsolicitedNotificationListener && !unsolicited) {
3459                 addUnsolicited();
3460             }
3461     }
3462 
3463     public void removeNamingListener(NamingListener l) throws NamingException {
3464         if (eventSupport == null)
3465             return; // no activity before, so just return
3466 
3467         eventSupport.removeNamingListener(l);
3468 
3469         // If removing an Unsol listener and it is the last one, let clnt know
3470         if (l instanceof UnsolicitedNotificationListener &&
3471             !eventSupport.hasUnsolicited()) {
3472             removeUnsolicited();
3473         }
3474     }
3475 
3476     public void addNamingListener(String nm, String filter, SearchControls ctls,
3477         NamingListener l) throws NamingException {
3478             if (eventSupport == null)
3479                 eventSupport = new EventSupport(this);
3480             eventSupport.addNamingListener(getTargetName(new CompositeName(nm)),
3481                 filter, cloneSearchControls(ctls), l);
3482 
3483             // If first time asking for unsol
3484             if (l instanceof UnsolicitedNotificationListener && !unsolicited) {
3485                 addUnsolicited();
3486             }
3487     }
3488 
3489     public void addNamingListener(Name nm, String filter, SearchControls ctls,
3490         NamingListener l) throws NamingException {
3491             addNamingListener(getTargetName(nm), filter, ctls, l);
3492     }
3493 
3494     public void addNamingListener(Name nm, String filter, Object[] filterArgs,
3495         SearchControls ctls, NamingListener l) throws NamingException {
3496             addNamingListener(getTargetName(nm), filter, filterArgs, ctls, l);
3497     }
3498 
3499     public void addNamingListener(String nm, String filterExpr, Object[] filterArgs,
3500         SearchControls ctls, NamingListener l) throws NamingException {
3501         String strfilter = SearchFilter.format(filterExpr, filterArgs);
3502         addNamingListener(getTargetName(new CompositeName(nm)), strfilter, ctls, l);
3503     }
3504 
3505     public boolean targetMustExist() {
3506         return true;
3507     }
3508 
3509     /**
3510      * Retrieves the target name for which the listener is registering.
3511      * If nm is a CompositeName, use its first and only component. It
3512      * cannot have more than one components because a target be outside of
3513      * this namespace. If nm is not a CompositeName, then treat it as a
3514      * compound name.
3515      * @param nm The non-null target name.
3516      */
3517     private static String getTargetName(Name nm) throws NamingException {
3518         if (nm instanceof CompositeName) {
3519             if (nm.size() > 1) {
3520                 throw new InvalidNameException(
3521                     "Target cannot span multiple namespaces: " + nm);
3522             } else if (nm.isEmpty()) {
3523                 return "";
3524             } else {
3525                 return nm.get(0);
3526             }
3527         } else {
3528             // treat as compound name
3529             return nm.toString();
3530         }
3531     }
3532 
3533     // ------------------ Unsolicited Notification ---------------
3534     // package private methods for handling unsolicited notification
3535 
3536     /**
3537      * Registers this context with the underlying LdapClient.
3538      * When the underlying LdapClient receives an unsolicited notification,
3539      * it will invoke LdapCtx.fireUnsolicited() so that this context
3540      * can (using EventSupport) notified any registered listeners.
3541      * This method is called by EventSupport when an unsolicited listener
3542      * first registers with this context (should be called just once).
3543      * @see #removeUnsolicited
3544      * @see #fireUnsolicited
3545      */
3546     private void addUnsolicited() throws NamingException {
3547         if (debug) {
3548             System.out.println("LdapCtx.addUnsolicited: " + this);
3549         }
3550 
3551         // addNamingListener must have created EventSupport already
3552         ensureOpen();
3553         synchronized (eventSupport) {
3554             clnt.addUnsolicited(this);
3555             unsolicited = true;
3556         }
3557     }
3558 
3559     /**
3560      * Removes this context from registering interest in unsolicited
3561      * notifications from the underlying LdapClient. This method is called
3562      * under any one of the following conditions:
3563      * <ul>
3564      * <li>All unsolicited listeners have been removed. (see removingNamingListener)
3565      * <li>This context is closed.
3566      * <li>This context's underlying LdapClient changes.
3567      *</ul>
3568      * After this method has been called, this context will not pass
3569      * on any events related to unsolicited notifications to EventSupport and
3570      * and its listeners.
3571      */
3572 
3573     private void removeUnsolicited() {
3574         if (debug) {
3575             System.out.println("LdapCtx.removeUnsolicited: " + unsolicited);
3576         }
3577         if (eventSupport == null) {
3578             return;
3579         }
3580 
3581         // addNamingListener must have created EventSupport already
3582         synchronized(eventSupport) {
3583             if (unsolicited && clnt != null) {
3584                 clnt.removeUnsolicited(this);
3585             }
3586             unsolicited = false;
3587         }
3588     }
3589 
3590     /**
3591      * Uses EventSupport to fire an event related to an unsolicited notification.
3592      * Called by LdapClient when LdapClient receives an unsolicited notification.
3593      */
3594     void fireUnsolicited(Object obj) {
3595         if (debug) {
3596             System.out.println("LdapCtx.fireUnsolicited: " + obj);
3597         }
3598         // addNamingListener must have created EventSupport already
3599         synchronized(eventSupport) {
3600             if (unsolicited) {
3601                 eventSupport.fireUnsolicited(obj);
3602 
3603                 if (obj instanceof NamingException) {
3604                     unsolicited = false;
3605                     // No need to notify clnt because clnt is the
3606                     // only one that can fire a NamingException to
3607                     // unsol listeners and it will handle its own cleanup
3608                 }
3609             }
3610         }
3611     }
3612 }
--- EOF ---