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