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