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 }