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