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