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