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