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