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