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}