001/*
002 * Copyright 2009-2019 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2009-2019 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<>(StaticUtils.computeMapCapacity(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 =
395               new LinkedHashSet<>(StaticUtils.computeMapCapacity(20));
396          newOptionalSet.addAll(
397               Arrays.asList(existing.getOptionalAttributes()));
398          newOptionalSet.addAll(newOptionalNames);
399
400          final String[] newOptional = new String[newOptionalSet.size()];
401          newOptionalSet.toArray(newOptional);
402
403          final ObjectClassDefinition newOC = new ObjectClassDefinition(
404               existing.getOID(), existing.getNames(),
405               existing.getDescription(), existing.isObsolete(),
406               existing.getSuperiorClasses(), existing.getObjectClassType(),
407               existing.getRequiredAttributes(), newOptional,
408               existing.getExtensions());
409          newOCList.add(newOC.toString());
410        }
411      }
412    }
413
414    final LinkedList<Modification> mods = new LinkedList<>();
415    if (! newAttrList.isEmpty())
416    {
417      final String[] newAttrValues = new String[newAttrList.size()];
418      mods.add(new Modification(ModificationType.ADD,
419           Schema.ATTR_ATTRIBUTE_TYPE, newAttrList.toArray(newAttrValues)));
420    }
421
422    if (! newOCList.isEmpty())
423    {
424      final String[] newOCValues = new String[newOCList.size()];
425      mods.add(new Modification(ModificationType.ADD,
426           Schema.ATTR_OBJECT_CLASS, newOCList.toArray(newOCValues)));
427    }
428
429    if (mods.isEmpty())
430    {
431      return false;
432    }
433    else
434    {
435      i.modify(s.getSchemaEntry().getDN(), mods);
436      return true;
437    }
438  }
439
440
441
442  /**
443   * Adds any missing attributes to the provided set.
444   *
445   * @param  names     The names of the attributes which may potentially be
446   *                   added.
447   * @param  required  The existing required definitions.
448   * @param  optional  The existing optional definitions.
449   * @param  missing   The set to which any missing names should be added.
450   */
451  private static void addMissingAttrs(final String[] names,
452                           final Set<AttributeTypeDefinition> required,
453                           final Set<AttributeTypeDefinition> optional,
454                           final Set<String> missing)
455  {
456    for (final String name : names)
457    {
458      boolean found = false;
459      for (final AttributeTypeDefinition eA : required)
460      {
461        if (eA.hasNameOrOID(name))
462        {
463          found = true;
464          break;
465        }
466      }
467
468      if (! found)
469      {
470        for (final AttributeTypeDefinition eA : optional)
471        {
472          if (eA.hasNameOrOID(name))
473          {
474            found = true;
475            break;
476          }
477        }
478
479        if (! found)
480        {
481          missing.add(name);
482        }
483      }
484    }
485  }
486
487
488
489  /**
490   * Encodes the provided object to an entry that is suitable for storing it in
491   * an LDAP directory server.
492   *
493   * @param  o         The object to be encoded.  It must not be {@code null}.
494   * @param  parentDN  The parent DN to use for the resulting entry.  If the
495   *                   provided object was previously read from a directory
496   *                   server and includes a field marked with the
497   *                   {@link LDAPDNField} or {@link LDAPEntryField} annotation,
498   *                   then that field may be used to retrieve the actual DN of
499   *                   the associated entry.  If the actual DN of the associated
500   *                   entry is not available, then a DN will be constructed
501   *                   from the RDN fields and/or getter methods declared in the
502   *                   class.  If the provided parent DN is {@code null}, then
503   *                   the default parent DN defined in the {@link LDAPObject}
504   *                   annotation will be used.
505   *
506   * @return  An entry containing the encoded representation of the provided
507   *          object.  It may be altered by the caller if necessary.
508   *
509   * @throws  LDAPPersistException  If a problem occurs while attempting to
510   *                                encode the provided object.
511   */
512  public Entry encode(final T o, final String parentDN)
513         throws LDAPPersistException
514  {
515    Validator.ensureNotNull(o);
516    return handler.encode(o, parentDN);
517  }
518
519
520
521  /**
522   * Creates an object and initializes it with the contents of the provided
523   * entry.
524   *
525   * @param  entry  The entry to use to create the object.  It must not be
526   *                {@code null}.
527   *
528   * @return  The object created from the provided entry.
529   *
530   * @throws  LDAPPersistException  If an error occurs while attempting to
531   *                                create or initialize the object from the
532   *                                provided entry.
533   */
534  public T decode(final Entry entry)
535         throws LDAPPersistException
536  {
537    Validator.ensureNotNull(entry);
538    return handler.decode(entry);
539  }
540
541
542
543  /**
544   * Initializes the provided object from the information contained in the
545   * given entry.
546   *
547   * @param  o      The object to initialize with the contents of the provided
548   *                entry.  It must not be {@code null}.
549   * @param  entry  The entry to use to create the object.  It must not be
550   *                {@code null}.
551   *
552   * @throws  LDAPPersistException  If an error occurs while attempting to
553   *                                initialize the object from the provided
554   *                                entry.  If an exception is thrown, then the
555   *                                provided object may or may not have been
556   *                                altered.
557   */
558  public void decode(final T o, final Entry entry)
559         throws LDAPPersistException
560  {
561    Validator.ensureNotNull(o, entry);
562    handler.decode(o, entry);
563  }
564
565
566
567  /**
568   * Adds the provided object to the directory server using the provided
569   * connection.
570   *
571   * @param  o         The object to be added.  It must not be {@code null}.
572   * @param  i         The interface to use to communicate with the directory
573   *                   server.  It must not be {@code null}.
574   * @param  parentDN  The parent DN to use for the resulting entry.  If the
575   *                   provided object was previously read from a directory
576   *                   server and includes a field marked with the
577   *                   {@link LDAPDNField} or {@link LDAPEntryField} annotation,
578   *                   then that field may be used to retrieve the actual DN of
579   *                   the associated entry.  If the actual DN of the associated
580   *                   entry is not available, then a DN will be constructed
581   *                   from the RDN fields and/or getter methods declared in the
582   *                   class.  If the provided parent DN is {@code null}, then
583   *                   the default parent DN defined in the {@link LDAPObject}
584   *                   annotation will be used.
585   * @param  controls  An optional set of controls to include in the add
586   *                   request.
587   *
588   * @return  The result of processing the add operation.
589   *
590   * @throws  LDAPPersistException  If a problem occurs while encoding or adding
591   *                                the entry.
592   */
593  public LDAPResult add(final T o, final LDAPInterface i, final String parentDN,
594                        final Control... controls)
595         throws LDAPPersistException
596  {
597    Validator.ensureNotNull(o, i);
598    final Entry e = encode(o, parentDN);
599
600    try
601    {
602      final AddRequest addRequest = new AddRequest(e);
603      if (controls != null)
604      {
605        addRequest.setControls(controls);
606      }
607
608      return i.add(addRequest);
609    }
610    catch (final LDAPException le)
611    {
612      Debug.debugException(le);
613      throw new LDAPPersistException(le);
614    }
615  }
616
617
618
619  /**
620   * Deletes the provided object from the directory.
621   *
622   * @param  o         The object to be deleted.  It must not be {@code null},
623   *                   and it must have been retrieved from the directory and
624   *                   have a field with either the {@link LDAPDNField} or
625   *                   {@link LDAPEntryField} annotations.
626   * @param  i         The interface to use to communicate with the directory
627   *                   server.  It must not be {@code null}.
628   * @param  controls  An optional set of controls to include in the add
629   *                   request.
630   *
631   * @return  The result of processing the delete operation.
632   *
633   * @throws  LDAPPersistException  If a problem occurs while attempting to
634   *                                delete the entry.
635   */
636  public LDAPResult delete(final T o, final LDAPInterface i,
637                           final Control... controls)
638         throws LDAPPersistException
639  {
640    Validator.ensureNotNull(o, i);
641    final String dn = handler.getEntryDN(o);
642    if (dn == null)
643    {
644      throw new LDAPPersistException(ERR_PERSISTER_DELETE_NO_DN.get());
645    }
646
647    try
648    {
649      final DeleteRequest deleteRequest = new DeleteRequest(dn);
650      if (controls != null)
651      {
652        deleteRequest.setControls(controls);
653      }
654
655      return i.delete(deleteRequest);
656    }
657    catch (final LDAPException le)
658    {
659      Debug.debugException(le);
660      throw new LDAPPersistException(le);
661    }
662  }
663
664
665
666  /**
667   * Retrieves a list of modifications that can be used to update the stored
668   * representation of the provided object in the directory.  If the provided
669   * object was retrieved from the directory using the persistence framework and
670   * includes a field with the {@link LDAPEntryField} annotation, then that
671   * entry will be used to make the returned set of modifications as efficient
672   * as possible.  Otherwise, the resulting modifications will include attempts
673   * to replace every attribute which are associated with fields or getters
674   * that should be used in modify operations.
675   *
676   * @param  o                 The object for which to generate the list of
677   *                           modifications.  It must not be {@code null}.
678   * @param  deleteNullValues  Indicates whether to include modifications that
679   *                           may completely remove an attribute from the
680   *                           entry if the corresponding field or getter method
681   *                           has a value of {@code null}.
682   * @param  attributes        The set of LDAP attributes for which to include
683   *                           modifications.  If this is empty or {@code null},
684   *                           then all attributes marked for inclusion in the
685   *                           modification will be examined.
686   *
687   * @return  An unmodifiable list of modifications that can be used to update
688   *          the stored representation of the provided object in the directory.
689   *          It may be empty if there are no differences identified in the
690   *          attributes to be evaluated.
691   *
692   * @throws  LDAPPersistException  If a problem occurs while computing the set
693   *                                of modifications.
694   */
695  public List<Modification> getModifications(final T o,
696                                             final boolean deleteNullValues,
697                                             final String... attributes)
698         throws LDAPPersistException
699  {
700    return getModifications(o, deleteNullValues, false, attributes);
701  }
702
703
704
705  /**
706   * Retrieves a list of modifications that can be used to update the stored
707   * representation of the provided object in the directory.  If the provided
708   * object was retrieved from the directory using the persistence framework and
709   * includes a field with the {@link LDAPEntryField} annotation, then that
710   * entry will be used to make the returned set of modifications as efficient
711   * as possible.  Otherwise, the resulting modifications will include attempts
712   * to replace every attribute which are associated with fields or getters
713   * that should be used in modify operations.
714   *
715   * @param  o                 The object for which to generate the list of
716   *                           modifications.  It must not be {@code null}.
717   * @param  deleteNullValues  Indicates whether to include modifications that
718   *                           may completely remove an attribute from the
719   *                           entry if the corresponding field or getter method
720   *                           has a value of {@code null}.
721   * @param  byteForByte       Indicates whether to use a byte-for-byte
722   *                           comparison to identify which attribute values
723   *                           have changed.  Using byte-for-byte comparison
724   *                           requires additional processing over using each
725   *                           attribute's associated matching rule, but it can
726   *                           detect changes that would otherwise be considered
727   *                           logically equivalent (e.g., changing the
728   *                           capitalization of a value that uses a
729   *                           case-insensitive matching rule).
730   * @param  attributes        The set of LDAP attributes for which to include
731   *                           modifications.  If this is empty or {@code null},
732   *                           then all attributes marked for inclusion in the
733   *                           modification will be examined.
734   *
735   * @return  An unmodifiable list of modifications that can be used to update
736   *          the stored representation of the provided object in the directory.
737   *          It may be empty if there are no differences identified in the
738   *          attributes to be evaluated.
739   *
740   * @throws  LDAPPersistException  If a problem occurs while computing the set
741   *                                of modifications.
742   */
743  public List<Modification> getModifications(final T o,
744                                             final boolean deleteNullValues,
745                                             final boolean byteForByte,
746                                             final String... attributes)
747         throws LDAPPersistException
748  {
749    Validator.ensureNotNull(o);
750    return handler.getModifications(o, deleteNullValues, byteForByte,
751         attributes);
752  }
753
754
755
756  /**
757   * Updates the stored representation of the provided object in the directory.
758   * If the provided object was retrieved from the directory using the
759   * persistence framework and includes a field with the {@link LDAPEntryField}
760   * annotation, then that entry will be used to make the returned set of
761   * modifications as efficient as possible.  Otherwise, the resulting
762   * modifications will include attempts to replace every attribute which are
763   * associated with fields or getters that should be used in modify operations.
764   * If there are no modifications, then no modification will be attempted, and
765   * this method will return {@code null} rather than an {@code LDAPResult}.
766   *
767   * @param  o                 The object for which to generate the list of
768   *                           modifications.  It must not be {@code null}.
769   * @param  i                 The interface to use to communicate with the
770   *                           directory server.  It must not be {@code null}.
771   * @param  dn                The DN to use for the entry.  It must not be
772   *                           {@code null} if the object was not retrieved from
773   *                           the directory using the persistence framework or
774   *                           does not have a field marked with the
775   *                           {@link LDAPDNField} or {@link LDAPEntryField}
776   *                           annotation.
777   * @param  deleteNullValues  Indicates whether to include modifications that
778   *                           may completely remove an attribute from the
779   *                           entry if the corresponding field or getter method
780   *                           has a value of {@code null}.
781   * @param  attributes        The set of LDAP attributes for which to include
782   *                           modifications.  If this is empty or {@code null},
783   *                           then all attributes marked for inclusion in the
784   *                           modification will be examined.
785   *
786   * @return  The result of processing the modify operation, or {@code null} if
787   *          there were no changes to apply (and therefore no modification was
788   *          performed).
789   *
790   * @throws  LDAPPersistException  If a problem occurs while computing the set
791   *                                of modifications.
792   */
793  public LDAPResult modify(final T o, final LDAPInterface i, final String dn,
794                           final boolean deleteNullValues,
795                           final String... attributes)
796         throws LDAPPersistException
797  {
798    return modify(o, i, dn, deleteNullValues, attributes, NO_CONTROLS);
799  }
800
801
802
803  /**
804   * Updates the stored representation of the provided object in the directory.
805   * If the provided object was retrieved from the directory using the
806   * persistence framework and includes a field with the {@link LDAPEntryField}
807   * annotation, then that entry will be used to make the returned set of
808   * modifications as efficient as possible.  Otherwise, the resulting
809   * modifications will include attempts to replace every attribute which are
810   * associated with fields or getters that should be used in modify operations.
811   * If there are no modifications, then no modification will be attempted, and
812   * this method will return {@code null} rather than an {@code LDAPResult}.
813   *
814   * @param  o                 The object for which to generate the list of
815   *                           modifications.  It must not be {@code null}.
816   * @param  i                 The interface to use to communicate with the
817   *                           directory server.  It must not be {@code null}.
818   * @param  dn                The DN to use for the entry.  It must not be
819   *                           {@code null} if the object was not retrieved from
820   *                           the directory using the persistence framework or
821   *                           does not have a field marked with the
822   *                           {@link LDAPDNField} or {@link LDAPEntryField}
823   *                           annotation.
824   * @param  deleteNullValues  Indicates whether to include modifications that
825   *                           may completely remove an attribute from the
826   *                           entry if the corresponding field or getter method
827   *                           has a value of {@code null}.
828   * @param  attributes        The set of LDAP attributes for which to include
829   *                           modifications.  If this is empty or {@code null},
830   *                           then all attributes marked for inclusion in the
831   *                           modification will be examined.
832   * @param  controls          The optional set of controls to include in the
833   *                           modify request.
834   *
835   * @return  The result of processing the modify operation, or {@code null} if
836   *          there were no changes to apply (and therefore no modification was
837   *          performed).
838   *
839   * @throws  LDAPPersistException  If a problem occurs while computing the set
840   *                                of modifications.
841   */
842  public LDAPResult modify(final T o, final LDAPInterface i, final String dn,
843                           final boolean deleteNullValues,
844                           final String[] attributes, final Control... controls)
845         throws LDAPPersistException
846  {
847    return modify(o, i, dn, deleteNullValues, false, attributes, controls);
848  }
849
850
851
852  /**
853   * Updates the stored representation of the provided object in the directory.
854   * If the provided object was retrieved from the directory using the
855   * persistence framework and includes a field with the {@link LDAPEntryField}
856   * annotation, then that entry will be used to make the returned set of
857   * modifications as efficient as possible.  Otherwise, the resulting
858   * modifications will include attempts to replace every attribute which are
859   * associated with fields or getters that should be used in modify operations.
860   * If there are no modifications, then no modification will be attempted, and
861   * this method will return {@code null} rather than an {@code LDAPResult}.
862   *
863   * @param  o                 The object for which to generate the list of
864   *                           modifications.  It must not be {@code null}.
865   * @param  i                 The interface to use to communicate with the
866   *                           directory server.  It must not be {@code null}.
867   * @param  dn                The DN to use for the entry.  It must not be
868   *                           {@code null} if the object was not retrieved from
869   *                           the directory using the persistence framework or
870   *                           does not have a field marked with the
871   *                           {@link LDAPDNField} or {@link LDAPEntryField}
872   *                           annotation.
873   * @param  deleteNullValues  Indicates whether to include modifications that
874   *                           may completely remove an attribute from the
875   *                           entry if the corresponding field or getter method
876   *                           has a value of {@code null}.
877   * @param  byteForByte       Indicates whether to use a byte-for-byte
878   *                           comparison to identify which attribute values
879   *                           have changed.  Using byte-for-byte comparison
880   *                           requires additional processing over using each
881   *                           attribute's associated matching rule, but it can
882   *                           detect changes that would otherwise be considered
883   *                           logically equivalent (e.g., changing the
884   *                           capitalization of a value that uses a
885   *                           case-insensitive matching rule).
886   * @param  attributes        The set of LDAP attributes for which to include
887   *                           modifications.  If this is empty or {@code null},
888   *                           then all attributes marked for inclusion in the
889   *                           modification will be examined.
890   * @param  controls          The optional set of controls to include in the
891   *                           modify request.
892   *
893   * @return  The result of processing the modify operation, or {@code null} if
894   *          there were no changes to apply (and therefore no modification was
895   *          performed).
896   *
897   * @throws  LDAPPersistException  If a problem occurs while computing the set
898   *                                of modifications.
899   */
900  public LDAPResult modify(final T o, final LDAPInterface i, final String dn,
901                           final boolean deleteNullValues,
902                           final boolean byteForByte, final String[] attributes,
903                           final Control... controls)
904         throws LDAPPersistException
905  {
906    Validator.ensureNotNull(o, i);
907    final List<Modification> mods =
908         handler.getModifications(o, deleteNullValues, byteForByte, attributes);
909    if (mods.isEmpty())
910    {
911      return null;
912    }
913
914    final String targetDN;
915    if (dn == null)
916    {
917      targetDN = handler.getEntryDN(o);
918      if (targetDN == null)
919      {
920        throw new LDAPPersistException(ERR_PERSISTER_MODIFY_NO_DN.get());
921      }
922    }
923    else
924    {
925      targetDN = dn;
926    }
927
928    try
929    {
930      final ModifyRequest modifyRequest = new ModifyRequest(targetDN, mods);
931      if (controls != null)
932      {
933        modifyRequest.setControls(controls);
934      }
935
936      return i.modify(modifyRequest);
937    }
938    catch (final LDAPException le)
939    {
940      Debug.debugException(le);
941      throw new LDAPPersistException(le);
942    }
943  }
944
945
946
947  /**
948   * Attempts to perform a simple bind as the user specified by the given object
949   * on the provided connection.  The object should represent some kind of entry
950   * capable suitable for use as the target of a simple bind operation.
951   * <BR><BR>
952   * If the provided object was retrieved from the directory and has either an
953   * {@link LDAPDNField} or {@link LDAPEntryField}, then that field will be used
954   * to obtain the DN.  Otherwise, a search will be performed to try to find the
955   * entry that corresponds to the provided object.
956   *
957   * @param  o         The object representing the user as whom to bind.  It
958   *                   must not be {@code null}.
959   * @param  baseDN    The base DN to use if it is necessary to search for the
960   *                   entry.  It may be {@code null} if the
961   *                   {@link LDAPObject#defaultParentDN} element in the
962   *                   {@code LDAPObject} should be used as the base DN.
963   * @param  password  The password to use for the bind.  It must not be
964   *                   {@code null}.
965   * @param  c         The connection to be authenticated.  It must not be
966   *                   {@code null}.
967   * @param  controls  An optional set of controls to include in the bind
968   *                   request.  It may be empty or {@code null} if no controls
969   *                   are needed.
970   *
971   * @return  The result of processing the bind operation.
972   *
973   * @throws  LDAPException  If a problem occurs while attempting to process the
974   *                         search or bind operation.
975   */
976  public BindResult bind(final T o, final String baseDN, final String password,
977                         final LDAPConnection c, final Control... controls)
978         throws LDAPException
979  {
980    Validator.ensureNotNull(o, password, c);
981
982    String dn = handler.getEntryDN(o);
983    if (dn == null)
984    {
985      String base = baseDN;
986      if (base == null)
987      {
988        base = handler.getDefaultParentDN().toString();
989      }
990
991      final SearchRequest r = new SearchRequest(base, SearchScope.SUB,
992           handler.createFilter(o), SearchRequest.NO_ATTRIBUTES);
993      r.setSizeLimit(1);
994
995      final Entry e = c.searchForEntry(r);
996      if (e == null)
997      {
998        throw new LDAPException(ResultCode.NO_RESULTS_RETURNED,
999             ERR_PERSISTER_BIND_NO_ENTRY_FOUND.get());
1000      }
1001      else
1002      {
1003        dn = e.getDN();
1004      }
1005    }
1006
1007    return c.bind(new SimpleBindRequest(dn, password, controls));
1008  }
1009
1010
1011
1012  /**
1013   * Constructs the DN of the associated entry from the provided object and
1014   * parent DN and retrieves the contents of that entry as a new instance of
1015   * that object.
1016   *
1017   * @param  o         An object instance to use to construct the DN of the
1018   *                   entry to retrieve.  It must not be {@code null}, and all
1019   *                   fields and/or getter methods marked for inclusion in the
1020   *                   entry RDN must have non-{@code null} values.
1021   * @param  i         The interface to use to communicate with the directory
1022   *                   server. It must not be {@code null}.
1023   * @param  parentDN  The parent DN to use for the entry to retrieve.  If the
1024   *                   provided object was previously read from a directory
1025   *                   server and includes a field marked with the
1026   *                   {@link LDAPDNField} or {@link LDAPEntryField} annotation,
1027   *                   then that field may be used to retrieve the actual DN of
1028   *                   the associated entry.  If the actual DN of the target
1029   *                   entry is not available, then a DN will be constructed
1030   *                   from the RDN fields and/or getter methods declared in the
1031   *                   class and this parent DN.  If the provided parent DN is
1032   *                   {@code null}, then the default parent DN defined in the
1033   *                   {@link LDAPObject} annotation will be used.
1034   *
1035   * @return  The object read from the entry with the provided DN, or
1036   *          {@code null} if no entry exists with the constructed DN.
1037   *
1038   * @throws  LDAPPersistException  If a problem occurs while attempting to
1039   *                                construct the entry DN, retrieve the
1040   *                                corresponding entry or decode it as an
1041   *                                object.
1042   */
1043  public T get(final T o, final LDAPInterface i, final String parentDN)
1044         throws LDAPPersistException
1045  {
1046    final String dn = handler.constructDN(o, parentDN);
1047
1048    final Entry entry;
1049    try
1050    {
1051      entry = i.getEntry(dn, handler.getAttributesToRequest());
1052      if (entry == null)
1053      {
1054        return null;
1055      }
1056    }
1057    catch (final LDAPException le)
1058    {
1059      Debug.debugException(le);
1060      throw new LDAPPersistException(le);
1061    }
1062
1063    return decode(entry);
1064  }
1065
1066
1067
1068  /**
1069   * Retrieves the object from the directory entry with the provided DN.
1070   *
1071   * @param  dn  The DN of the entry to retrieve and decode.  It must not be
1072   *             {@code null}.
1073   * @param  i   The interface to use to communicate with the directory server.
1074   *             It must not be {@code null}.
1075   *
1076   * @return  The object read from the entry with the provided DN, or
1077   *          {@code null} if no entry exists with the provided DN.
1078   *
1079   * @throws  LDAPPersistException  If a problem occurs while attempting to
1080   *                                retrieve the specified entry or decode it
1081   *                                as an object.
1082   */
1083  public T get(final String dn, final LDAPInterface i)
1084         throws LDAPPersistException
1085  {
1086    final Entry entry;
1087    try
1088    {
1089      entry = i.getEntry(dn, handler.getAttributesToRequest());
1090      if (entry == null)
1091      {
1092        return null;
1093      }
1094    }
1095    catch (final LDAPException le)
1096    {
1097      Debug.debugException(le);
1098      throw new LDAPPersistException(le);
1099    }
1100
1101    return decode(entry);
1102  }
1103
1104
1105
1106  /**
1107   * Initializes any fields in the provided object marked for lazy loading.
1108   *
1109   * @param  o       The object to be updated.  It must not be {@code null}.
1110   * @param  i       The interface to use to communicate with the directory
1111   *                 server.  It must not be {@code null}.
1112   * @param  fields  The set of fields that should be loaded.  Any fields
1113   *                 included in this list which aren't marked for lazy loading
1114   *                 will be ignored.  If this is empty or {@code null}, then
1115   *                 all lazily-loaded fields will be requested.
1116   *
1117   * @throws  LDAPPersistException  If a problem occurs while attempting to
1118   *                                retrieve or process the associated entry.
1119   *                                If an exception is thrown, then all content
1120   *                                from the provided object that is not lazily
1121   *                                loaded should remain valid, and some
1122   *                                lazily-loaded fields may have been
1123   *                                initialized.
1124   */
1125  public void lazilyLoad(final T o, final LDAPInterface i,
1126                         final FieldInfo... fields)
1127         throws LDAPPersistException
1128  {
1129    Validator.ensureNotNull(o, i);
1130
1131    final String[] attrs;
1132    if ((fields == null) || (fields.length == 0))
1133    {
1134      attrs = handler.getLazilyLoadedAttributes();
1135    }
1136    else
1137    {
1138      final ArrayList<String> attrList = new ArrayList<>(fields.length);
1139      for (final FieldInfo f : fields)
1140      {
1141        if (f.lazilyLoad())
1142        {
1143          attrList.add(f.getAttributeName());
1144        }
1145      }
1146      attrs = new String[attrList.size()];
1147      attrList.toArray(attrs);
1148    }
1149
1150    if (attrs.length == 0)
1151    {
1152      return;
1153    }
1154
1155    final String dn = handler.getEntryDN(o);
1156    if (dn == null)
1157    {
1158      throw new LDAPPersistException(ERR_PERSISTER_LAZILY_LOAD_NO_DN.get());
1159    }
1160
1161    final Entry entry;
1162    try
1163    {
1164      entry = i.getEntry(handler.getEntryDN(o), attrs);
1165    }
1166    catch (final LDAPException le)
1167    {
1168      Debug.debugException(le);
1169      throw new LDAPPersistException(le);
1170    }
1171
1172    if (entry == null)
1173    {
1174      throw new LDAPPersistException(
1175           ERR_PERSISTER_LAZILY_LOAD_NO_ENTRY.get(dn));
1176    }
1177
1178    boolean successful = true;
1179    final ArrayList<String> failureReasons = new ArrayList<>(5);
1180    final Map<String,FieldInfo> fieldMap = handler.getFields();
1181    for (final Attribute a : entry.getAttributes())
1182    {
1183      final String lowerName = StaticUtils.toLowerCase(a.getName());
1184      final FieldInfo f = fieldMap.get(lowerName);
1185      if (f != null)
1186      {
1187        successful &= f.decode(o, entry, failureReasons);
1188      }
1189    }
1190
1191    if (! successful)
1192    {
1193      throw new LDAPPersistException(
1194           StaticUtils.concatenateStrings(failureReasons), o, null);
1195    }
1196  }
1197
1198
1199
1200  /**
1201   * Performs a search in the directory for objects matching the contents of the
1202   * provided object.  A search filter will be generated from the provided
1203   * object containing all non-{@code null} values from fields and getter
1204   * methods whose {@link LDAPField} or {@link LDAPGetter} annotation has
1205   * the {@code inFilter} element set to {@code true}.
1206   * <BR><BR>
1207   * The search performed will be a subtree search using a base DN equal to the
1208   * {@link LDAPObject#defaultParentDN} element in the {@code LDAPObject}
1209   * annotation.  It will not enforce a client-side time limit or size limit.
1210   * <BR><BR>
1211   * Note that this method requires an {@link LDAPConnection} argument rather
1212   * than using the more generic {@link LDAPInterface} type because the search
1213   * is invoked as an asynchronous operation, which is not supported by the
1214   * generic {@code LDAPInterface} interface.  It also means that the provided
1215   * connection must not be configured to operate in synchronous mode (via the
1216   * {@link com.unboundid.ldap.sdk.LDAPConnectionOptions#setUseSynchronousMode}
1217   * option).
1218   *
1219   * @param  o  The object to use to construct the search filter.  It must not
1220   *            be {@code null}.
1221   * @param  c  The connection to use to communicate with the directory server.
1222   *            It must not be {@code null}.
1223   *
1224   * @return  A results object that may be used to iterate through the objects
1225   *          returned from the search.
1226   *
1227   * @throws  LDAPPersistException  If an error occurs while preparing or
1228   *                                sending the search request.
1229   */
1230  public PersistedObjects<T> search(final T o, final LDAPConnection c)
1231         throws LDAPPersistException
1232  {
1233    return search(o, c, null, SearchScope.SUB, DereferencePolicy.NEVER, 0, 0,
1234         null, NO_CONTROLS);
1235  }
1236
1237
1238
1239  /**
1240   * Performs a search in the directory for objects matching the contents of the
1241   * provided object.  A search filter will be generated from the provided
1242   * object containing all non-{@code null} values from fields and getter
1243   * methods whose {@link LDAPField} or {@link LDAPGetter} annotation has
1244   * the {@code inFilter} element set to {@code true}.
1245   * <BR><BR>
1246   * Note that this method requires an {@link LDAPConnection} argument rather
1247   * than using the more generic {@link LDAPInterface} type because the search
1248   * is invoked as an asynchronous operation, which is not supported by the
1249   * generic {@code LDAPInterface} interface.  It also means that the provided
1250   * connection must not be configured to operate in synchronous mode (via the
1251   * {@link com.unboundid.ldap.sdk.LDAPConnectionOptions#setUseSynchronousMode}
1252   * option).
1253   *
1254   * @param  o       The object to use to construct the search filter.  It must
1255   *                 not be {@code null}.
1256   * @param  c       The connection to use to communicate with the directory
1257   *                 server. It must not be {@code null}.
1258   * @param  baseDN  The base DN to use for the search.  It may be {@code null}
1259   *                 if the {@link LDAPObject#defaultParentDN} element in the
1260   *                 {@code LDAPObject} should be used as the base DN.
1261   * @param  scope   The scope to use for the search operation.  It must not be
1262   *                 {@code null}.
1263   *
1264   * @return  A results object that may be used to iterate through the objects
1265   *          returned from the search.
1266   *
1267   * @throws  LDAPPersistException  If an error occurs while preparing or
1268   *                                sending the search request.
1269   */
1270  public PersistedObjects<T> search(final T o, final LDAPConnection c,
1271                                    final String baseDN,
1272                                    final SearchScope scope)
1273         throws LDAPPersistException
1274  {
1275    return search(o, c, baseDN, scope, DereferencePolicy.NEVER, 0, 0, null,
1276         NO_CONTROLS);
1277  }
1278
1279
1280
1281  /**
1282   * Performs a search in the directory for objects matching the contents of
1283   * the provided object.  A search filter will be generated from the provided
1284   * object containing all non-{@code null} values from fields and getter
1285   * methods whose {@link LDAPField} or {@link LDAPGetter} annotation has
1286   * the {@code inFilter} element set to {@code true}.
1287   * <BR><BR>
1288   * Note that this method requires an {@link LDAPConnection} argument rather
1289   * than using the more generic {@link LDAPInterface} type because the search
1290   * is invoked as an asynchronous operation, which is not supported by the
1291   * generic {@code LDAPInterface} interface.  It also means that the provided
1292   * connection must not be configured to operate in synchronous mode (via the
1293   * {@link com.unboundid.ldap.sdk.LDAPConnectionOptions#setUseSynchronousMode}
1294   * option).
1295   *
1296   * @param  o            The object to use to construct the search filter.  It
1297   *                      must not be {@code null}.
1298   * @param  c            The connection to use to communicate with the
1299   *                      directory server.  It must not be {@code null}.
1300   * @param  baseDN       The base DN to use for the search.  It may be
1301   *                      {@code null} if the {@link LDAPObject#defaultParentDN}
1302   *                      element in the {@code LDAPObject} should be used as
1303   *                      the base DN.
1304   * @param  scope        The scope to use for the search operation.  It must
1305   *                      not be {@code null}.
1306   * @param  derefPolicy  The dereference policy to use for the search
1307   *                      operation.  It must not be {@code null}.
1308   * @param  sizeLimit    The maximum number of entries to retrieve from the
1309   *                      directory.  A value of zero indicates that no
1310   *                      client-requested size limit should be enforced.
1311   * @param  timeLimit    The maximum length of time in seconds that the server
1312   *                      should spend processing the search.  A value of zero
1313   *                      indicates that no client-requested time limit should
1314   *                      be enforced.
1315   * @param  extraFilter  An optional additional filter to be ANDed with the
1316   *                      filter generated from the provided object.  If this is
1317   *                      {@code null}, then only the filter generated from the
1318   *                      object will be used.
1319   * @param  controls     An optional set of controls to include in the search
1320   *                      request.  It may be empty or {@code null} if no
1321   *                      controls are needed.
1322   *
1323   * @return  A results object that may be used to iterate through the objects
1324   *          returned from the search.
1325   *
1326   * @throws  LDAPPersistException  If an error occurs while preparing or
1327   *                                sending the search request.
1328   */
1329  public PersistedObjects<T> search(final T o, final LDAPConnection c,
1330                                    final String baseDN,
1331                                    final SearchScope scope,
1332                                    final DereferencePolicy derefPolicy,
1333                                    final int sizeLimit, final int timeLimit,
1334                                    final Filter extraFilter,
1335                                    final Control... controls)
1336         throws LDAPPersistException
1337  {
1338    Validator.ensureNotNull(o, c, scope, derefPolicy);
1339
1340    final String base;
1341    if (baseDN == null)
1342    {
1343      base = handler.getDefaultParentDN().toString();
1344    }
1345    else
1346    {
1347      base = baseDN;
1348    }
1349
1350    final Filter filter;
1351    if (extraFilter == null)
1352    {
1353      filter = handler.createFilter(o);
1354    }
1355    else
1356    {
1357      filter = Filter.createANDFilter(handler.createFilter(o), extraFilter);
1358    }
1359
1360    final SearchRequest searchRequest = new SearchRequest(base, scope,
1361         derefPolicy, sizeLimit, timeLimit, false, filter,
1362         handler.getAttributesToRequest());
1363    if (controls != null)
1364    {
1365      searchRequest.setControls(controls);
1366    }
1367
1368    final LDAPEntrySource entrySource;
1369    try
1370    {
1371      entrySource = new LDAPEntrySource(c, searchRequest, false);
1372    }
1373    catch (final LDAPException le)
1374    {
1375      Debug.debugException(le);
1376      throw new LDAPPersistException(le);
1377    }
1378
1379    return new PersistedObjects<>(this, entrySource);
1380  }
1381
1382
1383
1384  /**
1385   * Performs a search in the directory for objects matching the contents of the
1386   * provided object.  A search filter will be generated from the provided
1387   * object containing all non-{@code null} values from fields and getter
1388   * methods whose {@link LDAPField} or {@link LDAPGetter} annotation has
1389   * the {@code inFilter} element set to {@code true}.
1390   * <BR><BR>
1391   * The search performed will be a subtree search using a base DN equal to the
1392   * {@link LDAPObject#defaultParentDN} element in the {@code LDAPObject}
1393   * annotation.  It will not enforce a client-side time limit or size limit.
1394   *
1395   * @param  o  The object to use to construct the search filter.  It must not
1396   *            be {@code null}.
1397   * @param  i  The interface to use to communicate with the directory server.
1398   *            It must not be {@code null}.
1399   * @param  l  The object search result listener that will be used to receive
1400   *            objects decoded from entries returned for the search.  It must
1401   *            not be {@code null}.
1402   *
1403   * @return  The result of the search operation that was processed.
1404   *
1405   * @throws  LDAPPersistException  If an error occurs while preparing or
1406   *                                sending the search request.
1407   */
1408  public SearchResult search(final T o, final LDAPInterface i,
1409                             final ObjectSearchListener<T> l)
1410         throws LDAPPersistException
1411  {
1412    return search(o, i, null, SearchScope.SUB, DereferencePolicy.NEVER, 0, 0,
1413         null, l, NO_CONTROLS);
1414  }
1415
1416
1417
1418  /**
1419   * Performs a search in the directory for objects matching the contents of the
1420   * provided object.  A search filter will be generated from the provided
1421   * object containing all non-{@code null} values from fields and getter
1422   * methods whose {@link LDAPField} or {@link LDAPGetter} annotation has
1423   * the {@code inFilter} element set to {@code true}.
1424   *
1425   * @param  o       The object to use to construct the search filter.  It must
1426   *                 not be {@code null}.
1427   * @param  i       The interface to use to communicate with the directory
1428   *                 server. It must not be {@code null}.
1429   * @param  baseDN  The base DN to use for the search.  It may be {@code null}
1430   *                 if the {@link LDAPObject#defaultParentDN} element in the
1431   *                 {@code LDAPObject} should be used as the base DN.
1432   * @param  scope   The scope to use for the search operation.  It must not be
1433   *                 {@code null}.
1434   * @param  l       The object search result listener that will be used to
1435   *                 receive objects decoded from entries returned for the
1436   *                 search.  It must not be {@code null}.
1437   *
1438   * @return  The result of the search operation that was processed.
1439   *
1440   * @throws  LDAPPersistException  If an error occurs while preparing or
1441   *                                sending the search request.
1442   */
1443  public SearchResult search(final T o, final LDAPInterface i,
1444                             final String baseDN, final SearchScope scope,
1445                             final ObjectSearchListener<T> l)
1446         throws LDAPPersistException
1447  {
1448    return search(o, i, baseDN, scope, DereferencePolicy.NEVER, 0, 0, null, l,
1449         NO_CONTROLS);
1450  }
1451
1452
1453
1454  /**
1455   * Performs a search in the directory for objects matching the contents of
1456   * the provided object.  A search filter will be generated from the provided
1457   * object containing all non-{@code null} values from fields and getter
1458   * methods whose {@link LDAPField} or {@link LDAPGetter} annotation has
1459   * the {@code inFilter} element set to {@code true}.
1460   *
1461   * @param  o            The object to use to construct the search filter.  It
1462   *                      must not be {@code null}.
1463   * @param  i            The connection to use to communicate with the
1464   *                      directory server.  It must not be {@code null}.
1465   * @param  baseDN       The base DN to use for the search.  It may be
1466   *                      {@code null} if the {@link LDAPObject#defaultParentDN}
1467   *                      element in the {@code LDAPObject} should be used as
1468   *                      the base DN.
1469   * @param  scope        The scope to use for the search operation.  It must
1470   *                      not be {@code null}.
1471   * @param  derefPolicy  The dereference policy to use for the search
1472   *                      operation.  It must not be {@code null}.
1473   * @param  sizeLimit    The maximum number of entries to retrieve from the
1474   *                      directory.  A value of zero indicates that no
1475   *                      client-requested size limit should be enforced.
1476   * @param  timeLimit    The maximum length of time in seconds that the server
1477   *                      should spend processing the search.  A value of zero
1478   *                      indicates that no client-requested time limit should
1479   *                      be enforced.
1480   * @param  extraFilter  An optional additional filter to be ANDed with the
1481   *                      filter generated from the provided object.  If this is
1482   *                      {@code null}, then only the filter generated from the
1483   *                      object will be used.
1484   * @param  l            The object search result listener that will be used
1485   *                      to receive objects decoded from entries returned for
1486   *                      the search.  It must not be {@code null}.
1487   * @param  controls     An optional set of controls to include in the search
1488   *                      request.  It may be empty or {@code null} if no
1489   *                      controls are needed.
1490   *
1491   * @return  The result of the search operation that was processed.
1492   *
1493   * @throws  LDAPPersistException  If an error occurs while preparing or
1494   *                                sending the search request.
1495   */
1496  public SearchResult search(final T o, final LDAPInterface i,
1497                             final String baseDN, final SearchScope scope,
1498                             final DereferencePolicy derefPolicy,
1499                             final int sizeLimit, final int timeLimit,
1500                             final Filter extraFilter,
1501                             final ObjectSearchListener<T> l,
1502                             final Control... controls)
1503         throws LDAPPersistException
1504  {
1505    Validator.ensureNotNull(o, i, scope, derefPolicy, l);
1506
1507    final String base;
1508    if (baseDN == null)
1509    {
1510      base = handler.getDefaultParentDN().toString();
1511    }
1512    else
1513    {
1514      base = baseDN;
1515    }
1516
1517    final Filter filter;
1518    if (extraFilter == null)
1519    {
1520      filter = handler.createFilter(o);
1521    }
1522    else
1523    {
1524      filter = Filter.simplifyFilter(
1525           Filter.createANDFilter(handler.createFilter(o), extraFilter), true);
1526    }
1527
1528    final SearchListenerBridge<T> bridge = new SearchListenerBridge<>(this, l);
1529
1530    final SearchRequest searchRequest = new SearchRequest(bridge, base, scope,
1531         derefPolicy, sizeLimit, timeLimit, false, filter,
1532         handler.getAttributesToRequest());
1533    if (controls != null)
1534    {
1535      searchRequest.setControls(controls);
1536    }
1537
1538    try
1539    {
1540      return i.search(searchRequest);
1541    }
1542    catch (final LDAPException le)
1543    {
1544      Debug.debugException(le);
1545      throw new LDAPPersistException(le);
1546    }
1547  }
1548
1549
1550
1551  /**
1552   * Performs a search in the directory using the provided search criteria and
1553   * decodes all entries returned as objects of the associated type.
1554   *
1555   * @param  c            The connection to use to communicate with the
1556   *                      directory server.  It must not be {@code null}.
1557   * @param  baseDN       The base DN to use for the search.  It may be
1558   *                      {@code null} if the {@link LDAPObject#defaultParentDN}
1559   *                      element in the {@code LDAPObject} should be used as
1560   *                      the base DN.
1561   * @param  scope        The scope to use for the search operation.  It must
1562   *                      not be {@code null}.
1563   * @param  derefPolicy  The dereference policy to use for the search
1564   *                      operation.  It must not be {@code null}.
1565   * @param  sizeLimit    The maximum number of entries to retrieve from the
1566   *                      directory.  A value of zero indicates that no
1567   *                      client-requested size limit should be enforced.
1568   * @param  timeLimit    The maximum length of time in seconds that the server
1569   *                      should spend processing the search.  A value of zero
1570   *                      indicates that no client-requested time limit should
1571   *                      be enforced.
1572   * @param  filter       The filter to use for the search.  It must not be
1573   *                      {@code null}.  It will automatically be ANDed with a
1574   *                      filter that will match entries with the structural and
1575   *                      auxiliary classes.
1576   * @param  controls     An optional set of controls to include in the search
1577   *                      request.  It may be empty or {@code null} if no
1578   *                      controls are needed.
1579   *
1580   * @return  The result of the search operation that was processed.
1581   *
1582   * @throws  LDAPPersistException  If an error occurs while preparing or
1583   *                                sending the search request.
1584   */
1585  public PersistedObjects<T> search(final LDAPConnection c, final String baseDN,
1586                                    final SearchScope scope,
1587                                    final DereferencePolicy derefPolicy,
1588                                    final int sizeLimit, final int timeLimit,
1589                                    final Filter filter,
1590                                    final Control... controls)
1591         throws LDAPPersistException
1592  {
1593    Validator.ensureNotNull(c, scope, derefPolicy, filter);
1594
1595    final String base;
1596    if (baseDN == null)
1597    {
1598      base = handler.getDefaultParentDN().toString();
1599    }
1600    else
1601    {
1602      base = baseDN;
1603    }
1604
1605    final Filter f = Filter.createANDFilter(filter, handler.createBaseFilter());
1606
1607    final SearchRequest searchRequest = new SearchRequest(base, scope,
1608         derefPolicy, sizeLimit, timeLimit, false, f,
1609         handler.getAttributesToRequest());
1610    if (controls != null)
1611    {
1612      searchRequest.setControls(controls);
1613    }
1614
1615    final LDAPEntrySource entrySource;
1616    try
1617    {
1618      entrySource = new LDAPEntrySource(c, searchRequest, false);
1619    }
1620    catch (final LDAPException le)
1621    {
1622      Debug.debugException(le);
1623      throw new LDAPPersistException(le);
1624    }
1625
1626    return new PersistedObjects<>(this, entrySource);
1627  }
1628
1629
1630
1631  /**
1632   * Performs a search in the directory using the provided search criteria and
1633   * decodes all entries returned as objects of the associated type.
1634   *
1635   * @param  i            The connection to use to communicate with the
1636   *                      directory server.  It must not be {@code null}.
1637   * @param  baseDN       The base DN to use for the search.  It may be
1638   *                      {@code null} if the {@link LDAPObject#defaultParentDN}
1639   *                      element in the {@code LDAPObject} should be used as
1640   *                      the base DN.
1641   * @param  scope        The scope to use for the search operation.  It must
1642   *                      not be {@code null}.
1643   * @param  derefPolicy  The dereference policy to use for the search
1644   *                      operation.  It must not be {@code null}.
1645   * @param  sizeLimit    The maximum number of entries to retrieve from the
1646   *                      directory.  A value of zero indicates that no
1647   *                      client-requested size limit should be enforced.
1648   * @param  timeLimit    The maximum length of time in seconds that the server
1649   *                      should spend processing the search.  A value of zero
1650   *                      indicates that no client-requested time limit should
1651   *                      be enforced.
1652   * @param  filter       The filter to use for the search.  It must not be
1653   *                      {@code null}.  It will automatically be ANDed with a
1654   *                      filter that will match entries with the structural and
1655   *                      auxiliary classes.
1656   * @param  l            The object search result listener that will be used
1657   *                      to receive objects decoded from entries returned for
1658   *                      the search.  It must not be {@code null}.
1659   * @param  controls     An optional set of controls to include in the search
1660   *                      request.  It may be empty or {@code null} if no
1661   *                      controls are needed.
1662   *
1663   * @return  The result of the search operation that was processed.
1664   *
1665   * @throws  LDAPPersistException  If an error occurs while preparing or
1666   *                                sending the search request.
1667   */
1668  public SearchResult search(final LDAPInterface i, final String baseDN,
1669                             final SearchScope scope,
1670                             final DereferencePolicy derefPolicy,
1671                             final int sizeLimit, final int timeLimit,
1672                             final Filter filter,
1673                             final ObjectSearchListener<T> l,
1674                             final Control... controls)
1675         throws LDAPPersistException
1676  {
1677    Validator.ensureNotNull(i, scope, derefPolicy, filter, l);
1678
1679    final String base;
1680    if (baseDN == null)
1681    {
1682      base = handler.getDefaultParentDN().toString();
1683    }
1684    else
1685    {
1686      base = baseDN;
1687    }
1688
1689    final Filter f = Filter.simplifyFilter(
1690         Filter.createANDFilter(filter, handler.createBaseFilter()), true);
1691    final SearchListenerBridge<T> bridge = new SearchListenerBridge<>(this, l);
1692
1693    final SearchRequest searchRequest = new SearchRequest(bridge, base, scope,
1694         derefPolicy, sizeLimit, timeLimit, false, f,
1695         handler.getAttributesToRequest());
1696    if (controls != null)
1697    {
1698      searchRequest.setControls(controls);
1699    }
1700
1701    try
1702    {
1703      return i.search(searchRequest);
1704    }
1705    catch (final LDAPException le)
1706    {
1707      Debug.debugException(le);
1708      throw new LDAPPersistException(le);
1709    }
1710  }
1711
1712
1713
1714  /**
1715   * Performs a search in the directory to retrieve the object whose contents
1716   * match the contents of the provided object.  It is expected that at most one
1717   * entry matches the provided criteria, and that it can be decoded as an
1718   * object of the associated type.  If multiple entries match the resulting
1719   * criteria, or if the matching entry cannot be decoded as the associated type
1720   * of object, then an exception will be thrown.
1721   * <BR><BR>
1722   * A search filter will be generated from the provided object containing all
1723   * non-{@code null} values from fields and getter methods whose
1724   * {@link LDAPField} or {@link LDAPGetter} annotation has the {@code inFilter}
1725   * element set to {@code true}.
1726   * <BR><BR>
1727   * The search performed will be a subtree search using a base DN equal to the
1728   * {@link LDAPObject#defaultParentDN} element in the {@code LDAPObject}
1729   * annotation.  It will not enforce a client-side time limit or size limit.
1730   *
1731   * @param  o  The object to use to construct the search filter.  It must not
1732   *            be {@code null}.
1733   * @param  i  The interface to use to communicate with the directory server.
1734   *            It must not be {@code null}.
1735   *
1736   * @return  The object constructed from the entry returned by the search, or
1737   *          {@code null} if no entry was returned.
1738   *
1739   * @throws  LDAPPersistException  If an error occurs while preparing or
1740   *                                sending the search request or decoding the
1741   *                                entry that was returned.
1742   */
1743  public T searchForObject(final T o, final LDAPInterface i)
1744         throws LDAPPersistException
1745  {
1746    return searchForObject(o, i, null, SearchScope.SUB, DereferencePolicy.NEVER,
1747         0, 0, null, NO_CONTROLS);
1748  }
1749
1750
1751
1752  /**
1753   * Performs a search in the directory to retrieve the object whose contents
1754   * match the contents of the provided object.  It is expected that at most one
1755   * entry matches the provided criteria, and that it can be decoded as an
1756   * object of the associated type.  If multiple entries match the resulting
1757   * criteria, or if the matching entry cannot be decoded as the associated type
1758   * of object, then an exception will be thrown.
1759   * <BR><BR>
1760   * A search filter will be generated from the provided object containing all
1761   * non-{@code null} values from fields and getter methods whose
1762   * {@link LDAPField} or {@link LDAPGetter} annotation has the {@code inFilter}
1763   * element set to {@code true}.
1764   *
1765   * @param  o       The object to use to construct the search filter.  It must
1766   *                 not be {@code null}.
1767   * @param  i       The interface to use to communicate with the directory
1768   *                 server. It must not be {@code null}.
1769   * @param  baseDN  The base DN to use for the search.  It may be {@code null}
1770   *                 if the {@link LDAPObject#defaultParentDN} element in the
1771   *                 {@code LDAPObject} should be used as the base DN.
1772   * @param  scope   The scope to use for the search operation.  It must not be
1773   *                 {@code null}.
1774   *
1775   * @return  The object constructed from the entry returned by the search, or
1776   *          {@code null} if no entry was returned.
1777   *
1778   * @throws  LDAPPersistException  If an error occurs while preparing or
1779   *                                sending the search request or decoding the
1780   *                                entry that was returned.
1781   */
1782  public T searchForObject(final T o, final LDAPInterface i,
1783                           final String baseDN, final SearchScope scope)
1784         throws LDAPPersistException
1785  {
1786    return searchForObject(o, i, baseDN, scope, DereferencePolicy.NEVER, 0, 0,
1787         null, NO_CONTROLS);
1788  }
1789
1790
1791
1792  /**
1793   * Performs a search in the directory to retrieve the object whose contents
1794   * match the contents of the provided object.  It is expected that at most one
1795   * entry matches the provided criteria, and that it can be decoded as an
1796   * object of the associated type.  If multiple entries match the resulting
1797   * criteria, or if the matching entry cannot be decoded as the associated type
1798   * of object, then an exception will be thrown.
1799   * <BR><BR>
1800   * A search filter will be generated from the provided object containing all
1801   * non-{@code null} values from fields and getter methods whose
1802   * {@link LDAPField} or {@link LDAPGetter} annotation has the {@code inFilter}
1803   * element set to {@code true}.
1804   *
1805   * @param  o            The object to use to construct the search filter.  It
1806   *                      must not be {@code null}.
1807   * @param  i            The connection to use to communicate with the
1808   *                      directory server.  It must not be {@code null}.
1809   * @param  baseDN       The base DN to use for the search.  It may be
1810   *                      {@code null} if the {@link LDAPObject#defaultParentDN}
1811   *                      element in the {@code LDAPObject} should be used as
1812   *                      the base DN.
1813   * @param  scope        The scope to use for the search operation.  It must
1814   *                      not be {@code null}.
1815   * @param  derefPolicy  The dereference policy to use for the search
1816   *                      operation.  It must not be {@code null}.
1817   * @param  sizeLimit    The maximum number of entries to retrieve from the
1818   *                      directory.  A value of zero indicates that no
1819   *                      client-requested size limit should be enforced.
1820   * @param  timeLimit    The maximum length of time in seconds that the server
1821   *                      should spend processing the search.  A value of zero
1822   *                      indicates that no client-requested time limit should
1823   *                      be enforced.
1824   * @param  extraFilter  An optional additional filter to be ANDed with the
1825   *                      filter generated from the provided object.  If this is
1826   *                      {@code null}, then only the filter generated from the
1827   *                      object will be used.
1828   * @param  controls     An optional set of controls to include in the search
1829   *                      request.  It may be empty or {@code null} if no
1830   *                      controls are needed.
1831   *
1832   * @return  The object constructed from the entry returned by the search, or
1833   *          {@code null} if no entry was returned.
1834   *
1835   * @throws  LDAPPersistException  If an error occurs while preparing or
1836   *                                sending the search request or decoding the
1837   *                                entry that was returned.
1838   */
1839  public T searchForObject(final T o, final LDAPInterface i,
1840                           final String baseDN, final SearchScope scope,
1841                           final DereferencePolicy derefPolicy,
1842                           final int sizeLimit, final int timeLimit,
1843                           final Filter extraFilter, final Control... controls)
1844         throws LDAPPersistException
1845  {
1846    Validator.ensureNotNull(o, i, scope, derefPolicy);
1847
1848    final String base;
1849    if (baseDN == null)
1850    {
1851      base = handler.getDefaultParentDN().toString();
1852    }
1853    else
1854    {
1855      base = baseDN;
1856    }
1857
1858    final Filter filter;
1859    if (extraFilter == null)
1860    {
1861      filter = handler.createFilter(o);
1862    }
1863    else
1864    {
1865      filter = Filter.simplifyFilter(
1866           Filter.createANDFilter(handler.createFilter(o), extraFilter), true);
1867    }
1868
1869    final SearchRequest searchRequest = new SearchRequest(base, scope,
1870         derefPolicy, sizeLimit, timeLimit, false, filter,
1871         handler.getAttributesToRequest());
1872    if (controls != null)
1873    {
1874      searchRequest.setControls(controls);
1875    }
1876
1877    try
1878    {
1879      final Entry e = i.searchForEntry(searchRequest);
1880      if (e == null)
1881      {
1882        return null;
1883      }
1884      else
1885      {
1886        return decode(e);
1887      }
1888    }
1889    catch (final LDAPPersistException lpe)
1890    {
1891      Debug.debugException(lpe);
1892      throw lpe;
1893    }
1894    catch (final LDAPException le)
1895    {
1896      Debug.debugException(le);
1897      throw new LDAPPersistException(le);
1898    }
1899  }
1900
1901
1902
1903  /**
1904   * Performs a search in the directory with an attempt to find all objects of
1905   * the specified type below the given base DN (or below the default parent DN
1906   * if no base DN is specified).  Note that this may result in an unindexed
1907   * search, which may be expensive to conduct.  Some servers may require
1908   * special permissions of clients wishing to perform unindexed searches.
1909   *
1910   * @param  i         The connection to use to communicate with the
1911   *                   directory server.  It must not be {@code null}.
1912   * @param  baseDN    The base DN to use for the search.  It may be
1913   *                   {@code null} if the {@link LDAPObject#defaultParentDN}
1914   *                   element in the {@code LDAPObject} should be used as the
1915   *                   base DN.
1916   * @param  l         The object search result listener that will be used to
1917   *                   receive objects decoded from entries returned for the
1918   *                   search.  It must not be {@code null}.
1919   * @param  controls  An optional set of controls to include in the search
1920   *                   request.  It may be empty or {@code null} if no controls
1921   *                   are needed.
1922   *
1923   * @return  The result of the search operation that was processed.
1924   *
1925   * @throws  LDAPPersistException  If an error occurs while preparing or
1926   *                                sending the search request.
1927   */
1928  public SearchResult getAll(final LDAPInterface i, final String baseDN,
1929                             final ObjectSearchListener<T> l,
1930                             final Control... controls)
1931         throws LDAPPersistException
1932  {
1933    Validator.ensureNotNull(i, l);
1934
1935    final String base;
1936    if (baseDN == null)
1937    {
1938      base = handler.getDefaultParentDN().toString();
1939    }
1940    else
1941    {
1942      base = baseDN;
1943    }
1944
1945    final SearchListenerBridge<T> bridge = new SearchListenerBridge<>(this, l);
1946    final SearchRequest searchRequest = new SearchRequest(bridge, base,
1947         SearchScope.SUB, DereferencePolicy.NEVER, 0, 0, false,
1948         handler.createBaseFilter(), handler.getAttributesToRequest());
1949    if (controls != null)
1950    {
1951      searchRequest.setControls(controls);
1952    }
1953
1954    try
1955    {
1956      return i.search(searchRequest);
1957    }
1958    catch (final LDAPException le)
1959    {
1960      Debug.debugException(le);
1961      throw new LDAPPersistException(le);
1962    }
1963  }
1964}