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
26package com.sun.jndi.ldap;
27
28import com.sun.jndi.toolkit.ctx.Continuation;
29import java.util.NoSuchElementException;
30import java.util.Vector;
31
32import javax.naming.*;
33import javax.naming.directory.Attributes;
34import javax.naming.ldap.Control;
35
36/**
37 * Basic enumeration for NameClassPair, Binding, and SearchResults.
38 */
39
40abstract 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<? extends NameClassPair> 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<? extends NameClassPair> 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    @SuppressWarnings("deprecation")
386    protected final void finalize() {
387        cleanup();
388    }
389
390    protected final void cleanup() {
391        if (cleaned) return; // been there; done that
392
393        if(enumClnt != null) {
394            enumClnt.clearSearchReply(res, homeCtx.reqCtls);
395        }
396
397        enumClnt = null;
398        cleaned = true;
399        if (homeCtx != null) {
400            homeCtx.decEnumCount();
401            homeCtx = null;
402        }
403    }
404
405    @Override
406    public final void close() {
407        cleanup();
408    }
409}
410