/*
 * Copyright (c) 1999, 2017, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.jndi.ldap;

import com.sun.jndi.toolkit.ctx.Continuation;
import java.util.NoSuchElementException;
import java.util.Vector;

import javax.naming.*;
import javax.naming.directory.Attributes;
import javax.naming.ldap.Control;

Basic enumeration for NameClassPair, Binding, and SearchResults.
/** * Basic enumeration for NameClassPair, Binding, and SearchResults. */
abstract class AbstractLdapNamingEnumeration<T extends NameClassPair> implements NamingEnumeration<T>, ReferralEnumeration<T> { protected Name listArg; private boolean cleaned = false; private LdapResult res; private LdapClient enumClnt; private Continuation cont; // used to fill in exceptions private Vector<LdapEntry> entries = null; private int limit = 0; private int posn = 0; protected LdapCtx homeCtx; private LdapReferralException refEx = null; private NamingException errEx = null; /* * Record the next set of entries and/or referrals. */ AbstractLdapNamingEnumeration(LdapCtx homeCtx, LdapResult answer, Name listArg, Continuation cont) throws NamingException { // These checks are to accommodate referrals and limit exceptions // which will generate an enumeration and defer the exception // to be thrown at the end of the enumeration. // All other exceptions are thrown immediately. // Exceptions shouldn't be thrown here anyhow because // process_return_code() is called before the constructor // is called, so these are just safety checks. if ((answer.status != LdapClient.LDAP_SUCCESS) && (answer.status != LdapClient.LDAP_SIZE_LIMIT_EXCEEDED) && (answer.status != LdapClient.LDAP_TIME_LIMIT_EXCEEDED) && (answer.status != LdapClient.LDAP_ADMIN_LIMIT_EXCEEDED) && (answer.status != LdapClient.LDAP_REFERRAL) && (answer.status != LdapClient.LDAP_PARTIAL_RESULTS)) { // %%% need to deal with referral NamingException e = new NamingException( LdapClient.getErrorMessage( answer.status, answer.errorMessage)); throw cont.fillInException(e); } // otherwise continue res = answer; entries = answer.entries; limit = (entries == null) ? 0 : entries.size(); // handle empty set this.listArg = listArg; this.cont = cont; if (answer.refEx != null) { refEx = answer.refEx; } // Ensures that context won't get closed from underneath us this.homeCtx = homeCtx; homeCtx.incEnumCount(); enumClnt = homeCtx.clnt; // remember } @Override public final T nextElement() { try { return next(); } catch (NamingException e) { // can't throw exception cleanup(); return null; } } @Override public final boolean hasMoreElements() { try { return hasMore(); } catch (NamingException e) { // can't throw exception cleanup(); return false; } } /* * Retrieve the next set of entries and/or referrals. */ private void getNextBatch() throws NamingException { res = homeCtx.getSearchReply(enumClnt, res); if (res == null) { limit = posn = 0; return; } entries = res.entries; limit = (entries == null) ? 0 : entries.size(); // handle empty set posn = 0; // reset // minimize the number of calls to processReturnCode() // (expensive when batchSize is small and there are many results) if ((res.status != LdapClient.LDAP_SUCCESS) || ((res.status == LdapClient.LDAP_SUCCESS) && (res.referrals != null))) { try { // convert referrals into a chain of LdapReferralException homeCtx.processReturnCode(res, listArg); } catch (LimitExceededException | PartialResultException e) { setNamingException(e); } } // merge any newly received referrals with any current referrals if (res.refEx != null) { if (refEx == null) { refEx = res.refEx; } else { refEx = refEx.appendUnprocessedReferrals(res.refEx); } res.refEx = null; // reset } if (res.resControls != null) { homeCtx.respCtls = res.resControls; } } private boolean more = true; // assume we have something to start with private boolean hasMoreCalled = false; /* * Test if unprocessed entries or referrals exist. */ @Override public final boolean hasMore() throws NamingException { if (hasMoreCalled) { return more; } hasMoreCalled = true; if (!more) { return false; } else { return (more = hasMoreImpl()); } } /* * Retrieve the next entry. */ @Override public final T next() throws NamingException { if (!hasMoreCalled) { hasMore(); } hasMoreCalled = false; return nextImpl(); } /* * Test if unprocessed entries or referrals exist. */ private boolean hasMoreImpl() throws NamingException { // when page size is supported, this // might generate an exception while attempting // to fetch the next batch to determine // whether there are any more elements // test if the current set of entries has been processed if (posn == limit) { getNextBatch(); } // test if any unprocessed entries exist if (posn < limit) { return true; } else { try { // try to process another referral return hasMoreReferrals(); } catch (LdapReferralException | LimitExceededException | PartialResultException e) { cleanup(); throw e; } catch (NamingException e) { cleanup(); PartialResultException pre = new PartialResultException(); pre.setRootCause(e); throw pre; } } } /* * Retrieve the next entry. */ private T nextImpl() throws NamingException { try { return nextAux(); } catch (NamingException e) { cleanup(); throw cont.fillInException(e); } } private T nextAux() throws NamingException { if (posn == limit) { getNextBatch(); // updates posn and limit } if (posn >= limit) { cleanup(); throw new NoSuchElementException("invalid enumeration handle"); } LdapEntry result = entries.elementAt(posn++); // gets and outputs DN from the entry return createItem(result.DN, result.attributes, result.respCtls); } protected final String getAtom(String dn) { // need to strip off all but lowest component of dn // so that is relative to current context (currentDN) try { Name parsed = new LdapName(dn); return parsed.get(parsed.size() - 1); } catch (NamingException e) { return dn; } } protected abstract T createItem(String dn, Attributes attrs, Vector<Control> respCtls) throws NamingException; /* * Append the supplied (chain of) referrals onto the * end of the current (chain of) referrals. */ @Override public void appendUnprocessedReferrals(LdapReferralException ex) { if (refEx != null) { refEx = refEx.appendUnprocessedReferrals(ex); } else { refEx = ex.appendUnprocessedReferrals(refEx); } } final void setNamingException(NamingException e) { errEx = e; } protected abstract AbstractLdapNamingEnumeration<? extends NameClassPair> getReferredResults( LdapReferralContext refCtx) throws NamingException; /* * Iterate through the URLs of a referral. If successful then perform * a search operation and merge the received results with the current * results. */ protected final boolean hasMoreReferrals() throws NamingException { if ((refEx != null) && (refEx.hasMoreReferrals() || refEx.hasMoreReferralExceptions())) { if (homeCtx.handleReferrals == LdapClient.LDAP_REF_THROW) { throw (NamingException)(refEx.fillInStackTrace()); } // process the referrals sequentially while (true) { LdapReferralContext refCtx = (LdapReferralContext)refEx.getReferralContext( homeCtx.envprops, homeCtx.reqCtls); try { update(getReferredResults(refCtx)); break; } catch (LdapReferralException re) { // record a previous exception if (errEx == null) { errEx = re.getNamingException(); } refEx = re; continue; } finally { // Make sure we close referral context refCtx.close(); } } return hasMoreImpl(); } else { cleanup(); if (errEx != null) { throw errEx; } return (false); } } /* * Merge the entries and/or referrals from the supplied enumeration * with those of the current enumeration. */ protected void update(AbstractLdapNamingEnumeration<? extends NameClassPair> ne) { // Cleanup previous context first homeCtx.decEnumCount(); // New enum will have already incremented enum count and recorded clnt homeCtx = ne.homeCtx; enumClnt = ne.enumClnt; // Do this to prevent referral enumeration (ne) from decrementing // enum count because we'll be doing that here from this // enumeration. ne.homeCtx = null; // Record rest of information from new enum posn = ne.posn; limit = ne.limit; res = ne.res; entries = ne.entries; refEx = ne.refEx; listArg = ne.listArg; } @SuppressWarnings("deprecation") protected final void finalize() { cleanup(); } protected final void cleanup() { if (cleaned) return; // been there; done that if(enumClnt != null) { enumClnt.clearSearchReply(res, homeCtx.reqCtls); } enumClnt = null; cleaned = true; if (homeCtx != null) { homeCtx.decEnumCount(); homeCtx = null; } } @Override public final void close() { cleanup(); } }