001/* 002 * Copyright 2009-2018 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2009-2018 Ping Identity Corporation 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU General Public License (GPLv2 only) 010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 011 * as published by the Free Software Foundation. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Public License for more details. 017 * 018 * You should have received a copy of the GNU General Public License 019 * along with this program; if not, see <http://www.gnu.org/licenses>. 020 */ 021package com.unboundid.ldap.sdk.persist; 022 023 024 025import java.io.Serializable; 026import java.util.ArrayList; 027import java.util.Arrays; 028import java.util.Collections; 029import java.util.LinkedHashSet; 030import java.util.LinkedList; 031import java.util.List; 032import java.util.Map; 033import java.util.Set; 034import java.util.concurrent.ConcurrentHashMap; 035 036import com.unboundid.ldap.sdk.AddRequest; 037import com.unboundid.ldap.sdk.Attribute; 038import com.unboundid.ldap.sdk.BindResult; 039import com.unboundid.ldap.sdk.Control; 040import com.unboundid.ldap.sdk.DeleteRequest; 041import com.unboundid.ldap.sdk.DereferencePolicy; 042import com.unboundid.ldap.sdk.Entry; 043import com.unboundid.ldap.sdk.Filter; 044import com.unboundid.ldap.sdk.LDAPConnection; 045import com.unboundid.ldap.sdk.LDAPEntrySource; 046import com.unboundid.ldap.sdk.LDAPException; 047import com.unboundid.ldap.sdk.LDAPInterface; 048import com.unboundid.ldap.sdk.LDAPResult; 049import com.unboundid.ldap.sdk.Modification; 050import com.unboundid.ldap.sdk.ModificationType; 051import com.unboundid.ldap.sdk.ModifyRequest; 052import com.unboundid.ldap.sdk.ResultCode; 053import com.unboundid.ldap.sdk.SearchRequest; 054import com.unboundid.ldap.sdk.SearchResult; 055import com.unboundid.ldap.sdk.SearchScope; 056import com.unboundid.ldap.sdk.SimpleBindRequest; 057import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition; 058import com.unboundid.ldap.sdk.schema.ObjectClassDefinition; 059import com.unboundid.ldap.sdk.schema.Schema; 060import com.unboundid.util.NotMutable; 061import com.unboundid.util.ThreadSafety; 062import com.unboundid.util.ThreadSafetyLevel; 063 064import static com.unboundid.ldap.sdk.persist.PersistMessages.*; 065import static com.unboundid.util.Debug.*; 066import static com.unboundid.util.StaticUtils.*; 067import static com.unboundid.util.Validator.*; 068 069 070 071/** 072 * This class provides an interface that can be used to store and update 073 * representations of Java objects in an LDAP directory server, and to find and 074 * retrieve Java objects from the directory server. The objects to store, 075 * update, and retrieve must be marked with the {@link LDAPObject} annotation. 076 * Fields and methods within the class should be marked with the 077 * {@link LDAPField}, {@link LDAPGetter}, or {@link LDAPSetter} 078 * annotations as appropriate to indicate how to convert between the LDAP and 079 * the Java representations of the content. 080 * 081 * @param <T> The type of object handled by this class. 082 */ 083@NotMutable() 084@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 085public final class LDAPPersister<T> 086 implements Serializable 087{ 088 /** 089 * The serial version UID for this serializable class. 090 */ 091 private static final long serialVersionUID = -4001743482496453961L; 092 093 094 095 /** 096 * An empty array of controls that will be used if none are specified. 097 */ 098 private static final Control[] NO_CONTROLS = new Control[0]; 099 100 101 102 /** 103 * The map of instances created so far. 104 */ 105 private static final ConcurrentHashMap<Class<?>,LDAPPersister<?>> INSTANCES = 106 new ConcurrentHashMap<Class<?>,LDAPPersister<?>>(); 107 108 109 110 // The LDAP object handler that will be used for this class. 111 private final LDAPObjectHandler<T> handler; 112 113 114 115 /** 116 * Creates a new instance of this LDAP persister that will be used to interact 117 * with objects of the specified type. 118 * 119 * @param type The type of object managed by this LDAP persister. It must 120 * not be {@code null}, and it must be marked with the 121 * {@link LDAPObject} annotation. 122 * 123 * @throws LDAPPersistException If the provided class is not suitable for 124 * persisting in an LDAP directory server. 125 */ 126 private LDAPPersister(final Class<T> type) 127 throws LDAPPersistException 128 { 129 handler = new LDAPObjectHandler<T>(type); 130 } 131 132 133 134 /** 135 * Retrieves an {@code LDAPPersister} instance for use with objects of the 136 * specified type. 137 * 138 * @param <T> The generic type for the {@code LDAPPersister} instance. 139 * @param type The type of object for which to retrieve the LDAP persister. 140 * It must not be {@code null}, and it must be marked with the 141 * {@link LDAPObject} annotation. 142 * 143 * @return The {@code LDAPPersister} instance for use with objects of the 144 * specified type. 145 * 146 * @throws LDAPPersistException If the provided class is not suitable for 147 * persisting in an LDAP directory server. 148 */ 149 @SuppressWarnings("unchecked") 150 public static <T> LDAPPersister<T> getInstance(final Class<T> type) 151 throws LDAPPersistException 152 { 153 ensureNotNull(type); 154 155 LDAPPersister<T> p = (LDAPPersister<T>) INSTANCES.get(type); 156 if (p == null) 157 { 158 p = new LDAPPersister<T>(type); 159 INSTANCES.put(type, p); 160 } 161 162 return p; 163 } 164 165 166 167 /** 168 * Retrieves the {@link LDAPObject} annotation of the class used for objects 169 * of the associated type. 170 * 171 * @return The {@code LDAPObject} annotation of the class used for objects of 172 * the associated type. 173 */ 174 public LDAPObject getLDAPObjectAnnotation() 175 { 176 return handler.getLDAPObjectAnnotation(); 177 } 178 179 180 181 /** 182 * Retrieves the {@link LDAPObjectHandler} instance associated with this 183 * LDAP persister class. It provides easy access to information about the 184 * {@link LDAPObject} annotation and the fields, getters, and setters used 185 * by the object. 186 * 187 * @return The {@code LDAPObjectHandler} instance associated with this LDAP 188 * persister class. 189 */ 190 public LDAPObjectHandler<T> getObjectHandler() 191 { 192 return handler; 193 } 194 195 196 197 /** 198 * Constructs a list of LDAP attribute type definitions which may be added to 199 * the directory server schema to allow it to hold objects of this type. Note 200 * that the object identifiers used for the constructed attribute type 201 * definitions are not required to be valid or unique. 202 * 203 * @return A list of attribute type definitions that may be used to represent 204 * objects of the associated type in an LDAP directory. 205 * 206 * @throws LDAPPersistException If a problem occurs while attempting to 207 * generate the list of attribute type 208 * definitions. 209 */ 210 public List<AttributeTypeDefinition> constructAttributeTypes() 211 throws LDAPPersistException 212 { 213 return constructAttributeTypes(DefaultOIDAllocator.getInstance()); 214 } 215 216 217 218 /** 219 * Constructs a list of LDAP attribute type definitions which may be added to 220 * the directory server schema to allow it to hold objects of this type. Note 221 * that the object identifiers used for the constructed attribute type 222 * definitions are not required to be valid or unique. 223 * 224 * @param a The OID allocator to use to generate the object identifiers for 225 * the constructed attribute types. It must not be {@code null}. 226 * 227 * @return A list of attribute type definitions that may be used to represent 228 * objects of the associated type in an LDAP directory. 229 * 230 * @throws LDAPPersistException If a problem occurs while attempting to 231 * generate the list of attribute type 232 * definitions. 233 */ 234 public List<AttributeTypeDefinition> constructAttributeTypes( 235 final OIDAllocator a) 236 throws LDAPPersistException 237 { 238 final LinkedList<AttributeTypeDefinition> attrList = 239 new LinkedList<AttributeTypeDefinition>(); 240 241 for (final FieldInfo i : handler.getFields().values()) 242 { 243 attrList.add(i.constructAttributeType(a)); 244 } 245 246 for (final GetterInfo i : handler.getGetters().values()) 247 { 248 attrList.add(i.constructAttributeType(a)); 249 } 250 251 return Collections.unmodifiableList(attrList); 252 } 253 254 255 256 /** 257 * Constructs a list of LDAP object class definitions which may be added to 258 * the directory server schema to allow it to hold objects of this type. Note 259 * that the object identifiers used for the constructed object class 260 * definitions are not required to be valid or unique. 261 * 262 * @return A list of object class definitions that may be used to represent 263 * objects of the associated type in an LDAP directory. 264 * 265 * @throws LDAPPersistException If a problem occurs while attempting to 266 * generate the list of object class 267 * definitions. 268 */ 269 public List<ObjectClassDefinition> constructObjectClasses() 270 throws LDAPPersistException 271 { 272 return constructObjectClasses(DefaultOIDAllocator.getInstance()); 273 } 274 275 276 277 /** 278 * Constructs a list of LDAP object class definitions which may be added to 279 * the directory server schema to allow it to hold objects of this type. Note 280 * that the object identifiers used for the constructed object class 281 * definitions are not required to be valid or unique. 282 * 283 * @param a The OID allocator to use to generate the object identifiers for 284 * the constructed object classes. It must not be {@code null}. 285 * 286 * @return A list of object class definitions that may be used to represent 287 * objects of the associated type in an LDAP directory. 288 * 289 * @throws LDAPPersistException If a problem occurs while attempting to 290 * generate the list of object class 291 * definitions. 292 */ 293 public List<ObjectClassDefinition> constructObjectClasses( 294 final OIDAllocator a) 295 throws LDAPPersistException 296 { 297 return handler.constructObjectClasses(a); 298 } 299 300 301 302 /** 303 * Attempts to update the schema for a directory server to ensure that it 304 * includes the attribute type and object class definitions used to store 305 * objects of the associated type. It will do this by attempting to add 306 * values to the attributeTypes and objectClasses attributes to the server 307 * schema. It will attempt to preserve existing schema elements. 308 * 309 * @param i The interface to use to communicate with the directory server. 310 * 311 * @return {@code true} if the schema was updated, or {@code false} if all of 312 * the necessary schema elements were already present. 313 * 314 * @throws LDAPException If an error occurs while attempting to update the 315 * server schema. 316 */ 317 public boolean updateSchema(final LDAPInterface i) 318 throws LDAPException 319 { 320 return updateSchema(i, DefaultOIDAllocator.getInstance()); 321 } 322 323 324 325 /** 326 * Attempts to update the schema for a directory server to ensure that it 327 * includes the attribute type and object class definitions used to store 328 * objects of the associated type. It will do this by attempting to add 329 * values to the attributeTypes and objectClasses attributes to the server 330 * schema. It will preserve existing attribute types, and will only modify 331 * existing object classes if the existing definition does not allow all of 332 * the attributes needed to store the associated object. 333 * <BR><BR> 334 * Note that because there is no standard process for altering a directory 335 * server's schema over LDAP, the approach used by this method may not work 336 * for all types of directory servers. In addition, some directory servers 337 * may place restrictions on schema updates, particularly around the 338 * modification of existing schema elements. This method is provided as a 339 * convenience, but it may not work as expected in all environments or under 340 * all conditions. 341 * 342 * @param i The interface to use to communicate with the directory server. 343 * @param a The OID allocator to use ot generate the object identifiers to 344 * use for the constructed attribute types and object classes. It 345 * must not be {@code null}. 346 * 347 * @return {@code true} if the schema was updated, or {@code false} if all of 348 * the necessary schema elements were already present. 349 * 350 * @throws LDAPException If an error occurs while attempting to update the 351 * server schema. 352 */ 353 public boolean updateSchema(final LDAPInterface i, final OIDAllocator a) 354 throws LDAPException 355 { 356 final Schema s = i.getSchema(); 357 358 final List<AttributeTypeDefinition> generatedTypes = 359 constructAttributeTypes(a); 360 final List<ObjectClassDefinition> generatedClasses = 361 constructObjectClasses(a); 362 363 final LinkedList<String> newAttrList = new LinkedList<String>(); 364 for (final AttributeTypeDefinition d : generatedTypes) 365 { 366 if (s.getAttributeType(d.getNameOrOID()) == null) 367 { 368 newAttrList.add(d.toString()); 369 } 370 } 371 372 final LinkedList<String> newOCList = new LinkedList<String>(); 373 for (final ObjectClassDefinition d : generatedClasses) 374 { 375 final ObjectClassDefinition existing = s.getObjectClass(d.getNameOrOID()); 376 if (existing == null) 377 { 378 newOCList.add(d.toString()); 379 } 380 else 381 { 382 final Set<AttributeTypeDefinition> existingRequired = 383 existing.getRequiredAttributes(s, true); 384 final Set<AttributeTypeDefinition> existingOptional = 385 existing.getOptionalAttributes(s, true); 386 387 final LinkedHashSet<String> newOptionalNames = 388 new LinkedHashSet<String>(0); 389 addMissingAttrs(d.getRequiredAttributes(), existingRequired, 390 existingOptional, newOptionalNames); 391 addMissingAttrs(d.getOptionalAttributes(), existingRequired, 392 existingOptional, newOptionalNames); 393 394 if (! newOptionalNames.isEmpty()) 395 { 396 final LinkedHashSet<String> newOptionalSet = 397 new LinkedHashSet<String>(); 398 newOptionalSet.addAll( 399 Arrays.asList(existing.getOptionalAttributes())); 400 newOptionalSet.addAll(newOptionalNames); 401 402 final String[] newOptional = new String[newOptionalSet.size()]; 403 newOptionalSet.toArray(newOptional); 404 405 final ObjectClassDefinition newOC = new ObjectClassDefinition( 406 existing.getOID(), existing.getNames(), 407 existing.getDescription(), existing.isObsolete(), 408 existing.getSuperiorClasses(), existing.getObjectClassType(), 409 existing.getRequiredAttributes(), newOptional, 410 existing.getExtensions()); 411 newOCList.add(newOC.toString()); 412 } 413 } 414 } 415 416 final LinkedList<Modification> mods = new LinkedList<Modification>(); 417 if (! newAttrList.isEmpty()) 418 { 419 final String[] newAttrValues = new String[newAttrList.size()]; 420 mods.add(new Modification(ModificationType.ADD, 421 Schema.ATTR_ATTRIBUTE_TYPE, newAttrList.toArray(newAttrValues))); 422 } 423 424 if (! newOCList.isEmpty()) 425 { 426 final String[] newOCValues = new String[newOCList.size()]; 427 mods.add(new Modification(ModificationType.ADD, 428 Schema.ATTR_OBJECT_CLASS, newOCList.toArray(newOCValues))); 429 } 430 431 if (mods.isEmpty()) 432 { 433 return false; 434 } 435 else 436 { 437 i.modify(s.getSchemaEntry().getDN(), mods); 438 return true; 439 } 440 } 441 442 443 444 /** 445 * Adds any missing attributes to the provided set. 446 * 447 * @param names The names of the attributes which may potentially be 448 * added. 449 * @param required The existing required definitions. 450 * @param optional The existing optional definitions. 451 * @param missing The set to which any missing names should be added. 452 */ 453 private static void addMissingAttrs(final String[] names, 454 final Set<AttributeTypeDefinition> required, 455 final Set<AttributeTypeDefinition> optional, 456 final Set<String> missing) 457 { 458 for (final String name : names) 459 { 460 boolean found = false; 461 for (final AttributeTypeDefinition eA : required) 462 { 463 if (eA.hasNameOrOID(name)) 464 { 465 found = true; 466 break; 467 } 468 } 469 470 if (! found) 471 { 472 for (final AttributeTypeDefinition eA : optional) 473 { 474 if (eA.hasNameOrOID(name)) 475 { 476 found = true; 477 break; 478 } 479 } 480 481 if (! found) 482 { 483 missing.add(name); 484 } 485 } 486 } 487 } 488 489 490 491 /** 492 * Encodes the provided object to an entry that is suitable for storing it in 493 * an LDAP directory server. 494 * 495 * @param o The object to be encoded. It must not be {@code null}. 496 * @param parentDN The parent DN to use for the resulting entry. If the 497 * provided object was previously read from a directory 498 * server and includes a field marked with the 499 * {@link LDAPDNField} or {@link LDAPEntryField} annotation, 500 * then that field may be used to retrieve the actual DN of 501 * the associated entry. If the actual DN of the associated 502 * entry is not available, then a DN will be constructed 503 * from the RDN fields and/or getter methods declared in the 504 * class. If the provided parent DN is {@code null}, then 505 * the default parent DN defined in the {@link LDAPObject} 506 * annotation will be used. 507 * 508 * @return An entry containing the encoded representation of the provided 509 * object. It may be altered by the caller if necessary. 510 * 511 * @throws LDAPPersistException If a problem occurs while attempting to 512 * encode the provided object. 513 */ 514 public Entry encode(final T o, final String parentDN) 515 throws LDAPPersistException 516 { 517 ensureNotNull(o); 518 return handler.encode(o, parentDN); 519 } 520 521 522 523 /** 524 * Creates an object and initializes it with the contents of the provided 525 * entry. 526 * 527 * @param entry The entry to use to create the object. It must not be 528 * {@code null}. 529 * 530 * @return The object created from the provided entry. 531 * 532 * @throws LDAPPersistException If an error occurs while attempting to 533 * create or initialize the object from the 534 * provided entry. 535 */ 536 public T decode(final Entry entry) 537 throws LDAPPersistException 538 { 539 ensureNotNull(entry); 540 return handler.decode(entry); 541 } 542 543 544 545 /** 546 * Initializes the provided object from the information contained in the 547 * given entry. 548 * 549 * @param o The object to initialize with the contents of the provided 550 * entry. It must not be {@code null}. 551 * @param entry The entry to use to create the object. It must not be 552 * {@code null}. 553 * 554 * @throws LDAPPersistException If an error occurs while attempting to 555 * initialize the object from the provided 556 * entry. If an exception is thrown, then the 557 * provided object may or may not have been 558 * altered. 559 */ 560 public void decode(final T o, final Entry entry) 561 throws LDAPPersistException 562 { 563 ensureNotNull(o, entry); 564 handler.decode(o, entry); 565 } 566 567 568 569 /** 570 * Adds the provided object to the directory server using the provided 571 * connection. 572 * 573 * @param o The object to be added. It must not be {@code null}. 574 * @param i The interface to use to communicate with the directory 575 * server. It must not be {@code null}. 576 * @param parentDN The parent DN to use for the resulting entry. If the 577 * provided object was previously read from a directory 578 * server and includes a field marked with the 579 * {@link LDAPDNField} or {@link LDAPEntryField} annotation, 580 * then that field may be used to retrieve the actual DN of 581 * the associated entry. If the actual DN of the associated 582 * entry is not available, then a DN will be constructed 583 * from the RDN fields and/or getter methods declared in the 584 * class. If the provided parent DN is {@code null}, then 585 * the default parent DN defined in the {@link LDAPObject} 586 * annotation will be used. 587 * @param controls An optional set of controls to include in the add 588 * request. 589 * 590 * @return The result of processing the add operation. 591 * 592 * @throws LDAPPersistException If a problem occurs while encoding or adding 593 * the entry. 594 */ 595 public LDAPResult add(final T o, final LDAPInterface i, final String parentDN, 596 final Control... controls) 597 throws LDAPPersistException 598 { 599 ensureNotNull(o, i); 600 final Entry e = encode(o, parentDN); 601 602 try 603 { 604 final AddRequest addRequest = new AddRequest(e); 605 if (controls != null) 606 { 607 addRequest.setControls(controls); 608 } 609 610 return i.add(addRequest); 611 } 612 catch (final LDAPException le) 613 { 614 debugException(le); 615 throw new LDAPPersistException(le); 616 } 617 } 618 619 620 621 /** 622 * Deletes the provided object from the directory. 623 * 624 * @param o The object to be deleted. It must not be {@code null}, 625 * and it must have been retrieved from the directory and 626 * have a field with either the {@link LDAPDNField} or 627 * {@link LDAPEntryField} annotations. 628 * @param i The interface to use to communicate with the directory 629 * server. It must not be {@code null}. 630 * @param controls An optional set of controls to include in the add 631 * request. 632 * 633 * @return The result of processing the delete operation. 634 * 635 * @throws LDAPPersistException If a problem occurs while attempting to 636 * delete the entry. 637 */ 638 public LDAPResult delete(final T o, final LDAPInterface i, 639 final Control... controls) 640 throws LDAPPersistException 641 { 642 ensureNotNull(o, i); 643 final String dn = handler.getEntryDN(o); 644 if (dn == null) 645 { 646 throw new LDAPPersistException(ERR_PERSISTER_DELETE_NO_DN.get()); 647 } 648 649 try 650 { 651 final DeleteRequest deleteRequest = new DeleteRequest(dn); 652 if (controls != null) 653 { 654 deleteRequest.setControls(controls); 655 } 656 657 return i.delete(deleteRequest); 658 } 659 catch (final LDAPException le) 660 { 661 debugException(le); 662 throw new LDAPPersistException(le); 663 } 664 } 665 666 667 668 /** 669 * Retrieves a list of modifications that can be used to update the stored 670 * representation of the provided object in the directory. If the provided 671 * object was retrieved from the directory using the persistence framework and 672 * includes a field with the {@link LDAPEntryField} annotation, then that 673 * entry will be used to make the returned set of modifications as efficient 674 * as possible. Otherwise, the resulting modifications will include attempts 675 * to replace every attribute which are associated with fields or getters 676 * that should be used in modify operations. 677 * 678 * @param o The object for which to generate the list of 679 * modifications. It must not be {@code null}. 680 * @param deleteNullValues Indicates whether to include modifications that 681 * may completely remove an attribute from the 682 * entry if the corresponding field or getter method 683 * has a value of {@code null}. 684 * @param attributes The set of LDAP attributes for which to include 685 * modifications. If this is empty or {@code null}, 686 * then all attributes marked for inclusion in the 687 * modification will be examined. 688 * 689 * @return An unmodifiable list of modifications that can be used to update 690 * the stored representation of the provided object in the directory. 691 * It may be empty if there are no differences identified in the 692 * attributes to be evaluated. 693 * 694 * @throws LDAPPersistException If a problem occurs while computing the set 695 * of modifications. 696 */ 697 public List<Modification> getModifications(final T o, 698 final boolean deleteNullValues, 699 final String... attributes) 700 throws LDAPPersistException 701 { 702 return getModifications(o, deleteNullValues, false, attributes); 703 } 704 705 706 707 /** 708 * Retrieves a list of modifications that can be used to update the stored 709 * representation of the provided object in the directory. If the provided 710 * object was retrieved from the directory using the persistence framework and 711 * includes a field with the {@link LDAPEntryField} annotation, then that 712 * entry will be used to make the returned set of modifications as efficient 713 * as possible. Otherwise, the resulting modifications will include attempts 714 * to replace every attribute which are associated with fields or getters 715 * that should be used in modify operations. 716 * 717 * @param o The object for which to generate the list of 718 * modifications. It must not be {@code null}. 719 * @param deleteNullValues Indicates whether to include modifications that 720 * may completely remove an attribute from the 721 * entry if the corresponding field or getter method 722 * has a value of {@code null}. 723 * @param byteForByte Indicates whether to use a byte-for-byte 724 * comparison to identify which attribute values 725 * have changed. Using byte-for-byte comparison 726 * requires additional processing over using each 727 * attribute's associated matching rule, but it can 728 * detect changes that would otherwise be considered 729 * logically equivalent (e.g., changing the 730 * capitalization of a value that uses a 731 * case-insensitive matching rule). 732 * @param attributes The set of LDAP attributes for which to include 733 * modifications. If this is empty or {@code null}, 734 * then all attributes marked for inclusion in the 735 * modification will be examined. 736 * 737 * @return An unmodifiable list of modifications that can be used to update 738 * the stored representation of the provided object in the directory. 739 * It may be empty if there are no differences identified in the 740 * attributes to be evaluated. 741 * 742 * @throws LDAPPersistException If a problem occurs while computing the set 743 * of modifications. 744 */ 745 public List<Modification> getModifications(final T o, 746 final boolean deleteNullValues, 747 final boolean byteForByte, 748 final String... attributes) 749 throws LDAPPersistException 750 { 751 ensureNotNull(o); 752 return handler.getModifications(o, deleteNullValues, byteForByte, 753 attributes); 754 } 755 756 757 758 /** 759 * Updates the stored representation of the provided object in the directory. 760 * If the provided object was retrieved from the directory using the 761 * persistence framework and includes a field with the {@link LDAPEntryField} 762 * annotation, then that entry will be used to make the returned set of 763 * modifications as efficient as possible. Otherwise, the resulting 764 * modifications will include attempts to replace every attribute which are 765 * associated with fields or getters that should be used in modify operations. 766 * If there are no modifications, then no modification will be attempted, and 767 * this method will return {@code null} rather than an {@code LDAPResult}. 768 * 769 * @param o The object for which to generate the list of 770 * modifications. It must not be {@code null}. 771 * @param i The interface to use to communicate with the 772 * directory server. It must not be {@code null}. 773 * @param dn The DN to use for the entry. It must not be 774 * {@code null} if the object was not retrieved from 775 * the directory using the persistence framework or 776 * does not have a field marked with the 777 * {@link LDAPDNField} or {@link LDAPEntryField} 778 * annotation. 779 * @param deleteNullValues Indicates whether to include modifications that 780 * may completely remove an attribute from the 781 * entry if the corresponding field or getter method 782 * has a value of {@code null}. 783 * @param attributes The set of LDAP attributes for which to include 784 * modifications. If this is empty or {@code null}, 785 * then all attributes marked for inclusion in the 786 * modification will be examined. 787 * 788 * @return The result of processing the modify operation, or {@code null} if 789 * there were no changes to apply (and therefore no modification was 790 * performed). 791 * 792 * @throws LDAPPersistException If a problem occurs while computing the set 793 * of modifications. 794 */ 795 public LDAPResult modify(final T o, final LDAPInterface i, final String dn, 796 final boolean deleteNullValues, 797 final String... attributes) 798 throws LDAPPersistException 799 { 800 return modify(o, i, dn, deleteNullValues, attributes, NO_CONTROLS); 801 } 802 803 804 805 /** 806 * Updates the stored representation of the provided object in the directory. 807 * If the provided object was retrieved from the directory using the 808 * persistence framework and includes a field with the {@link LDAPEntryField} 809 * annotation, then that entry will be used to make the returned set of 810 * modifications as efficient as possible. Otherwise, the resulting 811 * modifications will include attempts to replace every attribute which are 812 * associated with fields or getters that should be used in modify operations. 813 * If there are no modifications, then no modification will be attempted, and 814 * this method will return {@code null} rather than an {@code LDAPResult}. 815 * 816 * @param o The object for which to generate the list of 817 * modifications. It must not be {@code null}. 818 * @param i The interface to use to communicate with the 819 * directory server. It must not be {@code null}. 820 * @param dn The DN to use for the entry. It must not be 821 * {@code null} if the object was not retrieved from 822 * the directory using the persistence framework or 823 * does not have a field marked with the 824 * {@link LDAPDNField} or {@link LDAPEntryField} 825 * annotation. 826 * @param deleteNullValues Indicates whether to include modifications that 827 * may completely remove an attribute from the 828 * entry if the corresponding field or getter method 829 * has a value of {@code null}. 830 * @param attributes The set of LDAP attributes for which to include 831 * modifications. If this is empty or {@code null}, 832 * then all attributes marked for inclusion in the 833 * modification will be examined. 834 * @param controls The optional set of controls to include in the 835 * modify request. 836 * 837 * @return The result of processing the modify operation, or {@code null} if 838 * there were no changes to apply (and therefore no modification was 839 * performed). 840 * 841 * @throws LDAPPersistException If a problem occurs while computing the set 842 * of modifications. 843 */ 844 public LDAPResult modify(final T o, final LDAPInterface i, final String dn, 845 final boolean deleteNullValues, 846 final String[] attributes, final Control... controls) 847 throws LDAPPersistException 848 { 849 return modify(o, i, dn, deleteNullValues, false, attributes, controls); 850 } 851 852 853 854 /** 855 * Updates the stored representation of the provided object in the directory. 856 * If the provided object was retrieved from the directory using the 857 * persistence framework and includes a field with the {@link LDAPEntryField} 858 * annotation, then that entry will be used to make the returned set of 859 * modifications as efficient as possible. Otherwise, the resulting 860 * modifications will include attempts to replace every attribute which are 861 * associated with fields or getters that should be used in modify operations. 862 * If there are no modifications, then no modification will be attempted, and 863 * this method will return {@code null} rather than an {@code LDAPResult}. 864 * 865 * @param o The object for which to generate the list of 866 * modifications. It must not be {@code null}. 867 * @param i The interface to use to communicate with the 868 * directory server. It must not be {@code null}. 869 * @param dn The DN to use for the entry. It must not be 870 * {@code null} if the object was not retrieved from 871 * the directory using the persistence framework or 872 * does not have a field marked with the 873 * {@link LDAPDNField} or {@link LDAPEntryField} 874 * annotation. 875 * @param deleteNullValues Indicates whether to include modifications that 876 * may completely remove an attribute from the 877 * entry if the corresponding field or getter method 878 * has a value of {@code null}. 879 * @param byteForByte Indicates whether to use a byte-for-byte 880 * comparison to identify which attribute values 881 * have changed. Using byte-for-byte comparison 882 * requires additional processing over using each 883 * attribute's associated matching rule, but it can 884 * detect changes that would otherwise be considered 885 * logically equivalent (e.g., changing the 886 * capitalization of a value that uses a 887 * case-insensitive matching rule). 888 * @param attributes The set of LDAP attributes for which to include 889 * modifications. If this is empty or {@code null}, 890 * then all attributes marked for inclusion in the 891 * modification will be examined. 892 * @param controls The optional set of controls to include in the 893 * modify request. 894 * 895 * @return The result of processing the modify operation, or {@code null} if 896 * there were no changes to apply (and therefore no modification was 897 * performed). 898 * 899 * @throws LDAPPersistException If a problem occurs while computing the set 900 * of modifications. 901 */ 902 public LDAPResult modify(final T o, final LDAPInterface i, final String dn, 903 final boolean deleteNullValues, 904 final boolean byteForByte, final String[] attributes, 905 final Control... controls) 906 throws LDAPPersistException 907 { 908 ensureNotNull(o, i); 909 final List<Modification> mods = 910 handler.getModifications(o, deleteNullValues, byteForByte, attributes); 911 if (mods.isEmpty()) 912 { 913 return null; 914 } 915 916 final String targetDN; 917 if (dn == null) 918 { 919 targetDN = handler.getEntryDN(o); 920 if (targetDN == null) 921 { 922 throw new LDAPPersistException(ERR_PERSISTER_MODIFY_NO_DN.get()); 923 } 924 } 925 else 926 { 927 targetDN = dn; 928 } 929 930 try 931 { 932 final ModifyRequest modifyRequest = new ModifyRequest(targetDN, mods); 933 if (controls != null) 934 { 935 modifyRequest.setControls(controls); 936 } 937 938 return i.modify(modifyRequest); 939 } 940 catch (final LDAPException le) 941 { 942 debugException(le); 943 throw new LDAPPersistException(le); 944 } 945 } 946 947 948 949 /** 950 * Attempts to perform a simple bind as the user specified by the given object 951 * on the provided connection. The object should represent some kind of entry 952 * capable suitable for use as the target of a simple bind operation. 953 * <BR><BR> 954 * If the provided object was retrieved from the directory and has either an 955 * {@link LDAPDNField} or {@link LDAPEntryField}, then that field will be used 956 * to obtain the DN. Otherwise, a search will be performed to try to find the 957 * entry that corresponds to the provided object. 958 * 959 * @param o The object representing the user as whom to bind. It 960 * must not be {@code null}. 961 * @param baseDN The base DN to use if it is necessary to search for the 962 * entry. It may be {@code null} if the 963 * {@link LDAPObject#defaultParentDN} element in the 964 * {@code LDAPObject} should be used as the base DN. 965 * @param password The password to use for the bind. It must not be 966 * {@code null}. 967 * @param c The connection to be authenticated. It must not be 968 * {@code null}. 969 * @param controls An optional set of controls to include in the bind 970 * request. It may be empty or {@code null} if no controls 971 * are needed. 972 * 973 * @return The result of processing the bind operation. 974 * 975 * @throws LDAPException If a problem occurs while attempting to process the 976 * search or bind operation. 977 */ 978 public BindResult bind(final T o, final String baseDN, final String password, 979 final LDAPConnection c, final Control... controls) 980 throws LDAPException 981 { 982 ensureNotNull(o, password, c); 983 984 String dn = handler.getEntryDN(o); 985 if (dn == null) 986 { 987 String base = baseDN; 988 if (base == null) 989 { 990 base = handler.getDefaultParentDN().toString(); 991 } 992 993 final SearchRequest r = new SearchRequest(base, SearchScope.SUB, 994 handler.createFilter(o), SearchRequest.NO_ATTRIBUTES); 995 r.setSizeLimit(1); 996 997 final Entry e = c.searchForEntry(r); 998 if (e == null) 999 { 1000 throw new LDAPException(ResultCode.NO_RESULTS_RETURNED, 1001 ERR_PERSISTER_BIND_NO_ENTRY_FOUND.get()); 1002 } 1003 else 1004 { 1005 dn = e.getDN(); 1006 } 1007 } 1008 1009 return c.bind(new SimpleBindRequest(dn, password, controls)); 1010 } 1011 1012 1013 1014 /** 1015 * Constructs the DN of the associated entry from the provided object and 1016 * parent DN and retrieves the contents of that entry as a new instance of 1017 * that object. 1018 * 1019 * @param o An object instance to use to construct the DN of the 1020 * entry to retrieve. It must not be {@code null}, and all 1021 * fields and/or getter methods marked for inclusion in the 1022 * entry RDN must have non-{@code null} values. 1023 * @param i The interface to use to communicate with the directory 1024 * server. It must not be {@code null}. 1025 * @param parentDN The parent DN to use for the entry to retrieve. If the 1026 * provided object was previously read from a directory 1027 * server and includes a field marked with the 1028 * {@link LDAPDNField} or {@link LDAPEntryField} annotation, 1029 * then that field may be used to retrieve the actual DN of 1030 * the associated entry. If the actual DN of the target 1031 * entry is not available, then a DN will be constructed 1032 * from the RDN fields and/or getter methods declared in the 1033 * class and this parent DN. If the provided parent DN is 1034 * {@code null}, then the default parent DN defined in the 1035 * {@link LDAPObject} annotation will be used. 1036 * 1037 * @return The object read from the entry with the provided DN, or 1038 * {@code null} if no entry exists with the constructed DN. 1039 * 1040 * @throws LDAPPersistException If a problem occurs while attempting to 1041 * construct the entry DN, retrieve the 1042 * corresponding entry or decode it as an 1043 * object. 1044 */ 1045 public T get(final T o, final LDAPInterface i, final String parentDN) 1046 throws LDAPPersistException 1047 { 1048 final String dn = handler.constructDN(o, parentDN); 1049 1050 final Entry entry; 1051 try 1052 { 1053 entry = i.getEntry(dn, handler.getAttributesToRequest()); 1054 if (entry == null) 1055 { 1056 return null; 1057 } 1058 } 1059 catch (final LDAPException le) 1060 { 1061 debugException(le); 1062 throw new LDAPPersistException(le); 1063 } 1064 1065 return decode(entry); 1066 } 1067 1068 1069 1070 /** 1071 * Retrieves the object from the directory entry with the provided DN. 1072 * 1073 * @param dn The DN of the entry to retrieve and decode. It must not be 1074 * {@code null}. 1075 * @param i The interface to use to communicate with the directory server. 1076 * It must not be {@code null}. 1077 * 1078 * @return The object read from the entry with the provided DN, or 1079 * {@code null} if no entry exists with the provided DN. 1080 * 1081 * @throws LDAPPersistException If a problem occurs while attempting to 1082 * retrieve the specified entry or decode it 1083 * as an object. 1084 */ 1085 public T get(final String dn, final LDAPInterface i) 1086 throws LDAPPersistException 1087 { 1088 final Entry entry; 1089 try 1090 { 1091 entry = i.getEntry(dn, handler.getAttributesToRequest()); 1092 if (entry == null) 1093 { 1094 return null; 1095 } 1096 } 1097 catch (final LDAPException le) 1098 { 1099 debugException(le); 1100 throw new LDAPPersistException(le); 1101 } 1102 1103 return decode(entry); 1104 } 1105 1106 1107 1108 /** 1109 * Initializes any fields in the provided object marked for lazy loading. 1110 * 1111 * @param o The object to be updated. It must not be {@code null}. 1112 * @param i The interface to use to communicate with the directory 1113 * server. It must not be {@code null}. 1114 * @param fields The set of fields that should be loaded. Any fields 1115 * included in this list which aren't marked for lazy loading 1116 * will be ignored. If this is empty or {@code null}, then 1117 * all lazily-loaded fields will be requested. 1118 * 1119 * @throws LDAPPersistException If a problem occurs while attempting to 1120 * retrieve or process the associated entry. 1121 * If an exception is thrown, then all content 1122 * from the provided object that is not lazily 1123 * loaded should remain valid, and some 1124 * lazily-loaded fields may have been 1125 * initialized. 1126 */ 1127 public void lazilyLoad(final T o, final LDAPInterface i, 1128 final FieldInfo... fields) 1129 throws LDAPPersistException 1130 { 1131 ensureNotNull(o, i); 1132 1133 final String[] attrs; 1134 if ((fields == null) || (fields.length == 0)) 1135 { 1136 attrs = handler.getLazilyLoadedAttributes(); 1137 } 1138 else 1139 { 1140 final ArrayList<String> attrList = new ArrayList<String>(fields.length); 1141 for (final FieldInfo f : fields) 1142 { 1143 if (f.lazilyLoad()) 1144 { 1145 attrList.add(f.getAttributeName()); 1146 } 1147 } 1148 attrs = new String[attrList.size()]; 1149 attrList.toArray(attrs); 1150 } 1151 1152 if (attrs.length == 0) 1153 { 1154 return; 1155 } 1156 1157 final String dn = handler.getEntryDN(o); 1158 if (dn == null) 1159 { 1160 throw new LDAPPersistException(ERR_PERSISTER_LAZILY_LOAD_NO_DN.get()); 1161 } 1162 1163 final Entry entry; 1164 try 1165 { 1166 entry = i.getEntry(handler.getEntryDN(o), attrs); 1167 } 1168 catch (final LDAPException le) 1169 { 1170 debugException(le); 1171 throw new LDAPPersistException(le); 1172 } 1173 1174 if (entry == null) 1175 { 1176 throw new LDAPPersistException( 1177 ERR_PERSISTER_LAZILY_LOAD_NO_ENTRY.get(dn)); 1178 } 1179 1180 boolean successful = true; 1181 final ArrayList<String> failureReasons = new ArrayList<String>(5); 1182 final Map<String,FieldInfo> fieldMap = handler.getFields(); 1183 for (final Attribute a : entry.getAttributes()) 1184 { 1185 final String lowerName = toLowerCase(a.getName()); 1186 final FieldInfo f = fieldMap.get(lowerName); 1187 if (f != null) 1188 { 1189 successful &= f.decode(o, entry, failureReasons); 1190 } 1191 } 1192 1193 if (! successful) 1194 { 1195 throw new LDAPPersistException(concatenateStrings(failureReasons), o, 1196 null); 1197 } 1198 } 1199 1200 1201 1202 /** 1203 * Performs a search in the directory for objects matching the contents of the 1204 * provided object. A search filter will be generated from the provided 1205 * object containing all non-{@code null} values from fields and getter 1206 * methods whose {@link LDAPField} or {@link LDAPGetter} annotation has 1207 * the {@code inFilter} element set to {@code true}. 1208 * <BR><BR> 1209 * The search performed will be a subtree search using a base DN equal to the 1210 * {@link LDAPObject#defaultParentDN} element in the {@code LDAPObject} 1211 * annotation. It will not enforce a client-side time limit or size limit. 1212 * <BR><BR> 1213 * Note that this method requires an {@link LDAPConnection} argument rather 1214 * than using the more generic {@link LDAPInterface} type because the search 1215 * is invoked as an asynchronous operation, which is not supported by the 1216 * generic {@code LDAPInterface} interface. It also means that the provided 1217 * connection must not be configured to operate in synchronous mode (via the 1218 * {@link com.unboundid.ldap.sdk.LDAPConnectionOptions#setUseSynchronousMode} 1219 * option). 1220 * 1221 * @param o The object to use to construct the search filter. It must not 1222 * be {@code null}. 1223 * @param c The connection to use to communicate with the directory server. 1224 * It must not be {@code null}. 1225 * 1226 * @return A results object that may be used to iterate through the objects 1227 * returned from the search. 1228 * 1229 * @throws LDAPPersistException If an error occurs while preparing or 1230 * sending the search request. 1231 */ 1232 public PersistedObjects<T> search(final T o, final LDAPConnection c) 1233 throws LDAPPersistException 1234 { 1235 return search(o, c, null, SearchScope.SUB, DereferencePolicy.NEVER, 0, 0, 1236 null, NO_CONTROLS); 1237 } 1238 1239 1240 1241 /** 1242 * Performs a search in the directory for objects matching the contents of the 1243 * provided object. A search filter will be generated from the provided 1244 * object containing all non-{@code null} values from fields and getter 1245 * methods whose {@link LDAPField} or {@link LDAPGetter} annotation has 1246 * the {@code inFilter} element set to {@code true}. 1247 * <BR><BR> 1248 * Note that this method requires an {@link LDAPConnection} argument rather 1249 * than using the more generic {@link LDAPInterface} type because the search 1250 * is invoked as an asynchronous operation, which is not supported by the 1251 * generic {@code LDAPInterface} interface. It also means that the provided 1252 * connection must not be configured to operate in synchronous mode (via the 1253 * {@link com.unboundid.ldap.sdk.LDAPConnectionOptions#setUseSynchronousMode} 1254 * option). 1255 * 1256 * @param o The object to use to construct the search filter. It must 1257 * not be {@code null}. 1258 * @param c The connection to use to communicate with the directory 1259 * server. It must not be {@code null}. 1260 * @param baseDN The base DN to use for the search. It may be {@code null} 1261 * if the {@link LDAPObject#defaultParentDN} element in the 1262 * {@code LDAPObject} should be used as the base DN. 1263 * @param scope The scope to use for the search operation. It must not be 1264 * {@code null}. 1265 * 1266 * @return A results object that may be used to iterate through the objects 1267 * returned from the search. 1268 * 1269 * @throws LDAPPersistException If an error occurs while preparing or 1270 * sending the search request. 1271 */ 1272 public PersistedObjects<T> search(final T o, final LDAPConnection c, 1273 final String baseDN, 1274 final SearchScope scope) 1275 throws LDAPPersistException 1276 { 1277 return search(o, c, baseDN, scope, DereferencePolicy.NEVER, 0, 0, null, 1278 NO_CONTROLS); 1279 } 1280 1281 1282 1283 /** 1284 * Performs a search in the directory for objects matching the contents of 1285 * the provided object. A search filter will be generated from the provided 1286 * object containing all non-{@code null} values from fields and getter 1287 * methods whose {@link LDAPField} or {@link LDAPGetter} annotation has 1288 * the {@code inFilter} element set to {@code true}. 1289 * <BR><BR> 1290 * Note that this method requires an {@link LDAPConnection} argument rather 1291 * than using the more generic {@link LDAPInterface} type because the search 1292 * is invoked as an asynchronous operation, which is not supported by the 1293 * generic {@code LDAPInterface} interface. It also means that the provided 1294 * connection must not be configured to operate in synchronous mode (via the 1295 * {@link com.unboundid.ldap.sdk.LDAPConnectionOptions#setUseSynchronousMode} 1296 * option). 1297 * 1298 * @param o The object to use to construct the search filter. It 1299 * must not be {@code null}. 1300 * @param c The connection to use to communicate with the 1301 * directory server. It must not be {@code null}. 1302 * @param baseDN The base DN to use for the search. It may be 1303 * {@code null} if the {@link LDAPObject#defaultParentDN} 1304 * element in the {@code LDAPObject} should be used as 1305 * the base DN. 1306 * @param scope The scope to use for the search operation. It must 1307 * not be {@code null}. 1308 * @param derefPolicy The dereference policy to use for the search 1309 * operation. It must not be {@code null}. 1310 * @param sizeLimit The maximum number of entries to retrieve from the 1311 * directory. A value of zero indicates that no 1312 * client-requested size limit should be enforced. 1313 * @param timeLimit The maximum length of time in seconds that the server 1314 * should spend processing the search. A value of zero 1315 * indicates that no client-requested time limit should 1316 * be enforced. 1317 * @param extraFilter An optional additional filter to be ANDed with the 1318 * filter generated from the provided object. If this is 1319 * {@code null}, then only the filter generated from the 1320 * object will be used. 1321 * @param controls An optional set of controls to include in the search 1322 * request. It may be empty or {@code null} if no 1323 * controls are needed. 1324 * 1325 * @return A results object that may be used to iterate through the objects 1326 * returned from the search. 1327 * 1328 * @throws LDAPPersistException If an error occurs while preparing or 1329 * sending the search request. 1330 */ 1331 public PersistedObjects<T> search(final T o, final LDAPConnection c, 1332 final String baseDN, 1333 final SearchScope scope, 1334 final DereferencePolicy derefPolicy, 1335 final int sizeLimit, final int timeLimit, 1336 final Filter extraFilter, 1337 final Control... controls) 1338 throws LDAPPersistException 1339 { 1340 ensureNotNull(o, c, scope, derefPolicy); 1341 1342 final String base; 1343 if (baseDN == null) 1344 { 1345 base = handler.getDefaultParentDN().toString(); 1346 } 1347 else 1348 { 1349 base = baseDN; 1350 } 1351 1352 final Filter filter; 1353 if (extraFilter == null) 1354 { 1355 filter = handler.createFilter(o); 1356 } 1357 else 1358 { 1359 filter = Filter.createANDFilter(handler.createFilter(o), extraFilter); 1360 } 1361 1362 final SearchRequest searchRequest = new SearchRequest(base, scope, 1363 derefPolicy, sizeLimit, timeLimit, false, filter, 1364 handler.getAttributesToRequest()); 1365 if (controls != null) 1366 { 1367 searchRequest.setControls(controls); 1368 } 1369 1370 final LDAPEntrySource entrySource; 1371 try 1372 { 1373 entrySource = new LDAPEntrySource(c, searchRequest, false); 1374 } 1375 catch (final LDAPException le) 1376 { 1377 debugException(le); 1378 throw new LDAPPersistException(le); 1379 } 1380 1381 return new PersistedObjects<T>(this, entrySource); 1382 } 1383 1384 1385 1386 /** 1387 * Performs a search in the directory for objects matching the contents of the 1388 * provided object. A search filter will be generated from the provided 1389 * object containing all non-{@code null} values from fields and getter 1390 * methods whose {@link LDAPField} or {@link LDAPGetter} annotation has 1391 * the {@code inFilter} element set to {@code true}. 1392 * <BR><BR> 1393 * The search performed will be a subtree search using a base DN equal to the 1394 * {@link LDAPObject#defaultParentDN} element in the {@code LDAPObject} 1395 * annotation. It will not enforce a client-side time limit or size limit. 1396 * 1397 * @param o The object to use to construct the search filter. It must not 1398 * be {@code null}. 1399 * @param i The interface to use to communicate with the directory server. 1400 * It must not be {@code null}. 1401 * @param l The object search result listener that will be used to receive 1402 * objects decoded from entries returned for the search. It must 1403 * not be {@code null}. 1404 * 1405 * @return The result of the search operation that was processed. 1406 * 1407 * @throws LDAPPersistException If an error occurs while preparing or 1408 * sending the search request. 1409 */ 1410 public SearchResult search(final T o, final LDAPInterface i, 1411 final ObjectSearchListener<T> l) 1412 throws LDAPPersistException 1413 { 1414 return search(o, i, null, SearchScope.SUB, DereferencePolicy.NEVER, 0, 0, 1415 null, l, NO_CONTROLS); 1416 } 1417 1418 1419 1420 /** 1421 * Performs a search in the directory for objects matching the contents of the 1422 * provided object. A search filter will be generated from the provided 1423 * object containing all non-{@code null} values from fields and getter 1424 * methods whose {@link LDAPField} or {@link LDAPGetter} annotation has 1425 * the {@code inFilter} element set to {@code true}. 1426 * 1427 * @param o The object to use to construct the search filter. It must 1428 * not be {@code null}. 1429 * @param i The interface to use to communicate with the directory 1430 * server. It must not be {@code null}. 1431 * @param baseDN The base DN to use for the search. It may be {@code null} 1432 * if the {@link LDAPObject#defaultParentDN} element in the 1433 * {@code LDAPObject} should be used as the base DN. 1434 * @param scope The scope to use for the search operation. It must not be 1435 * {@code null}. 1436 * @param l The object search result listener that will be used to 1437 * receive objects decoded from entries returned for the 1438 * search. It must not be {@code null}. 1439 * 1440 * @return The result of the search operation that was processed. 1441 * 1442 * @throws LDAPPersistException If an error occurs while preparing or 1443 * sending the search request. 1444 */ 1445 public SearchResult search(final T o, final LDAPInterface i, 1446 final String baseDN, final SearchScope scope, 1447 final ObjectSearchListener<T> l) 1448 throws LDAPPersistException 1449 { 1450 return search(o, i, baseDN, scope, DereferencePolicy.NEVER, 0, 0, null, l, 1451 NO_CONTROLS); 1452 } 1453 1454 1455 1456 /** 1457 * Performs a search in the directory for objects matching the contents of 1458 * the provided object. A search filter will be generated from the provided 1459 * object containing all non-{@code null} values from fields and getter 1460 * methods whose {@link LDAPField} or {@link LDAPGetter} annotation has 1461 * the {@code inFilter} element set to {@code true}. 1462 * 1463 * @param o The object to use to construct the search filter. It 1464 * must not be {@code null}. 1465 * @param i The connection to use to communicate with the 1466 * directory server. It must not be {@code null}. 1467 * @param baseDN The base DN to use for the search. It may be 1468 * {@code null} if the {@link LDAPObject#defaultParentDN} 1469 * element in the {@code LDAPObject} should be used as 1470 * the base DN. 1471 * @param scope The scope to use for the search operation. It must 1472 * not be {@code null}. 1473 * @param derefPolicy The dereference policy to use for the search 1474 * operation. It must not be {@code null}. 1475 * @param sizeLimit The maximum number of entries to retrieve from the 1476 * directory. A value of zero indicates that no 1477 * client-requested size limit should be enforced. 1478 * @param timeLimit The maximum length of time in seconds that the server 1479 * should spend processing the search. A value of zero 1480 * indicates that no client-requested time limit should 1481 * be enforced. 1482 * @param extraFilter An optional additional filter to be ANDed with the 1483 * filter generated from the provided object. If this is 1484 * {@code null}, then only the filter generated from the 1485 * object will be used. 1486 * @param l The object search result listener that will be used 1487 * to receive objects decoded from entries returned for 1488 * the search. It must not be {@code null}. 1489 * @param controls An optional set of controls to include in the search 1490 * request. It may be empty or {@code null} if no 1491 * controls are needed. 1492 * 1493 * @return The result of the search operation that was processed. 1494 * 1495 * @throws LDAPPersistException If an error occurs while preparing or 1496 * sending the search request. 1497 */ 1498 public SearchResult search(final T o, final LDAPInterface i, 1499 final String baseDN, final SearchScope scope, 1500 final DereferencePolicy derefPolicy, 1501 final int sizeLimit, final int timeLimit, 1502 final Filter extraFilter, 1503 final ObjectSearchListener<T> l, 1504 final Control... controls) 1505 throws LDAPPersistException 1506 { 1507 ensureNotNull(o, i, scope, derefPolicy, l); 1508 1509 final String base; 1510 if (baseDN == null) 1511 { 1512 base = handler.getDefaultParentDN().toString(); 1513 } 1514 else 1515 { 1516 base = baseDN; 1517 } 1518 1519 final Filter filter; 1520 if (extraFilter == null) 1521 { 1522 filter = handler.createFilter(o); 1523 } 1524 else 1525 { 1526 filter = Filter.simplifyFilter( 1527 Filter.createANDFilter(handler.createFilter(o), extraFilter), true); 1528 } 1529 1530 final SearchListenerBridge<T> bridge = new SearchListenerBridge<T>(this, l); 1531 1532 final SearchRequest searchRequest = new SearchRequest(bridge, base, scope, 1533 derefPolicy, sizeLimit, timeLimit, false, filter, 1534 handler.getAttributesToRequest()); 1535 if (controls != null) 1536 { 1537 searchRequest.setControls(controls); 1538 } 1539 1540 try 1541 { 1542 return i.search(searchRequest); 1543 } 1544 catch (final LDAPException le) 1545 { 1546 debugException(le); 1547 throw new LDAPPersistException(le); 1548 } 1549 } 1550 1551 1552 1553 /** 1554 * Performs a search in the directory using the provided search criteria and 1555 * decodes all entries returned as objects of the associated type. 1556 * 1557 * @param c The connection to use to communicate with the 1558 * directory server. It must not be {@code null}. 1559 * @param baseDN The base DN to use for the search. It may be 1560 * {@code null} if the {@link LDAPObject#defaultParentDN} 1561 * element in the {@code LDAPObject} should be used as 1562 * the base DN. 1563 * @param scope The scope to use for the search operation. It must 1564 * not be {@code null}. 1565 * @param derefPolicy The dereference policy to use for the search 1566 * operation. It must not be {@code null}. 1567 * @param sizeLimit The maximum number of entries to retrieve from the 1568 * directory. A value of zero indicates that no 1569 * client-requested size limit should be enforced. 1570 * @param timeLimit The maximum length of time in seconds that the server 1571 * should spend processing the search. A value of zero 1572 * indicates that no client-requested time limit should 1573 * be enforced. 1574 * @param filter The filter to use for the search. It must not be 1575 * {@code null}. It will automatically be ANDed with a 1576 * filter that will match entries with the structural and 1577 * auxiliary classes. 1578 * @param controls An optional set of controls to include in the search 1579 * request. It may be empty or {@code null} if no 1580 * controls are needed. 1581 * 1582 * @return The result of the search operation that was processed. 1583 * 1584 * @throws LDAPPersistException If an error occurs while preparing or 1585 * sending the search request. 1586 */ 1587 public PersistedObjects<T> search(final LDAPConnection c, final String baseDN, 1588 final SearchScope scope, 1589 final DereferencePolicy derefPolicy, 1590 final int sizeLimit, final int timeLimit, 1591 final Filter filter, 1592 final Control... controls) 1593 throws LDAPPersistException 1594 { 1595 ensureNotNull(c, scope, derefPolicy, filter); 1596 1597 final String base; 1598 if (baseDN == null) 1599 { 1600 base = handler.getDefaultParentDN().toString(); 1601 } 1602 else 1603 { 1604 base = baseDN; 1605 } 1606 1607 final Filter f = Filter.createANDFilter(filter, handler.createBaseFilter()); 1608 1609 final SearchRequest searchRequest = new SearchRequest(base, scope, 1610 derefPolicy, sizeLimit, timeLimit, false, f, 1611 handler.getAttributesToRequest()); 1612 if (controls != null) 1613 { 1614 searchRequest.setControls(controls); 1615 } 1616 1617 final LDAPEntrySource entrySource; 1618 try 1619 { 1620 entrySource = new LDAPEntrySource(c, searchRequest, false); 1621 } 1622 catch (final LDAPException le) 1623 { 1624 debugException(le); 1625 throw new LDAPPersistException(le); 1626 } 1627 1628 return new PersistedObjects<T>(this, entrySource); 1629 } 1630 1631 1632 1633 /** 1634 * Performs a search in the directory using the provided search criteria and 1635 * decodes all entries returned as objects of the associated type. 1636 * 1637 * @param i The connection to use to communicate with the 1638 * directory server. It must not be {@code null}. 1639 * @param baseDN The base DN to use for the search. It may be 1640 * {@code null} if the {@link LDAPObject#defaultParentDN} 1641 * element in the {@code LDAPObject} should be used as 1642 * the base DN. 1643 * @param scope The scope to use for the search operation. It must 1644 * not be {@code null}. 1645 * @param derefPolicy The dereference policy to use for the search 1646 * operation. It must not be {@code null}. 1647 * @param sizeLimit The maximum number of entries to retrieve from the 1648 * directory. A value of zero indicates that no 1649 * client-requested size limit should be enforced. 1650 * @param timeLimit The maximum length of time in seconds that the server 1651 * should spend processing the search. A value of zero 1652 * indicates that no client-requested time limit should 1653 * be enforced. 1654 * @param filter The filter to use for the search. It must not be 1655 * {@code null}. It will automatically be ANDed with a 1656 * filter that will match entries with the structural and 1657 * auxiliary classes. 1658 * @param l The object search result listener that will be used 1659 * to receive objects decoded from entries returned for 1660 * the search. It must not be {@code null}. 1661 * @param controls An optional set of controls to include in the search 1662 * request. It may be empty or {@code null} if no 1663 * controls are needed. 1664 * 1665 * @return The result of the search operation that was processed. 1666 * 1667 * @throws LDAPPersistException If an error occurs while preparing or 1668 * sending the search request. 1669 */ 1670 public SearchResult search(final LDAPInterface i, final String baseDN, 1671 final SearchScope scope, 1672 final DereferencePolicy derefPolicy, 1673 final int sizeLimit, final int timeLimit, 1674 final Filter filter, 1675 final ObjectSearchListener<T> l, 1676 final Control... controls) 1677 throws LDAPPersistException 1678 { 1679 ensureNotNull(i, scope, derefPolicy, filter, l); 1680 1681 final String base; 1682 if (baseDN == null) 1683 { 1684 base = handler.getDefaultParentDN().toString(); 1685 } 1686 else 1687 { 1688 base = baseDN; 1689 } 1690 1691 final Filter f = Filter.simplifyFilter( 1692 Filter.createANDFilter(filter, handler.createBaseFilter()), true); 1693 final SearchListenerBridge<T> bridge = new SearchListenerBridge<T>(this, l); 1694 1695 final SearchRequest searchRequest = new SearchRequest(bridge, base, scope, 1696 derefPolicy, sizeLimit, timeLimit, false, f, 1697 handler.getAttributesToRequest()); 1698 if (controls != null) 1699 { 1700 searchRequest.setControls(controls); 1701 } 1702 1703 try 1704 { 1705 return i.search(searchRequest); 1706 } 1707 catch (final LDAPException le) 1708 { 1709 debugException(le); 1710 throw new LDAPPersistException(le); 1711 } 1712 } 1713 1714 1715 1716 /** 1717 * Performs a search in the directory to retrieve the object whose contents 1718 * match the contents of the provided object. It is expected that at most one 1719 * entry matches the provided criteria, and that it can be decoded as an 1720 * object of the associated type. If multiple entries match the resulting 1721 * criteria, or if the matching entry cannot be decoded as the associated type 1722 * of object, then an exception will be thrown. 1723 * <BR><BR> 1724 * A search filter will be generated from the provided object containing all 1725 * non-{@code null} values from fields and getter methods whose 1726 * {@link LDAPField} or {@link LDAPGetter} annotation has the {@code inFilter} 1727 * element set to {@code true}. 1728 * <BR><BR> 1729 * The search performed will be a subtree search using a base DN equal to the 1730 * {@link LDAPObject#defaultParentDN} element in the {@code LDAPObject} 1731 * annotation. It will not enforce a client-side time limit or size limit. 1732 * 1733 * @param o The object to use to construct the search filter. It must not 1734 * be {@code null}. 1735 * @param i The interface to use to communicate with the directory server. 1736 * It must not be {@code null}. 1737 * 1738 * @return The object constructed from the entry returned by the search, or 1739 * {@code null} if no entry was returned. 1740 * 1741 * @throws LDAPPersistException If an error occurs while preparing or 1742 * sending the search request or decoding the 1743 * entry that was returned. 1744 */ 1745 public T searchForObject(final T o, final LDAPInterface i) 1746 throws LDAPPersistException 1747 { 1748 return searchForObject(o, i, null, SearchScope.SUB, DereferencePolicy.NEVER, 1749 0, 0, null, NO_CONTROLS); 1750 } 1751 1752 1753 1754 /** 1755 * Performs a search in the directory to retrieve the object whose contents 1756 * match the contents of the provided object. It is expected that at most one 1757 * entry matches the provided criteria, and that it can be decoded as an 1758 * object of the associated type. If multiple entries match the resulting 1759 * criteria, or if the matching entry cannot be decoded as the associated type 1760 * of object, then an exception will be thrown. 1761 * <BR><BR> 1762 * A search filter will be generated from the provided object containing all 1763 * non-{@code null} values from fields and getter methods whose 1764 * {@link LDAPField} or {@link LDAPGetter} annotation has the {@code inFilter} 1765 * element set to {@code true}. 1766 * 1767 * @param o The object to use to construct the search filter. It must 1768 * not be {@code null}. 1769 * @param i The interface to use to communicate with the directory 1770 * server. It must not be {@code null}. 1771 * @param baseDN The base DN to use for the search. It may be {@code null} 1772 * if the {@link LDAPObject#defaultParentDN} element in the 1773 * {@code LDAPObject} should be used as the base DN. 1774 * @param scope The scope to use for the search operation. It must not be 1775 * {@code null}. 1776 * 1777 * @return The object constructed from the entry returned by the search, or 1778 * {@code null} if no entry was returned. 1779 * 1780 * @throws LDAPPersistException If an error occurs while preparing or 1781 * sending the search request or decoding the 1782 * entry that was returned. 1783 */ 1784 public T searchForObject(final T o, final LDAPInterface i, 1785 final String baseDN, final SearchScope scope) 1786 throws LDAPPersistException 1787 { 1788 return searchForObject(o, i, baseDN, scope, DereferencePolicy.NEVER, 0, 0, 1789 null, NO_CONTROLS); 1790 } 1791 1792 1793 1794 /** 1795 * Performs a search in the directory to retrieve the object whose contents 1796 * match the contents of the provided object. It is expected that at most one 1797 * entry matches the provided criteria, and that it can be decoded as an 1798 * object of the associated type. If multiple entries match the resulting 1799 * criteria, or if the matching entry cannot be decoded as the associated type 1800 * of object, then an exception will be thrown. 1801 * <BR><BR> 1802 * A search filter will be generated from the provided object containing all 1803 * non-{@code null} values from fields and getter methods whose 1804 * {@link LDAPField} or {@link LDAPGetter} annotation has the {@code inFilter} 1805 * element set to {@code true}. 1806 * 1807 * @param o The object to use to construct the search filter. It 1808 * must not be {@code null}. 1809 * @param i The connection to use to communicate with the 1810 * directory server. It must not be {@code null}. 1811 * @param baseDN The base DN to use for the search. It may be 1812 * {@code null} if the {@link LDAPObject#defaultParentDN} 1813 * element in the {@code LDAPObject} should be used as 1814 * the base DN. 1815 * @param scope The scope to use for the search operation. It must 1816 * not be {@code null}. 1817 * @param derefPolicy The dereference policy to use for the search 1818 * operation. It must not be {@code null}. 1819 * @param sizeLimit The maximum number of entries to retrieve from the 1820 * directory. A value of zero indicates that no 1821 * client-requested size limit should be enforced. 1822 * @param timeLimit The maximum length of time in seconds that the server 1823 * should spend processing the search. A value of zero 1824 * indicates that no client-requested time limit should 1825 * be enforced. 1826 * @param extraFilter An optional additional filter to be ANDed with the 1827 * filter generated from the provided object. If this is 1828 * {@code null}, then only the filter generated from the 1829 * object will be used. 1830 * @param controls An optional set of controls to include in the search 1831 * request. It may be empty or {@code null} if no 1832 * controls are needed. 1833 * 1834 * @return The object constructed from the entry returned by the search, or 1835 * {@code null} if no entry was returned. 1836 * 1837 * @throws LDAPPersistException If an error occurs while preparing or 1838 * sending the search request or decoding the 1839 * entry that was returned. 1840 */ 1841 public T searchForObject(final T o, final LDAPInterface i, 1842 final String baseDN, final SearchScope scope, 1843 final DereferencePolicy derefPolicy, 1844 final int sizeLimit, final int timeLimit, 1845 final Filter extraFilter, final Control... controls) 1846 throws LDAPPersistException 1847 { 1848 ensureNotNull(o, i, scope, derefPolicy); 1849 1850 final String base; 1851 if (baseDN == null) 1852 { 1853 base = handler.getDefaultParentDN().toString(); 1854 } 1855 else 1856 { 1857 base = baseDN; 1858 } 1859 1860 final Filter filter; 1861 if (extraFilter == null) 1862 { 1863 filter = handler.createFilter(o); 1864 } 1865 else 1866 { 1867 filter = Filter.simplifyFilter( 1868 Filter.createANDFilter(handler.createFilter(o), extraFilter), true); 1869 } 1870 1871 final SearchRequest searchRequest = new SearchRequest(base, scope, 1872 derefPolicy, sizeLimit, timeLimit, false, filter, 1873 handler.getAttributesToRequest()); 1874 if (controls != null) 1875 { 1876 searchRequest.setControls(controls); 1877 } 1878 1879 try 1880 { 1881 final Entry e = i.searchForEntry(searchRequest); 1882 if (e == null) 1883 { 1884 return null; 1885 } 1886 else 1887 { 1888 return decode(e); 1889 } 1890 } 1891 catch (final LDAPPersistException lpe) 1892 { 1893 debugException(lpe); 1894 throw lpe; 1895 } 1896 catch (final LDAPException le) 1897 { 1898 debugException(le); 1899 throw new LDAPPersistException(le); 1900 } 1901 } 1902 1903 1904 1905 /** 1906 * Performs a search in the directory with an attempt to find all objects of 1907 * the specified type below the given base DN (or below the default parent DN 1908 * if no base DN is specified). Note that this may result in an unindexed 1909 * search, which may be expensive to conduct. Some servers may require 1910 * special permissions of clients wishing to perform unindexed searches. 1911 * 1912 * @param i The connection to use to communicate with the 1913 * directory server. It must not be {@code null}. 1914 * @param baseDN The base DN to use for the search. It may be 1915 * {@code null} if the {@link LDAPObject#defaultParentDN} 1916 * element in the {@code LDAPObject} should be used as the 1917 * base DN. 1918 * @param l The object search result listener that will be used to 1919 * receive objects decoded from entries returned for the 1920 * search. It must not be {@code null}. 1921 * @param controls An optional set of controls to include in the search 1922 * request. It may be empty or {@code null} if no controls 1923 * are needed. 1924 * 1925 * @return The result of the search operation that was processed. 1926 * 1927 * @throws LDAPPersistException If an error occurs while preparing or 1928 * sending the search request. 1929 */ 1930 public SearchResult getAll(final LDAPInterface i, final String baseDN, 1931 final ObjectSearchListener<T> l, 1932 final Control... controls) 1933 throws LDAPPersistException 1934 { 1935 ensureNotNull(i, l); 1936 1937 final String base; 1938 if (baseDN == null) 1939 { 1940 base = handler.getDefaultParentDN().toString(); 1941 } 1942 else 1943 { 1944 base = baseDN; 1945 } 1946 1947 final SearchListenerBridge<T> bridge = new SearchListenerBridge<T>(this, l); 1948 final SearchRequest searchRequest = new SearchRequest(bridge, base, 1949 SearchScope.SUB, DereferencePolicy.NEVER, 0, 0, false, 1950 handler.createBaseFilter(), handler.getAttributesToRequest()); 1951 if (controls != null) 1952 { 1953 searchRequest.setControls(controls); 1954 } 1955 1956 try 1957 { 1958 return i.search(searchRequest); 1959 } 1960 catch (final LDAPException le) 1961 { 1962 debugException(le); 1963 throw new LDAPPersistException(le); 1964 } 1965 } 1966}