001    /*
002     * Copyright 2009-2013 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2009-2013 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.lang.reflect.Constructor;
027    import java.lang.reflect.Field;
028    import java.lang.reflect.InvocationTargetException;
029    import java.lang.reflect.Method;
030    import java.lang.reflect.Modifier;
031    import java.util.ArrayList;
032    import java.util.Arrays;
033    import java.util.Iterator;
034    import java.util.LinkedHashMap;
035    import java.util.LinkedList;
036    import java.util.Collections;
037    import java.util.HashSet;
038    import java.util.List;
039    import java.util.Map;
040    import java.util.TreeMap;
041    import java.util.TreeSet;
042    import java.util.concurrent.atomic.AtomicBoolean;
043    
044    import com.unboundid.asn1.ASN1OctetString;
045    import com.unboundid.ldap.sdk.Attribute;
046    import com.unboundid.ldap.sdk.DN;
047    import com.unboundid.ldap.sdk.Entry;
048    import com.unboundid.ldap.sdk.Filter;
049    import com.unboundid.ldap.sdk.LDAPException;
050    import com.unboundid.ldap.sdk.Modification;
051    import com.unboundid.ldap.sdk.ModificationType;
052    import com.unboundid.ldap.sdk.RDN;
053    import com.unboundid.ldap.sdk.ReadOnlyEntry;
054    import com.unboundid.ldap.sdk.schema.ObjectClassDefinition;
055    import com.unboundid.ldap.sdk.schema.ObjectClassType;
056    import com.unboundid.util.NotMutable;
057    import com.unboundid.util.ThreadSafety;
058    import com.unboundid.util.ThreadSafetyLevel;
059    
060    import static com.unboundid.ldap.sdk.persist.PersistMessages.*;
061    import static com.unboundid.util.Debug.*;
062    import static com.unboundid.util.StaticUtils.*;
063    
064    
065    
066    /**
067     * This class provides a mechanism for validating, encoding, and decoding
068     * objects marked with the {@link LDAPObject} annotation type.
069     *
070     * @param  <T>  The type of object handled by this class.
071     */
072    @NotMutable()
073    @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
074    public final class LDAPObjectHandler<T>
075           implements Serializable
076    {
077      /**
078       * The serial version UID for this serializable class.
079       */
080      private static final long serialVersionUID = -1480360011153517161L;
081    
082    
083    
084      // The object class attribute to include in entries that are created.
085      private final Attribute objectClassAttribute;
086    
087      // The type of object handled by this class.
088      private final Class<T> type;
089    
090      // The constructor to use to create a new instance of the class.
091      private final Constructor<T> constructor;
092    
093      // The default parent DN for entries created from objects of the associated
094      //  type.
095      private final DN defaultParentDN;
096    
097      // The field that will be used to hold the DN of the entry.
098      private final Field dnField;
099    
100      // The field that will be used to hold the entry contents.
101      private final Field entryField;
102    
103      // The LDAPObject annotation for the associated object.
104      private final LDAPObject ldapObject;
105    
106      // The LDAP object handler for the superclass, if applicable.
107      private final LDAPObjectHandler<? super T> superclassHandler;
108    
109      // The list of fields for with a filter usage of ALWAYS_ALLOWED.
110      private final List<FieldInfo> alwaysAllowedFilterFields;
111    
112      // The list of fields for with a filter usage of CONDITIONALLY_ALLOWED.
113      private final List<FieldInfo> conditionallyAllowedFilterFields;
114    
115      // The list of fields for with a filter usage of REQUIRED.
116      private final List<FieldInfo> requiredFilterFields;
117    
118      // The list of fields for this class that should be used to construct the RDN.
119      private final List<FieldInfo> rdnFields;
120    
121      // The list of getter methods for with a filter usage of ALWAYS_ALLOWED.
122      private final List<GetterInfo> alwaysAllowedFilterGetters;
123    
124      // The list of getter methods for with a filter usage of
125      // CONDITIONALLY_ALLOWED.
126      private final List<GetterInfo> conditionallyAllowedFilterGetters;
127    
128      // The list of getter methods for with a filter usage of REQUIRED.
129      private final List<GetterInfo> requiredFilterGetters;
130    
131      // The list of getters for this class that should be used to construct the
132      // RDN.
133      private final List<GetterInfo> rdnGetters;
134    
135      // The map of attribute names to their corresponding fields.
136      private final Map<String,FieldInfo> fieldMap;
137    
138      // The map of attribute names to their corresponding getter methods.
139      private final Map<String,GetterInfo> getterMap;
140    
141      // The map of attribute names to their corresponding setter methods.
142      private final Map<String,SetterInfo> setterMap;
143    
144      // The method that should be invoked on an object after all other decode
145      // processing has been performed.
146      private final Method postDecodeMethod;
147    
148      // The method that should be invoked on an object after all other encode
149      // processing has been performed.
150      private final Method postEncodeMethod;
151    
152      // The structural object class that should be used for entries created from
153      // objects of the associated type.
154      private final String structuralClass;
155    
156      // The set of attributes that should be requested when performing a search.
157      // It will not include lazily-loaded attributes.
158      private final String[] attributesToRequest;
159    
160      // The auxiliary object classes that should should used for entries created
161      // from objects of the associated type.
162      private final String[] auxiliaryClasses;
163    
164      // The set of attributes that should be lazily loaded.
165      private final String[] lazilyLoadedAttributes;
166    
167      // The superior object classes that should should used for entries created
168      // from objects of the associated type.
169      private final String[] superiorClasses;
170    
171    
172    
173      /**
174       * Creates a new instance of this handler that will handle objects of the
175       * specified type.
176       *
177       * @param  type  The type of object that will be handled by this class.
178       *
179       * @throws  LDAPPersistException  If there is a problem with the provided
180       *                                class that makes it unsuitable for use with
181       *                                the persistence framework.
182       */
183      @SuppressWarnings("unchecked")
184      LDAPObjectHandler(final Class<T> type)
185           throws LDAPPersistException
186      {
187        this.type = type;
188    
189        final Class<? super T> superclassType = type.getSuperclass();
190        if (superclassType == null)
191        {
192          superclassHandler = null;
193        }
194        else
195        {
196          final LDAPObject superclassAnnotation =
197               superclassType.getAnnotation(LDAPObject.class);
198          if (superclassAnnotation == null)
199          {
200            superclassHandler = null;
201          }
202          else
203          {
204            superclassHandler = new LDAPObjectHandler(superclassType);
205          }
206        }
207    
208        final TreeMap<String,FieldInfo>  fields  = new TreeMap<String,FieldInfo>();
209        final TreeMap<String,GetterInfo> getters = new TreeMap<String,GetterInfo>();
210        final TreeMap<String,SetterInfo> setters = new TreeMap<String,SetterInfo>();
211    
212        ldapObject = type.getAnnotation(LDAPObject.class);
213        if (ldapObject == null)
214        {
215          throw new LDAPPersistException(
216               ERR_OBJECT_HANDLER_OBJECT_NOT_ANNOTATED.get(type.getName()));
217        }
218    
219        final LinkedHashMap<String,String> objectClasses =
220             new LinkedHashMap<String,String>(10);
221    
222        final String oc = ldapObject.structuralClass();
223        if (oc.length() == 0)
224        {
225          structuralClass = getUnqualifiedClassName(type);
226        }
227        else
228        {
229          structuralClass = oc;
230        }
231    
232        final StringBuilder invalidReason = new StringBuilder();
233        if (PersistUtils.isValidLDAPName(structuralClass, invalidReason))
234        {
235          objectClasses.put(toLowerCase(structuralClass), structuralClass);
236        }
237        else
238        {
239          throw new LDAPPersistException(
240               ERR_OBJECT_HANDLER_INVALID_STRUCTURAL_CLASS.get(type.getName(),
241                    structuralClass, invalidReason.toString()));
242        }
243    
244        auxiliaryClasses = ldapObject.auxiliaryClass();
245        for (final String auxiliaryClass : auxiliaryClasses)
246        {
247          if (PersistUtils.isValidLDAPName(auxiliaryClass, invalidReason))
248          {
249            objectClasses.put(toLowerCase(auxiliaryClass), auxiliaryClass);
250          }
251          else
252          {
253            throw new LDAPPersistException(
254                 ERR_OBJECT_HANDLER_INVALID_AUXILIARY_CLASS.get(type.getName(),
255                      auxiliaryClass, invalidReason.toString()));
256          }
257        }
258    
259        superiorClasses = ldapObject.superiorClass();
260        for (final String superiorClass : superiorClasses)
261        {
262          if (PersistUtils.isValidLDAPName(superiorClass, invalidReason))
263          {
264            objectClasses.put(toLowerCase(superiorClass), superiorClass);
265          }
266          else
267          {
268            throw new LDAPPersistException(
269                 ERR_OBJECT_HANDLER_INVALID_SUPERIOR_CLASS.get(type.getName(),
270                      superiorClass, invalidReason.toString()));
271          }
272        }
273    
274        if (superclassHandler != null)
275        {
276          for (final String s : superclassHandler.objectClassAttribute.getValues())
277          {
278            objectClasses.put(toLowerCase(s), s);
279          }
280        }
281    
282        objectClassAttribute = new Attribute("objectClass", objectClasses.values());
283    
284    
285        final String parentDNStr = ldapObject.defaultParentDN();
286        try
287        {
288          defaultParentDN = new DN(parentDNStr);
289        }
290        catch (LDAPException le)
291        {
292          throw new LDAPPersistException(
293               ERR_OBJECT_HANDLER_INVALID_DEFAULT_PARENT.get(type.getName(),
294                    parentDNStr, le.getMessage()), le);
295        }
296    
297    
298        final String postDecodeMethodName = ldapObject.postDecodeMethod();
299        if (postDecodeMethodName.length() > 0)
300        {
301          try
302          {
303            postDecodeMethod = type.getDeclaredMethod(postDecodeMethodName);
304            postDecodeMethod.setAccessible(true);
305          }
306          catch (Exception e)
307          {
308            debugException(e);
309            throw new LDAPPersistException(
310                 ERR_OBJECT_HANDLER_INVALID_POST_DECODE_METHOD.get(type.getName(),
311                      postDecodeMethodName, getExceptionMessage(e)), e);
312          }
313        }
314        else
315        {
316          postDecodeMethod = null;
317        }
318    
319    
320        final String postEncodeMethodName = ldapObject.postEncodeMethod();
321        if (postEncodeMethodName.length() > 0)
322        {
323          try
324          {
325            postEncodeMethod = type.getDeclaredMethod(postEncodeMethodName,
326                 Entry.class);
327            postEncodeMethod.setAccessible(true);
328          }
329          catch (Exception e)
330          {
331            debugException(e);
332            throw new LDAPPersistException(
333                 ERR_OBJECT_HANDLER_INVALID_POST_ENCODE_METHOD.get(type.getName(),
334                      postEncodeMethodName, getExceptionMessage(e)), e);
335          }
336        }
337        else
338        {
339          postEncodeMethod = null;
340        }
341    
342    
343        try
344        {
345          constructor = type.getDeclaredConstructor();
346          constructor.setAccessible(true);
347        }
348        catch (Exception e)
349        {
350          debugException(e);
351          throw new LDAPPersistException(
352               ERR_OBJECT_HANDLER_NO_DEFAULT_CONSTRUCTOR.get(type.getName()), e);
353        }
354    
355        Field tmpDNField = null;
356        Field tmpEntryField = null;
357        final LinkedList<FieldInfo> tmpRFilterFields = new LinkedList<FieldInfo>();
358        final LinkedList<FieldInfo> tmpAAFilterFields = new LinkedList<FieldInfo>();
359        final LinkedList<FieldInfo> tmpCAFilterFields = new LinkedList<FieldInfo>();
360        final LinkedList<FieldInfo> tmpRDNFields = new LinkedList<FieldInfo>();
361        for (final Field f : type.getDeclaredFields())
362        {
363          final LDAPField fieldAnnotation = f.getAnnotation(LDAPField.class);
364          final LDAPDNField dnFieldAnnotation = f.getAnnotation(LDAPDNField.class);
365          final LDAPEntryField entryFieldAnnotation =
366               f.getAnnotation(LDAPEntryField.class);
367    
368          if (fieldAnnotation != null)
369          {
370            f.setAccessible(true);
371    
372            final FieldInfo fieldInfo = new FieldInfo(f, type);
373            final String attrName = toLowerCase(fieldInfo.getAttributeName());
374            if (fields.containsKey(attrName))
375            {
376              throw new LDAPPersistException(ERR_OBJECT_HANDLER_ATTR_CONFLICT.get(
377                   type.getName(), fieldInfo.getAttributeName()));
378            }
379            else
380            {
381              fields.put(attrName, fieldInfo);
382            }
383    
384            switch (fieldInfo.getFilterUsage())
385            {
386              case REQUIRED:
387                tmpRFilterFields.add(fieldInfo);
388                break;
389              case ALWAYS_ALLOWED:
390                tmpAAFilterFields.add(fieldInfo);
391                break;
392              case CONDITIONALLY_ALLOWED:
393                tmpCAFilterFields.add(fieldInfo);
394                break;
395              case EXCLUDED:
396              default:
397                // No action required.
398                break;
399            }
400    
401            if (fieldInfo.includeInRDN())
402            {
403              tmpRDNFields.add(fieldInfo);
404            }
405          }
406    
407          if (dnFieldAnnotation != null)
408          {
409            f.setAccessible(true);
410    
411            if (fieldAnnotation != null)
412            {
413              throw new LDAPPersistException(
414                   ERR_OBJECT_HANDLER_CONFLICTING_FIELD_ANNOTATIONS.get(
415                        type.getName(), "LDAPField", "LDAPDNField", f.getName()));
416            }
417    
418            if (tmpDNField != null)
419            {
420              throw new LDAPPersistException(
421                   ERR_OBJECT_HANDLER_MULTIPLE_DN_FIELDS.get(type.getName()));
422            }
423    
424            final int modifiers = f.getModifiers();
425            if (Modifier.isFinal(modifiers))
426            {
427              throw new LDAPPersistException(ERR_OBJECT_HANDLER_DN_FIELD_FINAL.get(
428                   f.getName(), type.getName()));
429            }
430            else if (Modifier.isStatic(modifiers))
431            {
432              throw new LDAPPersistException(ERR_OBJECT_HANDLER_DN_FIELD_STATIC.get(
433                   f.getName(), type.getName()));
434            }
435    
436            final Class<?> fieldType = f.getType();
437            if (fieldType.equals(String.class))
438            {
439              tmpDNField = f;
440            }
441            else
442            {
443              throw new LDAPPersistException(
444                   ERR_OBJECT_HANDLER_INVALID_DN_FIELD_TYPE.get(type.getName(),
445                        f.getName(), fieldType.getName()));
446            }
447          }
448    
449          if (entryFieldAnnotation != null)
450          {
451            f.setAccessible(true);
452    
453            if (fieldAnnotation != null)
454            {
455              throw new LDAPPersistException(
456                   ERR_OBJECT_HANDLER_CONFLICTING_FIELD_ANNOTATIONS.get(
457                        type.getName(), "LDAPField", "LDAPEntryField",
458                        f.getName()));
459            }
460    
461            if (tmpEntryField != null)
462            {
463              throw new LDAPPersistException(
464                   ERR_OBJECT_HANDLER_MULTIPLE_ENTRY_FIELDS.get(type.getName()));
465            }
466    
467            final int modifiers = f.getModifiers();
468            if (Modifier.isFinal(modifiers))
469            {
470              throw new LDAPPersistException(
471                   ERR_OBJECT_HANDLER_ENTRY_FIELD_FINAL.get(f.getName(),
472                        type.getName()));
473            }
474            else if (Modifier.isStatic(modifiers))
475            {
476              throw new LDAPPersistException(
477                   ERR_OBJECT_HANDLER_ENTRY_FIELD_STATIC.get(f.getName(),
478                        type.getName()));
479            }
480    
481            final Class<?> fieldType = f.getType();
482            if (fieldType.equals(ReadOnlyEntry.class))
483            {
484              tmpEntryField = f;
485            }
486            else
487            {
488              throw new LDAPPersistException(
489                   ERR_OBJECT_HANDLER_INVALID_ENTRY_FIELD_TYPE.get(type.getName(),
490                        f.getName(), fieldType.getName()));
491            }
492          }
493        }
494    
495        dnField = tmpDNField;
496        entryField = tmpEntryField;
497        requiredFilterFields = Collections.unmodifiableList(tmpRFilterFields);
498        alwaysAllowedFilterFields = Collections.unmodifiableList(tmpAAFilterFields);
499        conditionallyAllowedFilterFields =
500             Collections.unmodifiableList(tmpCAFilterFields);
501        rdnFields    = Collections.unmodifiableList(tmpRDNFields);
502    
503        final LinkedList<GetterInfo> tmpRFilterGetters =
504             new LinkedList<GetterInfo>();
505        final LinkedList<GetterInfo> tmpAAFilterGetters =
506             new LinkedList<GetterInfo>();
507        final LinkedList<GetterInfo> tmpCAFilterGetters =
508             new LinkedList<GetterInfo>();
509        final LinkedList<GetterInfo> tmpRDNGetters = new LinkedList<GetterInfo>();
510        for (final Method m : type.getDeclaredMethods())
511        {
512          final LDAPGetter getter = m.getAnnotation(LDAPGetter.class);
513          final LDAPSetter setter = m.getAnnotation(LDAPSetter.class);
514    
515          if (getter != null)
516          {
517            m.setAccessible(true);
518    
519            if (setter != null)
520            {
521              throw new LDAPPersistException(
522                   ERR_OBJECT_HANDLER_CONFLICTING_METHOD_ANNOTATIONS.get(
523                        type.getName(), "LDAPGetter", "LDAPSetter",
524                        m.getName()));
525            }
526    
527            final GetterInfo methodInfo = new GetterInfo(m, type);
528            final String attrName = toLowerCase(methodInfo.getAttributeName());
529            if (fields.containsKey(attrName) || getters.containsKey(attrName))
530            {
531              throw new LDAPPersistException(ERR_OBJECT_HANDLER_ATTR_CONFLICT.get(
532                   type.getName(), methodInfo.getAttributeName()));
533            }
534            else
535            {
536              getters.put(attrName, methodInfo);
537            }
538    
539            switch (methodInfo.getFilterUsage())
540            {
541              case REQUIRED:
542                tmpRFilterGetters.add(methodInfo);
543                break;
544              case ALWAYS_ALLOWED:
545                tmpAAFilterGetters.add(methodInfo);
546                break;
547              case CONDITIONALLY_ALLOWED:
548                tmpCAFilterGetters.add(methodInfo);
549                break;
550              case EXCLUDED:
551              default:
552                // No action required.
553                break;
554            }
555    
556            if (methodInfo.includeInRDN())
557            {
558              tmpRDNGetters.add(methodInfo);
559            }
560          }
561    
562          if (setter != null)
563          {
564            m.setAccessible(true);
565    
566            final SetterInfo methodInfo = new SetterInfo(m, type);
567            final String attrName = toLowerCase(methodInfo.getAttributeName());
568            if (fields.containsKey(attrName) || setters.containsKey(attrName))
569            {
570              throw new LDAPPersistException(ERR_OBJECT_HANDLER_ATTR_CONFLICT.get(
571                   type.getName(), methodInfo.getAttributeName()));
572            }
573            else
574            {
575              setters.put(attrName, methodInfo);
576            }
577          }
578        }
579    
580        requiredFilterGetters = Collections.unmodifiableList(tmpRFilterGetters);
581        alwaysAllowedFilterGetters =
582             Collections.unmodifiableList(tmpAAFilterGetters);
583        conditionallyAllowedFilterGetters =
584             Collections.unmodifiableList(tmpCAFilterGetters);
585    
586        rdnGetters = Collections.unmodifiableList(tmpRDNGetters);
587        if (rdnFields.isEmpty() && rdnGetters.isEmpty())
588        {
589          throw new LDAPPersistException(ERR_OBJECT_HANDLER_NO_RDN_DEFINED.get(
590               type.getName()));
591        }
592    
593        fieldMap  = Collections.unmodifiableMap(fields);
594        getterMap = Collections.unmodifiableMap(getters);
595        setterMap = Collections.unmodifiableMap(setters);
596    
597    
598        final TreeSet<String> attrSet = new TreeSet<String>();
599        final TreeSet<String> lazySet = new TreeSet<String>();
600        if (ldapObject.requestAllAttributes())
601        {
602          attrSet.add("*");
603          attrSet.add("+");
604        }
605        else
606        {
607          for (final FieldInfo i : fields.values())
608          {
609            if (i.lazilyLoad())
610            {
611              lazySet.add(i.getAttributeName());
612            }
613            else
614            {
615              attrSet.add(i.getAttributeName());
616            }
617          }
618    
619          for (final SetterInfo i : setters.values())
620          {
621            attrSet.add(i.getAttributeName());
622          }
623        }
624        attributesToRequest = new String[attrSet.size()];
625        attrSet.toArray(attributesToRequest);
626    
627        lazilyLoadedAttributes = new String[lazySet.size()];
628        lazySet.toArray(lazilyLoadedAttributes);
629      }
630    
631    
632    
633      /**
634       * Retrieves an {@code LDAPObjectHandler} instance of the specified type.
635       *
636       * @param  <T>  The type of object handler to create.
637       *
638       * @param  type  The class for the type of object handler to create.
639       *
640       * @return  The created {@code LDAPObjectHandler} instance.
641       *
642       * @throws  LDAPPersistException  If a problem occurs while creating the
643       *                                instance.
644       */
645      private static <T> LDAPObjectHandler<T> getHandler(final Class<T> type)
646              throws LDAPPersistException
647      {
648        return new LDAPObjectHandler<T>(type);
649      }
650    
651    
652    
653      /**
654       * Retrieves the type of object handled by this class.
655       *
656       * @return  The type of object handled by this class.
657       */
658      public Class<T> getType()
659      {
660        return type;
661      }
662    
663    
664    
665      /**
666       * Retrieves the {@code LDAPObjectHandler} object for the superclass of the
667       * associated type, if it is marked with the {@code LDAPObject annotation}.
668       *
669       * @return  The {@code LDAPObjectHandler} object for the superclass of the
670       *          associated type, or {@code null} if the superclass is not marked
671       *          with the {@code LDAPObject} annotation.
672       */
673      public LDAPObjectHandler<?> getSuperclassHandler()
674      {
675        return superclassHandler;
676      }
677    
678    
679    
680      /**
681       * Retrieves the {@link LDAPObject} annotation for the associated class.
682       *
683       * @return  The {@code LDAPObject} annotation for the associated class.
684       */
685      public LDAPObject getLDAPObjectAnnotation()
686      {
687        return ldapObject;
688      }
689    
690    
691    
692      /**
693       * Retrieves the constructor used to create a new instance of the appropriate
694       * type.
695       *
696       * @return  The constructor used to create a new instance of the appropriate
697       *          type.
698       */
699      public Constructor<T> getConstructor()
700      {
701        return constructor;
702      }
703    
704    
705    
706      /**
707       * Retrieves the field that will be used to hold the DN of the associated
708       * entry, if defined.
709       *
710       * @return  The field that will be used to hold the DN of the associated
711       *          entry, or {@code null} if no DN field is defined in the associated
712       *          object type.
713       */
714      public Field getDNField()
715      {
716        return dnField;
717      }
718    
719    
720    
721      /**
722       * Retrieves the field that will be used to hold a read-only copy of the entry
723       * used to create the object instance, if defined.
724       *
725       * @return  The field that will be used to hold a read-only copy of the entry
726       *          used to create the object instance, or {@code null} if no entry
727       *          field is defined in the associated object type.
728       */
729      public Field getEntryField()
730      {
731        return entryField;
732      }
733    
734    
735    
736      /**
737       * Retrieves the default parent DN for objects of the associated type.
738       *
739       * @return  The default parent DN for objects of the associated type.
740       */
741      public DN getDefaultParentDN()
742      {
743        return defaultParentDN;
744      }
745    
746    
747    
748      /**
749       * Retrieves the name of the structural object class for objects of the
750       * associated type.
751       *
752       * @return  The name of the structural object class for objects of the
753       *          associated type.
754       */
755      public String getStructuralClass()
756      {
757        return structuralClass;
758      }
759    
760    
761    
762      /**
763       * Retrieves the names of the auxiliary object classes for objects of the
764       * associated type.
765       *
766       * @return  The names of the auxiliary object classes for objects of the
767       *          associated type.  It may be empty if no auxiliary classes are
768       *          defined.
769       */
770      public String[] getAuxiliaryClasses()
771      {
772        return auxiliaryClasses;
773      }
774    
775    
776    
777      /**
778       * Retrieves the names of the superior object classes for objects of the
779       * associated type.
780       *
781       * @return  The names of the superior object classes for objects of the
782       *          associated type.  It may be empty if no superior classes are
783       *          defined.
784       */
785      public String[] getSuperiorClasses()
786      {
787        return superiorClasses;
788      }
789    
790    
791    
792      /**
793       * Retrieves the names of the attributes that should be requested when
794       * performing a search.  It will not include lazily-loaded attributes.
795       *
796       * @return  The names of the attributes that should be requested when
797       *          performing a search.
798       */
799      public String[] getAttributesToRequest()
800      {
801        return attributesToRequest;
802      }
803    
804    
805    
806      /**
807       * Retrieves the names of the attributes that should be lazily loaded for
808       * objects of this type.
809       *
810       * @return  The names of the attributes that should be lazily loaded for
811       *          objects of this type.  It may be empty if no attributes should be
812       *          lazily-loaded.
813       */
814      public String[] getLazilyLoadedAttributes()
815      {
816        return lazilyLoadedAttributes;
817      }
818    
819    
820    
821      /**
822       * Retrieves the DN of the entry in which the provided object is stored, if
823       * available.  The entry DN will not be available if the provided object was
824       * not retrieved using the persistence framework, or if the associated class
825       * does not have a field marked with either the {@link LDAPDNField} or
826       * {@link LDAPEntryField} annotation.
827       *
828       * @param  o  The object for which to retrieve the associated entry DN.
829       *
830       * @return  The DN of the entry in which the provided object is stored, or
831       *          {@code null} if that is not available.
832       *
833       * @throws  LDAPPersistException  If a problem occurred while attempting to
834       *                                obtain the entry DN.
835       */
836      public String getEntryDN(final T o)
837             throws LDAPPersistException
838      {
839        if (dnField != null)
840        {
841          try
842          {
843            final Object dnObject = dnField.get(o);
844            if (dnObject != null)
845            {
846              return String.valueOf(dnObject);
847            }
848          }
849          catch (Exception e)
850          {
851            debugException(e);
852            throw new LDAPPersistException(
853                 ERR_OBJECT_HANDLER_ERROR_ACCESSING_DN_FIELD.get(dnField.getName(),
854                      type.getName(), getExceptionMessage(e)), e);
855          }
856        }
857    
858        final ReadOnlyEntry entry = getEntry(o);
859        if (entry != null)
860        {
861          return entry.getDN();
862        }
863    
864        return null;
865      }
866    
867    
868    
869      /**
870       * Retrieves a read-only copy of the entry that was used to initialize the
871       * provided object, if available.  The entry will only be available if the
872       * object was retrieved from the directory using the persistence framework and
873       * the associated class has a field marked with the {@link LDAPEntryField}
874       * annotation.
875       *
876       * @param  o  The object for which to retrieve the read-only entry.
877       *
878       * @return  A read-only copy of the entry that was used to initialize the
879       *          provided object, or {@code null} if that is not available.
880       *
881       * @throws  LDAPPersistException  If a problem occurred while attempting to
882       *                                obtain the entry DN.
883       */
884      public ReadOnlyEntry getEntry(final T o)
885             throws LDAPPersistException
886      {
887        if (entryField != null)
888        {
889          try
890          {
891            final Object entryObject = entryField.get(o);
892            if (entryObject != null)
893            {
894              return (ReadOnlyEntry) entryObject;
895            }
896          }
897          catch (Exception e)
898          {
899            debugException(e);
900            throw new LDAPPersistException(
901                 ERR_OBJECT_HANDLER_ERROR_ACCESSING_ENTRY_FIELD.get(
902                      entryField.getName(), type.getName(), getExceptionMessage(e)),
903                 e);
904          }
905        }
906    
907        return null;
908      }
909    
910    
911    
912      /**
913       * Retrieves a map of all fields in the class that should be persisted as LDAP
914       * attributes.  The keys in the map will be the lowercase names of the LDAP
915       * attributes used to persist the information, and the values will be
916       * information about the fields associated with those attributes.
917       *
918       * @return  A map of all fields in the class that should be persisted as LDAP
919       *          attributes.
920       */
921      public Map<String,FieldInfo> getFields()
922      {
923        return fieldMap;
924      }
925    
926    
927    
928      /**
929       * Retrieves a map of all getter methods in the class whose values should be
930       * persisted as LDAP attributes.  The keys in the map will be the lowercase
931       * names of the LDAP attributes used to persist the information, and the
932       * values will be information about the getter methods associated with those
933       * attributes.
934       *
935       * @return  A map of all getter methods in the class whose values should be
936       *          persisted as LDAP attributes.
937       */
938      public Map<String,GetterInfo> getGetters()
939      {
940        return getterMap;
941      }
942    
943    
944    
945      /**
946       * Retrieves a map of all setter methods in the class that should be invoked
947       * with information read from LDAP attributes.  The keys in the map will be
948       * the lowercase names of the LDAP attributes with the information used to
949       * invoke the setter, and the values will be information about the setter
950       * methods associated with those attributes.
951       *
952       * @return  A map of all setter methods in the class that should be invoked
953       *          with information read from LDAP attributes.
954       */
955      public Map<String,SetterInfo> getSetters()
956      {
957        return setterMap;
958      }
959    
960    
961    
962      /**
963       * Constructs a list of LDAP object class definitions which may be added to
964       * the directory server schema to allow it to hold objects of this type.  Note
965       * that the object identifiers used for the constructed object class
966       * definitions are not required to be valid or unique.
967       *
968       * @param  a  The OID allocator to use to generate the object identifiers for
969       *            the constructed attribute types.  It must not be {@code null}.
970       *
971       * @return  A list of object class definitions that may be used to represent
972       *          objects of the associated type in an LDAP directory.
973       *
974       * @throws  LDAPPersistException  If a problem occurs while attempting to
975       *                                generate the list of object class
976       *                                definitions.
977       */
978      List<ObjectClassDefinition> constructObjectClasses(final OIDAllocator a)
979             throws LDAPPersistException
980      {
981        final LinkedHashMap<String,ObjectClassDefinition> ocMap =
982             new LinkedHashMap<String,ObjectClassDefinition>(
983                  1 + auxiliaryClasses.length);
984    
985        if (superclassHandler != null)
986        {
987          for (final ObjectClassDefinition d :
988               superclassHandler.constructObjectClasses(a))
989          {
990            ocMap.put(toLowerCase(d.getNameOrOID()), d);
991          }
992        }
993    
994        final String lowerStructuralClass = toLowerCase(structuralClass);
995        if (! ocMap.containsKey(lowerStructuralClass))
996        {
997          if (superclassHandler == null)
998          {
999            ocMap.put(lowerStructuralClass, constructObjectClass(structuralClass,
1000                 "top", ObjectClassType.STRUCTURAL, a));
1001          }
1002          else
1003          {
1004            ocMap.put(lowerStructuralClass, constructObjectClass(structuralClass,
1005                 superclassHandler.getStructuralClass(), ObjectClassType.STRUCTURAL,
1006                 a));
1007          }
1008        }
1009    
1010        for (final String s : auxiliaryClasses)
1011        {
1012          final String lowerName = toLowerCase(s);
1013          if (! ocMap.containsKey(lowerName))
1014          {
1015            ocMap.put(lowerName,
1016                 constructObjectClass(s, "top", ObjectClassType.AUXILIARY, a));
1017          }
1018        }
1019    
1020        return Collections.unmodifiableList(new ArrayList<ObjectClassDefinition>(
1021             ocMap.values()));
1022      }
1023    
1024    
1025    
1026      /**
1027       * Constructs an LDAP object class definition for the object class with the
1028       * specified name.
1029       *
1030       * @param  name  The name of the object class to create.  It must not be
1031       *               {@code null}.
1032       * @param  sup   The name of the superior object class.  It must not be
1033       *               {@code null}.
1034       * @param  type  The type of object class to create.  It must not be
1035       *               {@code null}.
1036       * @param  a     The OID allocator to use to generate the object identifiers
1037       *               for the constructed attribute types.  It must not be
1038       *               {@code null}.
1039       *
1040       * @return  The constructed object class definition.
1041       */
1042      ObjectClassDefinition constructObjectClass(final String name,
1043                                                 final String sup,
1044                                                 final ObjectClassType type,
1045                                                 final OIDAllocator a)
1046      {
1047        final TreeMap<String,String> requiredAttrs = new TreeMap<String,String>();
1048        final TreeMap<String,String> optionalAttrs = new TreeMap<String,String>();
1049    
1050    
1051        // Extract the attributes for all of the fields.
1052        for (final FieldInfo i : fieldMap.values())
1053        {
1054          boolean found = false;
1055          for (final String s : i.getObjectClasses())
1056          {
1057            if (name.equalsIgnoreCase(s))
1058            {
1059              found = true;
1060              break;
1061            }
1062          }
1063    
1064          if (! found)
1065          {
1066            continue;
1067          }
1068    
1069          final String attrName  = i.getAttributeName();
1070          final String lowerName = toLowerCase(attrName);
1071          if (i.includeInRDN() ||
1072              (i.isRequiredForDecode() && i.isRequiredForEncode()))
1073          {
1074            requiredAttrs.put(lowerName, attrName);
1075          }
1076          else
1077          {
1078            optionalAttrs.put(lowerName, attrName);
1079          }
1080        }
1081    
1082    
1083        // Extract the attributes for all of the getter methods.
1084        for (final GetterInfo i : getterMap.values())
1085        {
1086          boolean found = false;
1087          for (final String s : i.getObjectClasses())
1088          {
1089            if (name.equalsIgnoreCase(s))
1090            {
1091              found = true;
1092              break;
1093            }
1094          }
1095    
1096          if (! found)
1097          {
1098            continue;
1099          }
1100    
1101          final String attrName  = i.getAttributeName();
1102          final String lowerName = toLowerCase(attrName);
1103          if (i.includeInRDN())
1104          {
1105            requiredAttrs.put(lowerName, attrName);
1106          }
1107          else
1108          {
1109            optionalAttrs.put(lowerName, attrName);
1110          }
1111        }
1112    
1113    
1114        // Extract the attributes for all of the setter methods.  We'll assume that
1115        // they are all part of the structural object class and all optional.
1116        if (name.equalsIgnoreCase(structuralClass))
1117        {
1118          for (final SetterInfo i : setterMap.values())
1119          {
1120            final String attrName  = i.getAttributeName();
1121            final String lowerName = toLowerCase(attrName);
1122            if (requiredAttrs.containsKey(lowerName) ||
1123                 optionalAttrs.containsKey(lowerName))
1124            {
1125              continue;
1126            }
1127    
1128            optionalAttrs.put(lowerName, attrName);
1129          }
1130        }
1131    
1132        final String[] reqArray = new String[requiredAttrs.size()];
1133        requiredAttrs.values().toArray(reqArray);
1134    
1135        final String[] optArray = new String[optionalAttrs.size()];
1136        optionalAttrs.values().toArray(optArray);
1137    
1138        return new ObjectClassDefinition(a.allocateObjectClassOID(name),
1139             new String[] { name }, null, false, new String[] { sup }, type,
1140             reqArray, optArray, null);
1141      }
1142    
1143    
1144    
1145      /**
1146       * Creates a new object based on the contents of the provided entry.
1147       *
1148       * @param  e  The entry to use to create and initialize the object.
1149       *
1150       * @return  The object created from the provided entry.
1151       *
1152       * @throws  LDAPPersistException  If an error occurs while creating or
1153       *                                initializing the object from the information
1154       *                                in the provided entry.
1155       */
1156      T decode(final Entry e)
1157        throws LDAPPersistException
1158      {
1159        final T o;
1160        try
1161        {
1162          o = constructor.newInstance();
1163        }
1164        catch (Throwable t)
1165        {
1166          debugException(t);
1167    
1168          if (t instanceof InvocationTargetException)
1169          {
1170            t = ((InvocationTargetException) t).getTargetException();
1171          }
1172    
1173          throw new LDAPPersistException(
1174               ERR_OBJECT_HANDLER_ERROR_INVOKING_CONSTRUCTOR.get(type.getName(),
1175                    getExceptionMessage(t)), t);
1176        }
1177    
1178        decode(o, e);
1179        return o;
1180      }
1181    
1182    
1183    
1184      /**
1185       * Initializes the provided object from the contents of the provided entry.
1186       *
1187       * @param  o  The object to be initialized with the contents of the provided
1188       *            entry.
1189       * @param  e  The entry to use to initialize the object.
1190       *
1191       * @throws  LDAPPersistException  If an error occurs while initializing the
1192       *                                object from the information in the provided
1193       *                                entry.
1194       */
1195      void decode(final T o, final Entry e)
1196           throws LDAPPersistException
1197      {
1198        if (superclassHandler != null)
1199        {
1200          superclassHandler.decode(o, e);
1201        }
1202    
1203        setDNAndEntryFields(o, e);
1204    
1205        final ArrayList<String> failureReasons = new ArrayList<String>(5);
1206        boolean successful = true;
1207    
1208        for (final FieldInfo i : fieldMap.values())
1209        {
1210          successful &= i.decode(o, e, failureReasons);
1211        }
1212    
1213        for (final SetterInfo i : setterMap.values())
1214        {
1215          successful &= i.invokeSetter(o, e, failureReasons);
1216        }
1217    
1218        Throwable cause = null;
1219        if (postDecodeMethod != null)
1220        {
1221          try
1222          {
1223            postDecodeMethod.invoke(o);
1224          }
1225          catch (final Throwable t)
1226          {
1227            debugException(t);
1228    
1229            if (t instanceof InvocationTargetException)
1230            {
1231              cause = ((InvocationTargetException) t).getTargetException();
1232            }
1233            else
1234            {
1235              cause = t;
1236            }
1237    
1238            successful = false;
1239            failureReasons.add(
1240                 ERR_OBJECT_HANDLER_ERROR_INVOKING_POST_DECODE_METHOD.get(
1241                      postDecodeMethod.getName(), type.getName(),
1242                      getExceptionMessage(t)));
1243          }
1244        }
1245    
1246        if (! successful)
1247        {
1248          throw new LDAPPersistException(concatenateStrings(failureReasons), o,
1249               cause);
1250        }
1251      }
1252    
1253    
1254    
1255      /**
1256       * Encodes the provided object to an entry suitable for use in an add
1257       * operation.
1258       *
1259       * @param  o         The object to be encoded.
1260       * @param  parentDN  The parent DN to use by default for the entry that is
1261       *                   generated.  If the provided object was previously read
1262       *                   from a directory server and includes a DN field or an
1263       *                   entry field with the original DN used for the object,
1264       *                   then that original DN will be used even if it is not
1265       *                   an immediate subordinate of the provided parent.  This
1266       *                   may be {@code null} if the entry to create should not
1267       *                   have a parent but instead should have a DN consisting of
1268       *                   only a single RDN component.
1269       *
1270       * @return  The entry containing an encoded representation of the provided
1271       *          object.
1272       *
1273       * @throws  LDAPPersistException  If a problem occurs while encoding the
1274       *                                provided object.
1275       */
1276      Entry encode(final T o, final String parentDN)
1277            throws LDAPPersistException
1278      {
1279        // Get the attributes that should be included in the entry.
1280        final LinkedHashMap<String,Attribute> attrMap =
1281             new LinkedHashMap<String,Attribute>();
1282        attrMap.put("objectClass", objectClassAttribute);
1283    
1284        for (final Map.Entry<String,FieldInfo> e : fieldMap.entrySet())
1285        {
1286          final FieldInfo i = e.getValue();
1287          if (! i.includeInAdd())
1288          {
1289            continue;
1290          }
1291    
1292          final Attribute a = i.encode(o, false);
1293          if (a != null)
1294          {
1295            attrMap.put(e.getKey(), a);
1296          }
1297        }
1298    
1299        for (final Map.Entry<String,GetterInfo> e : getterMap.entrySet())
1300        {
1301          final GetterInfo i = e.getValue();
1302          if (! i.includeInAdd())
1303          {
1304            continue;
1305          }
1306    
1307          final Attribute a = i.encode(o);
1308          if (a != null)
1309          {
1310            attrMap.put(e.getKey(), a);
1311          }
1312        }
1313    
1314    
1315        // Get the DN to use for the entry.
1316        final String dn = constructDN(o, parentDN, attrMap);
1317        final Entry entry = new Entry(dn, attrMap.values());
1318    
1319        if (postEncodeMethod != null)
1320        {
1321          try
1322          {
1323            postEncodeMethod.invoke(o, entry);
1324          }
1325          catch (Throwable t)
1326          {
1327            debugException(t);
1328    
1329            if (t instanceof InvocationTargetException)
1330            {
1331              t = ((InvocationTargetException) t).getTargetException();
1332            }
1333    
1334            throw new LDAPPersistException(
1335                 ERR_OBJECT_HANDLER_ERROR_INVOKING_POST_ENCODE_METHOD.get(
1336                      postEncodeMethod.getName(), type.getName(),
1337                      getExceptionMessage(t)), t);
1338          }
1339        }
1340    
1341        setDNAndEntryFields(o, entry);
1342    
1343        if (superclassHandler != null)
1344        {
1345          final Entry e = superclassHandler.encode(o, parentDN);
1346          for (final Attribute a : e.getAttributes())
1347          {
1348            entry.addAttribute(a);
1349          }
1350        }
1351    
1352        return entry;
1353      }
1354    
1355    
1356    
1357      /**
1358       * Sets the DN and entry fields for the provided object, if appropriate.
1359       *
1360       * @param  o  The object to be updated.
1361       * @param  e  The entry with which the object is associated.
1362       *
1363       * @throws  LDAPPersistException  If a problem occurs while setting the value
1364       *                                of the DN or entry field.
1365       */
1366      private void setDNAndEntryFields(final T o, final Entry e)
1367              throws LDAPPersistException
1368      {
1369        if (dnField != null)
1370        {
1371          try
1372          {
1373            dnField.set(o, e.getDN());
1374          }
1375          catch (Exception ex)
1376          {
1377            debugException(ex);
1378            throw new LDAPPersistException(ERR_OBJECT_HANDLER_ERROR_SETTING_DN.get(
1379                 type.getName(), e.getDN(), dnField.getName(),
1380                 getExceptionMessage(ex)), ex);
1381          }
1382        }
1383    
1384        if (entryField != null)
1385        {
1386          try
1387          {
1388            entryField.set(o, new ReadOnlyEntry(e));
1389          }
1390          catch (Exception ex)
1391          {
1392            debugException(ex);
1393            throw new LDAPPersistException(
1394                 ERR_OBJECT_HANDLER_ERROR_SETTING_ENTRY.get(type.getName(),
1395                      entryField.getName(), getExceptionMessage(ex)), ex);
1396          }
1397        }
1398      }
1399    
1400    
1401    
1402      /**
1403       * Determines the DN that should be used for the entry associated with the
1404       * given object.  If the provided object was retrieved from the directory
1405       * using the persistence framework and has a field with either the
1406       * {@link LDAPDNField} or {@link LDAPEntryField} annotation, then the actual
1407       * DN of the corresponding entry will be returned.  Otherwise, it will be
1408       * constructed using the fields and getter methods marked for inclusion in
1409       * the entry RDN.
1410       *
1411       * @param  o         The object for which to determine the appropriate DN.
1412       * @param  parentDN  The parent DN to use for the constructed DN.  If a
1413       *                   non-{@code null} value is provided, then that value will
1414       *                   be used as the parent DN (and the empty string will
1415       *                   indicate that the generated DN should not have a parent).
1416       *                   If the value is {@code null}, then the default parent DN
1417       *                   as defined in the {@link LDAPObject} annotation will be
1418       *                   used.  If the provided parent DN is {@code null} and the
1419       *                   {@code LDAPObject} annotation does not specify a default
1420       *                   parent DN, then the generated DN will not have a parent.
1421       *
1422       * @return  The entry DN for the provided object.
1423       *
1424       * @throws  LDAPPersistException  If a problem occurs while obtaining the
1425       *                                entry DN, or if the provided parent DN
1426       *                                represents an invalid DN.
1427       */
1428      public String constructDN(final T o, final String parentDN)
1429             throws LDAPPersistException
1430      {
1431        final String existingDN = getEntryDN(o);
1432        if (existingDN != null)
1433        {
1434          return existingDN;
1435        }
1436    
1437        final LinkedHashMap<String,Attribute> attrMap =
1438             new LinkedHashMap<String,Attribute>(1);
1439    
1440        for (final FieldInfo i : rdnFields)
1441        {
1442          final Attribute a = i.encode(o, true);
1443          if (a == null)
1444          {
1445            throw new LDAPPersistException(
1446                 ERR_OBJECT_HANDLER_RDN_FIELD_MISSING_VALUE.get(type.getName(),
1447                      i.getField().getName()));
1448          }
1449    
1450          attrMap.put(toLowerCase(i.getAttributeName()), a);
1451        }
1452    
1453        for (final GetterInfo i : rdnGetters)
1454        {
1455          final Attribute a = i.encode(o);
1456          if (a == null)
1457          {
1458            throw new LDAPPersistException(
1459                 ERR_OBJECT_HANDLER_RDN_GETTER_MISSING_VALUE.get(type.getName(),
1460                      i.getMethod().getName()));
1461          }
1462    
1463          attrMap.put(toLowerCase(i.getAttributeName()), a);
1464        }
1465    
1466        return constructDN(o, parentDN, attrMap);
1467      }
1468    
1469    
1470    
1471      /**
1472       * Determines the DN that should be used for the entry associated with the
1473       * given object.  If the provided object was retrieved from the directory
1474       * using the persistence framework and has a field with either the
1475       * {@link LDAPDNField} or {@link LDAPEntryField} annotation, then the actual
1476       * DN of the corresponding entry will be returned.  Otherwise, it will be
1477       * constructed using the fields and getter methods marked for inclusion in
1478       * the entry RDN.
1479       *
1480       * @param  o         The object for which to determine the appropriate DN.
1481       * @param  parentDN  The parent DN to use for the constructed DN.  If a
1482       *                   non-{@code null} value is provided, then that value will
1483       *                   be used as the parent DN (and the empty string will
1484       *                   indicate that the generated DN should not have a parent).
1485       *                   If the value is {@code null}, then the default parent DN
1486       *                   as defined in the {@link LDAPObject} annotation will be
1487       *                   used.  If the provided parent DN is {@code null} and the
1488       *                   {@code LDAPObject} annotation does not specify a default
1489       *                   parent DN, then the generated DN will not have a parent.
1490       * @param  attrMap   A map of the attributes that will be included in the
1491       *                   entry and may be used to construct the RDN elements.
1492       *
1493       * @return  The entry DN for the provided object.
1494       *
1495       * @throws  LDAPPersistException  If a problem occurs while obtaining the
1496       *                                entry DN, or if the provided parent DN
1497       *                                represents an invalid DN.
1498       */
1499      String constructDN(final T o, final String parentDN,
1500                         final Map<String,Attribute> attrMap)
1501             throws LDAPPersistException
1502      {
1503        final String existingDN = getEntryDN(o);
1504        if (existingDN != null)
1505        {
1506          return existingDN;
1507        }
1508    
1509        final ArrayList<String> rdnNameList  = new ArrayList<String>(1);
1510        final ArrayList<byte[]> rdnValueList = new ArrayList<byte[]>(1);
1511        for (final FieldInfo i : rdnFields)
1512        {
1513          final Attribute a = attrMap.get(toLowerCase(i.getAttributeName()));
1514          if (a == null)
1515          {
1516            throw new LDAPPersistException(
1517                 ERR_OBJECT_HANDLER_RDN_FIELD_MISSING_VALUE.get(type.getName(),
1518                      i.getField().getName()));
1519          }
1520    
1521          rdnNameList.add(a.getName());
1522          rdnValueList.add(a.getValueByteArray());
1523        }
1524    
1525        for (final GetterInfo i : rdnGetters)
1526        {
1527          final Attribute a = attrMap.get(toLowerCase(i.getAttributeName()));
1528          if (a == null)
1529          {
1530            throw new LDAPPersistException(
1531                 ERR_OBJECT_HANDLER_RDN_GETTER_MISSING_VALUE.get(type.getName(),
1532                      i.getMethod().getName()));
1533          }
1534    
1535          rdnNameList.add(a.getName());
1536          rdnValueList.add(a.getValueByteArray());
1537        }
1538    
1539        final String[] rdnNames = new String[rdnNameList.size()];
1540        rdnNameList.toArray(rdnNames);
1541    
1542        final byte[][] rdnValues = new byte[rdnNames.length][];
1543        rdnValueList.toArray(rdnValues);
1544    
1545        final RDN rdn = new RDN(rdnNames, rdnValues);
1546    
1547        if (parentDN == null)
1548        {
1549          return new DN(rdn, defaultParentDN).toString();
1550        }
1551        else
1552        {
1553          try
1554          {
1555            final DN parsedParentDN = new DN(parentDN);
1556            return new DN(rdn, parsedParentDN).toString();
1557          }
1558          catch (LDAPException le)
1559          {
1560            debugException(le);
1561            throw new LDAPPersistException(ERR_OBJECT_HANDLER_INVALID_PARENT_DN.get(
1562                 type.getName(), parentDN, le.getMessage()), le);
1563          }
1564        }
1565      }
1566    
1567    
1568    
1569      /**
1570       * Creates a list of modifications that can be used to update the stored
1571       * representation of the provided object in the directory.  If the provided
1572       * object was retrieved from the directory using the persistence framework and
1573       * includes a field with the {@link LDAPEntryField} annotation, then that
1574       * entry will be used to make the returned set of modifications as efficient
1575       * as possible.  Otherwise, the resulting modifications will include attempts
1576       * to replace every attribute which are associated with fields or getters
1577       * that should be used in modify operations.
1578       *
1579       * @param  o                 The object to be encoded.
1580       * @param  deleteNullValues  Indicates whether to include modifications that
1581       *                           may completely remove an attribute from the
1582       *                           entry if the corresponding field or getter method
1583       *                           has a value of {@code null}.
1584       * @param  attributes        The set of LDAP attributes for which to include
1585       *                           modifications.  If this is empty or {@code null},
1586       *                           then all attributes marked for inclusion in the
1587       *                           modification will be examined.
1588       *
1589       * @return  A list of modifications that can be used to update the stored
1590       *          representation of the provided object in the directory.  It may
1591       *          be empty if there are no differences identified in the attributes
1592       *          to be evaluated.
1593       *
1594       * @throws  LDAPPersistException  If a problem occurs while computing the set
1595       *                                of modifications.
1596       */
1597      List<Modification> getModifications(final T o, final boolean deleteNullValues,
1598                                          final String... attributes)
1599             throws LDAPPersistException
1600      {
1601        final ReadOnlyEntry originalEntry;
1602        if (entryField != null)
1603        {
1604          originalEntry = getEntry(o);
1605        }
1606        else
1607        {
1608          originalEntry = null;
1609        }
1610    
1611        // If we have an original copy of the entry, then we can try encoding the
1612        // updated object to a new entry and diff the two entries.
1613        if (originalEntry != null)
1614        {
1615          try
1616          {
1617            final T decodedOrig = decode(originalEntry);
1618            final Entry reEncodedOriginal =
1619                 encode(decodedOrig, originalEntry.getParentDNString());
1620    
1621            final Entry newEntry = encode(o, originalEntry.getParentDNString());
1622            final List<Modification> mods = Entry.diff(reEncodedOriginal, newEntry,
1623                 true, false, attributes);
1624            if (! deleteNullValues)
1625            {
1626              final Iterator<Modification> iterator = mods.iterator();
1627              while (iterator.hasNext())
1628              {
1629                final Modification m = iterator.next();
1630                if (m.getRawValues().length == 0)
1631                {
1632                  iterator.remove();
1633                }
1634              }
1635            }
1636    
1637            // If there are any attributes that should be excluded from
1638            // modifications, then strip them out.
1639            HashSet<String> stripAttrs = null;
1640            for (final FieldInfo i : fieldMap.values())
1641            {
1642              if (! i.includeInModify())
1643              {
1644                if (stripAttrs == null)
1645                {
1646                  stripAttrs = new HashSet<String>(10);
1647                }
1648                stripAttrs.add(toLowerCase(i.getAttributeName()));
1649              }
1650            }
1651    
1652            for (final GetterInfo i : getterMap.values())
1653            {
1654              if (! i.includeInModify())
1655              {
1656                if (stripAttrs == null)
1657                {
1658                  stripAttrs = new HashSet<String>(10);
1659                }
1660                stripAttrs.add(toLowerCase(i.getAttributeName()));
1661              }
1662            }
1663    
1664            if (stripAttrs != null)
1665            {
1666              final Iterator<Modification> iterator = mods.iterator();
1667              while (iterator.hasNext())
1668              {
1669                final Modification m = iterator.next();
1670                if (stripAttrs.contains(toLowerCase(m.getAttributeName())))
1671                {
1672                  iterator.remove();
1673                }
1674              }
1675            }
1676    
1677            return mods;
1678          }
1679          catch (final Exception e)
1680          {
1681            debugException(e);
1682          }
1683          finally
1684          {
1685            setDNAndEntryFields(o, originalEntry);
1686          }
1687        }
1688    
1689        final HashSet<String> attrSet;
1690        if ((attributes == null) || (attributes.length == 0))
1691        {
1692          attrSet = null;
1693        }
1694        else
1695        {
1696          attrSet = new HashSet<String>(attributes.length);
1697          for (final String s : attributes)
1698          {
1699            attrSet.add(toLowerCase(s));
1700          }
1701        }
1702    
1703        final ArrayList<Modification> mods = new ArrayList<Modification>(5);
1704    
1705        for (final Map.Entry<String,FieldInfo> e : fieldMap.entrySet())
1706        {
1707          final String attrName = toLowerCase(e.getKey());
1708          if ((attrSet != null) && (! attrSet.contains(attrName)))
1709          {
1710            continue;
1711          }
1712    
1713          final FieldInfo i = e.getValue();
1714          if (! i.includeInModify())
1715          {
1716            continue;
1717          }
1718    
1719          final Attribute a = i.encode(o, false);
1720          if (a == null)
1721          {
1722            if (! deleteNullValues)
1723            {
1724              continue;
1725            }
1726    
1727            if ((originalEntry != null) && (! originalEntry.hasAttribute(attrName)))
1728            {
1729              continue;
1730            }
1731    
1732            mods.add(new Modification(ModificationType.REPLACE,
1733                 i.getAttributeName()));
1734            continue;
1735          }
1736    
1737          if (originalEntry != null)
1738          {
1739            final Attribute originalAttr = originalEntry.getAttribute(attrName);
1740            if ((originalAttr != null) && originalAttr.equals(a))
1741            {
1742            continue;
1743            }
1744          }
1745    
1746          mods.add(new Modification(ModificationType.REPLACE, i.getAttributeName(),
1747               a.getRawValues()));
1748        }
1749    
1750        for (final Map.Entry<String,GetterInfo> e : getterMap.entrySet())
1751        {
1752          final String attrName = toLowerCase(e.getKey());
1753          if ((attrSet != null) && (! attrSet.contains(attrName)))
1754          {
1755            continue;
1756          }
1757    
1758          final GetterInfo i = e.getValue();
1759          if (! i.includeInModify())
1760          {
1761            continue;
1762          }
1763    
1764          final Attribute a = i.encode(o);
1765          if (a == null)
1766          {
1767            if (! deleteNullValues)
1768            {
1769              continue;
1770            }
1771    
1772            if ((originalEntry != null) && (! originalEntry.hasAttribute(attrName)))
1773            {
1774              continue;
1775            }
1776    
1777            mods.add(new Modification(ModificationType.REPLACE,
1778                 i.getAttributeName()));
1779            continue;
1780          }
1781    
1782          if (originalEntry != null)
1783          {
1784            final Attribute originalAttr = originalEntry.getAttribute(attrName);
1785            if ((originalAttr != null) && originalAttr.equals(a))
1786            {
1787            continue;
1788            }
1789          }
1790    
1791          mods.add(new Modification(ModificationType.REPLACE, i.getAttributeName(),
1792               a.getRawValues()));
1793        }
1794    
1795        if (superclassHandler != null)
1796        {
1797          final List<Modification> superMods =
1798               superclassHandler.getModifications(o, deleteNullValues, attributes);
1799          final ArrayList<Modification> modsToAdd =
1800               new ArrayList<Modification>(superMods.size());
1801          for (final Modification sm : superMods)
1802          {
1803            boolean add = true;
1804            for (final Modification m : mods)
1805            {
1806              if (m.getAttributeName().equalsIgnoreCase(sm.getAttributeName()))
1807              {
1808                add = false;
1809                break;
1810              }
1811            }
1812            if (add)
1813            {
1814              modsToAdd.add(sm);
1815            }
1816          }
1817          mods.addAll(modsToAdd);
1818        }
1819    
1820        return Collections.unmodifiableList(mods);
1821      }
1822    
1823    
1824    
1825      /**
1826       * Retrieves a filter that will match any entry containing the structural and
1827       * auxiliary classes for this object type.
1828       *
1829       * @return  A filter that will match any entry containing the structural and
1830       *          auxiliary classes for this object type.
1831       */
1832      public Filter createBaseFilter()
1833      {
1834        if (auxiliaryClasses.length == 0)
1835        {
1836          return Filter.createEqualityFilter("objectClass", structuralClass);
1837        }
1838        else
1839        {
1840          final ArrayList<Filter> comps =
1841               new ArrayList<Filter>(1+auxiliaryClasses.length);
1842          comps.add(Filter.createEqualityFilter("objectClass", structuralClass));
1843          for (final String s : auxiliaryClasses)
1844          {
1845            comps.add(Filter.createEqualityFilter("objectClass", s));
1846          }
1847          return Filter.createANDFilter(comps);
1848        }
1849      }
1850    
1851    
1852    
1853      /**
1854       * Retrieves a filter that can be used to search for entries matching the
1855       * provided object.  It will be constructed as an AND search using all fields
1856       * with a non-{@code null} value and that have a {@link LDAPField} annotation
1857       * with the {@code inFilter} element set to {@code true}, and all  getter
1858       * methods that return a non-{@code null} value and have a
1859       * {@link LDAPGetter} annotation with the {@code inFilter} element set to
1860       * {@code true}.
1861       *
1862       * @param  o  The object for which to create the search filter.
1863       *
1864       * @return  A filter that can be used to search for entries matching the
1865       *          provided object.
1866       *
1867       * @throws  LDAPPersistException  If it is not possible to construct a search
1868       *                                filter for some reason (e.g., because the
1869       *                                provided object does not have any
1870       *                                non-{@code null} fields or getters that are
1871       *                                marked for inclusion in filters).
1872       */
1873      public Filter createFilter(final T o)
1874             throws LDAPPersistException
1875      {
1876        final AtomicBoolean addedRequiredOrAllowed = new AtomicBoolean(false);
1877    
1878        final Filter f = createFilter(o, addedRequiredOrAllowed);
1879        if (! addedRequiredOrAllowed.get())
1880        {
1881          throw new LDAPPersistException(
1882               ERR_OBJECT_HANDLER_FILTER_MISSING_REQUIRED_OR_ALLOWED.get());
1883        }
1884    
1885        return f;
1886      }
1887    
1888    
1889    
1890      /**
1891       * Retrieves a filter that can be used to search for entries matching the
1892       * provided object.  It will be constructed as an AND search using all fields
1893       * with a non-{@code null} value and that have a {@link LDAPField} annotation
1894       * with the {@code inFilter} element set to {@code true}, and all  getter
1895       * methods that return a non-{@code null} value and have a
1896       * {@link LDAPGetter} annotation with the {@code inFilter} element set to
1897       * {@code true}.
1898       *
1899       * @param  o                       The object for which to create the search
1900       *                                 filter.
1901       * @param  addedRequiredOrAllowed  Indicates whether any filter elements from
1902       *                                 required or allowed fields or getters have
1903       *                                 been added to the filter yet.
1904       *
1905       * @return  A filter that can be used to search for entries matching the
1906       *          provided object.
1907       *
1908       * @throws  LDAPPersistException  If it is not possible to construct a search
1909       *                                filter for some reason (e.g., because the
1910       *                                provided object does not have any
1911       *                                non-{@code null} fields or getters that are
1912       *                                marked for inclusion in filters).
1913       */
1914      private Filter createFilter(final T o,
1915                                  final AtomicBoolean addedRequiredOrAllowed)
1916              throws LDAPPersistException
1917      {
1918        final ArrayList<Attribute> attrs = new ArrayList<Attribute>(5);
1919        attrs.add(objectClassAttribute);
1920    
1921        for (final FieldInfo i : requiredFilterFields)
1922        {
1923          final Attribute a = i.encode(o, true);
1924          if (a == null)
1925          {
1926            throw new LDAPPersistException(
1927                 ERR_OBJECT_HANDLER_FILTER_MISSING_REQUIRED_FIELD.get(
1928                      i.getField().getName()));
1929          }
1930          else
1931          {
1932            attrs.add(a);
1933            addedRequiredOrAllowed.set(true);
1934          }
1935        }
1936    
1937        for (final GetterInfo i : requiredFilterGetters)
1938        {
1939          final Attribute a = i.encode(o);
1940          if (a == null)
1941          {
1942            throw new LDAPPersistException(
1943                 ERR_OBJECT_HANDLER_FILTER_MISSING_REQUIRED_GETTER.get(
1944                      i.getMethod().getName()));
1945          }
1946          else
1947          {
1948            attrs.add(a);
1949            addedRequiredOrAllowed.set(true);
1950          }
1951        }
1952    
1953        for (final FieldInfo i : alwaysAllowedFilterFields)
1954        {
1955          final Attribute a = i.encode(o, true);
1956          if (a != null)
1957          {
1958            attrs.add(a);
1959            addedRequiredOrAllowed.set(true);
1960          }
1961        }
1962    
1963        for (final GetterInfo i : alwaysAllowedFilterGetters)
1964        {
1965          final Attribute a = i.encode(o);
1966          if (a != null)
1967          {
1968            attrs.add(a);
1969            addedRequiredOrAllowed.set(true);
1970          }
1971        }
1972    
1973        for (final FieldInfo i : conditionallyAllowedFilterFields)
1974        {
1975          final Attribute a = i.encode(o, true);
1976          if (a != null)
1977          {
1978            attrs.add(a);
1979          }
1980        }
1981    
1982        for (final GetterInfo i : conditionallyAllowedFilterGetters)
1983        {
1984          final Attribute a = i.encode(o);
1985          if (a != null)
1986          {
1987            attrs.add(a);
1988          }
1989        }
1990    
1991        final ArrayList<Filter> comps = new ArrayList<Filter>(attrs.size());
1992        for (final Attribute a : attrs)
1993        {
1994          for (final ASN1OctetString v : a.getRawValues())
1995          {
1996            comps.add(Filter.createEqualityFilter(a.getName(), v.getValue()));
1997          }
1998        }
1999    
2000        if (superclassHandler != null)
2001        {
2002          final Filter f =
2003               superclassHandler.createFilter(o, addedRequiredOrAllowed);
2004          if (f.getFilterType() == Filter.FILTER_TYPE_AND)
2005          {
2006            comps.addAll(Arrays.asList(f.getComponents()));
2007          }
2008          else
2009          {
2010            comps.add(f);
2011          }
2012        }
2013    
2014        return Filter.createANDFilter(comps);
2015      }
2016    }