001/*
002 * Copyright 2010-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2010-2024 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2010-2024 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.ldap.sdk;
037
038
039
040import java.util.Arrays;
041import java.util.Collection;
042import java.util.Iterator;
043
044import com.unboundid.util.Debug;
045import com.unboundid.util.NotNull;
046import com.unboundid.util.Nullable;
047import com.unboundid.util.StaticUtils;
048import com.unboundid.util.ThreadSafety;
049import com.unboundid.util.ThreadSafetyLevel;
050import com.unboundid.util.Validator;
051
052import static com.unboundid.ldap.sdk.LDAPMessages.*;
053
054
055
056/**
057 * This class provides an {@link EntrySource} that will retrieve entries
058 * referenced by a provided set of DNs.  The connection will remain open after
059 * all entries have been read.
060 * <BR><BR>
061 * It is not necessary to close this entry source when it is no longer needed,
062 * although there is no cost or penalty in doing so.  Any exceptions thrown by
063 * the {@link #nextEntry()} method will have the {@code mayContinueReading}
064 * value set to {@code true}.
065 * <H2>Example</H2>
066 * The following example demonstrates the process for retrieving a static group
067 * entry and using a {@code DNEntrySource} to iterate across the members of that
068 * group:
069 * <PRE>
070 * Entry groupEntry =
071 *      connection.getEntry("cn=My Group,ou=Groups,dc=example,dc=com");
072 * String[] memberValues = groupEntry.getAttributeValues("member");
073 * int entriesReturned = 0;
074 * int exceptionsCaught = 0;
075 *
076 * if (memberValues != null)
077 * {
078 *   DNEntrySource entrySource =
079 *        new DNEntrySource(connection, memberValues, "cn");
080 *   try
081 *   {
082 *     while (true)
083 *     {
084 *       Entry memberEntry;
085 *       try
086 *       {
087 *         memberEntry = entrySource.nextEntry();
088 *       }
089 *       catch (EntrySourceException ese)
090 *       {
091 *         // A problem was encountered while attempting to obtain an entry.
092 *         // We may be able to continue reading entries (e.g., if the problem
093 *         // was that the group referenced an entry that doesn't exist), or
094 *         // we may not (e.g., if the problem was a significant search error
095 *         // or problem with the connection).
096 *         exceptionsCaught++;
097 *         if (ese.mayContinueReading())
098 *         {
099 *           continue;
100 *         }
101 *         else
102 *         {
103 *           break;
104 *         }
105 *       }
106 *
107 *       if (memberEntry == null)
108 *       {
109 *         // We've retrieved all of the entries for the given set of DNs.
110 *         break;
111 *       }
112 *       else
113 *       {
114 *         entriesReturned++;
115 *       }
116 *     }
117 *   }
118 *   finally
119 *   {
120 *     entrySource.close();
121 *   }
122 * }
123 * </PRE>
124 */
125@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
126public final class DNEntrySource
127       extends EntrySource
128{
129  // The iterator to use to access the DNs.  It will either be across DN or
130  // String objects.
131  @NotNull private final Iterator<?> dnIterator;
132
133  // The connection to use to communicate with the directory server.
134  @NotNull private final LDAPInterface connection;
135
136  // The set of attributes to include in entries that are returned.
137  @NotNull private final String[] attributes;
138
139
140
141  /**
142   * Creates a new DN entry source with the provided information.
143   *
144   * @param  connection  The connection to the directory server from which the
145   *                     entries will be read.  It must not be {@code null}.
146   * @param  dns         The set of DNs to be read.  It must not be
147   *                     {@code null}.
148   * @param  attributes  The set of attributes to include in entries that are
149   *                     returned.  If this is empty or {@code null}, then all
150   *                     user attributes will be requested.
151   */
152  public DNEntrySource(@NotNull final LDAPInterface connection,
153                       @NotNull final DN[] dns,
154                       @Nullable final String... attributes)
155  {
156    Validator.ensureNotNull(connection, dns);
157
158    this.connection = connection;
159    dnIterator = Arrays.asList(dns).iterator();
160
161    if (attributes == null)
162    {
163      this.attributes = StaticUtils.NO_STRINGS;
164    }
165    else
166    {
167      this.attributes = attributes;
168    }
169  }
170
171
172
173  /**
174   * Creates a new DN entry source with the provided information.
175   *
176   * @param  connection  The connection to the directory server from which the
177   *                     entries will be read.  It must not be {@code null}.
178   * @param  dns         The set of DNs to be read.  It must not be
179   *                     {@code null}.
180   * @param  attributes  The set of attributes to include in entries that are
181   *                     returned.  If this is empty or {@code null}, then all
182   *                     user attributes will be requested.
183   */
184  public DNEntrySource(@NotNull final LDAPInterface connection,
185                       @NotNull final String[] dns,
186                       @Nullable final String... attributes)
187  {
188    this(connection, Arrays.asList(dns), attributes);
189  }
190
191
192
193  /**
194   * Creates a new DN entry source with the provided information.
195   *
196   * @param  connection  The connection to the directory server from which the
197   *                     entries will be read.  It must not be {@code null}.
198   * @param  dns         The set of DNs to be read.  It must not be
199   *                     {@code null}.
200   * @param  attributes  The set of attributes to include in entries that are
201   *                     returned.  If this is empty or {@code null}, then all
202   *                     user attributes will be requested.
203   */
204  public DNEntrySource(@NotNull final LDAPInterface connection,
205                       @NotNull final Collection<String> dns,
206                       @Nullable final String... attributes)
207  {
208    Validator.ensureNotNull(connection, dns);
209
210    this.connection = connection;
211    dnIterator = dns.iterator();
212
213    if (attributes == null)
214    {
215      this.attributes = StaticUtils.NO_STRINGS;
216    }
217    else
218    {
219      this.attributes = attributes;
220    }
221  }
222
223
224
225  /**
226   * {@inheritDoc}
227   */
228  @Override()
229  @Nullable()
230  public Entry nextEntry()
231         throws EntrySourceException
232  {
233    if (! dnIterator.hasNext())
234    {
235      return null;
236    }
237
238    final String dn = String.valueOf(dnIterator.next());
239    try
240    {
241      final Entry e = connection.getEntry(dn, attributes);
242      if (e == null)
243      {
244        throw new EntrySourceException(true,
245             ERR_DN_ENTRY_SOURCE_NO_SUCH_ENTRY.get(dn),
246             new LDAPException(ResultCode.NO_RESULTS_RETURNED));
247      }
248      else
249      {
250        return e;
251      }
252    }
253    catch (final LDAPException le)
254    {
255      Debug.debugException(le);
256      throw new EntrySourceException(true,
257           ERR_DN_ENTRY_SOURCE_ERR_RETRIEVING_ENTRY.get(dn,
258                StaticUtils.getExceptionMessage(le)),
259           le);
260    }
261  }
262
263
264
265  /**
266   * {@inheritDoc}
267   */
268  @Override()
269  public void close()
270  {
271    // No implementation is required.
272  }
273}