001/*
002 * Copyright 2009-2014 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2009-2014 UnboundID Corp.
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 (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 (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    ensureNotNull(o);
703    return handler.getModifications(o, deleteNullValues, attributes);
704  }
705
706
707
708  /**
709   * Updates the stored representation of the provided object in the directory.
710   * If the provided object was retrieved from the directory using the
711   * persistence framework and includes a field with the {@link LDAPEntryField}
712   * annotation, then that entry will be used to make the returned set of
713   * modifications as efficient as possible.  Otherwise, the resulting
714   * modifications will include attempts to replace every attribute which are
715   * associated with fields or getters that should be used in modify operations.
716   * If there are no modifications, then no modification will be attempted, and
717   * this method will return {@code null} rather than an {@code LDAPResult}.
718   *
719   * @param  o                 The object for which to generate the list of
720   *                           modifications.  It must not be {@code null}.
721   * @param  i                 The interface to use to communicate with the
722   *                           directory server.  It must not be {@code null}.
723   * @param  dn                The DN to use for the entry.  It must not be
724   *                           {@code null} if the object was not retrieved from
725   *                           the directory using the persistence framework or
726   *                           does not have a field marked with the
727   *                           {@link LDAPDNField} or {@link LDAPEntryField}
728   *                           annotation.
729   * @param  deleteNullValues  Indicates whether to include modifications that
730   *                           may completely remove an attribute from the
731   *                           entry if the corresponding field or getter method
732   *                           has a value of {@code null}.
733   * @param  attributes        The set of LDAP attributes for which to include
734   *                           modifications.  If this is empty or {@code null},
735   *                           then all attributes marked for inclusion in the
736   *                           modification will be examined.
737   *
738   * @return  The result of processing the modify operation, or {@code null} if
739   *          there were no changes to apply (and therefore no modification was
740   *          performed).
741   *
742   * @throws  LDAPPersistException  If a problem occurs while computing the set
743   *                                of modifications.
744   */
745  public LDAPResult modify(final T o, final LDAPInterface i, final String dn,
746                           final boolean deleteNullValues,
747                           final String... attributes)
748         throws LDAPPersistException
749  {
750    return modify(o, i, dn, deleteNullValues, attributes, NO_CONTROLS);
751  }
752
753
754
755  /**
756   * Updates the stored representation of the provided object in the directory.
757   * If the provided object was retrieved from the directory using the
758   * persistence framework and includes a field with the {@link LDAPEntryField}
759   * annotation, then that entry will be used to make the returned set of
760   * modifications as efficient as possible.  Otherwise, the resulting
761   * modifications will include attempts to replace every attribute which are
762   * associated with fields or getters that should be used in modify operations.
763   * If there are no modifications, then no modification will be attempted, and
764   * this method will return {@code null} rather than an {@code LDAPResult}.
765   *
766   * @param  o                 The object for which to generate the list of
767   *                           modifications.  It must not be {@code null}.
768   * @param  i                 The interface to use to communicate with the
769   *                           directory server.  It must not be {@code null}.
770   * @param  dn                The DN to use for the entry.  It must not be
771   *                           {@code null} if the object was not retrieved from
772   *                           the directory using the persistence framework or
773   *                           does not have a field marked with the
774   *                           {@link LDAPDNField} or {@link LDAPEntryField}
775   *                           annotation.
776   * @param  deleteNullValues  Indicates whether to include modifications that
777   *                           may completely remove an attribute from the
778   *                           entry if the corresponding field or getter method
779   *                           has a value of {@code null}.
780   * @param  attributes        The set of LDAP attributes for which to include
781   *                           modifications.  If this is empty or {@code null},
782   *                           then all attributes marked for inclusion in the
783   *                           modification will be examined.
784   * @param  controls          The optional set of controls to include in the
785   *                           modify request.
786   *
787   * @return  The result of processing the modify operation, or {@code null} if
788   *          there were no changes to apply (and therefore no modification was
789   *          performed).
790   *
791   * @throws  LDAPPersistException  If a problem occurs while computing the set
792   *                                of modifications.
793   */
794  public LDAPResult modify(final T o, final LDAPInterface i, final String dn,
795                           final boolean deleteNullValues,
796                           final String[] attributes, final Control... controls)
797         throws LDAPPersistException
798  {
799    ensureNotNull(o, i);
800    final List<Modification> mods =
801         handler.getModifications(o, deleteNullValues, attributes);
802    if (mods.isEmpty())
803    {
804      return null;
805    }
806
807    final String targetDN;
808    if (dn == null)
809    {
810      targetDN = handler.getEntryDN(o);
811      if (targetDN == null)
812      {
813        throw new LDAPPersistException(ERR_PERSISTER_MODIFY_NO_DN.get());
814      }
815    }
816    else
817    {
818      targetDN = dn;
819    }
820
821    try
822    {
823      final ModifyRequest modifyRequest = new ModifyRequest(targetDN, mods);
824      if (controls != null)
825      {
826        modifyRequest.setControls(controls);
827      }
828
829      return i.modify(modifyRequest);
830    }
831    catch (LDAPException le)
832    {
833      debugException(le);
834      throw new LDAPPersistException(le);
835    }
836  }
837
838
839
840  /**
841   * Attempts to perform a simple bind as the user specified by the given object
842   * on the provided connection.  The object should represent some kind of entry
843   * capable suitable for use as the target of a simple bind operation.
844   * <BR><BR>
845   * If the provided object was retrieved from the directory and has either an
846   * {@link LDAPDNField} or {@link LDAPEntryField}, then that field will be used
847   * to obtain the DN.  Otherwise, a search will be performed to try to find the
848   * entry that corresponds to the provided object.
849   *
850   * @param  o         The object representing the user as whom to bind.  It
851   *                   must not be {@code null}.
852   * @param  baseDN    The base DN to use if it is necessary to search for the
853   *                   entry.  It may be {@code null} if the
854   *                   {@link LDAPObject#defaultParentDN} element in the
855   *                   {@code LDAPObject} should be used as the base DN.
856   * @param  password  The password to use for the bind.  It must not be
857   *                   {@code null}.
858   * @param  c         The connection to be authenticated.  It must not be
859   *                   {@code null}.
860   * @param  controls  An optional set of controls to include in the bind
861   *                   request.  It may be empty or {@code null} if no controls
862   *                   are needed.
863   *
864   * @return  The result of processing the bind operation.
865   *
866   * @throws  LDAPException  If a problem occurs while attempting to process the
867   *                         search or bind operation.
868   */
869  public BindResult bind(final T o, final String baseDN, final String password,
870                         final LDAPConnection c, final Control... controls)
871         throws LDAPException
872  {
873    ensureNotNull(o, password, c);
874
875    String dn = handler.getEntryDN(o);
876    if (dn == null)
877    {
878      String base = baseDN;
879      if (base == null)
880      {
881        base = handler.getDefaultParentDN().toString();
882      }
883
884      final SearchRequest r = new SearchRequest(base, SearchScope.SUB,
885           handler.createFilter(o), SearchRequest.NO_ATTRIBUTES);
886      r.setSizeLimit(1);
887
888      final Entry e = c.searchForEntry(r);
889      if (e == null)
890      {
891        throw new LDAPException(ResultCode.NO_RESULTS_RETURNED,
892             ERR_PERSISTER_BIND_NO_ENTRY_FOUND.get());
893      }
894      else
895      {
896        dn = e.getDN();
897      }
898    }
899
900    return c.bind(new SimpleBindRequest(dn, password, controls));
901  }
902
903
904
905  /**
906   * Constructs the DN of the associated entry from the provided object and
907   * parent DN and retrieves the contents of that entry as a new instance of
908   * that object.
909   *
910   * @param  o         An object instance to use to construct the DN of the
911   *                   entry to retrieve.  It must not be {@code null}, and all
912   *                   fields and/or getter methods marked for inclusion in the
913   *                   entry RDN must have non-{@code null} values.
914   * @param  i         The interface to use to communicate with the directory
915   *                   server. It must not be {@code null}.
916   * @param  parentDN  The parent DN to use for the entry to retrieve.  If the
917   *                   provided object was previously read from a directory
918   *                   server and includes a field marked with the
919   *                   {@link LDAPDNField} or {@link LDAPEntryField} annotation,
920   *                   then that field may be used to retrieve the actual DN of
921   *                   the associated entry.  If the actual DN of the target
922   *                   entry is not available, then a DN will be constructed
923   *                   from the RDN fields and/or getter methods declared in the
924   *                   class and this parent DN.  If the provided parent DN is
925   *                   {@code null}, then the default parent DN defined in the
926   *                   {@link LDAPObject} annotation will be used.
927   *
928   * @return  The object read from the entry with the provided DN, or
929   *          {@code null} if no entry exists with the constructed DN.
930   *
931   * @throws  LDAPPersistException  If a problem occurs while attempting to
932   *                                construct the entry DN, retrieve the
933   *                                corresponding entry or decode it as an
934   *                                object.
935   */
936  public T get(final T o, final LDAPInterface i, final String parentDN)
937         throws LDAPPersistException
938  {
939    final String dn = handler.constructDN(o, parentDN);
940
941    final Entry entry;
942    try
943    {
944      entry = i.getEntry(dn, handler.getAttributesToRequest());
945      if (entry == null)
946      {
947        return null;
948      }
949    }
950    catch (LDAPException le)
951    {
952      debugException(le);
953      throw new LDAPPersistException(le);
954    }
955
956    return decode(entry);
957  }
958
959
960
961  /**
962   * Retrieves the object from the directory entry with the provided DN.
963   *
964   * @param  dn  The DN of the entry to retrieve and decode.  It must not be
965   *             {@code null}.
966   * @param  i   The interface to use to communicate with the directory server.
967   *             It must not be {@code null}.
968   *
969   * @return  The object read from the entry with the provided DN, or
970   *          {@code null} if no entry exists with the provided DN.
971   *
972   * @throws  LDAPPersistException  If a problem occurs while attempting to
973   *                                retrieve the specified entry or decode it
974   *                                as an object.
975   */
976  public T get(final String dn, final LDAPInterface i)
977         throws LDAPPersistException
978  {
979    final Entry entry;
980    try
981    {
982      entry = i.getEntry(dn, handler.getAttributesToRequest());
983      if (entry == null)
984      {
985        return null;
986      }
987    }
988    catch (LDAPException le)
989    {
990      debugException(le);
991      throw new LDAPPersistException(le);
992    }
993
994    return decode(entry);
995  }
996
997
998
999  /**
1000   * Initializes any fields in the provided object marked for lazy loading.
1001   *
1002   * @param  o       The object to be updated.  It must not be {@code null}.
1003   * @param  i       The interface to use to communicate with the directory
1004   *                 server.  It must not be {@code null}.
1005   * @param  fields  The set of fields that should be loaded.  Any fields
1006   *                 included in this list which aren't marked for lazy loading
1007   *                 will be ignored.  If this is empty or {@code null}, then
1008   *                 all lazily-loaded fields will be requested.
1009   *
1010   * @throws  LDAPPersistException  If a problem occurs while attempting to
1011   *                                retrieve or process the associated entry.
1012   *                                If an exception is thrown, then all content
1013   *                                from the provided object that is not lazily
1014   *                                loaded should remain valid, and some
1015   *                                lazily-loaded fields may have been
1016   *                                initialized.
1017   */
1018  public void lazilyLoad(final T o, final LDAPInterface i,
1019                         final FieldInfo... fields)
1020         throws LDAPPersistException
1021  {
1022    ensureNotNull(o, i);
1023
1024    final String[] attrs;
1025    if ((fields == null) || (fields.length == 0))
1026    {
1027      attrs = handler.getLazilyLoadedAttributes();
1028    }
1029    else
1030    {
1031      final ArrayList<String> attrList = new ArrayList<String>(fields.length);
1032      for (final FieldInfo f : fields)
1033      {
1034        if (f.lazilyLoad())
1035        {
1036          attrList.add(f.getAttributeName());
1037        }
1038      }
1039      attrs = new String[attrList.size()];
1040      attrList.toArray(attrs);
1041    }
1042
1043    if (attrs.length == 0)
1044    {
1045      return;
1046    }
1047
1048    final String dn = handler.getEntryDN(o);
1049    if (dn == null)
1050    {
1051      throw new LDAPPersistException(ERR_PERSISTER_LAZILY_LOAD_NO_DN.get());
1052    }
1053
1054    final Entry entry;
1055    try
1056    {
1057      entry = i.getEntry(handler.getEntryDN(o), attrs);
1058    }
1059    catch (final LDAPException le)
1060    {
1061      debugException(le);
1062      throw new LDAPPersistException(le);
1063    }
1064
1065    if (entry == null)
1066    {
1067      throw new LDAPPersistException(
1068           ERR_PERSISTER_LAZILY_LOAD_NO_ENTRY.get(dn));
1069    }
1070
1071    boolean successful = true;
1072    final ArrayList<String> failureReasons = new ArrayList<String>(5);
1073    final Map<String,FieldInfo> fieldMap = handler.getFields();
1074    for (final Attribute a : entry.getAttributes())
1075    {
1076      final String lowerName = toLowerCase(a.getName());
1077      final FieldInfo f = fieldMap.get(lowerName);
1078      if (f != null)
1079      {
1080        successful &= f.decode(o, entry, failureReasons);
1081      }
1082    }
1083
1084    if (! successful)
1085    {
1086      throw new LDAPPersistException(concatenateStrings(failureReasons), o,
1087           null);
1088    }
1089  }
1090
1091
1092
1093  /**
1094   * Performs a search in the directory for objects matching the contents of the
1095   * provided object.  A search filter will be generated from the provided
1096   * object containing all non-{@code null} values from fields and getter
1097   * methods whose {@link LDAPField} or {@link LDAPGetter} annotation has
1098   * the {@code inFilter} element set to {@code true}.
1099   * <BR><BR>
1100   * The search performed will be a subtree search using a base DN equal to the
1101   * {@link LDAPObject#defaultParentDN} element in the {@code LDAPObject}
1102   * annotation.  It will not enforce a client-side time limit or size limit.
1103   * <BR><BR>
1104   * Note that this method requires an {@link LDAPConnection} argument rather
1105   * than using the more generic {@link LDAPInterface} type because the search
1106   * is invoked as an asynchronous operation, which is not supported by the
1107   * generic {@code LDAPInterface} interface.  It also means that the provided
1108   * connection must not be configured to operate in synchronous mode (via the
1109   * {@link com.unboundid.ldap.sdk.LDAPConnectionOptions#setUseSynchronousMode}
1110   * option).
1111   *
1112   * @param  o  The object to use to construct the search filter.  It must not
1113   *            be {@code null}.
1114   * @param  c  The connection to use to communicate with the directory server.
1115   *            It must not be {@code null}.
1116   *
1117   * @return  A results object that may be used to iterate through the objects
1118   *          returned from the search.
1119   *
1120   * @throws  LDAPPersistException  If an error occurs while preparing or
1121   *                                sending the search request.
1122   */
1123  public PersistedObjects<T> search(final T o, final LDAPConnection c)
1124         throws LDAPPersistException
1125  {
1126    return search(o, c, null, SearchScope.SUB, DereferencePolicy.NEVER, 0, 0,
1127         null, NO_CONTROLS);
1128  }
1129
1130
1131
1132  /**
1133   * Performs a search in the directory for objects matching the contents of the
1134   * provided object.  A search filter will be generated from the provided
1135   * object containing all non-{@code null} values from fields and getter
1136   * methods whose {@link LDAPField} or {@link LDAPGetter} annotation has
1137   * the {@code inFilter} element set to {@code true}.
1138   * <BR><BR>
1139   * Note that this method requires an {@link LDAPConnection} argument rather
1140   * than using the more generic {@link LDAPInterface} type because the search
1141   * is invoked as an asynchronous operation, which is not supported by the
1142   * generic {@code LDAPInterface} interface.  It also means that the provided
1143   * connection must not be configured to operate in synchronous mode (via the
1144   * {@link com.unboundid.ldap.sdk.LDAPConnectionOptions#setUseSynchronousMode}
1145   * option).
1146   *
1147   * @param  o       The object to use to construct the search filter.  It must
1148   *                 not be {@code null}.
1149   * @param  c       The connection to use to communicate with the directory
1150   *                 server. It must not be {@code null}.
1151   * @param  baseDN  The base DN to use for the search.  It may be {@code null}
1152   *                 if the {@link LDAPObject#defaultParentDN} element in the
1153   *                 {@code LDAPObject} should be used as the base DN.
1154   * @param  scope   The scope to use for the search operation.  It must not be
1155   *                 {@code null}.
1156   *
1157   * @return  A results object that may be used to iterate through the objects
1158   *          returned from the search.
1159   *
1160   * @throws  LDAPPersistException  If an error occurs while preparing or
1161   *                                sending the search request.
1162   */
1163  public PersistedObjects<T> search(final T o, final LDAPConnection c,
1164                                    final String baseDN,
1165                                    final SearchScope scope)
1166         throws LDAPPersistException
1167  {
1168    return search(o, c, baseDN, scope, DereferencePolicy.NEVER, 0, 0, null,
1169         NO_CONTROLS);
1170  }
1171
1172
1173
1174  /**
1175   * Performs a search in the directory for objects matching the contents of
1176   * the provided object.  A search filter will be generated from the provided
1177   * object containing all non-{@code null} values from fields and getter
1178   * methods whose {@link LDAPField} or {@link LDAPGetter} annotation has
1179   * the {@code inFilter} element set to {@code true}.
1180   * <BR><BR>
1181   * Note that this method requires an {@link LDAPConnection} argument rather
1182   * than using the more generic {@link LDAPInterface} type because the search
1183   * is invoked as an asynchronous operation, which is not supported by the
1184   * generic {@code LDAPInterface} interface.  It also means that the provided
1185   * connection must not be configured to operate in synchronous mode (via the
1186   * {@link com.unboundid.ldap.sdk.LDAPConnectionOptions#setUseSynchronousMode}
1187   * option).
1188   *
1189   * @param  o            The object to use to construct the search filter.  It
1190   *                      must not be {@code null}.
1191   * @param  c            The connection to use to communicate with the
1192   *                      directory server.  It must not be {@code null}.
1193   * @param  baseDN       The base DN to use for the search.  It may be
1194   *                      {@code null} if the {@link LDAPObject#defaultParentDN}
1195   *                      element in the {@code LDAPObject} should be used as
1196   *                      the base DN.
1197   * @param  scope        The scope to use for the search operation.  It must
1198   *                      not be {@code null}.
1199   * @param  derefPolicy  The dereference policy to use for the search
1200   *                      operation.  It must not be {@code null}.
1201   * @param  sizeLimit    The maximum number of entries to retrieve from the
1202   *                      directory.  A value of zero indicates that no
1203   *                      client-requested size limit should be enforced.
1204   * @param  timeLimit    The maximum length of time in seconds that the server
1205   *                      should spend processing the search.  A value of zero
1206   *                      indicates that no client-requested time limit should
1207   *                      be enforced.
1208   * @param  extraFilter  An optional additional filter to be ANDed with the
1209   *                      filter generated from the provided object.  If this is
1210   *                      {@code null}, then only the filter generated from the
1211   *                      object will be used.
1212   * @param  controls     An optional set of controls to include in the search
1213   *                      request.  It may be empty or {@code null} if no
1214   *                      controls are needed.
1215   *
1216   * @return  A results object that may be used to iterate through the objects
1217   *          returned from the search.
1218   *
1219   * @throws  LDAPPersistException  If an error occurs while preparing or
1220   *                                sending the search request.
1221   */
1222  public PersistedObjects<T> search(final T o, final LDAPConnection c,
1223                                    final String baseDN,
1224                                    final SearchScope scope,
1225                                    final DereferencePolicy derefPolicy,
1226                                    final int sizeLimit, final int timeLimit,
1227                                    final Filter extraFilter,
1228                                    final Control... controls)
1229         throws LDAPPersistException
1230  {
1231    ensureNotNull(o, c, scope, derefPolicy);
1232
1233    final String base;
1234    if (baseDN == null)
1235    {
1236      base = handler.getDefaultParentDN().toString();
1237    }
1238    else
1239    {
1240      base = baseDN;
1241    }
1242
1243    final Filter filter;
1244    if (extraFilter == null)
1245    {
1246      filter = handler.createFilter(o);
1247    }
1248    else
1249    {
1250      filter = Filter.createANDFilter(handler.createFilter(o), extraFilter);
1251    }
1252
1253    final SearchRequest searchRequest = new SearchRequest(base, scope,
1254         derefPolicy, sizeLimit, timeLimit, false, filter,
1255         handler.getAttributesToRequest());
1256    if (controls != null)
1257    {
1258      searchRequest.setControls(controls);
1259    }
1260
1261    final LDAPEntrySource entrySource;
1262    try
1263    {
1264      entrySource = new LDAPEntrySource(c, searchRequest, false);
1265    }
1266    catch (LDAPException le)
1267    {
1268      debugException(le);
1269      throw new LDAPPersistException(le);
1270    }
1271
1272    return new PersistedObjects<T>(this, entrySource);
1273  }
1274
1275
1276
1277  /**
1278   * Performs a search in the directory for objects matching the contents of the
1279   * provided object.  A search filter will be generated from the provided
1280   * object containing all non-{@code null} values from fields and getter
1281   * methods whose {@link LDAPField} or {@link LDAPGetter} annotation has
1282   * the {@code inFilter} element set to {@code true}.
1283   * <BR><BR>
1284   * The search performed will be a subtree search using a base DN equal to the
1285   * {@link LDAPObject#defaultParentDN} element in the {@code LDAPObject}
1286   * annotation.  It will not enforce a client-side time limit or size limit.
1287   *
1288   * @param  o  The object to use to construct the search filter.  It must not
1289   *            be {@code null}.
1290   * @param  i  The interface to use to communicate with the directory server.
1291   *            It must not be {@code null}.
1292   * @param  l  The object search result listener that will be used to receive
1293   *            objects decoded from entries returned for the search.  It must
1294   *            not be {@code null}.
1295   *
1296   * @return  The result of the search operation that was processed.
1297   *
1298   * @throws  LDAPPersistException  If an error occurs while preparing or
1299   *                                sending the search request.
1300   */
1301  public SearchResult search(final T o, final LDAPInterface i,
1302                             final ObjectSearchListener<T> l)
1303         throws LDAPPersistException
1304  {
1305    return search(o, i, null, SearchScope.SUB, DereferencePolicy.NEVER, 0, 0,
1306         null, l, NO_CONTROLS);
1307  }
1308
1309
1310
1311  /**
1312   * Performs a search in the directory for objects matching the contents of the
1313   * provided object.  A search filter will be generated from the provided
1314   * object containing all non-{@code null} values from fields and getter
1315   * methods whose {@link LDAPField} or {@link LDAPGetter} annotation has
1316   * the {@code inFilter} element set to {@code true}.
1317   *
1318   * @param  o       The object to use to construct the search filter.  It must
1319   *                 not be {@code null}.
1320   * @param  i       The interface to use to communicate with the directory
1321   *                 server. It must not be {@code null}.
1322   * @param  baseDN  The base DN to use for the search.  It may be {@code null}
1323   *                 if the {@link LDAPObject#defaultParentDN} element in the
1324   *                 {@code LDAPObject} should be used as the base DN.
1325   * @param  scope   The scope to use for the search operation.  It must not be
1326   *                 {@code null}.
1327   * @param  l       The object search result listener that will be used to
1328   *                 receive objects decoded from entries returned for the
1329   *                 search.  It must not be {@code null}.
1330   *
1331   * @return  The result of the search operation that was processed.
1332   *
1333   * @throws  LDAPPersistException  If an error occurs while preparing or
1334   *                                sending the search request.
1335   */
1336  public SearchResult search(final T o, final LDAPInterface i,
1337                             final String baseDN, final SearchScope scope,
1338                             final ObjectSearchListener<T> l)
1339         throws LDAPPersistException
1340  {
1341    return search(o, i, baseDN, scope, DereferencePolicy.NEVER, 0, 0, null, l,
1342         NO_CONTROLS);
1343  }
1344
1345
1346
1347  /**
1348   * Performs a search in the directory for objects matching the contents of
1349   * the provided object.  A search filter will be generated from the provided
1350   * object containing all non-{@code null} values from fields and getter
1351   * methods whose {@link LDAPField} or {@link LDAPGetter} annotation has
1352   * the {@code inFilter} element set to {@code true}.
1353   *
1354   * @param  o            The object to use to construct the search filter.  It
1355   *                      must not be {@code null}.
1356   * @param  i            The connection to use to communicate with the
1357   *                      directory server.  It must not be {@code null}.
1358   * @param  baseDN       The base DN to use for the search.  It may be
1359   *                      {@code null} if the {@link LDAPObject#defaultParentDN}
1360   *                      element in the {@code LDAPObject} should be used as
1361   *                      the base DN.
1362   * @param  scope        The scope to use for the search operation.  It must
1363   *                      not be {@code null}.
1364   * @param  derefPolicy  The dereference policy to use for the search
1365   *                      operation.  It must not be {@code null}.
1366   * @param  sizeLimit    The maximum number of entries to retrieve from the
1367   *                      directory.  A value of zero indicates that no
1368   *                      client-requested size limit should be enforced.
1369   * @param  timeLimit    The maximum length of time in seconds that the server
1370   *                      should spend processing the search.  A value of zero
1371   *                      indicates that no client-requested time limit should
1372   *                      be enforced.
1373   * @param  extraFilter  An optional additional filter to be ANDed with the
1374   *                      filter generated from the provided object.  If this is
1375   *                      {@code null}, then only the filter generated from the
1376   *                      object will be used.
1377   * @param  l            The object search result listener that will be used
1378   *                      to receive objects decoded from entries returned for
1379   *                      the search.  It must not be {@code null}.
1380   * @param  controls     An optional set of controls to include in the search
1381   *                      request.  It may be empty or {@code null} if no
1382   *                      controls are needed.
1383   *
1384   * @return  The result of the search operation that was processed.
1385   *
1386   * @throws  LDAPPersistException  If an error occurs while preparing or
1387   *                                sending the search request.
1388   */
1389  public SearchResult search(final T o, final LDAPInterface i,
1390                             final String baseDN, final SearchScope scope,
1391                             final DereferencePolicy derefPolicy,
1392                             final int sizeLimit, final int timeLimit,
1393                             final Filter extraFilter,
1394                             final ObjectSearchListener<T> l,
1395                             final Control... controls)
1396         throws LDAPPersistException
1397  {
1398    ensureNotNull(o, i, scope, derefPolicy, l);
1399
1400    final String base;
1401    if (baseDN == null)
1402    {
1403      base = handler.getDefaultParentDN().toString();
1404    }
1405    else
1406    {
1407      base = baseDN;
1408    }
1409
1410    final Filter filter;
1411    if (extraFilter == null)
1412    {
1413      filter = handler.createFilter(o);
1414    }
1415    else
1416    {
1417      filter = Filter.createANDFilter(handler.createFilter(o), extraFilter);
1418    }
1419
1420    final SearchListenerBridge<T> bridge = new SearchListenerBridge<T>(this, l);
1421
1422    final SearchRequest searchRequest = new SearchRequest(bridge, base, scope,
1423         derefPolicy, sizeLimit, timeLimit, false, filter,
1424         handler.getAttributesToRequest());
1425    if (controls != null)
1426    {
1427      searchRequest.setControls(controls);
1428    }
1429
1430    try
1431    {
1432      return i.search(searchRequest);
1433    }
1434    catch (LDAPException le)
1435    {
1436      debugException(le);
1437      throw new LDAPPersistException(le);
1438    }
1439  }
1440
1441
1442
1443  /**
1444   * Performs a search in the directory using the provided search criteria and
1445   * decodes all entries returned as objects of the associated type.
1446   *
1447   * @param  c            The connection to use to communicate with the
1448   *                      directory server.  It must not be {@code null}.
1449   * @param  baseDN       The base DN to use for the search.  It may be
1450   *                      {@code null} if the {@link LDAPObject#defaultParentDN}
1451   *                      element in the {@code LDAPObject} should be used as
1452   *                      the base DN.
1453   * @param  scope        The scope to use for the search operation.  It must
1454   *                      not be {@code null}.
1455   * @param  derefPolicy  The dereference policy to use for the search
1456   *                      operation.  It must not be {@code null}.
1457   * @param  sizeLimit    The maximum number of entries to retrieve from the
1458   *                      directory.  A value of zero indicates that no
1459   *                      client-requested size limit should be enforced.
1460   * @param  timeLimit    The maximum length of time in seconds that the server
1461   *                      should spend processing the search.  A value of zero
1462   *                      indicates that no client-requested time limit should
1463   *                      be enforced.
1464   * @param  filter       The filter to use for the search.  It must not be
1465   *                      {@code null}.  It will automatically be ANDed with a
1466   *                      filter that will match entries with the structural and
1467   *                      auxiliary classes.
1468   * @param  controls     An optional set of controls to include in the search
1469   *                      request.  It may be empty or {@code null} if no
1470   *                      controls are needed.
1471   *
1472   * @return  The result of the search operation that was processed.
1473   *
1474   * @throws  LDAPPersistException  If an error occurs while preparing or
1475   *                                sending the search request.
1476   */
1477  public PersistedObjects<T> search(final LDAPConnection c, final String baseDN,
1478                                    final SearchScope scope,
1479                                    final DereferencePolicy derefPolicy,
1480                                    final int sizeLimit, final int timeLimit,
1481                                    final Filter filter,
1482                                    final Control... controls)
1483         throws LDAPPersistException
1484  {
1485    ensureNotNull(c, scope, derefPolicy, filter);
1486
1487    final String base;
1488    if (baseDN == null)
1489    {
1490      base = handler.getDefaultParentDN().toString();
1491    }
1492    else
1493    {
1494      base = baseDN;
1495    }
1496
1497    final Filter f = Filter.createANDFilter(filter, handler.createBaseFilter());
1498
1499    final SearchRequest searchRequest = new SearchRequest(base, scope,
1500         derefPolicy, sizeLimit, timeLimit, false, f,
1501         handler.getAttributesToRequest());
1502    if (controls != null)
1503    {
1504      searchRequest.setControls(controls);
1505    }
1506
1507    final LDAPEntrySource entrySource;
1508    try
1509    {
1510      entrySource = new LDAPEntrySource(c, searchRequest, false);
1511    }
1512    catch (LDAPException le)
1513    {
1514      debugException(le);
1515      throw new LDAPPersistException(le);
1516    }
1517
1518    return new PersistedObjects<T>(this, entrySource);
1519  }
1520
1521
1522
1523  /**
1524   * Performs a search in the directory using the provided search criteria and
1525   * decodes all entries returned as objects of the associated type.
1526   *
1527   * @param  i            The connection to use to communicate with the
1528   *                      directory server.  It must not be {@code null}.
1529   * @param  baseDN       The base DN to use for the search.  It may be
1530   *                      {@code null} if the {@link LDAPObject#defaultParentDN}
1531   *                      element in the {@code LDAPObject} should be used as
1532   *                      the base DN.
1533   * @param  scope        The scope to use for the search operation.  It must
1534   *                      not be {@code null}.
1535   * @param  derefPolicy  The dereference policy to use for the search
1536   *                      operation.  It must not be {@code null}.
1537   * @param  sizeLimit    The maximum number of entries to retrieve from the
1538   *                      directory.  A value of zero indicates that no
1539   *                      client-requested size limit should be enforced.
1540   * @param  timeLimit    The maximum length of time in seconds that the server
1541   *                      should spend processing the search.  A value of zero
1542   *                      indicates that no client-requested time limit should
1543   *                      be enforced.
1544   * @param  filter       The filter to use for the search.  It must not be
1545   *                      {@code null}.  It will automatically be ANDed with a
1546   *                      filter that will match entries with the structural and
1547   *                      auxiliary classes.
1548   * @param  l            The object search result listener that will be used
1549   *                      to receive objects decoded from entries returned for
1550   *                      the search.  It must not be {@code null}.
1551   * @param  controls     An optional set of controls to include in the search
1552   *                      request.  It may be empty or {@code null} if no
1553   *                      controls are needed.
1554   *
1555   * @return  The result of the search operation that was processed.
1556   *
1557   * @throws  LDAPPersistException  If an error occurs while preparing or
1558   *                                sending the search request.
1559   */
1560  public SearchResult search(final LDAPInterface i, final String baseDN,
1561                             final SearchScope scope,
1562                             final DereferencePolicy derefPolicy,
1563                             final int sizeLimit, final int timeLimit,
1564                             final Filter filter,
1565                             final ObjectSearchListener<T> l,
1566                             final Control... controls)
1567         throws LDAPPersistException
1568  {
1569    ensureNotNull(i, scope, derefPolicy, filter, l);
1570
1571    final String base;
1572    if (baseDN == null)
1573    {
1574      base = handler.getDefaultParentDN().toString();
1575    }
1576    else
1577    {
1578      base = baseDN;
1579    }
1580
1581    final Filter f = Filter.createANDFilter(filter, handler.createBaseFilter());
1582    final SearchListenerBridge<T> bridge = new SearchListenerBridge<T>(this, l);
1583
1584    final SearchRequest searchRequest = new SearchRequest(bridge, base, scope,
1585         derefPolicy, sizeLimit, timeLimit, false, f,
1586         handler.getAttributesToRequest());
1587    if (controls != null)
1588    {
1589      searchRequest.setControls(controls);
1590    }
1591
1592    try
1593    {
1594      return i.search(searchRequest);
1595    }
1596    catch (LDAPException le)
1597    {
1598      debugException(le);
1599      throw new LDAPPersistException(le);
1600    }
1601  }
1602
1603
1604
1605  /**
1606   * Performs a search in the directory to retrieve the object whose contents
1607   * match the contents of the provided object.  It is expected that at most one
1608   * entry matches the provided criteria, and that it can be decoded as an
1609   * object of the associated type.  If multiple entries match the resulting
1610   * criteria, or if the matching entry cannot be decoded as the associated type
1611   * of object, then an exception will be thrown.
1612   * <BR><BR>
1613   * A search filter will be generated from the provided object containing all
1614   * non-{@code null} values from fields and getter methods whose
1615   * {@link LDAPField} or {@link LDAPGetter} annotation has the {@code inFilter}
1616   * element set to {@code true}.
1617   * <BR><BR>
1618   * The search performed will be a subtree search using a base DN equal to the
1619   * {@link LDAPObject#defaultParentDN} element in the {@code LDAPObject}
1620   * annotation.  It will not enforce a client-side time limit or size limit.
1621   *
1622   * @param  o  The object to use to construct the search filter.  It must not
1623   *            be {@code null}.
1624   * @param  i  The interface to use to communicate with the directory server.
1625   *            It must not be {@code null}.
1626   *
1627   * @return  The object constructed from the entry returned by the search, or
1628   *          {@code null} if no entry was returned.
1629   *
1630   * @throws  LDAPPersistException  If an error occurs while preparing or
1631   *                                sending the search request or decoding the
1632   *                                entry that was returned.
1633   */
1634  public T searchForObject(final T o, final LDAPInterface i)
1635         throws LDAPPersistException
1636  {
1637    return searchForObject(o, i, null, SearchScope.SUB, DereferencePolicy.NEVER,
1638         0, 0, null, NO_CONTROLS);
1639  }
1640
1641
1642
1643  /**
1644   * Performs a search in the directory to retrieve the object whose contents
1645   * match the contents of the provided object.  It is expected that at most one
1646   * entry matches the provided criteria, and that it can be decoded as an
1647   * object of the associated type.  If multiple entries match the resulting
1648   * criteria, or if the matching entry cannot be decoded as the associated type
1649   * of object, then an exception will be thrown.
1650   * <BR><BR>
1651   * A search filter will be generated from the provided object containing all
1652   * non-{@code null} values from fields and getter methods whose
1653   * {@link LDAPField} or {@link LDAPGetter} annotation has the {@code inFilter}
1654   * element set to {@code true}.
1655   *
1656   * @param  o       The object to use to construct the search filter.  It must
1657   *                 not be {@code null}.
1658   * @param  i       The interface to use to communicate with the directory
1659   *                 server. It must not be {@code null}.
1660   * @param  baseDN  The base DN to use for the search.  It may be {@code null}
1661   *                 if the {@link LDAPObject#defaultParentDN} element in the
1662   *                 {@code LDAPObject} should be used as the base DN.
1663   * @param  scope   The scope to use for the search operation.  It must not be
1664   *                 {@code null}.
1665   *
1666   * @return  The object constructed from the entry returned by the search, or
1667   *          {@code null} if no entry was returned.
1668   *
1669   * @throws  LDAPPersistException  If an error occurs while preparing or
1670   *                                sending the search request or decoding the
1671   *                                entry that was returned.
1672   */
1673  public T searchForObject(final T o, final LDAPInterface i,
1674                           final String baseDN, final SearchScope scope)
1675         throws LDAPPersistException
1676  {
1677    return searchForObject(o, i, baseDN, scope, DereferencePolicy.NEVER, 0, 0,
1678         null, NO_CONTROLS);
1679  }
1680
1681
1682
1683  /**
1684   * Performs a search in the directory to retrieve the object whose contents
1685   * match the contents of the provided object.  It is expected that at most one
1686   * entry matches the provided criteria, and that it can be decoded as an
1687   * object of the associated type.  If multiple entries match the resulting
1688   * criteria, or if the matching entry cannot be decoded as the associated type
1689   * of object, then an exception will be thrown.
1690   * <BR><BR>
1691   * A search filter will be generated from the provided object containing all
1692   * non-{@code null} values from fields and getter methods whose
1693   * {@link LDAPField} or {@link LDAPGetter} annotation has the {@code inFilter}
1694   * element set to {@code true}.
1695   *
1696   * @param  o            The object to use to construct the search filter.  It
1697   *                      must not be {@code null}.
1698   * @param  i            The connection to use to communicate with the
1699   *                      directory server.  It must not be {@code null}.
1700   * @param  baseDN       The base DN to use for the search.  It may be
1701   *                      {@code null} if the {@link LDAPObject#defaultParentDN}
1702   *                      element in the {@code LDAPObject} should be used as
1703   *                      the base DN.
1704   * @param  scope        The scope to use for the search operation.  It must
1705   *                      not be {@code null}.
1706   * @param  derefPolicy  The dereference policy to use for the search
1707   *                      operation.  It must not be {@code null}.
1708   * @param  sizeLimit    The maximum number of entries to retrieve from the
1709   *                      directory.  A value of zero indicates that no
1710   *                      client-requested size limit should be enforced.
1711   * @param  timeLimit    The maximum length of time in seconds that the server
1712   *                      should spend processing the search.  A value of zero
1713   *                      indicates that no client-requested time limit should
1714   *                      be enforced.
1715   * @param  extraFilter  An optional additional filter to be ANDed with the
1716   *                      filter generated from the provided object.  If this is
1717   *                      {@code null}, then only the filter generated from the
1718   *                      object will be used.
1719   * @param  controls     An optional set of controls to include in the search
1720   *                      request.  It may be empty or {@code null} if no
1721   *                      controls are needed.
1722   *
1723   * @return  The object constructed from the entry returned by the search, or
1724   *          {@code null} if no entry was returned.
1725   *
1726   * @throws  LDAPPersistException  If an error occurs while preparing or
1727   *                                sending the search request or decoding the
1728   *                                entry that was returned.
1729   */
1730  public T searchForObject(final T o, final LDAPInterface i,
1731                           final String baseDN, final SearchScope scope,
1732                           final DereferencePolicy derefPolicy,
1733                           final int sizeLimit, final int timeLimit,
1734                           final Filter extraFilter, final Control... controls)
1735         throws LDAPPersistException
1736  {
1737    ensureNotNull(o, i, scope, derefPolicy);
1738
1739    final String base;
1740    if (baseDN == null)
1741    {
1742      base = handler.getDefaultParentDN().toString();
1743    }
1744    else
1745    {
1746      base = baseDN;
1747    }
1748
1749    final Filter filter;
1750    if (extraFilter == null)
1751    {
1752      filter = handler.createFilter(o);
1753    }
1754    else
1755    {
1756      filter = Filter.createANDFilter(handler.createFilter(o), extraFilter);
1757    }
1758
1759    final SearchRequest searchRequest = new SearchRequest(base, scope,
1760         derefPolicy, sizeLimit, timeLimit, false, filter,
1761         handler.getAttributesToRequest());
1762    if (controls != null)
1763    {
1764      searchRequest.setControls(controls);
1765    }
1766
1767    try
1768    {
1769      final Entry e = i.searchForEntry(searchRequest);
1770      if (e == null)
1771      {
1772        return null;
1773      }
1774      else
1775      {
1776        return decode(e);
1777      }
1778    }
1779    catch (LDAPPersistException lpe)
1780    {
1781      debugException(lpe);
1782      throw lpe;
1783    }
1784    catch (LDAPException le)
1785    {
1786      debugException(le);
1787      throw new LDAPPersistException(le);
1788    }
1789  }
1790
1791
1792
1793  /**
1794   * Performs a search in the directory with an attempt to find all objects of
1795   * the specified type below the given base DN (or below the default parent DN
1796   * if no base DN is specified).  Note that this may result in an unindexed
1797   * search, which may be expensive to conduct.  Some servers may require
1798   * special permissions of clients wishing to perform unindexed searches.
1799   *
1800   * @param  i         The connection to use to communicate with the
1801   *                   directory server.  It must not be {@code null}.
1802   * @param  baseDN    The base DN to use for the search.  It may be
1803   *                   {@code null} if the {@link LDAPObject#defaultParentDN}
1804   *                   element in the {@code LDAPObject} should be used as the
1805   *                   base DN.
1806   * @param  l         The object search result listener that will be used to
1807   *                   receive objects decoded from entries returned for the
1808   *                   search.  It must not be {@code null}.
1809   * @param  controls  An optional set of controls to include in the search
1810   *                   request.  It may be empty or {@code null} if no controls
1811   *                   are needed.
1812   *
1813   * @return  The result of the search operation that was processed.
1814   *
1815   * @throws  LDAPPersistException  If an error occurs while preparing or
1816   *                                sending the search request.
1817   */
1818  public SearchResult getAll(final LDAPInterface i, final String baseDN,
1819                             final ObjectSearchListener<T> l,
1820                             final Control... controls)
1821         throws LDAPPersistException
1822  {
1823    ensureNotNull(i, l);
1824
1825    final String base;
1826    if (baseDN == null)
1827    {
1828      base = handler.getDefaultParentDN().toString();
1829    }
1830    else
1831    {
1832      base = baseDN;
1833    }
1834
1835    final SearchListenerBridge<T> bridge = new SearchListenerBridge<T>(this, l);
1836    final SearchRequest searchRequest = new SearchRequest(bridge, base,
1837         SearchScope.SUB, DereferencePolicy.NEVER, 0, 0, false,
1838         handler.createBaseFilter(), handler.getAttributesToRequest());
1839    if (controls != null)
1840    {
1841      searchRequest.setControls(controls);
1842    }
1843
1844    try
1845    {
1846      return i.search(searchRequest);
1847    }
1848    catch (LDAPException le)
1849    {
1850      debugException(le);
1851      throw new LDAPPersistException(le);
1852    }
1853  }
1854}