1 /*
   2  * Copyright (c) 1999, 2011, 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 com.sun.jndi.toolkit.ctx.Continuation;
  29 import java.util.NoSuchElementException;
  30 import java.util.Vector;
  31 
  32 import javax.naming.*;
  33 import javax.naming.directory.Attributes;
  34 import javax.naming.ldap.Control;
  35 
  36 /**
  37  * Basic enumeration for NameClassPair, Binding, and SearchResults.
  38  */
  39 
  40 abstract class AbstractLdapNamingEnumeration<T extends NameClassPair>
  41         implements NamingEnumeration<T>, ReferralEnumeration<T> {
  42 
  43     protected Name listArg;
  44 
  45     private boolean cleaned = false;
  46     private LdapResult res;
  47     private LdapClient enumClnt;
  48     private Continuation cont;  // used to fill in exceptions
  49     private Vector<LdapEntry> entries = null;
  50     private int limit = 0;
  51     private int posn = 0;
  52     protected LdapCtx homeCtx;
  53     private LdapReferralException refEx = null;
  54     private NamingException errEx = null;
  55 
  56     /*
  57      * Record the next set of entries and/or referrals.
  58      */
  59     AbstractLdapNamingEnumeration(LdapCtx homeCtx, LdapResult answer, Name listArg,
  60         Continuation cont) throws NamingException {
  61 
  62             // These checks are to accommodate referrals and limit exceptions
  63             // which will generate an enumeration and defer the exception
  64             // to be thrown at the end of the enumeration.
  65             // All other exceptions are thrown immediately.
  66             // Exceptions shouldn't be thrown here anyhow because
  67             // process_return_code() is called before the constructor
  68             // is called, so these are just safety checks.
  69 
  70             if ((answer.status != LdapClient.LDAP_SUCCESS) &&
  71                 (answer.status != LdapClient.LDAP_SIZE_LIMIT_EXCEEDED) &&
  72                 (answer.status != LdapClient.LDAP_TIME_LIMIT_EXCEEDED) &&
  73                 (answer.status != LdapClient.LDAP_ADMIN_LIMIT_EXCEEDED) &&
  74                 (answer.status != LdapClient.LDAP_REFERRAL) &&
  75                 (answer.status != LdapClient.LDAP_PARTIAL_RESULTS)) {
  76 
  77                 // %%% need to deal with referral
  78                 NamingException e = new NamingException(
  79                                     LdapClient.getErrorMessage(
  80                                     answer.status, answer.errorMessage));
  81 
  82                 throw cont.fillInException(e);
  83             }
  84 
  85             // otherwise continue
  86 
  87             res = answer;
  88             entries = answer.entries;
  89             limit = (entries == null) ? 0 : entries.size(); // handle empty set
  90             this.listArg = listArg;
  91             this.cont = cont;
  92 
  93             if (answer.refEx != null) {
  94                 refEx = answer.refEx;
  95             }
  96 
  97             // Ensures that context won't get closed from underneath us
  98             this.homeCtx = homeCtx;
  99             homeCtx.incEnumCount();
 100             enumClnt = homeCtx.clnt; // remember
 101     }
 102 
 103     @Override
 104     public final T nextElement() {
 105         try {
 106             return next();
 107         } catch (NamingException e) {
 108             // can't throw exception
 109             cleanup();
 110             return null;
 111         }
 112     }
 113 
 114     @Override
 115     public final boolean hasMoreElements() {
 116         try {
 117             return hasMore();
 118         } catch (NamingException e) {
 119             // can't throw exception
 120             cleanup();
 121             return false;
 122         }
 123     }
 124 
 125     /*
 126      * Retrieve the next set of entries and/or referrals.
 127      */
 128     private void getNextBatch() throws NamingException {
 129 
 130         res = homeCtx.getSearchReply(enumClnt, res);
 131         if (res == null) {
 132             limit = posn = 0;
 133             return;
 134         }
 135 
 136         entries = res.entries;
 137         limit = (entries == null) ? 0 : entries.size(); // handle empty set
 138         posn = 0; // reset
 139 
 140         // minimize the number of calls to processReturnCode()
 141         // (expensive when batchSize is small and there are many results)
 142         if ((res.status != LdapClient.LDAP_SUCCESS) ||
 143             ((res.status == LdapClient.LDAP_SUCCESS) &&
 144                 (res.referrals != null))) {
 145 
 146             try {
 147                 // convert referrals into a chain of LdapReferralException
 148                 homeCtx.processReturnCode(res, listArg);
 149 
 150             } catch (LimitExceededException | PartialResultException e) {
 151                 setNamingException(e);
 152 
 153             }
 154         }
 155 
 156         // merge any newly received referrals with any current referrals
 157         if (res.refEx != null) {
 158             if (refEx == null) {
 159                 refEx = res.refEx;
 160             } else {
 161                 refEx = refEx.appendUnprocessedReferrals(res.refEx);
 162             }
 163             res.refEx = null; // reset
 164         }
 165 
 166         if (res.resControls != null) {
 167             homeCtx.respCtls = res.resControls;
 168         }
 169     }
 170 
 171     private boolean more = true;  // assume we have something to start with
 172     private boolean hasMoreCalled = false;
 173 
 174     /*
 175      * Test if unprocessed entries or referrals exist.
 176      */
 177     @Override
 178     public final boolean hasMore() throws NamingException {
 179 
 180         if (hasMoreCalled) {
 181             return more;
 182         }
 183 
 184         hasMoreCalled = true;
 185 
 186         if (!more) {
 187             return false;
 188         } else {
 189             return (more = hasMoreImpl());
 190         }
 191     }
 192 
 193     /*
 194      * Retrieve the next entry.
 195      */
 196     @Override
 197     public final T next() throws NamingException {
 198 
 199         if (!hasMoreCalled) {
 200             hasMore();
 201         }
 202         hasMoreCalled = false;
 203         return nextImpl();
 204     }
 205 
 206     /*
 207      * Test if unprocessed entries or referrals exist.
 208      */
 209     private boolean hasMoreImpl() throws NamingException {
 210         // when page size is supported, this
 211         // might generate an exception while attempting
 212         // to fetch the next batch to determine
 213         // whether there are any more elements
 214 
 215         // test if the current set of entries has been processed
 216         if (posn == limit) {
 217             getNextBatch();
 218         }
 219 
 220         // test if any unprocessed entries exist
 221         if (posn < limit) {
 222             return true;
 223         } else {
 224 
 225             try {
 226                 // try to process another referral
 227                 return hasMoreReferrals();
 228 
 229             } catch (LdapReferralException |
 230                      LimitExceededException |
 231                      PartialResultException e) {
 232                 cleanup();
 233                 throw e;
 234 
 235             } catch (NamingException e) {
 236                 cleanup();
 237                 PartialResultException pre = new PartialResultException();
 238                 pre.setRootCause(e);
 239                 throw pre;
 240             }
 241         }
 242     }
 243 
 244     /*
 245      * Retrieve the next entry.
 246      */
 247     private T nextImpl() throws NamingException {
 248         try {
 249             return nextAux();
 250         } catch (NamingException e) {
 251             cleanup();
 252             throw cont.fillInException(e);
 253         }
 254     }
 255 
 256     private T nextAux() throws NamingException {
 257         if (posn == limit) {
 258             getNextBatch();  // updates posn and limit
 259         }
 260 
 261         if (posn >= limit) {
 262             cleanup();
 263             throw new NoSuchElementException("invalid enumeration handle");
 264         }
 265 
 266         LdapEntry result = entries.elementAt(posn++);
 267 
 268         // gets and outputs DN from the entry
 269         return createItem(result.DN, result.attributes, result.respCtls);
 270     }
 271 
 272     protected final String getAtom(String dn) {
 273         // need to strip off all but lowest component of dn
 274         // so that is relative to current context (currentDN)
 275         try {
 276             Name parsed = new LdapName(dn);
 277             return parsed.get(parsed.size() - 1);
 278         } catch (NamingException e) {
 279             return dn;
 280         }
 281     }
 282 
 283     protected abstract T createItem(String dn, Attributes attrs,
 284         Vector<Control> respCtls) throws NamingException;
 285 
 286     /*
 287      * Append the supplied (chain of) referrals onto the
 288      * end of the current (chain of) referrals.
 289      */
 290     @Override
 291     public void appendUnprocessedReferrals(LdapReferralException ex) {
 292         if (refEx != null) {
 293             refEx = refEx.appendUnprocessedReferrals(ex);
 294         } else {
 295             refEx = ex.appendUnprocessedReferrals(refEx);
 296         }
 297     }
 298 
 299     final void setNamingException(NamingException e) {
 300         errEx = e;
 301     }
 302 
 303     protected abstract AbstractLdapNamingEnumeration<T> getReferredResults(
 304             LdapReferralContext refCtx) throws NamingException;
 305 
 306     /*
 307      * Iterate through the URLs of a referral. If successful then perform
 308      * a search operation and merge the received results with the current
 309      * results.
 310      */
 311     protected final boolean hasMoreReferrals() throws NamingException {
 312 
 313         if ((refEx != null) &&
 314             (refEx.hasMoreReferrals() ||
 315              refEx.hasMoreReferralExceptions())) {
 316 
 317             if (homeCtx.handleReferrals == LdapClient.LDAP_REF_THROW) {
 318                 throw (NamingException)(refEx.fillInStackTrace());
 319             }
 320 
 321             // process the referrals sequentially
 322             while (true) {
 323 
 324                 LdapReferralContext refCtx =
 325                     (LdapReferralContext)refEx.getReferralContext(
 326                     homeCtx.envprops, homeCtx.reqCtls);
 327 
 328                 try {
 329 
 330                     update(getReferredResults(refCtx));
 331                     break;
 332 
 333                 } catch (LdapReferralException re) {
 334 
 335                     // record a previous exception
 336                     if (errEx == null) {
 337                         errEx = re.getNamingException();
 338                     }
 339                     refEx = re;
 340                     continue;
 341 
 342                 } finally {
 343                     // Make sure we close referral context
 344                     refCtx.close();
 345                 }
 346             }
 347             return hasMoreImpl();
 348 
 349         } else {
 350             cleanup();
 351 
 352             if (errEx != null) {
 353                 throw errEx;
 354             }
 355             return (false);
 356         }
 357     }
 358 
 359     /*
 360      * Merge the entries and/or referrals from the supplied enumeration
 361      * with those of the current enumeration.
 362      */
 363     protected void update(AbstractLdapNamingEnumeration<T> ne) {
 364         // Cleanup previous context first
 365         homeCtx.decEnumCount();
 366 
 367         // New enum will have already incremented enum count and recorded clnt
 368         homeCtx = ne.homeCtx;
 369         enumClnt = ne.enumClnt;
 370 
 371         // Do this to prevent referral enumeration (ne) from decrementing
 372         // enum count because we'll be doing that here from this
 373         // enumeration.
 374         ne.homeCtx = null;
 375 
 376         // Record rest of information from new enum
 377         posn = ne.posn;
 378         limit = ne.limit;
 379         res = ne.res;
 380         entries = ne.entries;
 381         refEx = ne.refEx;
 382         listArg = ne.listArg;
 383     }
 384 
 385     protected final void finalize() {
 386         cleanup();
 387     }
 388 
 389     protected final void cleanup() {
 390         if (cleaned) return; // been there; done that
 391 
 392         if(enumClnt != null) {
 393             enumClnt.clearSearchReply(res, homeCtx.reqCtls);
 394         }
 395 
 396         enumClnt = null;
 397         cleaned = true;
 398         if (homeCtx != null) {
 399             homeCtx.decEnumCount();
 400             homeCtx = null;
 401         }
 402     }
 403 
 404     @Override
 405     public final void close() {
 406         cleanup();
 407     }
 408 }