001    /*
002     * Copyright 2009-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2009-2015 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     */
021    package com.unboundid.ldap.sdk.persist;
022    
023    
024    
025    import java.io.Serializable;
026    import java.util.ArrayList;
027    import java.util.Arrays;
028    import java.util.Collections;
029    import java.util.LinkedHashSet;
030    import java.util.LinkedList;
031    import java.util.List;
032    import java.util.Map;
033    import java.util.Set;
034    import java.util.concurrent.ConcurrentHashMap;
035    
036    import com.unboundid.ldap.sdk.AddRequest;
037    import com.unboundid.ldap.sdk.Attribute;
038    import com.unboundid.ldap.sdk.BindResult;
039    import com.unboundid.ldap.sdk.Control;
040    import com.unboundid.ldap.sdk.DeleteRequest;
041    import com.unboundid.ldap.sdk.DereferencePolicy;
042    import com.unboundid.ldap.sdk.Entry;
043    import com.unboundid.ldap.sdk.Filter;
044    import com.unboundid.ldap.sdk.LDAPConnection;
045    import com.unboundid.ldap.sdk.LDAPEntrySource;
046    import com.unboundid.ldap.sdk.LDAPException;
047    import com.unboundid.ldap.sdk.LDAPInterface;
048    import com.unboundid.ldap.sdk.LDAPResult;
049    import com.unboundid.ldap.sdk.Modification;
050    import com.unboundid.ldap.sdk.ModificationType;
051    import com.unboundid.ldap.sdk.ModifyRequest;
052    import com.unboundid.ldap.sdk.ResultCode;
053    import com.unboundid.ldap.sdk.SearchRequest;
054    import com.unboundid.ldap.sdk.SearchResult;
055    import com.unboundid.ldap.sdk.SearchScope;
056    import com.unboundid.ldap.sdk.SimpleBindRequest;
057    import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition;
058    import com.unboundid.ldap.sdk.schema.ObjectClassDefinition;
059    import com.unboundid.ldap.sdk.schema.Schema;
060    import com.unboundid.util.NotMutable;
061    import com.unboundid.util.ThreadSafety;
062    import com.unboundid.util.ThreadSafetyLevel;
063    
064    import static com.unboundid.ldap.sdk.persist.PersistMessages.*;
065    import static com.unboundid.util.Debug.*;
066    import static com.unboundid.util.StaticUtils.*;
067    import 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)
085    public 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.simplifyFilter(
1418               Filter.createANDFilter(handler.createFilter(o), extraFilter), true);
1419        }
1420    
1421        final SearchListenerBridge<T> bridge = new SearchListenerBridge<T>(this, l);
1422    
1423        final SearchRequest searchRequest = new SearchRequest(bridge, base, scope,
1424             derefPolicy, sizeLimit, timeLimit, false, filter,
1425             handler.getAttributesToRequest());
1426        if (controls != null)
1427        {
1428          searchRequest.setControls(controls);
1429        }
1430    
1431        try
1432        {
1433          return i.search(searchRequest);
1434        }
1435        catch (LDAPException le)
1436        {
1437          debugException(le);
1438          throw new LDAPPersistException(le);
1439        }
1440      }
1441    
1442    
1443    
1444      /**
1445       * Performs a search in the directory using the provided search criteria and
1446       * decodes all entries returned as objects of the associated type.
1447       *
1448       * @param  c            The connection to use to communicate with the
1449       *                      directory server.  It must not be {@code null}.
1450       * @param  baseDN       The base DN to use for the search.  It may be
1451       *                      {@code null} if the {@link LDAPObject#defaultParentDN}
1452       *                      element in the {@code LDAPObject} should be used as
1453       *                      the base DN.
1454       * @param  scope        The scope to use for the search operation.  It must
1455       *                      not be {@code null}.
1456       * @param  derefPolicy  The dereference policy to use for the search
1457       *                      operation.  It must not be {@code null}.
1458       * @param  sizeLimit    The maximum number of entries to retrieve from the
1459       *                      directory.  A value of zero indicates that no
1460       *                      client-requested size limit should be enforced.
1461       * @param  timeLimit    The maximum length of time in seconds that the server
1462       *                      should spend processing the search.  A value of zero
1463       *                      indicates that no client-requested time limit should
1464       *                      be enforced.
1465       * @param  filter       The filter to use for the search.  It must not be
1466       *                      {@code null}.  It will automatically be ANDed with a
1467       *                      filter that will match entries with the structural and
1468       *                      auxiliary classes.
1469       * @param  controls     An optional set of controls to include in the search
1470       *                      request.  It may be empty or {@code null} if no
1471       *                      controls are needed.
1472       *
1473       * @return  The result of the search operation that was processed.
1474       *
1475       * @throws  LDAPPersistException  If an error occurs while preparing or
1476       *                                sending the search request.
1477       */
1478      public PersistedObjects<T> search(final LDAPConnection c, final String baseDN,
1479                                        final SearchScope scope,
1480                                        final DereferencePolicy derefPolicy,
1481                                        final int sizeLimit, final int timeLimit,
1482                                        final Filter filter,
1483                                        final Control... controls)
1484             throws LDAPPersistException
1485      {
1486        ensureNotNull(c, scope, derefPolicy, filter);
1487    
1488        final String base;
1489        if (baseDN == null)
1490        {
1491          base = handler.getDefaultParentDN().toString();
1492        }
1493        else
1494        {
1495          base = baseDN;
1496        }
1497    
1498        final Filter f = Filter.createANDFilter(filter, handler.createBaseFilter());
1499    
1500        final SearchRequest searchRequest = new SearchRequest(base, scope,
1501             derefPolicy, sizeLimit, timeLimit, false, f,
1502             handler.getAttributesToRequest());
1503        if (controls != null)
1504        {
1505          searchRequest.setControls(controls);
1506        }
1507    
1508        final LDAPEntrySource entrySource;
1509        try
1510        {
1511          entrySource = new LDAPEntrySource(c, searchRequest, false);
1512        }
1513        catch (LDAPException le)
1514        {
1515          debugException(le);
1516          throw new LDAPPersistException(le);
1517        }
1518    
1519        return new PersistedObjects<T>(this, entrySource);
1520      }
1521    
1522    
1523    
1524      /**
1525       * Performs a search in the directory using the provided search criteria and
1526       * decodes all entries returned as objects of the associated type.
1527       *
1528       * @param  i            The connection to use to communicate with the
1529       *                      directory server.  It must not be {@code null}.
1530       * @param  baseDN       The base DN to use for the search.  It may be
1531       *                      {@code null} if the {@link LDAPObject#defaultParentDN}
1532       *                      element in the {@code LDAPObject} should be used as
1533       *                      the base DN.
1534       * @param  scope        The scope to use for the search operation.  It must
1535       *                      not be {@code null}.
1536       * @param  derefPolicy  The dereference policy to use for the search
1537       *                      operation.  It must not be {@code null}.
1538       * @param  sizeLimit    The maximum number of entries to retrieve from the
1539       *                      directory.  A value of zero indicates that no
1540       *                      client-requested size limit should be enforced.
1541       * @param  timeLimit    The maximum length of time in seconds that the server
1542       *                      should spend processing the search.  A value of zero
1543       *                      indicates that no client-requested time limit should
1544       *                      be enforced.
1545       * @param  filter       The filter to use for the search.  It must not be
1546       *                      {@code null}.  It will automatically be ANDed with a
1547       *                      filter that will match entries with the structural and
1548       *                      auxiliary classes.
1549       * @param  l            The object search result listener that will be used
1550       *                      to receive objects decoded from entries returned for
1551       *                      the search.  It must not be {@code null}.
1552       * @param  controls     An optional set of controls to include in the search
1553       *                      request.  It may be empty or {@code null} if no
1554       *                      controls are needed.
1555       *
1556       * @return  The result of the search operation that was processed.
1557       *
1558       * @throws  LDAPPersistException  If an error occurs while preparing or
1559       *                                sending the search request.
1560       */
1561      public SearchResult search(final LDAPInterface i, final String baseDN,
1562                                 final SearchScope scope,
1563                                 final DereferencePolicy derefPolicy,
1564                                 final int sizeLimit, final int timeLimit,
1565                                 final Filter filter,
1566                                 final ObjectSearchListener<T> l,
1567                                 final Control... controls)
1568             throws LDAPPersistException
1569      {
1570        ensureNotNull(i, scope, derefPolicy, filter, l);
1571    
1572        final String base;
1573        if (baseDN == null)
1574        {
1575          base = handler.getDefaultParentDN().toString();
1576        }
1577        else
1578        {
1579          base = baseDN;
1580        }
1581    
1582        final Filter f = Filter.simplifyFilter(
1583             Filter.createANDFilter(filter, handler.createBaseFilter()), true);
1584        final SearchListenerBridge<T> bridge = new SearchListenerBridge<T>(this, l);
1585    
1586        final SearchRequest searchRequest = new SearchRequest(bridge, base, scope,
1587             derefPolicy, sizeLimit, timeLimit, false, f,
1588             handler.getAttributesToRequest());
1589        if (controls != null)
1590        {
1591          searchRequest.setControls(controls);
1592        }
1593    
1594        try
1595        {
1596          return i.search(searchRequest);
1597        }
1598        catch (LDAPException le)
1599        {
1600          debugException(le);
1601          throw new LDAPPersistException(le);
1602        }
1603      }
1604    
1605    
1606    
1607      /**
1608       * Performs a search in the directory to retrieve the object whose contents
1609       * match the contents of the provided object.  It is expected that at most one
1610       * entry matches the provided criteria, and that it can be decoded as an
1611       * object of the associated type.  If multiple entries match the resulting
1612       * criteria, or if the matching entry cannot be decoded as the associated type
1613       * of object, then an exception will be thrown.
1614       * <BR><BR>
1615       * A search filter will be generated from the provided object containing all
1616       * non-{@code null} values from fields and getter methods whose
1617       * {@link LDAPField} or {@link LDAPGetter} annotation has the {@code inFilter}
1618       * element set to {@code true}.
1619       * <BR><BR>
1620       * The search performed will be a subtree search using a base DN equal to the
1621       * {@link LDAPObject#defaultParentDN} element in the {@code LDAPObject}
1622       * annotation.  It will not enforce a client-side time limit or size limit.
1623       *
1624       * @param  o  The object to use to construct the search filter.  It must not
1625       *            be {@code null}.
1626       * @param  i  The interface to use to communicate with the directory server.
1627       *            It must not be {@code null}.
1628       *
1629       * @return  The object constructed from the entry returned by the search, or
1630       *          {@code null} if no entry was returned.
1631       *
1632       * @throws  LDAPPersistException  If an error occurs while preparing or
1633       *                                sending the search request or decoding the
1634       *                                entry that was returned.
1635       */
1636      public T searchForObject(final T o, final LDAPInterface i)
1637             throws LDAPPersistException
1638      {
1639        return searchForObject(o, i, null, SearchScope.SUB, DereferencePolicy.NEVER,
1640             0, 0, null, NO_CONTROLS);
1641      }
1642    
1643    
1644    
1645      /**
1646       * Performs a search in the directory to retrieve the object whose contents
1647       * match the contents of the provided object.  It is expected that at most one
1648       * entry matches the provided criteria, and that it can be decoded as an
1649       * object of the associated type.  If multiple entries match the resulting
1650       * criteria, or if the matching entry cannot be decoded as the associated type
1651       * of object, then an exception will be thrown.
1652       * <BR><BR>
1653       * A search filter will be generated from the provided object containing all
1654       * non-{@code null} values from fields and getter methods whose
1655       * {@link LDAPField} or {@link LDAPGetter} annotation has the {@code inFilter}
1656       * element set to {@code true}.
1657       *
1658       * @param  o       The object to use to construct the search filter.  It must
1659       *                 not be {@code null}.
1660       * @param  i       The interface to use to communicate with the directory
1661       *                 server. It must not be {@code null}.
1662       * @param  baseDN  The base DN to use for the search.  It may be {@code null}
1663       *                 if the {@link LDAPObject#defaultParentDN} element in the
1664       *                 {@code LDAPObject} should be used as the base DN.
1665       * @param  scope   The scope to use for the search operation.  It must not be
1666       *                 {@code null}.
1667       *
1668       * @return  The object constructed from the entry returned by the search, or
1669       *          {@code null} if no entry was returned.
1670       *
1671       * @throws  LDAPPersistException  If an error occurs while preparing or
1672       *                                sending the search request or decoding the
1673       *                                entry that was returned.
1674       */
1675      public T searchForObject(final T o, final LDAPInterface i,
1676                               final String baseDN, final SearchScope scope)
1677             throws LDAPPersistException
1678      {
1679        return searchForObject(o, i, baseDN, scope, DereferencePolicy.NEVER, 0, 0,
1680             null, NO_CONTROLS);
1681      }
1682    
1683    
1684    
1685      /**
1686       * Performs a search in the directory to retrieve the object whose contents
1687       * match the contents of the provided object.  It is expected that at most one
1688       * entry matches the provided criteria, and that it can be decoded as an
1689       * object of the associated type.  If multiple entries match the resulting
1690       * criteria, or if the matching entry cannot be decoded as the associated type
1691       * of object, then an exception will be thrown.
1692       * <BR><BR>
1693       * A search filter will be generated from the provided object containing all
1694       * non-{@code null} values from fields and getter methods whose
1695       * {@link LDAPField} or {@link LDAPGetter} annotation has the {@code inFilter}
1696       * element set to {@code true}.
1697       *
1698       * @param  o            The object to use to construct the search filter.  It
1699       *                      must not be {@code null}.
1700       * @param  i            The connection to use to communicate with the
1701       *                      directory server.  It must not be {@code null}.
1702       * @param  baseDN       The base DN to use for the search.  It may be
1703       *                      {@code null} if the {@link LDAPObject#defaultParentDN}
1704       *                      element in the {@code LDAPObject} should be used as
1705       *                      the base DN.
1706       * @param  scope        The scope to use for the search operation.  It must
1707       *                      not be {@code null}.
1708       * @param  derefPolicy  The dereference policy to use for the search
1709       *                      operation.  It must not be {@code null}.
1710       * @param  sizeLimit    The maximum number of entries to retrieve from the
1711       *                      directory.  A value of zero indicates that no
1712       *                      client-requested size limit should be enforced.
1713       * @param  timeLimit    The maximum length of time in seconds that the server
1714       *                      should spend processing the search.  A value of zero
1715       *                      indicates that no client-requested time limit should
1716       *                      be enforced.
1717       * @param  extraFilter  An optional additional filter to be ANDed with the
1718       *                      filter generated from the provided object.  If this is
1719       *                      {@code null}, then only the filter generated from the
1720       *                      object will be used.
1721       * @param  controls     An optional set of controls to include in the search
1722       *                      request.  It may be empty or {@code null} if no
1723       *                      controls are needed.
1724       *
1725       * @return  The object constructed from the entry returned by the search, or
1726       *          {@code null} if no entry was returned.
1727       *
1728       * @throws  LDAPPersistException  If an error occurs while preparing or
1729       *                                sending the search request or decoding the
1730       *                                entry that was returned.
1731       */
1732      public T searchForObject(final T o, final LDAPInterface i,
1733                               final String baseDN, final SearchScope scope,
1734                               final DereferencePolicy derefPolicy,
1735                               final int sizeLimit, final int timeLimit,
1736                               final Filter extraFilter, final Control... controls)
1737             throws LDAPPersistException
1738      {
1739        ensureNotNull(o, i, scope, derefPolicy);
1740    
1741        final String base;
1742        if (baseDN == null)
1743        {
1744          base = handler.getDefaultParentDN().toString();
1745        }
1746        else
1747        {
1748          base = baseDN;
1749        }
1750    
1751        final Filter filter;
1752        if (extraFilter == null)
1753        {
1754          filter = handler.createFilter(o);
1755        }
1756        else
1757        {
1758          filter = Filter.simplifyFilter(
1759               Filter.createANDFilter(handler.createFilter(o), extraFilter), true);
1760        }
1761    
1762        final SearchRequest searchRequest = new SearchRequest(base, scope,
1763             derefPolicy, sizeLimit, timeLimit, false, filter,
1764             handler.getAttributesToRequest());
1765        if (controls != null)
1766        {
1767          searchRequest.setControls(controls);
1768        }
1769    
1770        try
1771        {
1772          final Entry e = i.searchForEntry(searchRequest);
1773          if (e == null)
1774          {
1775            return null;
1776          }
1777          else
1778          {
1779            return decode(e);
1780          }
1781        }
1782        catch (LDAPPersistException lpe)
1783        {
1784          debugException(lpe);
1785          throw lpe;
1786        }
1787        catch (LDAPException le)
1788        {
1789          debugException(le);
1790          throw new LDAPPersistException(le);
1791        }
1792      }
1793    
1794    
1795    
1796      /**
1797       * Performs a search in the directory with an attempt to find all objects of
1798       * the specified type below the given base DN (or below the default parent DN
1799       * if no base DN is specified).  Note that this may result in an unindexed
1800       * search, which may be expensive to conduct.  Some servers may require
1801       * special permissions of clients wishing to perform unindexed searches.
1802       *
1803       * @param  i         The connection to use to communicate with the
1804       *                   directory server.  It must not be {@code null}.
1805       * @param  baseDN    The base DN to use for the search.  It may be
1806       *                   {@code null} if the {@link LDAPObject#defaultParentDN}
1807       *                   element in the {@code LDAPObject} should be used as the
1808       *                   base DN.
1809       * @param  l         The object search result listener that will be used to
1810       *                   receive objects decoded from entries returned for the
1811       *                   search.  It must not be {@code null}.
1812       * @param  controls  An optional set of controls to include in the search
1813       *                   request.  It may be empty or {@code null} if no controls
1814       *                   are needed.
1815       *
1816       * @return  The result of the search operation that was processed.
1817       *
1818       * @throws  LDAPPersistException  If an error occurs while preparing or
1819       *                                sending the search request.
1820       */
1821      public SearchResult getAll(final LDAPInterface i, final String baseDN,
1822                                 final ObjectSearchListener<T> l,
1823                                 final Control... controls)
1824             throws LDAPPersistException
1825      {
1826        ensureNotNull(i, l);
1827    
1828        final String base;
1829        if (baseDN == null)
1830        {
1831          base = handler.getDefaultParentDN().toString();
1832        }
1833        else
1834        {
1835          base = baseDN;
1836        }
1837    
1838        final SearchListenerBridge<T> bridge = new SearchListenerBridge<T>(this, l);
1839        final SearchRequest searchRequest = new SearchRequest(bridge, base,
1840             SearchScope.SUB, DereferencePolicy.NEVER, 0, 0, false,
1841             handler.createBaseFilter(), handler.getAttributesToRequest());
1842        if (controls != null)
1843        {
1844          searchRequest.setControls(controls);
1845        }
1846    
1847        try
1848        {
1849          return i.search(searchRequest);
1850        }
1851        catch (LDAPException le)
1852        {
1853          debugException(le);
1854          throw new LDAPPersistException(le);
1855        }
1856      }
1857    }