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