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