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