001/**
002 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003 *
004 * Copyright (c) 2005 Sun Microsystems Inc. All Rights Reserved
005 *
006 * The contents of this file are subject to the terms
007 * of the Common Development and Distribution License
008 * (the License). You may not use this file except in
009 * compliance with the License.
010 *
011 * You can obtain a copy of the License at
012 * https://opensso.dev.java.net/public/CDDLv1.0.html or
013 * opensso/legal/CDDLv1.0.txt
014 * See the License for the specific language governing
015 * permission and limitations under the License.
016 *
017 * When distributing Covered Code, include this CDDL
018 * Header Notice in each file and include the License file
019 * at opensso/legal/CDDLv1.0.txt.
020 * If applicable, add the following below the CDDL Header,
021 * with the fields enclosed by brackets [] replaced by
022 * your own identifying information:
023 * "Portions Copyrighted [year] [name of copyright owner]"
024 *
025 * $Id: SearchResults.java,v 1.7 2009/01/28 05:34:51 ww203982 Exp $
026 *
027 */
028
029/**
030 * Portions Copyrighted [2011] [ForgeRock AS]
031 */
032package com.iplanet.ums;
033
034import com.iplanet.services.ldap.Attr;
035import com.iplanet.services.ldap.AttrSet;
036import com.iplanet.services.util.I18n;
037import com.sun.identity.shared.debug.Debug;
038import java.security.Principal;
039import java.util.Hashtable;
040import java.util.NoSuchElementException;
041import com.sun.identity.shared.ldap.LDAPConnection;
042import com.sun.identity.shared.ldap.LDAPControl;
043import com.sun.identity.shared.ldap.LDAPEntry;
044import com.sun.identity.shared.ldap.LDAPException;
045import com.sun.identity.shared.ldap.LDAPSearchResults;
046import com.sun.identity.shared.ldap.controls.LDAPVirtualListResponse;
047
048/**
049 * Represents search results. Each search result is a PersistentObject
050 * 
051 * @supported.api
052 */
053public class SearchResults implements java.io.Serializable {
054
055    private static Debug debug;
056    static {
057        debug = Debug.getInstance(IUMSConstants.UMS_DEBUG);
058    }
059
060    private static I18n i18n = I18n.getInstance(IUMSConstants.UMS_PKG);
061
062    /**
063     * Attribute name used with Object get( String ). Expected return object is
064     * Integer getting the content count from the VirtualListResponse control
065     * returned by server after a search
066     * 
067     * @supported.api
068     */
069    public static final String VLVRESPONSE_CONTENT_COUNT = "vlvContentCount";
070
071    /**
072     * Attribute name used with Object get( String ). Expected return object is
073     * Integer getting the index of first position from VirtualListResponse
074     * control returned by server after a search
075     * 
076     * @supported.api
077     */
078    public static final String VLVRESPONSE_FIRST_POSITION = "vlvFirstPosition";
079
080    /**
081     * Attribute name used with Object get( String ). Expected return object is
082     * Integer getting the result code from from VirtualListResponse control
083     * returned by server after a search.
084     * 
085     * @supported.api
086     */
087    public static final String VLVRESPONSE_RESULT_CODE = "vlvResultCode";
088
089    /**
090     * Attribute name used with Object get( String ). Expected return object is
091     * String getting the context cookie from VirtualListResponse control
092     * returned by server after a search.
093     * 
094     * @supported.api
095     */
096    public static final String VLVRESPONSE_CONTEXT = "vlvContext";
097
098    static final String EXPECT_VLV_RESPONSE = "expectVlvResponse";
099
100    static final String BASE_ID = "baseID";
101
102    static final String SEARCH_FILTER = "searchFilter";
103
104    static final String SORT_KEYS = "sortKeys";
105
106    static final String SEARCH_SCOPE = "searchScope";
107
108    /**
109     * Constructs SearchResults from <code>ldapSearchResult</code>.
110     * 
111     * @param ldapSearchResult <code>LDAPSearchResults</code> to construct from
112     * @param conn <code>LDAPConnection</code> assosciated with the search
113     *        results.
114     * @param dataLayer Data Layer assosciated with the connection.
115     */
116    protected SearchResults(
117        LDAPSearchResults ldapSearchResult,
118        LDAPConnection conn,
119        DataLayer dataLayer
120    ) {
121        // TODO: SearchResults is tightly coupled with DataLayer and
122        // PersistentObject. That could make it harder to separate them
123        // in the future.
124        //
125        m_ldapSearchResults = ldapSearchResult;
126        m_conn = conn;
127        m_dataLayer = dataLayer;
128        if (debug.messageEnabled()) {
129            debug.message("Constructing SearchResults: " + this
130                    + " with connection : " + conn);
131        }
132    }
133
134    /**
135     * Constructs Search Results from <code>ldapSearchResult</code>.
136     * 
137     * @param ldapSearchResult <code>LDAPSearchResults</code> to construct
138     *        from.
139     * @param conn <code>LDAPConnection</code> associated with the search
140     *        results.
141     */
142    protected SearchResults(LDAPSearchResults ldapSearchResult,
143            LDAPConnection conn) {
144        this(ldapSearchResult, conn, null);
145    }
146
147    /**
148     * Secret constructor for iterating through the values of an entry.
149     * 
150     * @param attr
151     *            An attribute containing 0 or more DN values, to be treated as
152     *            individual results
153     */
154    SearchResults(Attr attr) {
155        if (attr == null) {
156            m_attrVals = new String[0];
157        } else {
158            m_attrVals = attr.getStringValues();
159        }
160    }
161
162    /**
163     * Checks whether there are entries available.
164     * 
165     * @return <code>true</code> if there is more to read
166     * @supported.api
167     */
168    public boolean hasMoreElements() {
169        boolean hasGotMoreElements;
170        hasGotMoreElements = (m_attrVals != null) ? 
171                (m_attrIndex < m_attrVals.length)
172                : m_ldapSearchResults.hasMoreElements();
173        if (!hasGotMoreElements && m_conn != null) {
174            if (debug.messageEnabled()) {
175                debug.message("Finishing SearchResults: " + this
176                        + "  with connection : " + m_conn);
177                debug.message("SearchResults: " + this
178                        + "  releasing connection : " + m_conn);
179            }
180
181            m_dataLayer.releaseConnection(m_conn);
182
183        }
184        return hasGotMoreElements;
185    }
186
187    /**
188     * Returns the next entry in the search results.
189     * 
190     * @throws UMSException
191     *             No more entries in the search results.
192     * @supported.api
193     */
194    public PersistentObject next() throws UMSException {
195        // TODO: define detailed exception list (eg. referral, ...)
196        //
197
198        LDAPEntry ldapEntry;
199
200        try {
201            if (m_attrVals != null) {
202                if (m_attrIndex < m_attrVals.length) {
203                    String dn = m_attrVals[m_attrIndex++];
204                    PersistentObject pO = new PersistentObject();
205                    pO.setGuid(new Guid(dn));
206                    pO.setPrincipal(m_principal);
207                    return pO;
208                } else {
209                    throw new NoSuchElementException();
210                }
211            }
212            if ((ldapEntry = m_ldapSearchResults.next()) != null) {
213                String id = ldapEntry.getDN();
214                AttrSet attrSet = new AttrSet(ldapEntry.getAttributeSet());
215                Class javaClass = TemplateManager.getTemplateManager()
216                        .getJavaClassForEntry(id, attrSet);
217                PersistentObject pO = null;
218                try {
219                    pO = (PersistentObject) javaClass.newInstance();
220                } catch (Exception e) {
221                    String args[] = new String[1];
222
223                    args[0] = e.toString();
224                    String msg = i18n.getString(
225                            IUMSConstants.NEW_INSTANCE_FAILED, args);
226                    throw new UMSException(msg);
227                }
228                // Make it a live object
229                pO.setAttrSet(attrSet);
230                pO.setGuid(new Guid(ldapEntry.getDN()));
231                pO.setPrincipal(m_principal);
232                return pO;
233            }
234        } catch (LDAPException e) {
235            abandon();
236            debug.error("Exception in SearchResults.next: ", e);
237            String args[] = new String[1];
238            args[0] = e.errorCodeToString();
239            String msg = i18n.getString(IUMSConstants.NEXT_ENTRY_FAILED, args);
240
241            int errorCode = e.getLDAPResultCode();
242            switch (errorCode) {
243            case LDAPException.TIME_LIMIT_EXCEEDED: {
244                throw new TimeLimitExceededException(msg, e);
245            }
246            case LDAPException.SIZE_LIMIT_EXCEEDED: {
247                throw new SizeLimitExceededException(msg, e);
248            }
249            case LDAPException.ADMIN_LIMIT_EXCEEDED: {
250                throw new SizeLimitExceededException(msg, e);
251            }
252            default:
253                throw new UMSException(msg, e);
254            }
255        }
256        return null;
257    }
258
259    /**
260     * Assert if the search result contains one and only one entry.
261     * 
262     * @return Entry if and only if there is one single entry
263     * @throws EntryNotFoundException
264     *             if there is no entry at all
265     * 
266     * @supported.api
267     */
268    public PersistentObject assertOneEntry() throws EntryNotFoundException,
269            UMSException {
270        PersistentObject entry = null;
271        while (hasMoreElements()) {
272            entry = next();
273            break;
274        }
275
276        if (entry == null) {
277            throw new EntryNotFoundException();
278        }
279
280        if (hasMoreElements()) {
281            abandon();
282            // TODO: to be replaced by new exception
283            //
284            throw new UMSException(
285                        i18n.getString(IUMSConstants.MULTIPLE_ENTRY));
286        }
287
288        return entry;
289    }
290
291    /**
292     * Get search result attributes related to the search operation performed.
293     * 
294     * @param name
295     *            Name of attribute to return, null if attribute is unknown or
296     *            not found
297     * @throws UMSException
298     *             from accessor methods on LDAPVirtualListResponse control
299     * 
300     * @supported.api
301     */
302    public Object get(String name) throws UMSException {
303
304        // For non vlv related attributes, get it
305        // from the generic hash table
306        //
307        if (!isVLVAttrs(name)) {
308            return m_attrHash == null ? null : m_attrHash.get(name);
309        }
310
311        // The rest is related to vlv response control
312        //
313        if (m_ldapSearchResults == null)
314            return null;
315
316        LDAPControl[] ctrls = m_ldapSearchResults.getResponseControls();
317
318        if (ctrls == null && expectVlvResponse() == true) {
319
320            //
321            // Code to deal with response controls not being returned yet. It
322            // instructs a small search with vlv ranage that expect one result
323            // to
324            // return so that the response is returned. This probe is only
325            // launched if EXPECT_VLV_RESPONSE is set for true in SearchResults
326            //
327
328            PersistentObject parent = getParentContainer();
329
330            synchronized (this) {
331                // The following code fragment uses a test control that only
332                // asks
333                // one result to return. This is done so that the response
334                // control
335                // can be queried for the vlvContentCount. This is a search
336                // probe to
337                // get the vlvCount
338                //
339                String[] sortAttrNames = { "objectclass" };
340                SortKey[] sortKeys = (SortKey[]) get(SearchResults.SORT_KEYS);
341                String filter = (String) get(SearchResults.SEARCH_FILTER);
342                Integer scopeVal = (Integer) get(SearchResults.SEARCH_SCOPE);
343                int scope = scopeVal == null ? SearchControl.SCOPE_SUB
344                        : scopeVal.intValue();
345
346                SearchControl testControl = new SearchControl();
347                testControl.setVLVRange(1, 0, 0);
348
349                if (sortKeys == null) {
350                    testControl.setSortKeys(sortAttrNames);
351                } else {
352                    testControl.setSortKeys(sortKeys);
353                }
354
355                testControl.setSearchScope(scope);
356
357                SearchResults testResults = parent.search(filter,
358                        sortAttrNames, testControl);
359                while (testResults.hasMoreElements()) {
360                    // This while loop is required to
361                    // enumerate the result set to get the response control
362                    testResults.next();
363                }
364
365                // After all the hazzle, now the response should be in after the
366                // search probe, use the probe's search results to get the vlv
367                // related attribute(s). Set the internal flag not to launch
368                // the probe again in subsequent get.
369                //
370                testResults.set(SearchResults.EXPECT_VLV_RESPONSE, Boolean.FALSE);
371                return testResults.get(name);
372            }
373        }
374
375        // the control can be null
376        if (ctrls == null)
377            return null;
378
379        LDAPVirtualListResponse vlvResponse = null;
380
381        // Find the VLV response control recorded in SearchResults
382        //
383        for (int i = 0; i < ctrls.length; i++) {
384            if (ctrls[i].getType() ==
385                LDAPControl.LDAP_VIRTUAL_LIST_RESPONSE_CONTROL) {
386                vlvResponse = (LDAPVirtualListResponse) ctrls[i];
387            }
388        }
389
390        // Check on the attribute to return and
391        // return the value from the response control
392        // Currently only expose the VirtualListResponse control
393        // returned after a search operation
394        //
395        if (name.equalsIgnoreCase(VLVRESPONSE_CONTENT_COUNT)
396                && vlvResponse != null) {
397            return new Integer(vlvResponse.getContentCount());
398        } else if (name.equalsIgnoreCase(VLVRESPONSE_FIRST_POSITION)
399                && vlvResponse != null) {
400            return new Integer(vlvResponse.getFirstPosition());
401        } else if (name.equalsIgnoreCase(VLVRESPONSE_RESULT_CODE)
402                && vlvResponse != null) {
403            return new Integer(vlvResponse.getResultCode());
404        } else if (name.equalsIgnoreCase(VLVRESPONSE_CONTEXT)
405                && vlvResponse != null) {
406            return vlvResponse.getContext();
407        }
408
409        // For all other unknown attribute names,
410        // just return a null object
411        //
412        return null;
413
414    }
415
416    /**
417     * Abandons a current search operation, notifying the server not to send
418     * additional search results.
419     * 
420     * @throws UMSException
421     *             from abandoning a search operation from LDAP
422     * @supported.api
423     */
424    public void abandon() throws UMSException {
425
426        /*
427         * If we add m_conn.isConnected() in the following check, we get
428         * LDAPException on the m_conn.abandon(...) line, if there are more
429         * results in the searchresults. The LDAPException is Failed to send
430         * abandon request to the server. (80); Unknown error
431         */
432        if (m_conn != null && m_ldapSearchResults != null) {
433            try {
434                m_dataLayer.releaseConnection(m_conn);
435                if (debug.messageEnabled()) {
436                    debug.message("Abandoning SearchResults: " + this
437                            + " connection : " + m_conn);
438                }
439                if (hasMoreElements()) {
440                    m_conn.abandon(m_ldapSearchResults);
441                }
442
443                if (debug.messageEnabled()) {
444                    debug.message("SearchResults: " + this
445                            + " releasing connection : " + m_conn);
446                }
447            } catch (LDAPException e) {
448                throw new UMSException(m_conn.toString(), e);
449            }
450        }
451    }
452
453    /**
454     * Sets the principal with which to associate search results.
455     * 
456     * @param principal Authenticated principal.
457     */
458    protected void setPrincipal(Principal principal) {
459        m_principal = principal;
460    }
461
462    /**
463     * Set attribute internal to search result. This set function is explicitly
464     * coded for package scope.
465     * 
466     * @param name
467     *            Name of attribute to set.
468     * @param value
469     *            Value of attribute to set
470     */
471    void set(String name, Object value) {
472
473        if (m_attrHash == null) {
474            synchronized (this) {
475                if (m_attrHash == null) {
476                    m_attrHash = new Hashtable();
477                }
478            }
479        }
480
481        m_attrHash.put(name, value);
482    }
483
484    /**
485     * Check if this search result expects a VLV response control
486     * 
487     * @return <code>true</code> if search result expects a VLV response
488     *         control and <code>false</code> otherwise
489     */
490    private boolean expectVlvResponse() {
491        Boolean expected = Boolean.FALSE;
492
493        try {
494            expected = (Boolean) get(EXPECT_VLV_RESPONSE);
495        } catch (Exception e) {
496        }
497
498        return expected == null ? false : expected.booleanValue();
499    }
500
501    /**
502     * Gets the original container that the search result is originated from
503     * 
504     * @return PersistentObject The parent container object.
505     * @throws UMSException
506     *             If an exception occurs.
507     */
508    private PersistentObject getParentContainer() throws UMSException {
509        String parentID = null;
510        PersistentObject parent = null;
511
512        try {
513            parentID = (String) get(BASE_ID);
514            Guid parentGuid = new Guid(parentID);
515            parent = new PersistentObject(m_principal, parentGuid);
516        } catch (UMSException e) {
517            throw new UMSException(e.getMessage());
518        }
519
520        return parent;
521    }
522
523    /**
524     * Check if attribute name is related to vlv response attributes
525     * 
526     */
527    private boolean isVLVAttrs(String name) {
528
529        for (int i = 0; i < vlvAttrNames.length; i++) {
530            if (name.equalsIgnoreCase(vlvAttrNames[i])) {
531                return true;
532            }
533        }
534        return false;
535    }
536
537    /*
538     * Iterator iterator() { return new Iterator() { public boolean hasNext() {
539     * return SearchResults.this.hasMoreElements(); }
540     * 
541     * public Object next() { PersistentObject po = null; try { po =
542     * SearchResults.this.next(); } catch ( Exception ignored) { } return po; }
543     * 
544     * public void remove() { throw new UnsupportedOperationException(); } }; }
545     */
546
547    private LDAPSearchResults m_ldapSearchResults = null;
548
549    private LDAPConnection m_conn = null;
550
551    private Principal m_principal = null;
552
553    private Hashtable m_attrHash = null;
554
555    private static String[] vlvAttrNames = { VLVRESPONSE_CONTENT_COUNT,
556            VLVRESPONSE_FIRST_POSITION, VLVRESPONSE_RESULT_CODE,
557            VLVRESPONSE_CONTEXT };
558
559    //
560    // These are only used for the tricky constructor with SearchResults(Attr)
561    //
562    private String[] m_attrVals = null;
563
564    private int m_attrIndex = 0;
565
566    private DataLayer m_dataLayer = null;
567
568}




























































Copyright © 2010-2017, ForgeRock All Rights Reserved.