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