001 /* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at 010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE 011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE. 012 * See the License for the specific language governing permissions 013 * and limitations under the License. 014 * 015 * When distributing Covered Code, include this CDDL HEADER in each 016 * file and include the License file at 017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, 018 * add the following below this CDDL HEADER, with the fields enclosed 019 * by brackets "[]" replaced with your own identifying information: 020 * Portions Copyright [yyyy] [name of copyright owner] 021 * 022 * CDDL HEADER END 023 * 024 * 025 * Copyright 2008 Sun Microsystems, Inc. 026 */ 027 package org.opends.server.extensions; 028 029 030 031 import java.util.Iterator; 032 import java.util.LinkedHashMap; 033 import java.util.LinkedHashSet; 034 import java.util.LinkedList; 035 import java.util.Set; 036 import java.util.concurrent.LinkedBlockingQueue; 037 import java.util.concurrent.TimeUnit; 038 039 import org.opends.server.types.DirectoryException; 040 import org.opends.server.types.DN; 041 import org.opends.server.types.Entry; 042 import org.opends.server.types.LDAPURL; 043 import org.opends.server.types.MemberList; 044 import org.opends.server.types.MembershipException; 045 import org.opends.server.types.SearchFilter; 046 import org.opends.server.types.SearchScope; 047 048 049 050 /** 051 * This class defines a mechanism that may be used to iterate over the 052 * members of a dynamic group, optionally using an additional set of 053 * criteria to further filter the results. 054 */ 055 public class DynamicGroupMemberList 056 extends MemberList 057 { 058 // Indicates whether the search thread has completed its processing. 059 private boolean searchesCompleted; 060 061 // The base DN to use when filtering the set of group members. 062 private final DN baseDN; 063 064 // The DN of the entry containing the group definition. 065 private final DN groupDN; 066 067 // The queue into which results will be placed while they are waiting to be 068 // returned. The types of objects that may be placed in this queue are Entry 069 // objects to return or MembershipException objects to throw. 070 private final LinkedBlockingQueue<Object> resultQueue; 071 072 // The search filter to use when filtering the set of group members. 073 private final SearchFilter filter; 074 075 // The search scope to use when filtering the set of group members. 076 private final SearchScope scope; 077 078 // The set of LDAP URLs that define the membership criteria. 079 private final Set<LDAPURL> memberURLs; 080 081 082 083 /** 084 * Creates a new dynamic group member list with the provided information. 085 * 086 * @param groupDN The DN of the entry containing the group definition. 087 * @param memberURLs The set of LDAP URLs that define the membership 088 * criteria for the associated group. 089 * 090 * @throws DirectoryException If a problem occurs while creating the member 091 * list. 092 */ 093 public DynamicGroupMemberList(DN groupDN, Set<LDAPURL> memberURLs) 094 throws DirectoryException 095 { 096 this(groupDN, memberURLs, null, null, null); 097 } 098 099 100 101 /** 102 * Creates a new dynamic group member list with the provided information. 103 * 104 * @param groupDN The DN of the entry containing the group definition. 105 * @param memberURLs The set of LDAP URLs that define the membership 106 * criteria for the associated group. 107 * @param baseDN The base DN that should be enforced for all entries to 108 * return. 109 * @param scope The scope that should be enforced for all entries to 110 * return. 111 * @param filter The filter that should be enforced for all entries to 112 * return. 113 * 114 * @throws DirectoryException If a problem occurs while creating the member 115 * list. 116 */ 117 public DynamicGroupMemberList(DN groupDN, Set<LDAPURL> memberURLs, 118 DN baseDN, SearchScope scope, 119 SearchFilter filter) 120 throws DirectoryException 121 { 122 this.groupDN = groupDN; 123 this.memberURLs = memberURLs; 124 this.baseDN = baseDN; 125 this.filter = filter; 126 127 if (scope == null) 128 { 129 this.scope = SearchScope.WHOLE_SUBTREE; 130 } 131 else 132 { 133 this.scope = scope; 134 } 135 136 searchesCompleted = false; 137 resultQueue = new LinkedBlockingQueue<Object>(10); 138 139 140 // We're going to have to perform one or more internal searches in order to 141 // get the results. We need to be careful about the way that we construct 142 // them in order to avoid the possibility of getting duplicate results, so 143 // searches with overlapping bases will need to be combined. 144 LinkedHashMap<DN,LinkedList<LDAPURL>> baseDNs = 145 new LinkedHashMap<DN,LinkedList<LDAPURL>>(); 146 for (LDAPURL memberURL : memberURLs) 147 { 148 // First, determine the base DN for the search. It needs to be evaluated 149 // as relative to both the overall base DN specified in the set of 150 // criteria, as well as any other existing base DNs in the same hierarchy. 151 DN urlBaseDN = memberURL.getBaseDN(); 152 if (baseDN != null) 153 { 154 if (baseDN.isDescendantOf(urlBaseDN)) 155 { 156 // The base DN requested by the user is below the base DN for this 157 // URL, so we'll use the base DN requested by the user. 158 urlBaseDN = baseDN; 159 } 160 else if (! urlBaseDN.isDescendantOf(baseDN)) 161 { 162 // The base DN from the URL is outside the base requested by the user, 163 // so we can skip this URL altogether. 164 continue; 165 } 166 } 167 168 // If this is the first URL, then we can just add it with the base DN. 169 // Otherwise, we need to see if it needs to be merged with other URLs in 170 // the same hierarchy. 171 if (baseDNs.isEmpty()) 172 { 173 LinkedList<LDAPURL> urlList = new LinkedList<LDAPURL>(); 174 urlList.add(memberURL); 175 baseDNs.put(urlBaseDN, urlList); 176 } 177 else 178 { 179 // See if the specified base DN is already in the map. If so, then 180 // just add the new URL to the existing list. 181 LinkedList<LDAPURL> urlList = baseDNs.get(urlBaseDN); 182 if (urlList == null) 183 { 184 // There's no existing list for the same base DN, but there might be 185 // DNs in an overlapping hierarchy. If so, then use the base DN that 186 // is closest to the naming context. If not, then add a new list with 187 // the current base DN. 188 boolean found = false; 189 Iterator<DN> iterator = baseDNs.keySet().iterator(); 190 while (iterator.hasNext()) 191 { 192 DN existingBaseDN = iterator.next(); 193 if (urlBaseDN.isDescendantOf(existingBaseDN)) 194 { 195 // The base DN for the current URL is below an existing base DN, 196 // so we can just add this URL to the existing list and be done. 197 urlList = baseDNs.get(existingBaseDN); 198 urlList.add(memberURL); 199 found = true; 200 break; 201 } 202 else if (existingBaseDN.isDescendantOf(urlBaseDN)) 203 { 204 // The base DN for the current URL is above the existing base DN, 205 // so we should use the base DN for the current URL instead of the 206 // existing one. 207 urlList = baseDNs.get(existingBaseDN); 208 urlList.add(memberURL); 209 iterator.remove(); 210 baseDNs.put(urlBaseDN, urlList); 211 found = true; 212 break; 213 } 214 } 215 216 if (! found) 217 { 218 urlList = new LinkedList<LDAPURL>(); 219 urlList.add(memberURL); 220 baseDNs.put(urlBaseDN, urlList); 221 } 222 } 223 else 224 { 225 // There was already a list with the same base DN, so just add the 226 // URL. 227 urlList.add(memberURL); 228 } 229 } 230 } 231 232 233 // At this point, we should know what base DN(s) we need to use, so we can 234 // create the filter to use with that base DN. There are some special-case 235 // optimizations that we can do here, but in general the filter will look 236 // like "(&(filter)(|(urlFilters)))". 237 LinkedHashMap<DN,SearchFilter> searchMap = 238 new LinkedHashMap<DN,SearchFilter>(); 239 for (DN urlBaseDN : baseDNs.keySet()) 240 { 241 LinkedList<LDAPURL> urlList = baseDNs.get(urlBaseDN); 242 LinkedHashSet<SearchFilter> urlFilters = 243 new LinkedHashSet<SearchFilter>(); 244 for (LDAPURL url : urlList) 245 { 246 urlFilters.add(url.getFilter()); 247 } 248 249 SearchFilter combinedFilter; 250 if (filter == null) 251 { 252 if (urlFilters.size() == 1) 253 { 254 combinedFilter = urlFilters.iterator().next(); 255 } 256 else 257 { 258 combinedFilter = SearchFilter.createORFilter(urlFilters); 259 } 260 } 261 else 262 { 263 if (urlFilters.size() == 1) 264 { 265 SearchFilter urlFilter = urlFilters.iterator().next(); 266 if (urlFilter.equals(filter)) 267 { 268 combinedFilter = filter; 269 } 270 else 271 { 272 LinkedHashSet<SearchFilter> filterSet = 273 new LinkedHashSet<SearchFilter>(); 274 filterSet.add(filter); 275 filterSet.add(urlFilter); 276 combinedFilter = SearchFilter.createANDFilter(filterSet); 277 } 278 } 279 else 280 { 281 if (urlFilters.contains(filter)) 282 { 283 combinedFilter = filter; 284 } 285 else 286 { 287 LinkedHashSet<SearchFilter> filterSet = 288 new LinkedHashSet<SearchFilter>(); 289 filterSet.add(filter); 290 filterSet.add(SearchFilter.createORFilter(urlFilters)); 291 combinedFilter = SearchFilter.createANDFilter(filterSet); 292 } 293 } 294 } 295 296 searchMap.put(urlBaseDN, combinedFilter); 297 } 298 299 300 // At this point, we should have all the information we need to perform the 301 // searches. Create arrays of the elements for each. 302 DN[] baseDNArray = new DN[baseDNs.size()]; 303 SearchFilter[] filterArray = new SearchFilter[baseDNArray.length]; 304 LDAPURL[][] urlArray = new LDAPURL[baseDNArray.length][]; 305 Iterator<DN> iterator = baseDNs.keySet().iterator(); 306 for (int i=0; i < baseDNArray.length; i++) 307 { 308 baseDNArray[i] = iterator.next(); 309 filterArray[i] = searchMap.get(baseDNArray[i]); 310 311 LinkedList<LDAPURL> urlList = baseDNs.get(baseDNArray[i]); 312 urlArray[i] = new LDAPURL[urlList.size()]; 313 int j=0; 314 for (LDAPURL url : urlList) 315 { 316 urlArray[i][j++] = url; 317 } 318 } 319 320 321 DynamicGroupSearchThread searchThread = 322 new DynamicGroupSearchThread(this, baseDNArray, filterArray, urlArray); 323 searchThread.start(); 324 } 325 326 327 328 /** 329 * Retrieves the DN of the dynamic group with which this dynamic group member 330 * list is associated. 331 * 332 * @return The DN of the dynamic group with which this dynamic group member 333 * list is associated. 334 */ 335 public final DN getDynamicGroupDN() 336 { 337 return groupDN; 338 } 339 340 341 342 /** 343 * Indicates that all of the searches needed to iterate across the member list 344 * have completed and there will not be any more results provided. 345 */ 346 final void setSearchesCompleted() 347 { 348 searchesCompleted = true; 349 } 350 351 352 353 /** 354 * Adds the provided entry to the set of results that should be returned for 355 * this member list. 356 * 357 * @param entry The entry to add to the set of results that should be 358 * returned for this member list. 359 * 360 * @return {@code true} if the entry was added to the result set, or 361 * {@code false} if it was not (either because a timeout expired or 362 * the attempt was interrupted). If this method returns 363 * {@code false}, then the search thread should terminate 364 * immediately. 365 */ 366 final boolean addResult(Entry entry) 367 { 368 try 369 { 370 return resultQueue.offer(entry, 10, TimeUnit.SECONDS); 371 } 372 catch (InterruptedException ie) 373 { 374 return false; 375 } 376 } 377 378 379 380 /** 381 * Adds the provided membership exception so that it will be thrown along with 382 * the set of results for this member list. 383 * 384 * @param membershipException The membership exception to be thrown. 385 * 386 * @return {@code true} if the exception was added to the result set, or 387 * {@code false} if it was not (either because a timeout expired or 388 * the attempt was interrupted). If this method returns 389 * {@code false}, then the search thread should terminate 390 * immediately. 391 */ 392 final boolean addResult(MembershipException membershipException) 393 { 394 try 395 { 396 return resultQueue.offer(membershipException, 10, TimeUnit.SECONDS); 397 } 398 catch (InterruptedException ie) 399 { 400 return false; 401 } 402 } 403 404 405 406 /** 407 * {@inheritDoc} 408 */ 409 @Override() 410 public boolean hasMoreMembers() 411 { 412 while (! searchesCompleted) 413 { 414 if (resultQueue.peek() != null) 415 { 416 return true; 417 } 418 419 try 420 { 421 Thread.sleep(0, 1000); 422 } catch (Exception e) {} 423 } 424 425 return (resultQueue.peek() != null); 426 } 427 428 429 430 /** 431 * {@inheritDoc} 432 */ 433 @Override() 434 public Entry nextMemberEntry() 435 throws MembershipException 436 { 437 if (! hasMoreMembers()) 438 { 439 return null; 440 } 441 442 Object result = resultQueue.poll(); 443 if (result == null) 444 { 445 close(); 446 return null; 447 } 448 else if (result instanceof Entry) 449 { 450 return (Entry) result; 451 } 452 else if (result instanceof MembershipException) 453 { 454 MembershipException me = (MembershipException) result; 455 if (! me.continueIterating()) 456 { 457 close(); 458 } 459 460 throw me; 461 } 462 463 // We should never get here. 464 close(); 465 return null; 466 } 467 468 469 470 /** 471 * {@inheritDoc} 472 */ 473 @Override() 474 public void close() 475 { 476 searchesCompleted = true; 477 resultQueue.clear(); 478 } 479 } 480