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