/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ package netscape.ldap; import java.util.*; import netscape.ldap.client.*; import netscape.ldap.client.opers.*; import java.io.*; /** * The results of an LDAP search operation, represented as an enumeration. * Note that you can only iterate through this enumeration once: if you * need to use these results more than once, make sure to save the * results in a separate location. *

* * You can also use the results of a search in progress to abandon that search * operation. *

* * @version 1.0 * @see netscape.ldap.LDAPConnection#search(java.lang.String, int, java.lang.String, java.lang.String[], boolean) * @see netscape.ldap.LDAPConnection#abandon(netscape.ldap.LDAPSearchResults) */ public class LDAPSearchResults implements Enumeration, java.io.Serializable { static final long serialVersionUID = -501692208613904825L; private Vector entries = null; private LDAPSearchListener resultSource; private boolean searchComplete = false; private LDAPConnection connectionToClose; private LDAPConnection currConn; private boolean persistentSearch = false; private LDAPSearchConstraints currCons; private String currBase; private int currScope; private String currFilter; private String[] currAttrs; private boolean currAttrsOnly; private Vector referralResults = new Vector(); private Vector exceptions; private int msgID = -1; // only used for the persistent search private boolean firstResult = false; /** * Constructs an enumeration of search results. * Note that this does not actually generate the results; * you need to call LDAPConnection.search to * perform the search and get the results. * @see netscape.ldap.LDAPConnection#search(java.lang.String, int, java.lang.String, java.lang.String[], boolean) */ public LDAPSearchResults() { entries = new Vector(); connectionToClose = null; searchComplete = true; currCons = new LDAPSearchConstraints(); } LDAPSearchResults(LDAPConnection conn, LDAPSearchConstraints cons, String base, int scope, String filter, String[] attrs, boolean attrsOnly) { this(); currConn = conn; currCons = cons; currBase = base; currScope = scope; currFilter = filter; currAttrs = attrs; currAttrsOnly = attrsOnly; } /** * Constructs an enumeration of search results. Used when returning results * from a cache. * @param v the vector containing LDAPEntries * @see netscape.ldap.LDAPConnection#search(java.lang.String, int, java.lang.String, java.lang.String[], boolean) */ LDAPSearchResults(Vector v) { this(); entries = (Vector)v.clone(); if ((entries != null) && (entries.size() >= 1)) { // Each cache value is represented by a vector. The first element // represents the size of all the LDAPEntries. This needs to be // removed before we iterate through each LDAPEntry. entries.removeElementAt(0); } } LDAPSearchResults(Vector v, LDAPConnection conn, LDAPSearchConstraints cons, String base, int scope, String filter, String[] attrs, boolean attrsOnly) { this(v); currConn = conn; currCons = cons; currBase = base; currScope = scope; currFilter = filter; currAttrs = attrs; currAttrsOnly = attrsOnly; } /** * Add search entry of referral * @param msg LDAPSearchResult or LDAPsearchResultReference */ void add( LDAPMessage msg ) { if (msg instanceof LDAPSearchResult) { entries.addElement( ((LDAPSearchResult)msg).getEntry()); } else if (msg instanceof LDAPSearchResultReference) { /* convert to LDAPReferralException */ String urls[] = ((LDAPSearchResultReference)msg).getUrls(); if (urls != null) { if (exceptions == null) { exceptions = new Vector(); } exceptions.addElement(new LDAPReferralException(null, 0, urls)); } } } /** * Add exception * @param e exception */ void add(LDAPException e) { if (exceptions == null) { exceptions = new Vector(); } exceptions.addElement(e); } /** * Prepares to return asynchronous results from a search * @param l Listener which will provide results */ void associate( LDAPSearchListener l) { resultSource = l; searchComplete = false; } void associatePersistentSearch( LDAPSearchListener l) { resultSource = l; persistentSearch = true; searchComplete = false; firstResult = true; } void addReferralEntries(LDAPSearchResults res) { referralResults.addElement(res); } /** * For asynchronous search, this mechanism allows the programmer to * close a connection whenever the search completes. * @param toClose connection to close when the search terminates */ void closeOnCompletion (LDAPConnection toClose) { if (searchComplete) { try { toClose.disconnect(); } catch (LDAPException e) { } } else { connectionToClose = toClose; } } /** * Basic quicksort algorithm. */ void quicksort (LDAPEntry[] toSort, LDAPEntryComparator compare, int low, int high) { if (low >= high) { return; } LDAPEntry pivot = toSort[low]; int slow = low-1, shigh = high+1; while (true) { do { shigh--; } while (compare.isGreater (toSort[shigh], pivot)); do { slow++; } while (compare.isGreater (pivot, toSort[slow])); if (slow >= shigh) { break; } LDAPEntry temp = toSort[slow]; toSort[slow] = toSort[shigh]; toSort[shigh] = temp; } quicksort (toSort, compare, low, shigh); quicksort (toSort, compare, shigh+1, high); } /** * Sets the message ID for this search request. msgID is used * to retrieve response controls. * @param msgID Message ID for this search request */ void setMsgID(int msgID) { this.msgID = msgID; } /** * Returns the controls returned with this search result. If any control * is registered with LDAPControl, an attempt is made to * instantiate the control. If the instantiation fails, the control is * returned as a basic LDAPControl. * @return an array of type LDAPControl. * @see netscape.ldap.LDAPControl#register */ public LDAPControl[] getResponseControls() { return currConn.getResponseControls(msgID); } /** * Sorts the search results. *

* * The comparator (LDAPEntryComparator) determines the * sort order used. For example, if the comparator uses the uid * attribute for comparison, the search results are sorted according to * uid. *

* * The following section of code sorts results in ascending order, * first by surname and then by common name. * *

     * String[]  sortAttrs = {"sn", "cn"};
     * boolean[] ascending = {true, true};
     *
     * LDAPConnection ld = new LDAPConnection();
     * ld.connect( ... );
     * LDAPSearchResults res = ld.search( ... );
     * res.sort( new LDAPCompareAttrNames(sortAttrs, ascending) );
     * 
* NOTE: If the search results arrive asynchronously, the sort * method blocks until all the results are returned. *

* * If some of the elements of the Enumeration have already been fetched, * the cursor is reset to the (new) first element. *

* * @param compare comparator used to determine the sort order of the results * @see LDAPEntryComparator */ public synchronized void sort(LDAPEntryComparator compare) { while (!searchComplete) { fetchResult(); } // if automatic referral, then add to the entries, otherwise, dont do it // since the elements in referralResults are LDAPReferralException. if (currCons.getReferrals()) { while (referralResults.size() > 0) { Object obj = null; if ((obj=nextReferralElement()) != null) { if (obj instanceof LDAPException) { add((LDAPException)obj); // put it back } else { entries.addElement(obj); } } } } int numEntries = entries.size(); if (numEntries <= 0) { return; } LDAPEntry[] toSort = new LDAPEntry[numEntries]; entries.copyInto (toSort); if (toSort.length > 1) { quicksort (toSort, compare, 0, numEntries-1); } entries.removeAllElements(); for (int i = 0; i < numEntries; i++) { entries.addElement (toSort[i]); } } /** * Returns the next LDAP entry from the search results * and throws an exception if the next result is a referral, or * if a sizelimit or timelimit error occurred. *

* * You can use this method in conjunction with the * hasMoreElements method to iterate through * each entry in the search results. For example: *

     * LDAPSearchResults res = ld.search( MY_SEARCHBASE,
     *                         LDAPConnection.SCOPE_BASE, MY_FILTER,
     *                         null, false );
     * while ( res.hasMoreElements() ) {
     *   try {
     *     LDAPEntry findEntry = res.next();
     *   } catch ( LDAPReferralException e ) {
     *     LDAPUrl refUrls[] = e.getURLs();
     *     for ( int i = 0; i < refUrls.length; i++ ) {
     *     // Your code for handling referrals
     *     }
     *     continue;
     *   } catch ( LDAPException e ) {
     *     // Your code for handling errors on limits exceeded 
     *     continue; 
     *   } 
     *   ...
     * }
     * 
* @return the next LDAP entry in the search results. * @exception LDAPReferralException A referral (thrown * if the next result is a referral), or LDAPException * if a limit on the number of entries or the time was * exceeded. * @see netscape.ldap.LDAPSearchResults#hasMoreElements() */ public LDAPEntry next() throws LDAPException { Object o = nextElement(); if ((o instanceof LDAPReferralException) || (o instanceof LDAPException)) { throw (LDAPException)o; } if (o instanceof LDAPEntry) { return (LDAPEntry)o; } return null; } /** * Returns the next result from a search. You can use this method * in conjunction with the hasMoreElements method to * iterate through all elements in the search results. *

* * Make sure to cast the * returned element as the correct type. For example: *

     * LDAPSearchResults res = ld.search( MY_SEARCHBASE,
     *                         LDAPConnection.SCOPE_BASE, MY_FILTER,
     *                         null, false );
     * while ( res.hasMoreElements() ) {
     *   Object o = res.nextElement(); 
     *   if ( o instanceof LDAPEntry ) { 
     *     LDAPEntry findEntry = (LDAPEntry)o; 
     *     ... 
     *   } else if ( o instanceof LDAPReferralException ) { 
     *     LDAPReferralException e = (LDAPReferralException)o; 
     *     LDAPUrl refUrls[] = e.getURLs(); 
     *     ... 
     *   } else if ( o instanceof LDAPException ) { 
     *     LDAPException e = (LDAPException)o; 
     *     ... 
     *   } 
     * } 
     * 
* @return the next element in the search results. * @see netscape.ldap.LDAPSearchResults#hasMoreElements() */ public Object nextElement() { if ( entries.size() > 0 ) { Object obj = entries.elementAt(0); entries.removeElementAt(0); return obj; } if (referralResults.size() > 0) { return nextReferralElement(); } if ((exceptions != null) && (exceptions.size() > 0)) { Object obj = exceptions.elementAt(0); exceptions.removeElementAt(0); return obj; } return null; } Object nextReferralElement() { LDAPSearchResults res = (LDAPSearchResults)referralResults.elementAt(0); if ((!res.persistentSearch && res.hasMoreElements()) || (res.persistentSearch)) { Object obj = res.nextElement(); if (obj != null) { return obj; } if ((obj == null) || (!res.hasMoreElements())) { referralResults.removeElementAt(0); } } else { referralResults.removeElementAt(0); } return null; } /** * Returns true if there are more search results * to be returned. You can use this method in conjunction with the * nextElement or next methods to iterate * through each entry in the results. For example: *
     * LDAPSearchResults res = ld.search( MY_SEARCHBASE,
     *                         LDAPConnection.SCOPE_BASE, MY_FILTER,
     *                         null, false );
     * while ( res.hasMoreElements() ) {
     *   LDAPEntry findEntry = (LDAPEntry)res.nextElement();
     *   ...
     * }
     * 
* @return true if there are more search results. * @see netscape.ldap.LDAPSearchResults#nextElement() * @see netscape.ldap.LDAPSearchResults#next() */ public boolean hasMoreElements() { while ((entries.size() == 0) && (!searchComplete)) { fetchResult(); } if ((entries.size() == 0) && ((exceptions == null) || (exceptions.size() == 0))) { while (referralResults.size() > 0) { LDAPSearchResults res = (LDAPSearchResults)referralResults.elementAt(0); if (res.hasMoreElements()) return true; else referralResults.removeElementAt(0); } } return ((entries.size() > 0) || ((exceptions != null) && (exceptions.size() > 0))); } /** * Returns a count of queued search results immediately available for * processing. * A search result is either a search entry or an exception. If the * search is asynchronous (batch size not 0), this reports the number * of results received so far. * @return count of search results immediatly available for processing */ public int getCount() { while (resultSource != null && resultSource.getMessageCount() > 0) { fetchResult(); } int count = entries.size(); for ( int i = 0; i < referralResults.size(); i++ ) { LDAPSearchResults res = (LDAPSearchResults)referralResults.elementAt(i); count += res.getCount(); } if ( exceptions != null ) { count += exceptions.size(); } return count; } /** * Returns message ID. * @return Message ID. */ int getMessageID() { if ( resultSource == null ) { return -1; } return resultSource.getMessageID(); } /** * Fetchs the next result, for asynchronous searches. */ private synchronized void fetchResult() { /* Asynchronous case */ if ( resultSource != null ) { synchronized( this ) { if (searchComplete || firstResult) { firstResult = false; return; } LDAPMessage msg = null; try { msg = resultSource.nextMessage(); } catch (LDAPException e) { add(e); currConn.releaseSearchListener(resultSource); searchComplete = true; return; } if (msg == null) { // Request abandoned searchComplete = true; currConn.releaseSearchListener(resultSource); return; } else if (msg instanceof LDAPResponse) { try { // check response and see if we need to do referral // v2: referral stored in the JDAPResult currConn.checkSearchMsg(this, msg, currCons, currBase, currScope, currFilter, currAttrs, currAttrsOnly); } catch (LDAPException e) { add(e); } finally { currConn.releaseSearchListener(resultSource); } searchComplete = true; if (connectionToClose != null) { try { connectionToClose.disconnect (); } catch (LDAPException e) { } connectionToClose = null; } return; } else { try { currConn.checkSearchMsg(this, msg, currCons, currBase, currScope, currFilter, currAttrs, currAttrsOnly); } catch (LDAPException e) { add(e); } } } } } }