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