001/*
002 * Copyright 2009-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2009-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) 2009-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.persist;
037
038
039
040import java.io.Serializable;
041import java.util.ArrayList;
042import java.util.Arrays;
043import java.util.Collections;
044import java.util.LinkedHashSet;
045import java.util.LinkedList;
046import java.util.List;
047import java.util.Map;
048import java.util.Set;
049import java.util.concurrent.ConcurrentHashMap;
050
051import com.unboundid.ldap.sdk.AddRequest;
052import com.unboundid.ldap.sdk.Attribute;
053import com.unboundid.ldap.sdk.BindResult;
054import com.unboundid.ldap.sdk.Control;
055import com.unboundid.ldap.sdk.DeleteRequest;
056import com.unboundid.ldap.sdk.DereferencePolicy;
057import com.unboundid.ldap.sdk.Entry;
058import com.unboundid.ldap.sdk.Filter;
059import com.unboundid.ldap.sdk.LDAPConnection;
060import com.unboundid.ldap.sdk.LDAPEntrySource;
061import com.unboundid.ldap.sdk.LDAPException;
062import com.unboundid.ldap.sdk.LDAPInterface;
063import com.unboundid.ldap.sdk.LDAPResult;
064import com.unboundid.ldap.sdk.Modification;
065import com.unboundid.ldap.sdk.ModificationType;
066import com.unboundid.ldap.sdk.ModifyRequest;
067import com.unboundid.ldap.sdk.ResultCode;
068import com.unboundid.ldap.sdk.SearchRequest;
069import com.unboundid.ldap.sdk.SearchResult;
070import com.unboundid.ldap.sdk.SearchScope;
071import com.unboundid.ldap.sdk.SimpleBindRequest;
072import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition;
073import com.unboundid.ldap.sdk.schema.ObjectClassDefinition;
074import com.unboundid.ldap.sdk.schema.Schema;
075import com.unboundid.util.Debug;
076import com.unboundid.util.NotMutable;
077import com.unboundid.util.NotNull;
078import com.unboundid.util.Nullable;
079import com.unboundid.util.StaticUtils;
080import com.unboundid.util.ThreadSafety;
081import com.unboundid.util.ThreadSafetyLevel;
082import com.unboundid.util.Validator;
083
084import static com.unboundid.ldap.sdk.persist.PersistMessages.*;
085
086
087
088/**
089 * This class provides an interface that can be used to store and update
090 * representations of Java objects in an LDAP directory server, and to find and
091 * retrieve Java objects from the directory server.  The objects to store,
092 * update, and retrieve must be marked with the {@link LDAPObject} annotation.
093 * Fields and methods within the class should be marked with the
094 * {@link LDAPField}, {@link LDAPGetter}, or {@link LDAPSetter}
095 * annotations as appropriate to indicate how to convert between the LDAP and
096 * the Java representations of the content.
097 *
098 * @param  <T>  The type of object handled by this class.
099 */
100@NotMutable()
101@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
102public final class LDAPPersister<T>
103       implements Serializable
104{
105  /**
106   * The serial version UID for this serializable class.
107   */
108  private static final long serialVersionUID = -4001743482496453961L;
109
110
111
112  /**
113   * An empty array of controls that will be used if none are specified.
114   */
115  @NotNull private static final Control[] NO_CONTROLS = new Control[0];
116
117
118
119  /**
120   * The map of instances created so far.
121   */
122  @NotNull private static final ConcurrentHashMap<Class<?>,LDAPPersister<?>>
123       INSTANCES = new ConcurrentHashMap<>(StaticUtils.computeMapCapacity(10));
124
125
126
127  // The LDAP object handler that will be used for this class.
128  @NotNull private final LDAPObjectHandler<T> handler;
129
130
131
132  /**
133   * Creates a new instance of this LDAP persister that will be used to interact
134   * with objects of the specified type.
135   *
136   * @param  type  The type of object managed by this LDAP persister.  It must
137   *               not be {@code null}, and it must be marked with the
138   *               {@link LDAPObject} annotation.
139   *
140   * @throws  LDAPPersistException  If the provided class is not suitable for
141   *                                persisting in an LDAP directory server.
142   */
143  private LDAPPersister(@NotNull final Class<T> type)
144          throws LDAPPersistException
145  {
146    handler = new LDAPObjectHandler<>(type);
147  }
148
149
150
151  /**
152   * Retrieves an {@code LDAPPersister} instance for use with objects of the
153   * specified type.
154   *
155   * @param  <T>   The generic type for the {@code LDAPPersister} instance.
156   * @param  type  The type of object for which to retrieve the LDAP persister.
157   *               It must not be {@code null}, and it must be marked with the
158   *               {@link LDAPObject} annotation.
159   *
160   * @return  The {@code LDAPPersister} instance for use with objects of the
161   *          specified type.
162   *
163   * @throws  LDAPPersistException  If the provided class is not suitable for
164   *                                persisting in an LDAP directory server.
165   */
166  @SuppressWarnings("unchecked")
167  @NotNull()
168  public static <T> LDAPPersister<T> getInstance(@NotNull final Class<T> type)
169         throws LDAPPersistException
170  {
171    Validator.ensureNotNull(type);
172
173    LDAPPersister<T> p = (LDAPPersister<T>) INSTANCES.get(type);
174    if (p == null)
175    {
176      p = new LDAPPersister<>(type);
177      INSTANCES.put(type, p);
178    }
179
180    return p;
181  }
182
183
184
185  /**
186   * Retrieves the {@link LDAPObject} annotation of the class used for objects
187   * of the associated type.
188   *
189   * @return  The {@code LDAPObject} annotation of the class used for objects of
190   *          the associated type.
191   */
192  @NotNull()
193  public LDAPObject getLDAPObjectAnnotation()
194  {
195    return handler.getLDAPObjectAnnotation();
196  }
197
198
199
200  /**
201   * Retrieves the {@link LDAPObjectHandler} instance associated with this
202   * LDAP persister class.  It provides easy access to information about the
203   * {@link LDAPObject} annotation and the fields, getters, and setters used
204   * by the object.
205   *
206   * @return  The {@code LDAPObjectHandler} instance associated with this LDAP
207   *          persister class.
208   */
209  @NotNull()
210  public LDAPObjectHandler<T> getObjectHandler()
211  {
212    return handler;
213  }
214
215
216
217  /**
218   * Constructs a list of LDAP attribute type definitions which may be added to
219   * the directory server schema to allow it to hold objects of this type.  Note
220   * that the object identifiers used for the constructed attribute type
221   * definitions are not required to be valid or unique.
222   *
223   * @return  A list of attribute type definitions that may be used to represent
224   *          objects of the associated type in an LDAP directory.
225   *
226   * @throws  LDAPPersistException  If a problem occurs while attempting to
227   *                                generate the list of attribute type
228   *                                definitions.
229   */
230  @NotNull()
231  public List<AttributeTypeDefinition> constructAttributeTypes()
232         throws LDAPPersistException
233  {
234    return constructAttributeTypes(DefaultOIDAllocator.getInstance());
235  }
236
237
238
239  /**
240   * Constructs a list of LDAP attribute type definitions which may be added to
241   * the directory server schema to allow it to hold objects of this type.  Note
242   * that the object identifiers used for the constructed attribute type
243   * definitions are not required to be valid or unique.
244   *
245   * @param  a  The OID allocator to use to generate the object identifiers for
246   *            the constructed attribute types.  It must not be {@code null}.
247   *
248   * @return  A list of attribute type definitions that may be used to represent
249   *          objects of the associated type in an LDAP directory.
250   *
251   * @throws  LDAPPersistException  If a problem occurs while attempting to
252   *                                generate the list of attribute type
253   *                                definitions.
254   */
255  @NotNull()
256  public List<AttributeTypeDefinition> constructAttributeTypes(
257                                            @NotNull final OIDAllocator a)
258         throws LDAPPersistException
259  {
260    final LinkedList<AttributeTypeDefinition> attrList = new LinkedList<>();
261
262    for (final FieldInfo i : handler.getFields().values())
263    {
264      attrList.add(i.constructAttributeType(a));
265    }
266
267    for (final GetterInfo i : handler.getGetters().values())
268    {
269      attrList.add(i.constructAttributeType(a));
270    }
271
272    return Collections.unmodifiableList(attrList);
273  }
274
275
276
277  /**
278   * Constructs a list of LDAP object class definitions which may be added to
279   * the directory server schema to allow it to hold objects of this type.  Note
280   * that the object identifiers used for the constructed object class
281   * definitions are not required to be valid or unique.
282   *
283   * @return  A list of object class definitions that may be used to represent
284   *          objects of the associated type in an LDAP directory.
285   *
286   * @throws  LDAPPersistException  If a problem occurs while attempting to
287   *                                generate the list of object class
288   *                                definitions.
289   */
290  @NotNull()
291  public List<ObjectClassDefinition> constructObjectClasses()
292         throws LDAPPersistException
293  {
294    return constructObjectClasses(DefaultOIDAllocator.getInstance());
295  }
296
297
298
299  /**
300   * Constructs a list of LDAP object class definitions which may be added to
301   * the directory server schema to allow it to hold objects of this type.  Note
302   * that the object identifiers used for the constructed object class
303   * definitions are not required to be valid or unique.
304   *
305   * @param  a  The OID allocator to use to generate the object identifiers for
306   *            the constructed object classes.  It must not be {@code null}.
307   *
308   * @return  A list of object class definitions that may be used to represent
309   *          objects of the associated type in an LDAP directory.
310   *
311   * @throws  LDAPPersistException  If a problem occurs while attempting to
312   *                                generate the list of object class
313   *                                definitions.
314   */
315  @NotNull()
316  public List<ObjectClassDefinition> constructObjectClasses(
317                                          @NotNull final OIDAllocator a)
318         throws LDAPPersistException
319  {
320    return handler.constructObjectClasses(a);
321  }
322
323
324
325  /**
326   * Attempts to update the schema for a directory server to ensure that it
327   * includes the attribute type and object class definitions used to store
328   * objects of the associated type.  It will do this by attempting to add
329   * values to the attributeTypes and objectClasses attributes to the server
330   * schema.  It will attempt to preserve existing schema elements.
331   *
332   * @param  i  The interface to use to communicate with the directory server.
333   *
334   * @return  {@code true} if the schema was updated, or {@code false} if all of
335   *          the necessary schema elements were already present.
336   *
337   * @throws  LDAPException  If an error occurs while attempting to update the
338   *                         server schema.
339   */
340  public boolean updateSchema(@NotNull final LDAPInterface i)
341         throws LDAPException
342  {
343    return updateSchema(i, DefaultOIDAllocator.getInstance());
344  }
345
346
347
348  /**
349   * Attempts to update the schema for a directory server to ensure that it
350   * includes the attribute type and object class definitions used to store
351   * objects of the associated type.  It will do this by attempting to add
352   * values to the attributeTypes and objectClasses attributes to the server
353   * schema.  It will preserve existing attribute types, and will only modify
354   * existing object classes if the existing definition does not allow all of
355   * the attributes needed to store the associated object.
356   * <BR><BR>
357   * Note that because there is no standard process for altering a directory
358   * server's schema over LDAP, the approach used by this method may not work
359   * for all types of directory servers.  In addition, some directory servers
360   * may place restrictions on schema updates, particularly around the
361   * modification of existing schema elements.  This method is provided as a
362   * convenience, but it may not work as expected in all environments or under
363   * all conditions.
364   *
365   * @param  i  The interface to use to communicate with the directory server.
366   * @param  a  The OID allocator to use ot generate the object identifiers to
367   *            use for the constructed attribute types and object classes.  It
368   *            must not be {@code null}.
369   *
370   * @return  {@code true} if the schema was updated, or {@code false} if all of
371   *          the necessary schema elements were already present.
372   *
373   * @throws  LDAPException  If an error occurs while attempting to update the
374   *                         server schema.
375   */
376  public boolean updateSchema(@NotNull final LDAPInterface i,
377                              @NotNull final OIDAllocator a)
378         throws LDAPException
379  {
380    final Schema s = i.getSchema();
381
382    final List<AttributeTypeDefinition> generatedTypes =
383         constructAttributeTypes(a);
384    final List<ObjectClassDefinition> generatedClasses =
385         constructObjectClasses(a);
386
387    final LinkedList<String> newAttrList = new LinkedList<>();
388    for (final AttributeTypeDefinition d : generatedTypes)
389    {
390      if (s.getAttributeType(d.getNameOrOID()) == null)
391      {
392        newAttrList.add(d.toString());
393      }
394    }
395
396    final LinkedList<String> newOCList = new LinkedList<>();
397    for (final ObjectClassDefinition d : generatedClasses)
398    {
399      final ObjectClassDefinition existing = s.getObjectClass(d.getNameOrOID());
400      if (existing == null)
401      {
402        newOCList.add(d.toString());
403      }
404      else
405      {
406        final Set<AttributeTypeDefinition> existingRequired =
407             existing.getRequiredAttributes(s, true);
408        final Set<AttributeTypeDefinition> existingOptional =
409             existing.getOptionalAttributes(s, true);
410
411        final LinkedHashSet<String> newOptionalNames = new LinkedHashSet<>(0);
412        addMissingAttrs(d.getRequiredAttributes(), existingRequired,
413             existingOptional, newOptionalNames);
414        addMissingAttrs(d.getOptionalAttributes(), existingRequired,
415             existingOptional, newOptionalNames);
416
417        if (! newOptionalNames.isEmpty())
418        {
419          final LinkedHashSet<String> newOptionalSet =
420               new LinkedHashSet<>(StaticUtils.computeMapCapacity(20));
421          newOptionalSet.addAll(
422               Arrays.asList(existing.getOptionalAttributes()));
423          newOptionalSet.addAll(newOptionalNames);
424
425          final String[] newOptional = new String[newOptionalSet.size()];
426          newOptionalSet.toArray(newOptional);
427
428          final ObjectClassDefinition newOC = new ObjectClassDefinition(
429               existing.getOID(), existing.getNames(),
430               existing.getDescription(), existing.isObsolete(),
431               existing.getSuperiorClasses(), existing.getObjectClassType(),
432               existing.getRequiredAttributes(), newOptional,
433               existing.getExtensions());
434          newOCList.add(newOC.toString());
435        }
436      }
437    }
438
439    final LinkedList<Modification> mods = new LinkedList<>();
440    if (! newAttrList.isEmpty())
441    {
442      final String[] newAttrValues = new String[newAttrList.size()];
443      mods.add(new Modification(ModificationType.ADD,
444           Schema.ATTR_ATTRIBUTE_TYPE, newAttrList.toArray(newAttrValues)));
445    }
446
447    if (! newOCList.isEmpty())
448    {
449      final String[] newOCValues = new String[newOCList.size()];
450      mods.add(new Modification(ModificationType.ADD,
451           Schema.ATTR_OBJECT_CLASS, newOCList.toArray(newOCValues)));
452    }
453
454    if (mods.isEmpty())
455    {
456      return false;
457    }
458    else
459    {
460      i.modify(s.getSchemaEntry().getDN(), mods);
461      return true;
462    }
463  }
464
465
466
467  /**
468   * Adds any missing attributes to the provided set.
469   *
470   * @param  names     The names of the attributes which may potentially be
471   *                   added.
472   * @param  required  The existing required definitions.
473   * @param  optional  The existing optional definitions.
474   * @param  missing   The set to which any missing names should be added.
475   */
476  private static void addMissingAttrs(@NotNull final String[] names,
477                           @NotNull final Set<AttributeTypeDefinition> required,
478                           @NotNull final Set<AttributeTypeDefinition> optional,
479                           @NotNull final Set<String> missing)
480  {
481    for (final String name : names)
482    {
483      boolean found = false;
484      for (final AttributeTypeDefinition eA : required)
485      {
486        if (eA.hasNameOrOID(name))
487        {
488          found = true;
489          break;
490        }
491      }
492
493      if (! found)
494      {
495        for (final AttributeTypeDefinition eA : optional)
496        {
497          if (eA.hasNameOrOID(name))
498          {
499            found = true;
500            break;
501          }
502        }
503
504        if (! found)
505        {
506          missing.add(name);
507        }
508      }
509    }
510  }
511
512
513
514  /**
515   * Encodes the provided object to an entry that is suitable for storing it in
516   * an LDAP directory server.
517   *
518   * @param  o         The object to be encoded.  It must not be {@code null}.
519   * @param  parentDN  The parent DN to use for the resulting entry.  If the
520   *                   provided object was previously read from a directory
521   *                   server and includes a field marked with the
522   *                   {@link LDAPDNField} or {@link LDAPEntryField} annotation,
523   *                   then that field may be used to retrieve the actual DN of
524   *                   the associated entry.  If the actual DN of the associated
525   *                   entry is not available, then a DN will be constructed
526   *                   from the RDN fields and/or getter methods declared in the
527   *                   class.  If the provided parent DN is {@code null}, then
528   *                   the default parent DN defined in the {@link LDAPObject}
529   *                   annotation will be used.
530   *
531   * @return  An entry containing the encoded representation of the provided
532   *          object.  It may be altered by the caller if necessary.
533   *
534   * @throws  LDAPPersistException  If a problem occurs while attempting to
535   *                                encode the provided object.
536   */
537  @NotNull()
538  public Entry encode(@NotNull final T o, @Nullable final String parentDN)
539         throws LDAPPersistException
540  {
541    Validator.ensureNotNull(o);
542    return handler.encode(o, parentDN);
543  }
544
545
546
547  /**
548   * Creates an object and initializes it with the contents of the provided
549   * entry.
550   *
551   * @param  entry  The entry to use to create the object.  It must not be
552   *                {@code null}.
553   *
554   * @return  The object created from the provided entry.
555   *
556   * @throws  LDAPPersistException  If an error occurs while attempting to
557   *                                create or initialize the object from the
558   *                                provided entry.
559   */
560  @NotNull()
561  public T decode(@NotNull final Entry entry)
562         throws LDAPPersistException
563  {
564    Validator.ensureNotNull(entry);
565    return handler.decode(entry);
566  }
567
568
569
570  /**
571   * Initializes the provided object from the information contained in the
572   * given entry.
573   *
574   * @param  o      The object to initialize with the contents of the provided
575   *                entry.  It must not be {@code null}.
576   * @param  entry  The entry to use to create the object.  It must not be
577   *                {@code null}.
578   *
579   * @throws  LDAPPersistException  If an error occurs while attempting to
580   *                                initialize the object from the provided
581   *                                entry.  If an exception is thrown, then the
582   *                                provided object may or may not have been
583   *                                altered.
584   */
585  public void decode(@NotNull final T o, @NotNull final Entry entry)
586         throws LDAPPersistException
587  {
588    Validator.ensureNotNull(o, entry);
589    handler.decode(o, entry);
590  }
591
592
593
594  /**
595   * Adds the provided object to the directory server using the provided
596   * connection.
597   *
598   * @param  o         The object to be added.  It must not be {@code null}.
599   * @param  i         The interface to use to communicate with the directory
600   *                   server.  It must not be {@code null}.
601   * @param  parentDN  The parent DN to use for the resulting entry.  If the
602   *                   provided object was previously read from a directory
603   *                   server and includes a field marked with the
604   *                   {@link LDAPDNField} or {@link LDAPEntryField} annotation,
605   *                   then that field may be used to retrieve the actual DN of
606   *                   the associated entry.  If the actual DN of the associated
607   *                   entry is not available, then a DN will be constructed
608   *                   from the RDN fields and/or getter methods declared in the
609   *                   class.  If the provided parent DN is {@code null}, then
610   *                   the default parent DN defined in the {@link LDAPObject}
611   *                   annotation will be used.
612   * @param  controls  An optional set of controls to include in the add
613   *                   request.
614   *
615   * @return  The result of processing the add operation.
616   *
617   * @throws  LDAPPersistException  If a problem occurs while encoding or adding
618   *                                the entry.
619   */
620  @NotNull()
621  public LDAPResult add(@NotNull final T o, @NotNull final LDAPInterface i,
622                        @Nullable final String parentDN,
623                        @Nullable final Control... controls)
624         throws LDAPPersistException
625  {
626    Validator.ensureNotNull(o, i);
627    final Entry e = encode(o, parentDN);
628
629    try
630    {
631      final AddRequest addRequest = new AddRequest(e);
632      if (controls != null)
633      {
634        addRequest.setControls(controls);
635      }
636
637      return i.add(addRequest);
638    }
639    catch (final LDAPException le)
640    {
641      Debug.debugException(le);
642      throw new LDAPPersistException(le);
643    }
644  }
645
646
647
648  /**
649   * Deletes the provided object from the directory.
650   *
651   * @param  o         The object to be deleted.  It must not be {@code null},
652   *                   and it must have been retrieved from the directory and
653   *                   have a field with either the {@link LDAPDNField} or
654   *                   {@link LDAPEntryField} annotations.
655   * @param  i         The interface to use to communicate with the directory
656   *                   server.  It must not be {@code null}.
657   * @param  controls  An optional set of controls to include in the add
658   *                   request.
659   *
660   * @return  The result of processing the delete operation.
661   *
662   * @throws  LDAPPersistException  If a problem occurs while attempting to
663   *                                delete the entry.
664   */
665  @NotNull()
666  public LDAPResult delete(@NotNull final T o, @NotNull final LDAPInterface i,
667                           @Nullable final Control... controls)
668         throws LDAPPersistException
669  {
670    Validator.ensureNotNull(o, i);
671    final String dn = handler.getEntryDN(o);
672    if (dn == null)
673    {
674      throw new LDAPPersistException(ERR_PERSISTER_DELETE_NO_DN.get());
675    }
676
677    try
678    {
679      final DeleteRequest deleteRequest = new DeleteRequest(dn);
680      if (controls != null)
681      {
682        deleteRequest.setControls(controls);
683      }
684
685      return i.delete(deleteRequest);
686    }
687    catch (final LDAPException le)
688    {
689      Debug.debugException(le);
690      throw new LDAPPersistException(le);
691    }
692  }
693
694
695
696  /**
697   * Retrieves a list of modifications that can be used to update the stored
698   * representation of the provided object in the directory.  If the provided
699   * object was retrieved from the directory using the persistence framework and
700   * includes a field with the {@link LDAPEntryField} annotation, then that
701   * entry will be used to make the returned set of modifications as efficient
702   * as possible.  Otherwise, the resulting modifications will include attempts
703   * to replace every attribute which are associated with fields or getters
704   * that should be used in modify operations.
705   *
706   * @param  o                 The object for which to generate the list of
707   *                           modifications.  It must not be {@code null}.
708   * @param  deleteNullValues  Indicates whether to include modifications that
709   *                           may completely remove an attribute from the
710   *                           entry if the corresponding field or getter method
711   *                           has a value of {@code null}.
712   * @param  attributes        The set of LDAP attributes for which to include
713   *                           modifications.  If this is empty or {@code null},
714   *                           then all attributes marked for inclusion in the
715   *                           modification will be examined.
716   *
717   * @return  An unmodifiable list of modifications that can be used to update
718   *          the stored representation of the provided object in the directory.
719   *          It may be empty if there are no differences identified in the
720   *          attributes to be evaluated.
721   *
722   * @throws  LDAPPersistException  If a problem occurs while computing the set
723   *                                of modifications.
724   */
725  @NotNull()
726  public List<Modification> getModifications(@NotNull final T o,
727                                 final boolean deleteNullValues,
728                                 @Nullable final String... attributes)
729         throws LDAPPersistException
730  {
731    return getModifications(o, deleteNullValues, false, attributes);
732  }
733
734
735
736  /**
737   * Retrieves a list of modifications that can be used to update the stored
738   * representation of the provided object in the directory.  If the provided
739   * object was retrieved from the directory using the persistence framework and
740   * includes a field with the {@link LDAPEntryField} annotation, then that
741   * entry will be used to make the returned set of modifications as efficient
742   * as possible.  Otherwise, the resulting modifications will include attempts
743   * to replace every attribute which are associated with fields or getters
744   * that should be used in modify operations.
745   *
746   * @param  o                 The object for which to generate the list of
747   *                           modifications.  It must not be {@code null}.
748   * @param  deleteNullValues  Indicates whether to include modifications that
749   *                           may completely remove an attribute from the
750   *                           entry if the corresponding field or getter method
751   *                           has a value of {@code null}.
752   * @param  byteForByte       Indicates whether to use a byte-for-byte
753   *                           comparison to identify which attribute values
754   *                           have changed.  Using byte-for-byte comparison
755   *                           requires additional processing over using each
756   *                           attribute's associated matching rule, but it can
757   *                           detect changes that would otherwise be considered
758   *                           logically equivalent (e.g., changing the
759   *                           capitalization of a value that uses a
760   *                           case-insensitive matching rule).
761   * @param  attributes        The set of LDAP attributes for which to include
762   *                           modifications.  If this is empty or {@code null},
763   *                           then all attributes marked for inclusion in the
764   *                           modification will be examined.
765   *
766   * @return  An unmodifiable list of modifications that can be used to update
767   *          the stored representation of the provided object in the directory.
768   *          It may be empty if there are no differences identified in the
769   *          attributes to be evaluated.
770   *
771   * @throws  LDAPPersistException  If a problem occurs while computing the set
772   *                                of modifications.
773   */
774  @NotNull()
775  public List<Modification> getModifications(@NotNull final T o,
776                                 final boolean deleteNullValues,
777                                 final boolean byteForByte,
778                                 @Nullable final String... attributes)
779         throws LDAPPersistException
780  {
781    Validator.ensureNotNull(o);
782    return handler.getModifications(o, deleteNullValues, byteForByte,
783         attributes);
784  }
785
786
787
788  /**
789   * Updates the stored representation of the provided object in the directory.
790   * If the provided object was retrieved from the directory using the
791   * persistence framework and includes a field with the {@link LDAPEntryField}
792   * annotation, then that entry will be used to make the returned set of
793   * modifications as efficient as possible.  Otherwise, the resulting
794   * modifications will include attempts to replace every attribute which are
795   * associated with fields or getters that should be used in modify operations.
796   * If there are no modifications, then no modification will be attempted, and
797   * this method will return {@code null} rather than an {@code LDAPResult}.
798   *
799   * @param  o                 The object for which to generate the list of
800   *                           modifications.  It must not be {@code null}.
801   * @param  i                 The interface to use to communicate with the
802   *                           directory server.  It must not be {@code null}.
803   * @param  dn                The DN to use for the entry.  It must not be
804   *                           {@code null} if the object was not retrieved from
805   *                           the directory using the persistence framework or
806   *                           does not have a field marked with the
807   *                           {@link LDAPDNField} or {@link LDAPEntryField}
808   *                           annotation.
809   * @param  deleteNullValues  Indicates whether to include modifications that
810   *                           may completely remove an attribute from the
811   *                           entry if the corresponding field or getter method
812   *                           has a value of {@code null}.
813   * @param  attributes        The set of LDAP attributes for which to include
814   *                           modifications.  If this is empty or {@code null},
815   *                           then all attributes marked for inclusion in the
816   *                           modification will be examined.
817   *
818   * @return  The result of processing the modify operation, or {@code null} if
819   *          there were no changes to apply (and therefore no modification was
820   *          performed).
821   *
822   * @throws  LDAPPersistException  If a problem occurs while computing the set
823   *                                of modifications.
824   */
825  @NotNull()
826  public LDAPResult modify(@NotNull final T o, @NotNull final LDAPInterface i,
827                           @Nullable final String dn,
828                           final boolean deleteNullValues,
829                           @Nullable final String... attributes)
830         throws LDAPPersistException
831  {
832    return modify(o, i, dn, deleteNullValues, attributes, NO_CONTROLS);
833  }
834
835
836
837  /**
838   * Updates the stored representation of the provided object in the directory.
839   * If the provided object was retrieved from the directory using the
840   * persistence framework and includes a field with the {@link LDAPEntryField}
841   * annotation, then that entry will be used to make the returned set of
842   * modifications as efficient as possible.  Otherwise, the resulting
843   * modifications will include attempts to replace every attribute which are
844   * associated with fields or getters that should be used in modify operations.
845   * If there are no modifications, then no modification will be attempted, and
846   * this method will return {@code null} rather than an {@code LDAPResult}.
847   *
848   * @param  o                 The object for which to generate the list of
849   *                           modifications.  It must not be {@code null}.
850   * @param  i                 The interface to use to communicate with the
851   *                           directory server.  It must not be {@code null}.
852   * @param  dn                The DN to use for the entry.  It must not be
853   *                           {@code null} if the object was not retrieved from
854   *                           the directory using the persistence framework or
855   *                           does not have a field marked with the
856   *                           {@link LDAPDNField} or {@link LDAPEntryField}
857   *                           annotation.
858   * @param  deleteNullValues  Indicates whether to include modifications that
859   *                           may completely remove an attribute from the
860   *                           entry if the corresponding field or getter method
861   *                           has a value of {@code null}.
862   * @param  attributes        The set of LDAP attributes for which to include
863   *                           modifications.  If this is empty or {@code null},
864   *                           then all attributes marked for inclusion in the
865   *                           modification will be examined.
866   * @param  controls          The optional set of controls to include in the
867   *                           modify request.
868   *
869   * @return  The result of processing the modify operation, or {@code null} if
870   *          there were no changes to apply (and therefore no modification was
871   *          performed).
872   *
873   * @throws  LDAPPersistException  If a problem occurs while computing the set
874   *                                of modifications.
875   */
876  @NotNull()
877  public LDAPResult modify(@NotNull final T o, @NotNull final LDAPInterface i,
878                           @Nullable final String dn,
879                           final boolean deleteNullValues,
880                           @Nullable final String[] attributes,
881                           @Nullable final Control... controls)
882         throws LDAPPersistException
883  {
884    return modify(o, i, dn, deleteNullValues, false, attributes, controls);
885  }
886
887
888
889  /**
890   * Updates the stored representation of the provided object in the directory.
891   * If the provided object was retrieved from the directory using the
892   * persistence framework and includes a field with the {@link LDAPEntryField}
893   * annotation, then that entry will be used to make the returned set of
894   * modifications as efficient as possible.  Otherwise, the resulting
895   * modifications will include attempts to replace every attribute which are
896   * associated with fields or getters that should be used in modify operations.
897   * If there are no modifications, then no modification will be attempted, and
898   * this method will return {@code null} rather than an {@code LDAPResult}.
899   *
900   * @param  o                 The object for which to generate the list of
901   *                           modifications.  It must not be {@code null}.
902   * @param  i                 The interface to use to communicate with the
903   *                           directory server.  It must not be {@code null}.
904   * @param  dn                The DN to use for the entry.  It must not be
905   *                           {@code null} if the object was not retrieved from
906   *                           the directory using the persistence framework or
907   *                           does not have a field marked with the
908   *                           {@link LDAPDNField} or {@link LDAPEntryField}
909   *                           annotation.
910   * @param  deleteNullValues  Indicates whether to include modifications that
911   *                           may completely remove an attribute from the
912   *                           entry if the corresponding field or getter method
913   *                           has a value of {@code null}.
914   * @param  byteForByte       Indicates whether to use a byte-for-byte
915   *                           comparison to identify which attribute values
916   *                           have changed.  Using byte-for-byte comparison
917   *                           requires additional processing over using each
918   *                           attribute's associated matching rule, but it can
919   *                           detect changes that would otherwise be considered
920   *                           logically equivalent (e.g., changing the
921   *                           capitalization of a value that uses a
922   *                           case-insensitive matching rule).
923   * @param  attributes        The set of LDAP attributes for which to include
924   *                           modifications.  If this is empty or {@code null},
925   *                           then all attributes marked for inclusion in the
926   *                           modification will be examined.
927   * @param  controls          The optional set of controls to include in the
928   *                           modify request.
929   *
930   * @return  The result of processing the modify operation, or {@code null} if
931   *          there were no changes to apply (and therefore no modification was
932   *          performed).
933   *
934   * @throws  LDAPPersistException  If a problem occurs while computing the set
935   *                                of modifications.
936   */
937  @Nullable()
938  public LDAPResult modify(@NotNull final T o, @NotNull final LDAPInterface i,
939                           @Nullable final String dn,
940                           final boolean deleteNullValues,
941                           final boolean byteForByte,
942                           @Nullable final String[] attributes,
943                           @Nullable final Control... controls)
944         throws LDAPPersistException
945  {
946    Validator.ensureNotNull(o, i);
947    final List<Modification> mods =
948         handler.getModifications(o, deleteNullValues, byteForByte, attributes);
949    if (mods.isEmpty())
950    {
951      return null;
952    }
953
954    final String targetDN;
955    if (dn == null)
956    {
957      targetDN = handler.getEntryDN(o);
958      if (targetDN == null)
959      {
960        throw new LDAPPersistException(ERR_PERSISTER_MODIFY_NO_DN.get());
961      }
962    }
963    else
964    {
965      targetDN = dn;
966    }
967
968    try
969    {
970      final ModifyRequest modifyRequest = new ModifyRequest(targetDN, mods);
971      if (controls != null)
972      {
973        modifyRequest.setControls(controls);
974      }
975
976      return i.modify(modifyRequest);
977    }
978    catch (final LDAPException le)
979    {
980      Debug.debugException(le);
981      throw new LDAPPersistException(le);
982    }
983  }
984
985
986
987  /**
988   * Attempts to perform a simple bind as the user specified by the given object
989   * on the provided connection.  The object should represent some kind of entry
990   * capable suitable for use as the target of a simple bind operation.
991   * <BR><BR>
992   * If the provided object was retrieved from the directory and has either an
993   * {@link LDAPDNField} or {@link LDAPEntryField}, then that field will be used
994   * to obtain the DN.  Otherwise, a search will be performed to try to find the
995   * entry that corresponds to the provided object.
996   *
997   * @param  o         The object representing the user as whom to bind.  It
998   *                   must not be {@code null}.
999   * @param  baseDN    The base DN to use if it is necessary to search for the
1000   *                   entry.  It may be {@code null} if the
1001   *                   {@link LDAPObject#defaultParentDN} element in the
1002   *                   {@code LDAPObject} should be used as the base DN.
1003   * @param  password  The password to use for the bind.  It must not be
1004   *                   {@code null}.
1005   * @param  c         The connection to be authenticated.  It must not be
1006   *                   {@code null}.
1007   * @param  controls  An optional set of controls to include in the bind
1008   *                   request.  It may be empty or {@code null} if no controls
1009   *                   are needed.
1010   *
1011   * @return  The result of processing the bind operation.
1012   *
1013   * @throws  LDAPException  If a problem occurs while attempting to process the
1014   *                         search or bind operation.
1015   */
1016  @NotNull()
1017  public BindResult bind(@NotNull final T o, @Nullable final String baseDN,
1018                         @NotNull final String password,
1019                         @NotNull final LDAPConnection c,
1020                         @Nullable final Control... controls)
1021         throws LDAPException
1022  {
1023    Validator.ensureNotNull(o, password, c);
1024
1025    String dn = handler.getEntryDN(o);
1026    if (dn == null)
1027    {
1028      String base = baseDN;
1029      if (base == null)
1030      {
1031        base = handler.getDefaultParentDN().toString();
1032      }
1033
1034      final SearchRequest r = new SearchRequest(base, SearchScope.SUB,
1035           handler.createFilter(o), SearchRequest.NO_ATTRIBUTES);
1036      r.setSizeLimit(1);
1037
1038      final Entry e = c.searchForEntry(r);
1039      if (e == null)
1040      {
1041        throw new LDAPException(ResultCode.NO_RESULTS_RETURNED,
1042             ERR_PERSISTER_BIND_NO_ENTRY_FOUND.get());
1043      }
1044      else
1045      {
1046        dn = e.getDN();
1047      }
1048    }
1049
1050    return c.bind(new SimpleBindRequest(dn, password, controls));
1051  }
1052
1053
1054
1055  /**
1056   * Constructs the DN of the associated entry from the provided object and
1057   * parent DN and retrieves the contents of that entry as a new instance of
1058   * that object.
1059   *
1060   * @param  o         An object instance to use to construct the DN of the
1061   *                   entry to retrieve.  It must not be {@code null}, and all
1062   *                   fields and/or getter methods marked for inclusion in the
1063   *                   entry RDN must have non-{@code null} values.
1064   * @param  i         The interface to use to communicate with the directory
1065   *                   server. It must not be {@code null}.
1066   * @param  parentDN  The parent DN to use for the entry to retrieve.  If the
1067   *                   provided object was previously read from a directory
1068   *                   server and includes a field marked with the
1069   *                   {@link LDAPDNField} or {@link LDAPEntryField} annotation,
1070   *                   then that field may be used to retrieve the actual DN of
1071   *                   the associated entry.  If the actual DN of the target
1072   *                   entry is not available, then a DN will be constructed
1073   *                   from the RDN fields and/or getter methods declared in the
1074   *                   class and this parent DN.  If the provided parent DN is
1075   *                   {@code null}, then the default parent DN defined in the
1076   *                   {@link LDAPObject} annotation will be used.
1077   *
1078   * @return  The object read from the entry with the provided DN, or
1079   *          {@code null} if no entry exists with the constructed DN.
1080   *
1081   * @throws  LDAPPersistException  If a problem occurs while attempting to
1082   *                                construct the entry DN, retrieve the
1083   *                                corresponding entry or decode it as an
1084   *                                object.
1085   */
1086  @Nullable()
1087  public T get(@NotNull final T o, @NotNull final LDAPInterface i,
1088               @Nullable final String parentDN)
1089         throws LDAPPersistException
1090  {
1091    final String dn = handler.constructDN(o, parentDN);
1092
1093    final Entry entry;
1094    try
1095    {
1096      entry = i.getEntry(dn, handler.getAttributesToRequest());
1097      if (entry == null)
1098      {
1099        return null;
1100      }
1101    }
1102    catch (final LDAPException le)
1103    {
1104      Debug.debugException(le);
1105      throw new LDAPPersistException(le);
1106    }
1107
1108    return decode(entry);
1109  }
1110
1111
1112
1113  /**
1114   * Retrieves the object from the directory entry with the provided DN.
1115   *
1116   * @param  dn  The DN of the entry to retrieve and decode.  It must not be
1117   *             {@code null}.
1118   * @param  i   The interface to use to communicate with the directory server.
1119   *             It must not be {@code null}.
1120   *
1121   * @return  The object read from the entry with the provided DN, or
1122   *          {@code null} if no entry exists with the provided DN.
1123   *
1124   * @throws  LDAPPersistException  If a problem occurs while attempting to
1125   *                                retrieve the specified entry or decode it
1126   *                                as an object.
1127   */
1128  @Nullable()
1129  public T get(@NotNull final String dn, @NotNull final LDAPInterface i)
1130         throws LDAPPersistException
1131  {
1132    final Entry entry;
1133    try
1134    {
1135      entry = i.getEntry(dn, handler.getAttributesToRequest());
1136      if (entry == null)
1137      {
1138        return null;
1139      }
1140    }
1141    catch (final LDAPException le)
1142    {
1143      Debug.debugException(le);
1144      throw new LDAPPersistException(le);
1145    }
1146
1147    return decode(entry);
1148  }
1149
1150
1151
1152  /**
1153   * Initializes any fields in the provided object marked for lazy loading.
1154   *
1155   * @param  o       The object to be updated.  It must not be {@code null}.
1156   * @param  i       The interface to use to communicate with the directory
1157   *                 server.  It must not be {@code null}.
1158   * @param  fields  The set of fields that should be loaded.  Any fields
1159   *                 included in this list which aren't marked for lazy loading
1160   *                 will be ignored.  If this is empty or {@code null}, then
1161   *                 all lazily-loaded fields will be requested.
1162   *
1163   * @throws  LDAPPersistException  If a problem occurs while attempting to
1164   *                                retrieve or process the associated entry.
1165   *                                If an exception is thrown, then all content
1166   *                                from the provided object that is not lazily
1167   *                                loaded should remain valid, and some
1168   *                                lazily-loaded fields may have been
1169   *                                initialized.
1170   */
1171  public void lazilyLoad(@NotNull final T o, @NotNull final LDAPInterface i,
1172                         @Nullable final FieldInfo... fields)
1173         throws LDAPPersistException
1174  {
1175    Validator.ensureNotNull(o, i);
1176
1177    final String[] attrs;
1178    if ((fields == null) || (fields.length == 0))
1179    {
1180      attrs = handler.getLazilyLoadedAttributes();
1181    }
1182    else
1183    {
1184      final ArrayList<String> attrList = new ArrayList<>(fields.length);
1185      for (final FieldInfo f : fields)
1186      {
1187        if (f.lazilyLoad())
1188        {
1189          attrList.add(f.getAttributeName());
1190        }
1191      }
1192      attrs = new String[attrList.size()];
1193      attrList.toArray(attrs);
1194    }
1195
1196    if (attrs.length == 0)
1197    {
1198      return;
1199    }
1200
1201    final String dn = handler.getEntryDN(o);
1202    if (dn == null)
1203    {
1204      throw new LDAPPersistException(ERR_PERSISTER_LAZILY_LOAD_NO_DN.get());
1205    }
1206
1207    final Entry entry;
1208    try
1209    {
1210      entry = i.getEntry(handler.getEntryDN(o), attrs);
1211    }
1212    catch (final LDAPException le)
1213    {
1214      Debug.debugException(le);
1215      throw new LDAPPersistException(le);
1216    }
1217
1218    if (entry == null)
1219    {
1220      throw new LDAPPersistException(
1221           ERR_PERSISTER_LAZILY_LOAD_NO_ENTRY.get(dn));
1222    }
1223
1224    boolean successful = true;
1225    final ArrayList<String> failureReasons = new ArrayList<>(5);
1226    final Map<String,FieldInfo> fieldMap = handler.getFields();
1227    for (final Attribute a : entry.getAttributes())
1228    {
1229      final String lowerName = StaticUtils.toLowerCase(a.getName());
1230      final FieldInfo f = fieldMap.get(lowerName);
1231      if (f != null)
1232      {
1233        successful &= f.decode(o, entry, failureReasons);
1234      }
1235    }
1236
1237    if (! successful)
1238    {
1239      throw new LDAPPersistException(
1240           StaticUtils.concatenateStrings(failureReasons), o, null);
1241    }
1242  }
1243
1244
1245
1246  /**
1247   * Performs a search in the directory for objects matching the contents of the
1248   * provided object.  A search filter will be generated from the provided
1249   * object containing all non-{@code null} values from fields and getter
1250   * methods whose {@link LDAPField} or {@link LDAPGetter} annotation has
1251   * the {@code inFilter} element set to {@code true}.
1252   * <BR><BR>
1253   * The search performed will be a subtree search using a base DN equal to the
1254   * {@link LDAPObject#defaultParentDN} element in the {@code LDAPObject}
1255   * annotation.  It will not enforce a client-side time limit or size limit.
1256   * <BR><BR>
1257   * Note that this method requires an {@link LDAPConnection} argument rather
1258   * than using the more generic {@link LDAPInterface} type because the search
1259   * is invoked as an asynchronous operation, which is not supported by the
1260   * generic {@code LDAPInterface} interface.  It also means that the provided
1261   * connection must not be configured to operate in synchronous mode (via the
1262   * {@link com.unboundid.ldap.sdk.LDAPConnectionOptions#setUseSynchronousMode}
1263   * option).
1264   *
1265   * @param  o  The object to use to construct the search filter.  It must not
1266   *            be {@code null}.
1267   * @param  c  The connection to use to communicate with the directory server.
1268   *            It must not be {@code null}.
1269   *
1270   * @return  A results object that may be used to iterate through the objects
1271   *          returned from the search.
1272   *
1273   * @throws  LDAPPersistException  If an error occurs while preparing or
1274   *                                sending the search request.
1275   */
1276  @NotNull()
1277  public PersistedObjects<T> search(@NotNull final T o,
1278                                    @NotNull final LDAPConnection c)
1279         throws LDAPPersistException
1280  {
1281    return search(o, c, null, SearchScope.SUB, DereferencePolicy.NEVER, 0, 0,
1282         null, NO_CONTROLS);
1283  }
1284
1285
1286
1287  /**
1288   * Performs a search in the directory for objects matching the contents of the
1289   * provided object.  A search filter will be generated from the provided
1290   * object containing all non-{@code null} values from fields and getter
1291   * methods whose {@link LDAPField} or {@link LDAPGetter} annotation has
1292   * the {@code inFilter} element set to {@code true}.
1293   * <BR><BR>
1294   * Note that this method requires an {@link LDAPConnection} argument rather
1295   * than using the more generic {@link LDAPInterface} type because the search
1296   * is invoked as an asynchronous operation, which is not supported by the
1297   * generic {@code LDAPInterface} interface.  It also means that the provided
1298   * connection must not be configured to operate in synchronous mode (via the
1299   * {@link com.unboundid.ldap.sdk.LDAPConnectionOptions#setUseSynchronousMode}
1300   * option).
1301   *
1302   * @param  o       The object to use to construct the search filter.  It must
1303   *                 not be {@code null}.
1304   * @param  c       The connection to use to communicate with the directory
1305   *                 server. It must not be {@code null}.
1306   * @param  baseDN  The base DN to use for the search.  It may be {@code null}
1307   *                 if the {@link LDAPObject#defaultParentDN} element in the
1308   *                 {@code LDAPObject} should be used as the base DN.
1309   * @param  scope   The scope to use for the search operation.  It must not be
1310   *                 {@code null}.
1311   *
1312   * @return  A results object that may be used to iterate through the objects
1313   *          returned from the search.
1314   *
1315   * @throws  LDAPPersistException  If an error occurs while preparing or
1316   *                                sending the search request.
1317   */
1318  @NotNull()
1319  public PersistedObjects<T> search(@NotNull final T o,
1320                                    @NotNull final LDAPConnection c,
1321                                    @Nullable final String baseDN,
1322                                    @NotNull final SearchScope scope)
1323         throws LDAPPersistException
1324  {
1325    return search(o, c, baseDN, scope, DereferencePolicy.NEVER, 0, 0, null,
1326         NO_CONTROLS);
1327  }
1328
1329
1330
1331  /**
1332   * Performs a search in the directory for objects matching the contents of
1333   * the provided object.  A search filter will be generated from the provided
1334   * object containing all non-{@code null} values from fields and getter
1335   * methods whose {@link LDAPField} or {@link LDAPGetter} annotation has
1336   * the {@code inFilter} element set to {@code true}.
1337   * <BR><BR>
1338   * Note that this method requires an {@link LDAPConnection} argument rather
1339   * than using the more generic {@link LDAPInterface} type because the search
1340   * is invoked as an asynchronous operation, which is not supported by the
1341   * generic {@code LDAPInterface} interface.  It also means that the provided
1342   * connection must not be configured to operate in synchronous mode (via the
1343   * {@link com.unboundid.ldap.sdk.LDAPConnectionOptions#setUseSynchronousMode}
1344   * option).
1345   *
1346   * @param  o            The object to use to construct the search filter.  It
1347   *                      must not be {@code null}.
1348   * @param  c            The connection to use to communicate with the
1349   *                      directory server.  It must not be {@code null}.
1350   * @param  baseDN       The base DN to use for the search.  It may be
1351   *                      {@code null} if the {@link LDAPObject#defaultParentDN}
1352   *                      element in the {@code LDAPObject} should be used as
1353   *                      the base DN.
1354   * @param  scope        The scope to use for the search operation.  It must
1355   *                      not be {@code null}.
1356   * @param  derefPolicy  The dereference policy to use for the search
1357   *                      operation.  It must not be {@code null}.
1358   * @param  sizeLimit    The maximum number of entries to retrieve from the
1359   *                      directory.  A value of zero indicates that no
1360   *                      client-requested size limit should be enforced.
1361   * @param  timeLimit    The maximum length of time in seconds that the server
1362   *                      should spend processing the search.  A value of zero
1363   *                      indicates that no client-requested time limit should
1364   *                      be enforced.
1365   * @param  extraFilter  An optional additional filter to be ANDed with the
1366   *                      filter generated from the provided object.  If this is
1367   *                      {@code null}, then only the filter generated from the
1368   *                      object will be used.
1369   * @param  controls     An optional set of controls to include in the search
1370   *                      request.  It may be empty or {@code null} if no
1371   *                      controls are needed.
1372   *
1373   * @return  A results object that may be used to iterate through the objects
1374   *          returned from the search.
1375   *
1376   * @throws  LDAPPersistException  If an error occurs while preparing or
1377   *                                sending the search request.
1378   */
1379  @NotNull()
1380  public PersistedObjects<T> search(@NotNull final T o,
1381              @NotNull final LDAPConnection c,
1382              @Nullable final String baseDN,
1383              @NotNull final SearchScope scope,
1384              @NotNull final DereferencePolicy derefPolicy,
1385              final int sizeLimit, final int timeLimit,
1386              @Nullable final Filter extraFilter,
1387              @Nullable final Control... controls)
1388         throws LDAPPersistException
1389  {
1390    Validator.ensureNotNull(o, c, scope, derefPolicy);
1391
1392    final String base;
1393    if (baseDN == null)
1394    {
1395      base = handler.getDefaultParentDN().toString();
1396    }
1397    else
1398    {
1399      base = baseDN;
1400    }
1401
1402    final Filter filter;
1403    if (extraFilter == null)
1404    {
1405      filter = handler.createFilter(o);
1406    }
1407    else
1408    {
1409      filter = Filter.createANDFilter(handler.createFilter(o), extraFilter);
1410    }
1411
1412    final SearchRequest searchRequest = new SearchRequest(base, scope,
1413         derefPolicy, sizeLimit, timeLimit, false, filter,
1414         handler.getAttributesToRequest());
1415    if (controls != null)
1416    {
1417      searchRequest.setControls(controls);
1418    }
1419
1420    final LDAPEntrySource entrySource;
1421    try
1422    {
1423      entrySource = new LDAPEntrySource(c, searchRequest, false);
1424    }
1425    catch (final LDAPException le)
1426    {
1427      Debug.debugException(le);
1428      throw new LDAPPersistException(le);
1429    }
1430
1431    return new PersistedObjects<>(this, entrySource);
1432  }
1433
1434
1435
1436  /**
1437   * Performs a search in the directory for objects matching the contents of the
1438   * provided object.  A search filter will be generated from the provided
1439   * object containing all non-{@code null} values from fields and getter
1440   * methods whose {@link LDAPField} or {@link LDAPGetter} annotation has
1441   * the {@code inFilter} element set to {@code true}.
1442   * <BR><BR>
1443   * The search performed will be a subtree search using a base DN equal to the
1444   * {@link LDAPObject#defaultParentDN} element in the {@code LDAPObject}
1445   * annotation.  It will not enforce a client-side time limit or size limit.
1446   *
1447   * @param  o  The object to use to construct the search filter.  It must not
1448   *            be {@code null}.
1449   * @param  i  The interface to use to communicate with the directory server.
1450   *            It must not be {@code null}.
1451   * @param  l  The object search result listener that will be used to receive
1452   *            objects decoded from entries returned for the search.  It must
1453   *            not be {@code null}.
1454   *
1455   * @return  The result of the search operation that was processed.
1456   *
1457   * @throws  LDAPPersistException  If an error occurs while preparing or
1458   *                                sending the search request.
1459   */
1460  @NotNull()
1461  public SearchResult search(@NotNull final T o,
1462                             @NotNull final LDAPInterface i,
1463                             @NotNull final ObjectSearchListener<T> l)
1464         throws LDAPPersistException
1465  {
1466    return search(o, i, null, SearchScope.SUB, DereferencePolicy.NEVER, 0, 0,
1467         null, l, NO_CONTROLS);
1468  }
1469
1470
1471
1472  /**
1473   * Performs a search in the directory for objects matching the contents of the
1474   * provided object.  A search filter will be generated from the provided
1475   * object containing all non-{@code null} values from fields and getter
1476   * methods whose {@link LDAPField} or {@link LDAPGetter} annotation has
1477   * the {@code inFilter} element set to {@code true}.
1478   *
1479   * @param  o       The object to use to construct the search filter.  It must
1480   *                 not be {@code null}.
1481   * @param  i       The interface to use to communicate with the directory
1482   *                 server. It must not be {@code null}.
1483   * @param  baseDN  The base DN to use for the search.  It may be {@code null}
1484   *                 if the {@link LDAPObject#defaultParentDN} element in the
1485   *                 {@code LDAPObject} should be used as the base DN.
1486   * @param  scope   The scope to use for the search operation.  It must not be
1487   *                 {@code null}.
1488   * @param  l       The object search result listener that will be used to
1489   *                 receive objects decoded from entries returned for the
1490   *                 search.  It must not be {@code null}.
1491   *
1492   * @return  The result of the search operation that was processed.
1493   *
1494   * @throws  LDAPPersistException  If an error occurs while preparing or
1495   *                                sending the search request.
1496   */
1497  @NotNull()
1498  public SearchResult search(@NotNull final T o, @NotNull final LDAPInterface i,
1499                             @Nullable final String baseDN,
1500                             @NotNull final SearchScope scope,
1501                             @NotNull final ObjectSearchListener<T> l)
1502         throws LDAPPersistException
1503  {
1504    return search(o, i, baseDN, scope, DereferencePolicy.NEVER, 0, 0, null, l,
1505         NO_CONTROLS);
1506  }
1507
1508
1509
1510  /**
1511   * Performs a search in the directory for objects matching the contents of
1512   * the provided object.  A search filter will be generated from the provided
1513   * object containing all non-{@code null} values from fields and getter
1514   * methods whose {@link LDAPField} or {@link LDAPGetter} annotation has
1515   * the {@code inFilter} element set to {@code true}.
1516   *
1517   * @param  o            The object to use to construct the search filter.  It
1518   *                      must not be {@code null}.
1519   * @param  i            The connection to use to communicate with the
1520   *                      directory server.  It must not be {@code null}.
1521   * @param  baseDN       The base DN to use for the search.  It may be
1522   *                      {@code null} if the {@link LDAPObject#defaultParentDN}
1523   *                      element in the {@code LDAPObject} should be used as
1524   *                      the base DN.
1525   * @param  scope        The scope to use for the search operation.  It must
1526   *                      not be {@code null}.
1527   * @param  derefPolicy  The dereference policy to use for the search
1528   *                      operation.  It must not be {@code null}.
1529   * @param  sizeLimit    The maximum number of entries to retrieve from the
1530   *                      directory.  A value of zero indicates that no
1531   *                      client-requested size limit should be enforced.
1532   * @param  timeLimit    The maximum length of time in seconds that the server
1533   *                      should spend processing the search.  A value of zero
1534   *                      indicates that no client-requested time limit should
1535   *                      be enforced.
1536   * @param  extraFilter  An optional additional filter to be ANDed with the
1537   *                      filter generated from the provided object.  If this is
1538   *                      {@code null}, then only the filter generated from the
1539   *                      object will be used.
1540   * @param  l            The object search result listener that will be used
1541   *                      to receive objects decoded from entries returned for
1542   *                      the search.  It must not be {@code null}.
1543   * @param  controls     An optional set of controls to include in the search
1544   *                      request.  It may be empty or {@code null} if no
1545   *                      controls are needed.
1546   *
1547   * @return  The result of the search operation that was processed.
1548   *
1549   * @throws  LDAPPersistException  If an error occurs while preparing or
1550   *                                sending the search request.
1551   */
1552  @NotNull()
1553  public SearchResult search(@NotNull final T o, @NotNull final LDAPInterface i,
1554                             @Nullable final String baseDN,
1555                             @NotNull final SearchScope scope,
1556                             @NotNull final DereferencePolicy derefPolicy,
1557                             final int sizeLimit, final int timeLimit,
1558                             @Nullable final Filter extraFilter,
1559                             @NotNull final ObjectSearchListener<T> l,
1560                             @Nullable final Control... controls)
1561         throws LDAPPersistException
1562  {
1563    Validator.ensureNotNull(o, i, scope, derefPolicy, l);
1564
1565    final String base;
1566    if (baseDN == null)
1567    {
1568      base = handler.getDefaultParentDN().toString();
1569    }
1570    else
1571    {
1572      base = baseDN;
1573    }
1574
1575    final Filter filter;
1576    if (extraFilter == null)
1577    {
1578      filter = handler.createFilter(o);
1579    }
1580    else
1581    {
1582      filter = Filter.simplifyFilter(
1583           Filter.createANDFilter(handler.createFilter(o), extraFilter), true);
1584    }
1585
1586    final SearchListenerBridge<T> bridge = new SearchListenerBridge<>(this, l);
1587
1588    final SearchRequest searchRequest = new SearchRequest(bridge, base, scope,
1589         derefPolicy, sizeLimit, timeLimit, false, filter,
1590         handler.getAttributesToRequest());
1591    if (controls != null)
1592    {
1593      searchRequest.setControls(controls);
1594    }
1595
1596    try
1597    {
1598      return i.search(searchRequest);
1599    }
1600    catch (final LDAPException le)
1601    {
1602      Debug.debugException(le);
1603      throw new LDAPPersistException(le);
1604    }
1605  }
1606
1607
1608
1609  /**
1610   * Performs a search in the directory using the provided search criteria and
1611   * decodes all entries returned as objects of the associated type.
1612   *
1613   * @param  c            The connection to use to communicate with the
1614   *                      directory server.  It must not be {@code null}.
1615   * @param  baseDN       The base DN to use for the search.  It may be
1616   *                      {@code null} if the {@link LDAPObject#defaultParentDN}
1617   *                      element in the {@code LDAPObject} should be used as
1618   *                      the base DN.
1619   * @param  scope        The scope to use for the search operation.  It must
1620   *                      not be {@code null}.
1621   * @param  derefPolicy  The dereference policy to use for the search
1622   *                      operation.  It must not be {@code null}.
1623   * @param  sizeLimit    The maximum number of entries to retrieve from the
1624   *                      directory.  A value of zero indicates that no
1625   *                      client-requested size limit should be enforced.
1626   * @param  timeLimit    The maximum length of time in seconds that the server
1627   *                      should spend processing the search.  A value of zero
1628   *                      indicates that no client-requested time limit should
1629   *                      be enforced.
1630   * @param  filter       The filter to use for the search.  It must not be
1631   *                      {@code null}.  It will automatically be ANDed with a
1632   *                      filter that will match entries with the structural and
1633   *                      auxiliary classes.
1634   * @param  controls     An optional set of controls to include in the search
1635   *                      request.  It may be empty or {@code null} if no
1636   *                      controls are needed.
1637   *
1638   * @return  The result of the search operation that was processed.
1639   *
1640   * @throws  LDAPPersistException  If an error occurs while preparing or
1641   *                                sending the search request.
1642   */
1643  @NotNull()
1644  public PersistedObjects<T> search(@NotNull final LDAPConnection c,
1645              @Nullable final String baseDN,
1646              @NotNull final SearchScope scope,
1647              @NotNull final DereferencePolicy derefPolicy,
1648              final int sizeLimit, final int timeLimit,
1649              @NotNull final Filter filter,
1650              @Nullable final Control... controls)
1651         throws LDAPPersistException
1652  {
1653    Validator.ensureNotNull(c, scope, derefPolicy, filter);
1654
1655    final String base;
1656    if (baseDN == null)
1657    {
1658      base = handler.getDefaultParentDN().toString();
1659    }
1660    else
1661    {
1662      base = baseDN;
1663    }
1664
1665    final Filter f = Filter.createANDFilter(filter, handler.createBaseFilter());
1666
1667    final SearchRequest searchRequest = new SearchRequest(base, scope,
1668         derefPolicy, sizeLimit, timeLimit, false, f,
1669         handler.getAttributesToRequest());
1670    if (controls != null)
1671    {
1672      searchRequest.setControls(controls);
1673    }
1674
1675    final LDAPEntrySource entrySource;
1676    try
1677    {
1678      entrySource = new LDAPEntrySource(c, searchRequest, false);
1679    }
1680    catch (final LDAPException le)
1681    {
1682      Debug.debugException(le);
1683      throw new LDAPPersistException(le);
1684    }
1685
1686    return new PersistedObjects<>(this, entrySource);
1687  }
1688
1689
1690
1691  /**
1692   * Performs a search in the directory using the provided search criteria and
1693   * decodes all entries returned as objects of the associated type.
1694   *
1695   * @param  i            The connection to use to communicate with the
1696   *                      directory server.  It must not be {@code null}.
1697   * @param  baseDN       The base DN to use for the search.  It may be
1698   *                      {@code null} if the {@link LDAPObject#defaultParentDN}
1699   *                      element in the {@code LDAPObject} should be used as
1700   *                      the base DN.
1701   * @param  scope        The scope to use for the search operation.  It must
1702   *                      not be {@code null}.
1703   * @param  derefPolicy  The dereference policy to use for the search
1704   *                      operation.  It must not be {@code null}.
1705   * @param  sizeLimit    The maximum number of entries to retrieve from the
1706   *                      directory.  A value of zero indicates that no
1707   *                      client-requested size limit should be enforced.
1708   * @param  timeLimit    The maximum length of time in seconds that the server
1709   *                      should spend processing the search.  A value of zero
1710   *                      indicates that no client-requested time limit should
1711   *                      be enforced.
1712   * @param  filter       The filter to use for the search.  It must not be
1713   *                      {@code null}.  It will automatically be ANDed with a
1714   *                      filter that will match entries with the structural and
1715   *                      auxiliary classes.
1716   * @param  l            The object search result listener that will be used
1717   *                      to receive objects decoded from entries returned for
1718   *                      the search.  It must not be {@code null}.
1719   * @param  controls     An optional set of controls to include in the search
1720   *                      request.  It may be empty or {@code null} if no
1721   *                      controls are needed.
1722   *
1723   * @return  The result of the search operation that was processed.
1724   *
1725   * @throws  LDAPPersistException  If an error occurs while preparing or
1726   *                                sending the search request.
1727   */
1728  @NotNull()
1729  public SearchResult search(@NotNull final LDAPInterface i,
1730                             @Nullable final String baseDN,
1731                             @NotNull final SearchScope scope,
1732                             @NotNull final DereferencePolicy derefPolicy,
1733                             final int sizeLimit, final int timeLimit,
1734                             @NotNull final Filter filter,
1735                             @NotNull final ObjectSearchListener<T> l,
1736                             @Nullable final Control... controls)
1737         throws LDAPPersistException
1738  {
1739    Validator.ensureNotNull(i, scope, derefPolicy, filter, l);
1740
1741    final String base;
1742    if (baseDN == null)
1743    {
1744      base = handler.getDefaultParentDN().toString();
1745    }
1746    else
1747    {
1748      base = baseDN;
1749    }
1750
1751    final Filter f = Filter.simplifyFilter(
1752         Filter.createANDFilter(filter, handler.createBaseFilter()), true);
1753    final SearchListenerBridge<T> bridge = new SearchListenerBridge<>(this, l);
1754
1755    final SearchRequest searchRequest = new SearchRequest(bridge, base, scope,
1756         derefPolicy, sizeLimit, timeLimit, false, f,
1757         handler.getAttributesToRequest());
1758    if (controls != null)
1759    {
1760      searchRequest.setControls(controls);
1761    }
1762
1763    try
1764    {
1765      return i.search(searchRequest);
1766    }
1767    catch (final LDAPException le)
1768    {
1769      Debug.debugException(le);
1770      throw new LDAPPersistException(le);
1771    }
1772  }
1773
1774
1775
1776  /**
1777   * Performs a search in the directory to retrieve the object whose contents
1778   * match the contents of the provided object.  It is expected that at most one
1779   * entry matches the provided criteria, and that it can be decoded as an
1780   * object of the associated type.  If multiple entries match the resulting
1781   * criteria, or if the matching entry cannot be decoded as the associated type
1782   * of object, then an exception will be thrown.
1783   * <BR><BR>
1784   * A search filter will be generated from the provided object containing all
1785   * non-{@code null} values from fields and getter methods whose
1786   * {@link LDAPField} or {@link LDAPGetter} annotation has the {@code inFilter}
1787   * element set to {@code true}.
1788   * <BR><BR>
1789   * The search performed will be a subtree search using a base DN equal to the
1790   * {@link LDAPObject#defaultParentDN} element in the {@code LDAPObject}
1791   * annotation.  It will not enforce a client-side time limit or size limit.
1792   *
1793   * @param  o  The object to use to construct the search filter.  It must not
1794   *            be {@code null}.
1795   * @param  i  The interface to use to communicate with the directory server.
1796   *            It must not be {@code null}.
1797   *
1798   * @return  The object constructed from the entry returned by the search, or
1799   *          {@code null} if no entry was returned.
1800   *
1801   * @throws  LDAPPersistException  If an error occurs while preparing or
1802   *                                sending the search request or decoding the
1803   *                                entry that was returned.
1804   */
1805  @Nullable()
1806  public T searchForObject(@NotNull final T o, @NotNull final LDAPInterface i)
1807         throws LDAPPersistException
1808  {
1809    return searchForObject(o, i, null, SearchScope.SUB, DereferencePolicy.NEVER,
1810         0, 0, null, NO_CONTROLS);
1811  }
1812
1813
1814
1815  /**
1816   * Performs a search in the directory to retrieve the object whose contents
1817   * match the contents of the provided object.  It is expected that at most one
1818   * entry matches the provided criteria, and that it can be decoded as an
1819   * object of the associated type.  If multiple entries match the resulting
1820   * criteria, or if the matching entry cannot be decoded as the associated type
1821   * of object, then an exception will be thrown.
1822   * <BR><BR>
1823   * A search filter will be generated from the provided object containing all
1824   * non-{@code null} values from fields and getter methods whose
1825   * {@link LDAPField} or {@link LDAPGetter} annotation has the {@code inFilter}
1826   * element set to {@code true}.
1827   *
1828   * @param  o       The object to use to construct the search filter.  It must
1829   *                 not be {@code null}.
1830   * @param  i       The interface to use to communicate with the directory
1831   *                 server. It must not be {@code null}.
1832   * @param  baseDN  The base DN to use for the search.  It may be {@code null}
1833   *                 if the {@link LDAPObject#defaultParentDN} element in the
1834   *                 {@code LDAPObject} should be used as the base DN.
1835   * @param  scope   The scope to use for the search operation.  It must not be
1836   *                 {@code null}.
1837   *
1838   * @return  The object constructed from the entry returned by the search, or
1839   *          {@code null} if no entry was returned.
1840   *
1841   * @throws  LDAPPersistException  If an error occurs while preparing or
1842   *                                sending the search request or decoding the
1843   *                                entry that was returned.
1844   */
1845  @Nullable()
1846  public T searchForObject(@NotNull final T o, @NotNull final LDAPInterface i,
1847                           @Nullable final String baseDN,
1848                           @NotNull final SearchScope scope)
1849         throws LDAPPersistException
1850  {
1851    return searchForObject(o, i, baseDN, scope, DereferencePolicy.NEVER, 0, 0,
1852         null, NO_CONTROLS);
1853  }
1854
1855
1856
1857  /**
1858   * Performs a search in the directory to retrieve the object whose contents
1859   * match the contents of the provided object.  It is expected that at most one
1860   * entry matches the provided criteria, and that it can be decoded as an
1861   * object of the associated type.  If multiple entries match the resulting
1862   * criteria, or if the matching entry cannot be decoded as the associated type
1863   * of object, then an exception will be thrown.
1864   * <BR><BR>
1865   * A search filter will be generated from the provided object containing all
1866   * non-{@code null} values from fields and getter methods whose
1867   * {@link LDAPField} or {@link LDAPGetter} annotation has the {@code inFilter}
1868   * element set to {@code true}.
1869   *
1870   * @param  o            The object to use to construct the search filter.  It
1871   *                      must not be {@code null}.
1872   * @param  i            The connection to use to communicate with the
1873   *                      directory server.  It must not be {@code null}.
1874   * @param  baseDN       The base DN to use for the search.  It may be
1875   *                      {@code null} if the {@link LDAPObject#defaultParentDN}
1876   *                      element in the {@code LDAPObject} should be used as
1877   *                      the base DN.
1878   * @param  scope        The scope to use for the search operation.  It must
1879   *                      not be {@code null}.
1880   * @param  derefPolicy  The dereference policy to use for the search
1881   *                      operation.  It must not be {@code null}.
1882   * @param  sizeLimit    The maximum number of entries to retrieve from the
1883   *                      directory.  A value of zero indicates that no
1884   *                      client-requested size limit should be enforced.
1885   * @param  timeLimit    The maximum length of time in seconds that the server
1886   *                      should spend processing the search.  A value of zero
1887   *                      indicates that no client-requested time limit should
1888   *                      be enforced.
1889   * @param  extraFilter  An optional additional filter to be ANDed with the
1890   *                      filter generated from the provided object.  If this is
1891   *                      {@code null}, then only the filter generated from the
1892   *                      object will be used.
1893   * @param  controls     An optional set of controls to include in the search
1894   *                      request.  It may be empty or {@code null} if no
1895   *                      controls are needed.
1896   *
1897   * @return  The object constructed from the entry returned by the search, or
1898   *          {@code null} if no entry was returned.
1899   *
1900   * @throws  LDAPPersistException  If an error occurs while preparing or
1901   *                                sending the search request or decoding the
1902   *                                entry that was returned.
1903   */
1904  @Nullable()
1905  public T searchForObject(@NotNull final T o, @NotNull final LDAPInterface i,
1906                           @Nullable final String baseDN,
1907                           @NotNull final SearchScope scope,
1908                           @NotNull final DereferencePolicy derefPolicy,
1909                           final int sizeLimit, final int timeLimit,
1910                           @Nullable final Filter extraFilter,
1911                           @Nullable final Control... controls)
1912         throws LDAPPersistException
1913  {
1914    Validator.ensureNotNull(o, i, scope, derefPolicy);
1915
1916    final String base;
1917    if (baseDN == null)
1918    {
1919      base = handler.getDefaultParentDN().toString();
1920    }
1921    else
1922    {
1923      base = baseDN;
1924    }
1925
1926    final Filter filter;
1927    if (extraFilter == null)
1928    {
1929      filter = handler.createFilter(o);
1930    }
1931    else
1932    {
1933      filter = Filter.simplifyFilter(
1934           Filter.createANDFilter(handler.createFilter(o), extraFilter), true);
1935    }
1936
1937    final SearchRequest searchRequest = new SearchRequest(base, scope,
1938         derefPolicy, sizeLimit, timeLimit, false, filter,
1939         handler.getAttributesToRequest());
1940    if (controls != null)
1941    {
1942      searchRequest.setControls(controls);
1943    }
1944
1945    try
1946    {
1947      final Entry e = i.searchForEntry(searchRequest);
1948      if (e == null)
1949      {
1950        return null;
1951      }
1952      else
1953      {
1954        return decode(e);
1955      }
1956    }
1957    catch (final LDAPPersistException lpe)
1958    {
1959      Debug.debugException(lpe);
1960      throw lpe;
1961    }
1962    catch (final LDAPException le)
1963    {
1964      Debug.debugException(le);
1965      throw new LDAPPersistException(le);
1966    }
1967  }
1968
1969
1970
1971  /**
1972   * Performs a search in the directory with an attempt to find all objects of
1973   * the specified type below the given base DN (or below the default parent DN
1974   * if no base DN is specified).  Note that this may result in an unindexed
1975   * search, which may be expensive to conduct.  Some servers may require
1976   * special permissions of clients wishing to perform unindexed searches.
1977   *
1978   * @param  i         The connection to use to communicate with the
1979   *                   directory server.  It must not be {@code null}.
1980   * @param  baseDN    The base DN to use for the search.  It may be
1981   *                   {@code null} if the {@link LDAPObject#defaultParentDN}
1982   *                   element in the {@code LDAPObject} should be used as the
1983   *                   base DN.
1984   * @param  l         The object search result listener that will be used to
1985   *                   receive objects decoded from entries returned for the
1986   *                   search.  It must not be {@code null}.
1987   * @param  controls  An optional set of controls to include in the search
1988   *                   request.  It may be empty or {@code null} if no controls
1989   *                   are needed.
1990   *
1991   * @return  The result of the search operation that was processed.
1992   *
1993   * @throws  LDAPPersistException  If an error occurs while preparing or
1994   *                                sending the search request.
1995   */
1996  @NotNull()
1997  public SearchResult getAll(@NotNull final LDAPInterface i,
1998                             @Nullable final String baseDN,
1999                             @NotNull final ObjectSearchListener<T> l,
2000                             @Nullable final Control... controls)
2001         throws LDAPPersistException
2002  {
2003    Validator.ensureNotNull(i, l);
2004
2005    final String base;
2006    if (baseDN == null)
2007    {
2008      base = handler.getDefaultParentDN().toString();
2009    }
2010    else
2011    {
2012      base = baseDN;
2013    }
2014
2015    final SearchListenerBridge<T> bridge = new SearchListenerBridge<>(this, l);
2016    final SearchRequest searchRequest = new SearchRequest(bridge, base,
2017         SearchScope.SUB, DereferencePolicy.NEVER, 0, 0, false,
2018         handler.createBaseFilter(), handler.getAttributesToRequest());
2019    if (controls != null)
2020    {
2021      searchRequest.setControls(controls);
2022    }
2023
2024    try
2025    {
2026      return i.search(searchRequest);
2027    }
2028    catch (final LDAPException le)
2029    {
2030      Debug.debugException(le);
2031      throw new LDAPPersistException(le);
2032    }
2033  }
2034}