001/* 002 * Copyright 2009-2018 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2009-2018 Ping Identity Corporation 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 */ 021package com.unboundid.ldap.sdk.persist; 022 023 024 025import java.io.Serializable; 026import java.lang.reflect.Constructor; 027import java.lang.reflect.Field; 028import java.lang.reflect.InvocationTargetException; 029import java.lang.reflect.Method; 030import java.lang.reflect.Modifier; 031import java.util.ArrayList; 032import java.util.Arrays; 033import java.util.Iterator; 034import java.util.LinkedHashMap; 035import java.util.LinkedList; 036import java.util.Collections; 037import java.util.HashSet; 038import java.util.List; 039import java.util.Map; 040import java.util.TreeMap; 041import java.util.TreeSet; 042import java.util.concurrent.atomic.AtomicBoolean; 043 044import com.unboundid.asn1.ASN1OctetString; 045import com.unboundid.ldap.sdk.Attribute; 046import com.unboundid.ldap.sdk.DN; 047import com.unboundid.ldap.sdk.Entry; 048import com.unboundid.ldap.sdk.Filter; 049import com.unboundid.ldap.sdk.LDAPException; 050import com.unboundid.ldap.sdk.Modification; 051import com.unboundid.ldap.sdk.ModificationType; 052import com.unboundid.ldap.sdk.RDN; 053import com.unboundid.ldap.sdk.ReadOnlyEntry; 054import com.unboundid.ldap.sdk.schema.ObjectClassDefinition; 055import com.unboundid.ldap.sdk.schema.ObjectClassType; 056import com.unboundid.util.NotMutable; 057import com.unboundid.util.ThreadSafety; 058import com.unboundid.util.ThreadSafetyLevel; 059 060import static com.unboundid.ldap.sdk.persist.PersistMessages.*; 061import static com.unboundid.util.Debug.*; 062import 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) 074public 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 (final 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 (final 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 (final 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 (final 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 (final 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 (final Throwable t) 1229 { 1230 debugException(t); 1231 1232 if (t instanceof InvocationTargetException) 1233 { 1234 final Throwable targetException = 1235 ((InvocationTargetException) t).getTargetException(); 1236 throw new LDAPPersistException( 1237 ERR_OBJECT_HANDLER_ERROR_INVOKING_CONSTRUCTOR.get(type.getName(), 1238 getExceptionMessage(targetException)), targetException); 1239 } 1240 else 1241 { 1242 throw new LDAPPersistException( 1243 ERR_OBJECT_HANDLER_ERROR_INVOKING_CONSTRUCTOR.get(type.getName(), 1244 getExceptionMessage(t)), t); 1245 } 1246 } 1247 1248 decode(o, e); 1249 return o; 1250 } 1251 1252 1253 1254 /** 1255 * Initializes the provided object from the contents of the provided entry. 1256 * 1257 * @param o The object to be initialized with the contents of the provided 1258 * entry. 1259 * @param e The entry to use to initialize the object. 1260 * 1261 * @throws LDAPPersistException If an error occurs while initializing the 1262 * object from the information in the provided 1263 * entry. 1264 */ 1265 void decode(final T o, final Entry e) 1266 throws LDAPPersistException 1267 { 1268 if (superclassHandler != null) 1269 { 1270 superclassHandler.decode(o, e); 1271 } 1272 1273 setDNAndEntryFields(o, e); 1274 1275 final ArrayList<String> failureReasons = new ArrayList<String>(5); 1276 boolean successful = true; 1277 1278 for (final FieldInfo i : fieldMap.values()) 1279 { 1280 successful &= i.decode(o, e, failureReasons); 1281 } 1282 1283 for (final SetterInfo i : setterMap.values()) 1284 { 1285 successful &= i.invokeSetter(o, e, failureReasons); 1286 } 1287 1288 Throwable cause = null; 1289 if (postDecodeMethod != null) 1290 { 1291 try 1292 { 1293 postDecodeMethod.invoke(o); 1294 } 1295 catch (final Throwable t) 1296 { 1297 debugException(t); 1298 1299 if (t instanceof InvocationTargetException) 1300 { 1301 cause = ((InvocationTargetException) t).getTargetException(); 1302 } 1303 else 1304 { 1305 cause = t; 1306 } 1307 1308 successful = false; 1309 failureReasons.add( 1310 ERR_OBJECT_HANDLER_ERROR_INVOKING_POST_DECODE_METHOD.get( 1311 postDecodeMethod.getName(), type.getName(), 1312 getExceptionMessage(t))); 1313 } 1314 } 1315 1316 if (! successful) 1317 { 1318 throw new LDAPPersistException(concatenateStrings(failureReasons), o, 1319 cause); 1320 } 1321 } 1322 1323 1324 1325 /** 1326 * Encodes the provided object to an entry suitable for use in an add 1327 * operation. 1328 * 1329 * @param o The object to be encoded. 1330 * @param parentDN The parent DN to use by default for the entry that is 1331 * generated. If the provided object was previously read 1332 * from a directory server and includes a DN field or an 1333 * entry field with the original DN used for the object, 1334 * then that original DN will be used even if it is not 1335 * an immediate subordinate of the provided parent. This 1336 * may be {@code null} if the entry to create should not 1337 * have a parent but instead should have a DN consisting of 1338 * only a single RDN component. 1339 * 1340 * @return The entry containing an encoded representation of the provided 1341 * object. 1342 * 1343 * @throws LDAPPersistException If a problem occurs while encoding the 1344 * provided object. 1345 */ 1346 Entry encode(final T o, final String parentDN) 1347 throws LDAPPersistException 1348 { 1349 // Get the attributes that should be included in the entry. 1350 final LinkedHashMap<String,Attribute> attrMap = 1351 new LinkedHashMap<String,Attribute>(); 1352 attrMap.put("objectClass", objectClassAttribute); 1353 1354 for (final Map.Entry<String,FieldInfo> e : fieldMap.entrySet()) 1355 { 1356 final FieldInfo i = e.getValue(); 1357 if (! i.includeInAdd()) 1358 { 1359 continue; 1360 } 1361 1362 final Attribute a = i.encode(o, false); 1363 if (a != null) 1364 { 1365 attrMap.put(e.getKey(), a); 1366 } 1367 } 1368 1369 for (final Map.Entry<String,GetterInfo> e : getterMap.entrySet()) 1370 { 1371 final GetterInfo i = e.getValue(); 1372 if (! i.includeInAdd()) 1373 { 1374 continue; 1375 } 1376 1377 final Attribute a = i.encode(o); 1378 if (a != null) 1379 { 1380 attrMap.put(e.getKey(), a); 1381 } 1382 } 1383 1384 1385 // Get the DN to use for the entry. 1386 final String dn = constructDN(o, parentDN, attrMap); 1387 final Entry entry = new Entry(dn, attrMap.values()); 1388 1389 if (postEncodeMethod != null) 1390 { 1391 try 1392 { 1393 postEncodeMethod.invoke(o, entry); 1394 } 1395 catch (final Throwable t) 1396 { 1397 debugException(t); 1398 1399 if (t instanceof InvocationTargetException) 1400 { 1401 final Throwable targetException = 1402 ((InvocationTargetException) t).getTargetException(); 1403 throw new LDAPPersistException( 1404 ERR_OBJECT_HANDLER_ERROR_INVOKING_POST_ENCODE_METHOD.get( 1405 postEncodeMethod.getName(), type.getName(), 1406 getExceptionMessage(targetException)), targetException); 1407 } 1408 else 1409 { 1410 throw new LDAPPersistException( 1411 ERR_OBJECT_HANDLER_ERROR_INVOKING_POST_ENCODE_METHOD.get( 1412 postEncodeMethod.getName(), type.getName(), 1413 getExceptionMessage(t)), t); 1414 } 1415 } 1416 } 1417 1418 setDNAndEntryFields(o, entry); 1419 1420 if (superclassHandler != null) 1421 { 1422 final Entry e = superclassHandler.encode(o, parentDN); 1423 for (final Attribute a : e.getAttributes()) 1424 { 1425 entry.addAttribute(a); 1426 } 1427 } 1428 1429 return entry; 1430 } 1431 1432 1433 1434 /** 1435 * Sets the DN and entry fields for the provided object, if appropriate. 1436 * 1437 * @param o The object to be updated. 1438 * @param e The entry with which the object is associated. 1439 * 1440 * @throws LDAPPersistException If a problem occurs while setting the value 1441 * of the DN or entry field. 1442 */ 1443 private void setDNAndEntryFields(final T o, final Entry e) 1444 throws LDAPPersistException 1445 { 1446 if (dnField != null) 1447 { 1448 try 1449 { 1450 if (dnField.get(o) == null) 1451 { 1452 dnField.set(o, e.getDN()); 1453 } 1454 } 1455 catch (final Exception ex) 1456 { 1457 debugException(ex); 1458 throw new LDAPPersistException(ERR_OBJECT_HANDLER_ERROR_SETTING_DN.get( 1459 type.getName(), e.getDN(), dnField.getName(), 1460 getExceptionMessage(ex)), ex); 1461 } 1462 } 1463 1464 if (entryField != null) 1465 { 1466 try 1467 { 1468 if (entryField.get(o) == null) 1469 { 1470 entryField.set(o, new ReadOnlyEntry(e)); 1471 } 1472 } 1473 catch (final Exception ex) 1474 { 1475 debugException(ex); 1476 throw new LDAPPersistException( 1477 ERR_OBJECT_HANDLER_ERROR_SETTING_ENTRY.get(type.getName(), 1478 entryField.getName(), getExceptionMessage(ex)), ex); 1479 } 1480 } 1481 1482 if (superclassHandler != null) 1483 { 1484 superclassHandler.setDNAndEntryFields(o, e); 1485 } 1486 } 1487 1488 1489 1490 /** 1491 * Determines the DN that should be used for the entry associated with the 1492 * given object. If the provided object was retrieved from the directory 1493 * using the persistence framework and has a field with either the 1494 * {@link LDAPDNField} or {@link LDAPEntryField} annotation, then the actual 1495 * DN of the corresponding entry will be returned. Otherwise, it will be 1496 * constructed using the fields and getter methods marked for inclusion in 1497 * the entry RDN. 1498 * 1499 * @param o The object for which to determine the appropriate DN. 1500 * @param parentDN The parent DN to use for the constructed DN. If a 1501 * non-{@code null} value is provided, then that value will 1502 * be used as the parent DN (and the empty string will 1503 * indicate that the generated DN should not have a parent). 1504 * If the value is {@code null}, then the default parent DN 1505 * as defined in the {@link LDAPObject} annotation will be 1506 * used. If the provided parent DN is {@code null} and the 1507 * {@code LDAPObject} annotation does not specify a default 1508 * parent DN, then the generated DN will not have a parent. 1509 * 1510 * @return The entry DN for the provided object. 1511 * 1512 * @throws LDAPPersistException If a problem occurs while obtaining the 1513 * entry DN, or if the provided parent DN 1514 * represents an invalid DN. 1515 */ 1516 public String constructDN(final T o, final String parentDN) 1517 throws LDAPPersistException 1518 { 1519 final String existingDN = getEntryDN(o); 1520 if (existingDN != null) 1521 { 1522 return existingDN; 1523 } 1524 1525 final int numRDNs = rdnFields.size() + rdnGetters.size(); 1526 if (numRDNs == 0) 1527 { 1528 return superclassHandler.constructDN(o, parentDN); 1529 } 1530 1531 final LinkedHashMap<String,Attribute> attrMap = 1532 new LinkedHashMap<String,Attribute>(numRDNs); 1533 1534 for (final FieldInfo i : rdnFields) 1535 { 1536 final Attribute a = i.encode(o, true); 1537 if (a == null) 1538 { 1539 throw new LDAPPersistException( 1540 ERR_OBJECT_HANDLER_RDN_FIELD_MISSING_VALUE.get(type.getName(), 1541 i.getField().getName())); 1542 } 1543 1544 attrMap.put(toLowerCase(i.getAttributeName()), a); 1545 } 1546 1547 for (final GetterInfo i : rdnGetters) 1548 { 1549 final Attribute a = i.encode(o); 1550 if (a == null) 1551 { 1552 throw new LDAPPersistException( 1553 ERR_OBJECT_HANDLER_RDN_GETTER_MISSING_VALUE.get(type.getName(), 1554 i.getMethod().getName())); 1555 } 1556 1557 attrMap.put(toLowerCase(i.getAttributeName()), a); 1558 } 1559 1560 return constructDN(o, parentDN, attrMap); 1561 } 1562 1563 1564 1565 /** 1566 * Determines the DN that should be used for the entry associated with the 1567 * given object. If the provided object was retrieved from the directory 1568 * using the persistence framework and has a field with either the 1569 * {@link LDAPDNField} or {@link LDAPEntryField} annotation, then the actual 1570 * DN of the corresponding entry will be returned. Otherwise, it will be 1571 * constructed using the fields and getter methods marked for inclusion in 1572 * the entry RDN. 1573 * 1574 * @param o The object for which to determine the appropriate DN. 1575 * @param parentDN The parent DN to use for the constructed DN. If a 1576 * non-{@code null} value is provided, then that value will 1577 * be used as the parent DN (and the empty string will 1578 * indicate that the generated DN should not have a parent). 1579 * If the value is {@code null}, then the default parent DN 1580 * as defined in the {@link LDAPObject} annotation will be 1581 * used. If the provided parent DN is {@code null} and the 1582 * {@code LDAPObject} annotation does not specify a default 1583 * parent DN, then the generated DN will not have a parent. 1584 * @param attrMap A map of the attributes that will be included in the 1585 * entry and may be used to construct the RDN elements. 1586 * 1587 * @return The entry DN for the provided object. 1588 * 1589 * @throws LDAPPersistException If a problem occurs while obtaining the 1590 * entry DN, or if the provided parent DN 1591 * represents an invalid DN. 1592 */ 1593 String constructDN(final T o, final String parentDN, 1594 final Map<String,Attribute> attrMap) 1595 throws LDAPPersistException 1596 { 1597 final String existingDN = getEntryDN(o); 1598 if (existingDN != null) 1599 { 1600 return existingDN; 1601 } 1602 1603 final int numRDNs = rdnFields.size() + rdnGetters.size(); 1604 if (numRDNs == 0) 1605 { 1606 return superclassHandler.constructDN(o, parentDN); 1607 } 1608 1609 final ArrayList<String> rdnNameList = new ArrayList<String>(numRDNs); 1610 final ArrayList<byte[]> rdnValueList = new ArrayList<byte[]>(numRDNs); 1611 for (final FieldInfo i : rdnFields) 1612 { 1613 final Attribute a = attrMap.get(toLowerCase(i.getAttributeName())); 1614 if (a == null) 1615 { 1616 throw new LDAPPersistException( 1617 ERR_OBJECT_HANDLER_RDN_FIELD_MISSING_VALUE.get(type.getName(), 1618 i.getField().getName())); 1619 } 1620 1621 rdnNameList.add(a.getName()); 1622 rdnValueList.add(a.getValueByteArray()); 1623 } 1624 1625 for (final GetterInfo i : rdnGetters) 1626 { 1627 final Attribute a = attrMap.get(toLowerCase(i.getAttributeName())); 1628 if (a == null) 1629 { 1630 throw new LDAPPersistException( 1631 ERR_OBJECT_HANDLER_RDN_GETTER_MISSING_VALUE.get(type.getName(), 1632 i.getMethod().getName())); 1633 } 1634 1635 rdnNameList.add(a.getName()); 1636 rdnValueList.add(a.getValueByteArray()); 1637 } 1638 1639 final String[] rdnNames = new String[rdnNameList.size()]; 1640 rdnNameList.toArray(rdnNames); 1641 1642 final byte[][] rdnValues = new byte[rdnNames.length][]; 1643 rdnValueList.toArray(rdnValues); 1644 1645 final RDN rdn = new RDN(rdnNames, rdnValues); 1646 1647 if (parentDN == null) 1648 { 1649 return new DN(rdn, defaultParentDN).toString(); 1650 } 1651 else 1652 { 1653 try 1654 { 1655 final DN parsedParentDN = new DN(parentDN); 1656 return new DN(rdn, parsedParentDN).toString(); 1657 } 1658 catch (final LDAPException le) 1659 { 1660 debugException(le); 1661 throw new LDAPPersistException(ERR_OBJECT_HANDLER_INVALID_PARENT_DN.get( 1662 type.getName(), parentDN, le.getMessage()), le); 1663 } 1664 } 1665 } 1666 1667 1668 1669 /** 1670 * Creates a list of modifications that can be used to update the stored 1671 * representation of the provided object in the directory. If the provided 1672 * object was retrieved from the directory using the persistence framework and 1673 * includes a field with the {@link LDAPEntryField} annotation, then that 1674 * entry will be used to make the returned set of modifications as efficient 1675 * as possible. Otherwise, the resulting modifications will include attempts 1676 * to replace every attribute which are associated with fields or getters 1677 * that should be used in modify operations. 1678 * 1679 * @param o The object to be encoded. 1680 * @param deleteNullValues Indicates whether to include modifications that 1681 * may completely remove an attribute from the 1682 * entry if the corresponding field or getter method 1683 * has a value of {@code null}. 1684 * @param byteForByte Indicates whether to use a byte-for-byte 1685 * comparison to identify which attribute values 1686 * have changed. Using byte-for-byte comparison 1687 * requires additional processing over using each 1688 * attribute's associated matching rule, but it can 1689 * detect changes that would otherwise be considered 1690 * logically equivalent (e.g., changing the 1691 * capitalization of a value that uses a 1692 * case-insensitive matching rule). 1693 * @param attributes The set of LDAP attributes for which to include 1694 * modifications. If this is empty or {@code null}, 1695 * then all attributes marked for inclusion in the 1696 * modification will be examined. 1697 * 1698 * @return A list of modifications that can be used to update the stored 1699 * representation of the provided object in the directory. It may 1700 * be empty if there are no differences identified in the attributes 1701 * to be evaluated. 1702 * 1703 * @throws LDAPPersistException If a problem occurs while computing the set 1704 * of modifications. 1705 */ 1706 List<Modification> getModifications(final T o, final boolean deleteNullValues, 1707 final boolean byteForByte, 1708 final String... attributes) 1709 throws LDAPPersistException 1710 { 1711 final ReadOnlyEntry originalEntry; 1712 if (entryField != null) 1713 { 1714 originalEntry = getEntry(o); 1715 } 1716 else 1717 { 1718 originalEntry = null; 1719 } 1720 1721 // If we have an original copy of the entry, then we can try encoding the 1722 // updated object to a new entry and diff the two entries. 1723 if (originalEntry != null) 1724 { 1725 try 1726 { 1727 final T decodedOrig = decode(originalEntry); 1728 final Entry reEncodedOriginal = 1729 encode(decodedOrig, originalEntry.getParentDNString()); 1730 1731 final Entry newEntry = encode(o, originalEntry.getParentDNString()); 1732 final List<Modification> mods = Entry.diff(reEncodedOriginal, newEntry, 1733 true, false, byteForByte, attributes); 1734 if (! deleteNullValues) 1735 { 1736 final Iterator<Modification> iterator = mods.iterator(); 1737 while (iterator.hasNext()) 1738 { 1739 final Modification m = iterator.next(); 1740 if (m.getRawValues().length == 0) 1741 { 1742 iterator.remove(); 1743 } 1744 } 1745 } 1746 1747 // If there are any attributes that should be excluded from 1748 // modifications, then strip them out. 1749 HashSet<String> stripAttrs = null; 1750 for (final FieldInfo i : fieldMap.values()) 1751 { 1752 if (! i.includeInModify()) 1753 { 1754 if (stripAttrs == null) 1755 { 1756 stripAttrs = new HashSet<String>(10); 1757 } 1758 stripAttrs.add(toLowerCase(i.getAttributeName())); 1759 } 1760 } 1761 1762 for (final GetterInfo i : getterMap.values()) 1763 { 1764 if (! i.includeInModify()) 1765 { 1766 if (stripAttrs == null) 1767 { 1768 stripAttrs = new HashSet<String>(10); 1769 } 1770 stripAttrs.add(toLowerCase(i.getAttributeName())); 1771 } 1772 } 1773 1774 if (stripAttrs != null) 1775 { 1776 final Iterator<Modification> iterator = mods.iterator(); 1777 while (iterator.hasNext()) 1778 { 1779 final Modification m = iterator.next(); 1780 if (stripAttrs.contains(toLowerCase(m.getAttributeName()))) 1781 { 1782 iterator.remove(); 1783 } 1784 } 1785 } 1786 1787 return mods; 1788 } 1789 catch (final Exception e) 1790 { 1791 debugException(e); 1792 } 1793 finally 1794 { 1795 setDNAndEntryFields(o, originalEntry); 1796 } 1797 } 1798 1799 final HashSet<String> attrSet; 1800 if ((attributes == null) || (attributes.length == 0)) 1801 { 1802 attrSet = null; 1803 } 1804 else 1805 { 1806 attrSet = new HashSet<String>(attributes.length); 1807 for (final String s : attributes) 1808 { 1809 attrSet.add(toLowerCase(s)); 1810 } 1811 } 1812 1813 final ArrayList<Modification> mods = new ArrayList<Modification>(5); 1814 1815 for (final Map.Entry<String,FieldInfo> e : fieldMap.entrySet()) 1816 { 1817 final String attrName = toLowerCase(e.getKey()); 1818 if ((attrSet != null) && (! attrSet.contains(attrName))) 1819 { 1820 continue; 1821 } 1822 1823 final FieldInfo i = e.getValue(); 1824 if (! i.includeInModify()) 1825 { 1826 continue; 1827 } 1828 1829 final Attribute a = i.encode(o, false); 1830 if (a == null) 1831 { 1832 if (! deleteNullValues) 1833 { 1834 continue; 1835 } 1836 1837 if ((originalEntry != null) && (! originalEntry.hasAttribute(attrName))) 1838 { 1839 continue; 1840 } 1841 1842 mods.add(new Modification(ModificationType.REPLACE, 1843 i.getAttributeName())); 1844 continue; 1845 } 1846 1847 if (originalEntry != null) 1848 { 1849 final Attribute originalAttr = originalEntry.getAttribute(attrName); 1850 if ((originalAttr != null) && originalAttr.equals(a)) 1851 { 1852 continue; 1853 } 1854 } 1855 1856 mods.add(new Modification(ModificationType.REPLACE, i.getAttributeName(), 1857 a.getRawValues())); 1858 } 1859 1860 for (final Map.Entry<String,GetterInfo> e : getterMap.entrySet()) 1861 { 1862 final String attrName = toLowerCase(e.getKey()); 1863 if ((attrSet != null) && (! attrSet.contains(attrName))) 1864 { 1865 continue; 1866 } 1867 1868 final GetterInfo i = e.getValue(); 1869 if (! i.includeInModify()) 1870 { 1871 continue; 1872 } 1873 1874 final Attribute a = i.encode(o); 1875 if (a == null) 1876 { 1877 if (! deleteNullValues) 1878 { 1879 continue; 1880 } 1881 1882 if ((originalEntry != null) && (! originalEntry.hasAttribute(attrName))) 1883 { 1884 continue; 1885 } 1886 1887 mods.add(new Modification(ModificationType.REPLACE, 1888 i.getAttributeName())); 1889 continue; 1890 } 1891 1892 if (originalEntry != null) 1893 { 1894 final Attribute originalAttr = originalEntry.getAttribute(attrName); 1895 if ((originalAttr != null) && originalAttr.equals(a)) 1896 { 1897 continue; 1898 } 1899 } 1900 1901 mods.add(new Modification(ModificationType.REPLACE, i.getAttributeName(), 1902 a.getRawValues())); 1903 } 1904 1905 if (superclassHandler != null) 1906 { 1907 final List<Modification> superMods = 1908 superclassHandler.getModifications(o, deleteNullValues, byteForByte, 1909 attributes); 1910 final ArrayList<Modification> modsToAdd = 1911 new ArrayList<Modification>(superMods.size()); 1912 for (final Modification sm : superMods) 1913 { 1914 boolean add = true; 1915 for (final Modification m : mods) 1916 { 1917 if (m.getAttributeName().equalsIgnoreCase(sm.getAttributeName())) 1918 { 1919 add = false; 1920 break; 1921 } 1922 } 1923 if (add) 1924 { 1925 modsToAdd.add(sm); 1926 } 1927 } 1928 mods.addAll(modsToAdd); 1929 } 1930 1931 return Collections.unmodifiableList(mods); 1932 } 1933 1934 1935 1936 /** 1937 * Retrieves a filter that will match any entry containing the structural and 1938 * auxiliary classes for this object type. 1939 * 1940 * @return A filter that will match any entry containing the structural and 1941 * auxiliary classes for this object type. 1942 */ 1943 public Filter createBaseFilter() 1944 { 1945 if (auxiliaryClasses.length == 0) 1946 { 1947 return Filter.createEqualityFilter("objectClass", structuralClass); 1948 } 1949 else 1950 { 1951 final ArrayList<Filter> comps = 1952 new ArrayList<Filter>(1+auxiliaryClasses.length); 1953 comps.add(Filter.createEqualityFilter("objectClass", structuralClass)); 1954 for (final String s : auxiliaryClasses) 1955 { 1956 comps.add(Filter.createEqualityFilter("objectClass", s)); 1957 } 1958 return Filter.createANDFilter(comps); 1959 } 1960 } 1961 1962 1963 1964 /** 1965 * Retrieves a filter that can be used to search for entries matching the 1966 * provided object. It will be constructed as an AND search using all fields 1967 * with a non-{@code null} value and that have a {@link LDAPField} annotation 1968 * with the {@code inFilter} element set to {@code true}, and all getter 1969 * methods that return a non-{@code null} value and have a 1970 * {@link LDAPGetter} annotation with the {@code inFilter} element set to 1971 * {@code true}. 1972 * 1973 * @param o The object for which to create the search filter. 1974 * 1975 * @return A filter that can be used to search for entries matching the 1976 * provided object. 1977 * 1978 * @throws LDAPPersistException If it is not possible to construct a search 1979 * filter for some reason (e.g., because the 1980 * provided object does not have any 1981 * non-{@code null} fields or getters that are 1982 * marked for inclusion in filters). 1983 */ 1984 public Filter createFilter(final T o) 1985 throws LDAPPersistException 1986 { 1987 final AtomicBoolean addedRequiredOrAllowed = new AtomicBoolean(false); 1988 1989 final Filter f = createFilter(o, addedRequiredOrAllowed); 1990 if (! addedRequiredOrAllowed.get()) 1991 { 1992 throw new LDAPPersistException( 1993 ERR_OBJECT_HANDLER_FILTER_MISSING_REQUIRED_OR_ALLOWED.get()); 1994 } 1995 1996 return f; 1997 } 1998 1999 2000 2001 /** 2002 * Retrieves a filter that can be used to search for entries matching the 2003 * provided object. It will be constructed as an AND search using all fields 2004 * with a non-{@code null} value and that have a {@link LDAPField} annotation 2005 * with the {@code inFilter} element set to {@code true}, and all getter 2006 * methods that return a non-{@code null} value and have a 2007 * {@link LDAPGetter} annotation with the {@code inFilter} element set to 2008 * {@code true}. 2009 * 2010 * @param o The object for which to create the search 2011 * filter. 2012 * @param addedRequiredOrAllowed Indicates whether any filter elements from 2013 * required or allowed fields or getters have 2014 * been added to the filter yet. 2015 * 2016 * @return A filter that can be used to search for entries matching the 2017 * provided object. 2018 * 2019 * @throws LDAPPersistException If it is not possible to construct a search 2020 * filter for some reason (e.g., because the 2021 * provided object does not have any 2022 * non-{@code null} fields or getters that are 2023 * marked for inclusion in filters). 2024 */ 2025 private Filter createFilter(final T o, 2026 final AtomicBoolean addedRequiredOrAllowed) 2027 throws LDAPPersistException 2028 { 2029 final ArrayList<Attribute> attrs = new ArrayList<Attribute>(5); 2030 attrs.add(objectClassAttribute); 2031 2032 for (final FieldInfo i : requiredFilterFields) 2033 { 2034 final Attribute a = i.encode(o, true); 2035 if (a == null) 2036 { 2037 throw new LDAPPersistException( 2038 ERR_OBJECT_HANDLER_FILTER_MISSING_REQUIRED_FIELD.get( 2039 i.getField().getName())); 2040 } 2041 else 2042 { 2043 attrs.add(a); 2044 addedRequiredOrAllowed.set(true); 2045 } 2046 } 2047 2048 for (final GetterInfo i : requiredFilterGetters) 2049 { 2050 final Attribute a = i.encode(o); 2051 if (a == null) 2052 { 2053 throw new LDAPPersistException( 2054 ERR_OBJECT_HANDLER_FILTER_MISSING_REQUIRED_GETTER.get( 2055 i.getMethod().getName())); 2056 } 2057 else 2058 { 2059 attrs.add(a); 2060 addedRequiredOrAllowed.set(true); 2061 } 2062 } 2063 2064 for (final FieldInfo i : alwaysAllowedFilterFields) 2065 { 2066 final Attribute a = i.encode(o, true); 2067 if (a != null) 2068 { 2069 attrs.add(a); 2070 addedRequiredOrAllowed.set(true); 2071 } 2072 } 2073 2074 for (final GetterInfo i : alwaysAllowedFilterGetters) 2075 { 2076 final Attribute a = i.encode(o); 2077 if (a != null) 2078 { 2079 attrs.add(a); 2080 addedRequiredOrAllowed.set(true); 2081 } 2082 } 2083 2084 for (final FieldInfo i : conditionallyAllowedFilterFields) 2085 { 2086 final Attribute a = i.encode(o, true); 2087 if (a != null) 2088 { 2089 attrs.add(a); 2090 } 2091 } 2092 2093 for (final GetterInfo i : conditionallyAllowedFilterGetters) 2094 { 2095 final Attribute a = i.encode(o); 2096 if (a != null) 2097 { 2098 attrs.add(a); 2099 } 2100 } 2101 2102 final ArrayList<Filter> comps = new ArrayList<Filter>(attrs.size()); 2103 for (final Attribute a : attrs) 2104 { 2105 for (final ASN1OctetString v : a.getRawValues()) 2106 { 2107 comps.add(Filter.createEqualityFilter(a.getName(), v.getValue())); 2108 } 2109 } 2110 2111 if (superclassHandler != null) 2112 { 2113 final Filter f = 2114 superclassHandler.createFilter(o, addedRequiredOrAllowed); 2115 if (f.getFilterType() == Filter.FILTER_TYPE_AND) 2116 { 2117 comps.addAll(Arrays.asList(f.getComponents())); 2118 } 2119 else 2120 { 2121 comps.add(f); 2122 } 2123 } 2124 2125 return Filter.createANDFilter(comps); 2126 } 2127}