001 /*
002 * Copyright 2009-2014 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2009-2014 UnboundID Corp.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
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 }