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