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 * Portions Copyrighted 2011-2015 ForgeRock AS. 028 */ 029package com.iplanet.ums; 030 031import com.iplanet.services.ldap.Attr; 032import com.iplanet.services.ldap.AttrSet; 033import com.iplanet.services.util.I18n; 034import com.sun.identity.shared.debug.Debug; 035import java.security.Principal; 036import java.util.ArrayList; 037import java.util.Collection; 038import java.util.Hashtable; 039import java.util.List; 040import java.util.NoSuchElementException; 041import org.forgerock.openam.utils.IOUtils; 042import org.forgerock.opendj.ldap.Attribute; 043import org.forgerock.opendj.ldap.Connection; 044import org.forgerock.opendj.ldap.LdapException; 045import org.forgerock.opendj.ldap.SearchResultReferenceIOException; 046import org.forgerock.opendj.ldap.controls.Control; 047import org.forgerock.opendj.ldap.controls.VirtualListViewResponseControl; 048import org.forgerock.opendj.ldap.responses.SearchResultEntry; 049import org.forgerock.opendj.ldif.ConnectionEntryReader; 050 051/** 052 * Represents search results. Each search result is a PersistentObject 053 * 054 * @supported.api 055 */ 056public class SearchResults implements java.io.Serializable { 057 058 private static Debug debug; 059 static { 060 debug = Debug.getInstance(IUMSConstants.UMS_DEBUG); 061 } 062 063 private static I18n i18n = I18n.getInstance(IUMSConstants.UMS_PKG); 064 065 /** 066 * Attribute name used with Object get( String ). Expected return object is 067 * Integer getting the content count from the VirtualListResponse control 068 * returned by server after a search 069 * 070 * @supported.api 071 */ 072 public static final String VLVRESPONSE_CONTENT_COUNT = "vlvContentCount"; 073 074 /** 075 * Attribute name used with Object get( String ). Expected return object is 076 * Integer getting the index of first position from VirtualListResponse 077 * control returned by server after a search 078 * 079 * @supported.api 080 */ 081 public static final String VLVRESPONSE_FIRST_POSITION = "vlvFirstPosition"; 082 083 /** 084 * Attribute name used with Object get( String ). Expected return object is 085 * Integer getting the result code from from VirtualListResponse control 086 * returned by server after a search. 087 * 088 * @supported.api 089 */ 090 public static final String VLVRESPONSE_RESULT_CODE = "vlvResultCode"; 091 092 /** 093 * Attribute name used with Object get( String ). Expected return object is 094 * String getting the context cookie from VirtualListResponse control 095 * returned by server after a search. 096 * 097 * @supported.api 098 */ 099 public static final String VLVRESPONSE_CONTEXT = "vlvContext"; 100 101 static final String EXPECT_VLV_RESPONSE = "expectVlvResponse"; 102 103 static final String BASE_ID = "baseID"; 104 105 static final String SEARCH_FILTER = "searchFilter"; 106 107 static final String SORT_KEYS = "sortKeys"; 108 109 static final String SEARCH_SCOPE = "searchScope"; 110 111 /** 112 * Constructs SearchResults from <code>ldapSearchResult</code>. 113 * 114 * @param ldapSearchResult <code>LDAPSearchResults</code> to construct from 115 * @param conn <code>LDAPConnection</code> assosciated with the search 116 * results. 117 * @param dataLayer Data Layer assosciated with the connection. 118 */ 119 protected SearchResults(Connection connection, ConnectionEntryReader ldapSearchResult, Connection conn, 120 DataLayer dataLayer) { 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 this.connection = connection; 126 m_ldapSearchResults = ldapSearchResult; 127 m_conn = conn; 128 m_dataLayer = dataLayer; 129 if (debug.messageEnabled()) { 130 debug.message("Constructing SearchResults: " + this 131 + " with connection : " + conn); 132 } 133 } 134 135 /** 136 * Constructs Search Results from <code>ldapSearchResult</code>. 137 * 138 * @param ldapSearchResult <code>LDAPSearchResults</code> to construct 139 * from. 140 * @param conn <code>LDAPConnection</code> associated with the search 141 * results. 142 */ 143 protected SearchResults(Connection connection, ConnectionEntryReader ldapSearchResult, Connection conn) { 144 this(connection, 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 = false; 170 try { 171 hasGotMoreElements = (m_attrVals != null) ? 172 (m_attrIndex < m_attrVals.length) 173 : m_ldapSearchResults.hasNext(); 174 if (hasGotMoreElements) { 175 readEntry(); 176 } 177 if (debug.messageEnabled()) { 178 if (!hasGotMoreElements && m_conn != null) { 179 debug.message("Finishing SearchResults: " + this 180 + " with connection : " + m_conn); 181 debug.message("SearchResults: " + this 182 + " releasing connection : " + m_conn); 183 } 184 } 185 } catch (LdapException | SearchResultReferenceIOException ignored) { 186 } 187 return hasGotMoreElements; 188 } 189 190 private void readEntry() throws SearchResultReferenceIOException, LdapException { 191 if (m_ldapSearchResults != null) { 192 if (m_ldapSearchResults.isReference()) { 193 //Ignoring references 194 m_ldapSearchResults.readReference(); 195 } 196 currentEntry = m_ldapSearchResults.readEntry(); 197 } 198 } 199 200 /** 201 * Returns the next entry in the search results. 202 * 203 * @throws UMSException 204 * No more entries in the search results. 205 * @supported.api 206 */ 207 public PersistentObject next() throws UMSException { 208 // TODO: define detailed exception list (eg. referral, ...) 209 // 210 211 SearchResultEntry ldapEntry; 212 213 if (m_attrVals != null) { 214 if (m_attrIndex < m_attrVals.length) { 215 String dn = m_attrVals[m_attrIndex++]; 216 PersistentObject pO = new PersistentObject(); 217 pO.setGuid(new Guid(dn)); 218 pO.setPrincipal(m_principal); 219 return pO; 220 } else { 221 throw new NoSuchElementException(); 222 } 223 } 224 225 if ((ldapEntry = currentEntry) != null) { 226 String id = ldapEntry.getName().toString(); 227 Collection<Attribute> attributes = new ArrayList<>(); 228 for (Attribute attribute : ldapEntry.getAllAttributes()) { 229 attributes.add(attribute); 230 } 231 AttrSet attrSet = new AttrSet(attributes); 232 Class javaClass = TemplateManager.getTemplateManager() 233 .getJavaClassForEntry(id, attrSet); 234 PersistentObject pO = null; 235 try { 236 pO = (PersistentObject) javaClass.newInstance(); 237 } catch (Exception e) { 238 String args[] = new String[1]; 239 240 args[0] = e.toString(); 241 String msg = i18n.getString( 242 IUMSConstants.NEW_INSTANCE_FAILED, args); 243 throw new UMSException(msg); 244 } 245 // Make it a live object 246 pO.setAttrSet(attrSet); 247 pO.setGuid(new Guid(ldapEntry.getName().toString())); 248 pO.setPrincipal(m_principal); 249 return pO; 250 } 251 return null; 252 } 253 254 /** 255 * Assert if the search result contains one and only one entry. 256 * 257 * @return Entry if and only if there is one single entry 258 * @throws EntryNotFoundException 259 * if there is no entry at all 260 * 261 * @supported.api 262 */ 263 public PersistentObject assertOneEntry() throws EntryNotFoundException, 264 UMSException { 265 PersistentObject entry = null; 266 while (hasMoreElements()) { 267 entry = next(); 268 break; 269 } 270 271 if (entry == null) { 272 throw new EntryNotFoundException(); 273 } 274 275 if (hasMoreElements()) { 276 abandon(); 277 // TODO: to be replaced by new exception 278 // 279 throw new UMSException( 280 i18n.getString(IUMSConstants.MULTIPLE_ENTRY)); 281 } 282 283 return entry; 284 } 285 286 /** 287 * Get search result attributes related to the search operation performed. 288 * 289 * @param name 290 * Name of attribute to return, null if attribute is unknown or 291 * not found 292 * @throws UMSException 293 * from accessor methods on LDAPVirtualListResponse control 294 * 295 * @supported.api 296 */ 297 public Object get(String name) throws UMSException { 298 299 // For non vlv related attributes, get it 300 // from the generic hash table 301 // 302 if (!isVLVAttrs(name)) { 303 return m_attrHash == null ? null : m_attrHash.get(name); 304 } 305 306 // The rest is related to vlv response control 307 // 308 if (currentEntry == null) 309 return null; 310 311 List<Control> ctrls = currentEntry.getControls(); 312 313 if (ctrls == null && expectVlvResponse() == true) { 314 315 // 316 // Code to deal with response controls not being returned yet. It 317 // instructs a small search with vlv ranage that expect one result 318 // to 319 // return so that the response is returned. This probe is only 320 // launched if EXPECT_VLV_RESPONSE is set for true in SearchResults 321 // 322 323 PersistentObject parent = getParentContainer(); 324 325 synchronized (this) { 326 // The following code fragment uses a test control that only 327 // asks 328 // one result to return. This is done so that the response 329 // control 330 // can be queried for the vlvContentCount. This is a search 331 // probe to 332 // get the vlvCount 333 // 334 String[] sortAttrNames = { "objectclass" }; 335 SortKey[] sortKeys = (SortKey[]) get(SearchResults.SORT_KEYS); 336 String filter = (String) get(SearchResults.SEARCH_FILTER); 337 Integer scopeVal = (Integer) get(SearchResults.SEARCH_SCOPE); 338 int scope = scopeVal == null ? SearchControl.SCOPE_SUB 339 : scopeVal.intValue(); 340 341 SearchControl testControl = new SearchControl(); 342 testControl.setVLVRange(1, 0, 0); 343 344 if (sortKeys == null) { 345 testControl.setSortKeys(sortAttrNames); 346 } else { 347 testControl.setSortKeys(sortKeys); 348 } 349 350 testControl.setSearchScope(scope); 351 352 SearchResults testResults = parent.search(filter, 353 sortAttrNames, testControl); 354 while (testResults.hasMoreElements()) { 355 // This while loop is required to 356 // enumerate the result set to get the response control 357 testResults.next(); 358 } 359 360 // After all the hazzle, now the response should be in after the 361 // search probe, use the probe's search results to get the vlv 362 // related attribute(s). Set the internal flag not to launch 363 // the probe again in subsequent get. 364 // 365 testResults.set(SearchResults.EXPECT_VLV_RESPONSE, Boolean.FALSE); 366 return testResults.get(name); 367 } 368 } 369 370 // the control can be null 371 if (ctrls == null) 372 return null; 373 374 VirtualListViewResponseControl vlvResponse = null; 375 376 // Find the VLV response control recorded in SearchResults 377 // 378 for (Control control : ctrls) { 379 if (VirtualListViewResponseControl.OID.equals(control.getOID())) { 380 vlvResponse = (VirtualListViewResponseControl) control; 381 } 382 } 383 384 // Check on the attribute to return and 385 // return the value from the response control 386 // Currently only expose the VirtualListResponse control 387 // returned after a search operation 388 // 389 if (vlvResponse != null) { 390 if (name.equalsIgnoreCase(VLVRESPONSE_CONTENT_COUNT)) { 391 return vlvResponse.getContentCount(); 392 } else if (name.equalsIgnoreCase(VLVRESPONSE_FIRST_POSITION)) { 393 return vlvResponse.getTargetPosition(); 394 } else if (name.equalsIgnoreCase(VLVRESPONSE_RESULT_CODE)) { 395 return vlvResponse.getResult().intValue(); 396 } else if (name.equalsIgnoreCase(VLVRESPONSE_CONTEXT)) { 397 return vlvResponse.getValue().toString(); 398 } 399 } 400 401 // For all other unknown attribute names, 402 // just return a null object 403 // 404 return null; 405 406 } 407 408 /** 409 * Abandons a current search operation, notifying the server not to send 410 * additional search results. 411 * 412 * @throws UMSException 413 * from abandoning a search operation from LDAP 414 * @supported.api 415 */ 416 public void abandon() throws UMSException { 417 //Nothing to do 418 IOUtils.closeIfNotNull(connection, m_ldapSearchResults); 419 } 420 421 /** 422 * Sets the principal with which to associate search results. 423 * 424 * @param principal Authenticated principal. 425 */ 426 protected void setPrincipal(Principal principal) { 427 m_principal = principal; 428 } 429 430 /** 431 * Set attribute internal to search result. This set function is explicitly 432 * coded for package scope. 433 * 434 * @param name 435 * Name of attribute to set. 436 * @param value 437 * Value of attribute to set 438 */ 439 void set(String name, Object value) { 440 441 if (m_attrHash == null) { 442 synchronized (this) { 443 if (m_attrHash == null) { 444 m_attrHash = new Hashtable(); 445 } 446 } 447 } 448 449 m_attrHash.put(name, value); 450 } 451 452 /** 453 * Check if this search result expects a VLV response control 454 * 455 * @return <code>true</code> if search result expects a VLV response 456 * control and <code>false</code> otherwise 457 */ 458 private boolean expectVlvResponse() { 459 Boolean expected = Boolean.FALSE; 460 461 try { 462 expected = (Boolean) get(EXPECT_VLV_RESPONSE); 463 } catch (Exception e) { 464 } 465 466 return expected == null ? false : expected.booleanValue(); 467 } 468 469 /** 470 * Gets the original container that the search result is originated from 471 * 472 * @return PersistentObject The parent container object. 473 * @throws UMSException 474 * If an exception occurs. 475 */ 476 private PersistentObject getParentContainer() throws UMSException { 477 String parentID = null; 478 PersistentObject parent = null; 479 480 try { 481 parentID = (String) get(BASE_ID); 482 Guid parentGuid = new Guid(parentID); 483 parent = new PersistentObject(m_principal, parentGuid); 484 } catch (UMSException e) { 485 throw new UMSException(e.getMessage()); 486 } 487 488 return parent; 489 } 490 491 /** 492 * Check if attribute name is related to vlv response attributes 493 * 494 */ 495 private boolean isVLVAttrs(String name) { 496 497 for (int i = 0; i < vlvAttrNames.length; i++) { 498 if (name.equalsIgnoreCase(vlvAttrNames[i])) { 499 return true; 500 } 501 } 502 return false; 503 } 504 505 /* 506 * Iterator iterator() { return new Iterator() { public boolean hasNext() { 507 * return SearchResults.this.hasMoreElements(); } 508 * 509 * public Object next() { PersistentObject po = null; try { po = 510 * SearchResults.this.next(); } catch ( Exception ignored) { } return po; } 511 * 512 * public void remove() { throw new UnsupportedOperationException(); } }; } 513 */ 514 515 private SearchResultEntry currentEntry = null; 516 517 private Connection connection; 518 519 private ConnectionEntryReader m_ldapSearchResults = null; 520 521 private Connection m_conn = null; 522 523 private Principal m_principal = null; 524 525 private Hashtable m_attrHash = null; 526 527 private static String[] vlvAttrNames = { VLVRESPONSE_CONTENT_COUNT, 528 VLVRESPONSE_FIRST_POSITION, VLVRESPONSE_RESULT_CODE, 529 VLVRESPONSE_CONTEXT }; 530 531 // 532 // These are only used for the tricky constructor with SearchResults(Attr) 533 // 534 private String[] m_attrVals = null; 535 536 private int m_attrIndex = 0; 537 538 private DataLayer m_dataLayer = null; 539 540}