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