001/* 002 * Copyright 2009-2014 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2009-2014 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.createANDFilter(handler.createFilter(o), extraFilter); 1418 } 1419 1420 final SearchListenerBridge<T> bridge = new SearchListenerBridge<T>(this, l); 1421 1422 final SearchRequest searchRequest = new SearchRequest(bridge, base, scope, 1423 derefPolicy, sizeLimit, timeLimit, false, filter, 1424 handler.getAttributesToRequest()); 1425 if (controls != null) 1426 { 1427 searchRequest.setControls(controls); 1428 } 1429 1430 try 1431 { 1432 return i.search(searchRequest); 1433 } 1434 catch (LDAPException le) 1435 { 1436 debugException(le); 1437 throw new LDAPPersistException(le); 1438 } 1439 } 1440 1441 1442 1443 /** 1444 * Performs a search in the directory using the provided search criteria and 1445 * decodes all entries returned as objects of the associated type. 1446 * 1447 * @param c The connection to use to communicate with the 1448 * directory server. It must not be {@code null}. 1449 * @param baseDN The base DN to use for the search. It may be 1450 * {@code null} if the {@link LDAPObject#defaultParentDN} 1451 * element in the {@code LDAPObject} should be used as 1452 * the base DN. 1453 * @param scope The scope to use for the search operation. It must 1454 * not be {@code null}. 1455 * @param derefPolicy The dereference policy to use for the search 1456 * operation. It must not be {@code null}. 1457 * @param sizeLimit The maximum number of entries to retrieve from the 1458 * directory. A value of zero indicates that no 1459 * client-requested size limit should be enforced. 1460 * @param timeLimit The maximum length of time in seconds that the server 1461 * should spend processing the search. A value of zero 1462 * indicates that no client-requested time limit should 1463 * be enforced. 1464 * @param filter The filter to use for the search. It must not be 1465 * {@code null}. It will automatically be ANDed with a 1466 * filter that will match entries with the structural and 1467 * auxiliary classes. 1468 * @param controls An optional set of controls to include in the search 1469 * request. It may be empty or {@code null} if no 1470 * controls are needed. 1471 * 1472 * @return The result of the search operation that was processed. 1473 * 1474 * @throws LDAPPersistException If an error occurs while preparing or 1475 * sending the search request. 1476 */ 1477 public PersistedObjects<T> search(final LDAPConnection c, final String baseDN, 1478 final SearchScope scope, 1479 final DereferencePolicy derefPolicy, 1480 final int sizeLimit, final int timeLimit, 1481 final Filter filter, 1482 final Control... controls) 1483 throws LDAPPersistException 1484 { 1485 ensureNotNull(c, scope, derefPolicy, filter); 1486 1487 final String base; 1488 if (baseDN == null) 1489 { 1490 base = handler.getDefaultParentDN().toString(); 1491 } 1492 else 1493 { 1494 base = baseDN; 1495 } 1496 1497 final Filter f = Filter.createANDFilter(filter, handler.createBaseFilter()); 1498 1499 final SearchRequest searchRequest = new SearchRequest(base, scope, 1500 derefPolicy, sizeLimit, timeLimit, false, f, 1501 handler.getAttributesToRequest()); 1502 if (controls != null) 1503 { 1504 searchRequest.setControls(controls); 1505 } 1506 1507 final LDAPEntrySource entrySource; 1508 try 1509 { 1510 entrySource = new LDAPEntrySource(c, searchRequest, false); 1511 } 1512 catch (LDAPException le) 1513 { 1514 debugException(le); 1515 throw new LDAPPersistException(le); 1516 } 1517 1518 return new PersistedObjects<T>(this, entrySource); 1519 } 1520 1521 1522 1523 /** 1524 * Performs a search in the directory using the provided search criteria and 1525 * decodes all entries returned as objects of the associated type. 1526 * 1527 * @param i The connection to use to communicate with the 1528 * directory server. It must not be {@code null}. 1529 * @param baseDN The base DN to use for the search. It may be 1530 * {@code null} if the {@link LDAPObject#defaultParentDN} 1531 * element in the {@code LDAPObject} should be used as 1532 * the base DN. 1533 * @param scope The scope to use for the search operation. It must 1534 * not be {@code null}. 1535 * @param derefPolicy The dereference policy to use for the search 1536 * operation. It must not be {@code null}. 1537 * @param sizeLimit The maximum number of entries to retrieve from the 1538 * directory. A value of zero indicates that no 1539 * client-requested size limit should be enforced. 1540 * @param timeLimit The maximum length of time in seconds that the server 1541 * should spend processing the search. A value of zero 1542 * indicates that no client-requested time limit should 1543 * be enforced. 1544 * @param filter The filter to use for the search. It must not be 1545 * {@code null}. It will automatically be ANDed with a 1546 * filter that will match entries with the structural and 1547 * auxiliary classes. 1548 * @param l The object search result listener that will be used 1549 * to receive objects decoded from entries returned for 1550 * the search. It must not be {@code null}. 1551 * @param controls An optional set of controls to include in the search 1552 * request. It may be empty or {@code null} if no 1553 * controls are needed. 1554 * 1555 * @return The result of the search operation that was processed. 1556 * 1557 * @throws LDAPPersistException If an error occurs while preparing or 1558 * sending the search request. 1559 */ 1560 public SearchResult search(final LDAPInterface i, final String baseDN, 1561 final SearchScope scope, 1562 final DereferencePolicy derefPolicy, 1563 final int sizeLimit, final int timeLimit, 1564 final Filter filter, 1565 final ObjectSearchListener<T> l, 1566 final Control... controls) 1567 throws LDAPPersistException 1568 { 1569 ensureNotNull(i, scope, derefPolicy, filter, l); 1570 1571 final String base; 1572 if (baseDN == null) 1573 { 1574 base = handler.getDefaultParentDN().toString(); 1575 } 1576 else 1577 { 1578 base = baseDN; 1579 } 1580 1581 final Filter f = Filter.createANDFilter(filter, handler.createBaseFilter()); 1582 final SearchListenerBridge<T> bridge = new SearchListenerBridge<T>(this, l); 1583 1584 final SearchRequest searchRequest = new SearchRequest(bridge, base, scope, 1585 derefPolicy, sizeLimit, timeLimit, false, f, 1586 handler.getAttributesToRequest()); 1587 if (controls != null) 1588 { 1589 searchRequest.setControls(controls); 1590 } 1591 1592 try 1593 { 1594 return i.search(searchRequest); 1595 } 1596 catch (LDAPException le) 1597 { 1598 debugException(le); 1599 throw new LDAPPersistException(le); 1600 } 1601 } 1602 1603 1604 1605 /** 1606 * Performs a search in the directory to retrieve the object whose contents 1607 * match the contents of the provided object. It is expected that at most one 1608 * entry matches the provided criteria, and that it can be decoded as an 1609 * object of the associated type. If multiple entries match the resulting 1610 * criteria, or if the matching entry cannot be decoded as the associated type 1611 * of object, then an exception will be thrown. 1612 * <BR><BR> 1613 * A search filter will be generated from the provided object containing all 1614 * non-{@code null} values from fields and getter methods whose 1615 * {@link LDAPField} or {@link LDAPGetter} annotation has the {@code inFilter} 1616 * element set to {@code true}. 1617 * <BR><BR> 1618 * The search performed will be a subtree search using a base DN equal to the 1619 * {@link LDAPObject#defaultParentDN} element in the {@code LDAPObject} 1620 * annotation. It will not enforce a client-side time limit or size limit. 1621 * 1622 * @param o The object to use to construct the search filter. It must not 1623 * be {@code null}. 1624 * @param i The interface to use to communicate with the directory server. 1625 * It must not be {@code null}. 1626 * 1627 * @return The object constructed from the entry returned by the search, or 1628 * {@code null} if no entry was returned. 1629 * 1630 * @throws LDAPPersistException If an error occurs while preparing or 1631 * sending the search request or decoding the 1632 * entry that was returned. 1633 */ 1634 public T searchForObject(final T o, final LDAPInterface i) 1635 throws LDAPPersistException 1636 { 1637 return searchForObject(o, i, null, SearchScope.SUB, DereferencePolicy.NEVER, 1638 0, 0, null, NO_CONTROLS); 1639 } 1640 1641 1642 1643 /** 1644 * Performs a search in the directory to retrieve the object whose contents 1645 * match the contents of the provided object. It is expected that at most one 1646 * entry matches the provided criteria, and that it can be decoded as an 1647 * object of the associated type. If multiple entries match the resulting 1648 * criteria, or if the matching entry cannot be decoded as the associated type 1649 * of object, then an exception will be thrown. 1650 * <BR><BR> 1651 * A search filter will be generated from the provided object containing all 1652 * non-{@code null} values from fields and getter methods whose 1653 * {@link LDAPField} or {@link LDAPGetter} annotation has the {@code inFilter} 1654 * element set to {@code true}. 1655 * 1656 * @param o The object to use to construct the search filter. It must 1657 * not be {@code null}. 1658 * @param i The interface to use to communicate with the directory 1659 * server. It must not be {@code null}. 1660 * @param baseDN The base DN to use for the search. It may be {@code null} 1661 * if the {@link LDAPObject#defaultParentDN} element in the 1662 * {@code LDAPObject} should be used as the base DN. 1663 * @param scope The scope to use for the search operation. It must not be 1664 * {@code null}. 1665 * 1666 * @return The object constructed from the entry returned by the search, or 1667 * {@code null} if no entry was returned. 1668 * 1669 * @throws LDAPPersistException If an error occurs while preparing or 1670 * sending the search request or decoding the 1671 * entry that was returned. 1672 */ 1673 public T searchForObject(final T o, final LDAPInterface i, 1674 final String baseDN, final SearchScope scope) 1675 throws LDAPPersistException 1676 { 1677 return searchForObject(o, i, baseDN, scope, DereferencePolicy.NEVER, 0, 0, 1678 null, NO_CONTROLS); 1679 } 1680 1681 1682 1683 /** 1684 * Performs a search in the directory to retrieve the object whose contents 1685 * match the contents of the provided object. It is expected that at most one 1686 * entry matches the provided criteria, and that it can be decoded as an 1687 * object of the associated type. If multiple entries match the resulting 1688 * criteria, or if the matching entry cannot be decoded as the associated type 1689 * of object, then an exception will be thrown. 1690 * <BR><BR> 1691 * A search filter will be generated from the provided object containing all 1692 * non-{@code null} values from fields and getter methods whose 1693 * {@link LDAPField} or {@link LDAPGetter} annotation has the {@code inFilter} 1694 * element set to {@code true}. 1695 * 1696 * @param o The object to use to construct the search filter. It 1697 * must not be {@code null}. 1698 * @param i The connection to use to communicate with the 1699 * directory server. It must not be {@code null}. 1700 * @param baseDN The base DN to use for the search. It may be 1701 * {@code null} if the {@link LDAPObject#defaultParentDN} 1702 * element in the {@code LDAPObject} should be used as 1703 * the base DN. 1704 * @param scope The scope to use for the search operation. It must 1705 * not be {@code null}. 1706 * @param derefPolicy The dereference policy to use for the search 1707 * operation. It must not be {@code null}. 1708 * @param sizeLimit The maximum number of entries to retrieve from the 1709 * directory. A value of zero indicates that no 1710 * client-requested size limit should be enforced. 1711 * @param timeLimit The maximum length of time in seconds that the server 1712 * should spend processing the search. A value of zero 1713 * indicates that no client-requested time limit should 1714 * be enforced. 1715 * @param extraFilter An optional additional filter to be ANDed with the 1716 * filter generated from the provided object. If this is 1717 * {@code null}, then only the filter generated from the 1718 * object will be used. 1719 * @param controls An optional set of controls to include in the search 1720 * request. It may be empty or {@code null} if no 1721 * controls are needed. 1722 * 1723 * @return The object constructed from the entry returned by the search, or 1724 * {@code null} if no entry was returned. 1725 * 1726 * @throws LDAPPersistException If an error occurs while preparing or 1727 * sending the search request or decoding the 1728 * entry that was returned. 1729 */ 1730 public T searchForObject(final T o, final LDAPInterface i, 1731 final String baseDN, final SearchScope scope, 1732 final DereferencePolicy derefPolicy, 1733 final int sizeLimit, final int timeLimit, 1734 final Filter extraFilter, final Control... controls) 1735 throws LDAPPersistException 1736 { 1737 ensureNotNull(o, i, scope, derefPolicy); 1738 1739 final String base; 1740 if (baseDN == null) 1741 { 1742 base = handler.getDefaultParentDN().toString(); 1743 } 1744 else 1745 { 1746 base = baseDN; 1747 } 1748 1749 final Filter filter; 1750 if (extraFilter == null) 1751 { 1752 filter = handler.createFilter(o); 1753 } 1754 else 1755 { 1756 filter = Filter.createANDFilter(handler.createFilter(o), extraFilter); 1757 } 1758 1759 final SearchRequest searchRequest = new SearchRequest(base, scope, 1760 derefPolicy, sizeLimit, timeLimit, false, filter, 1761 handler.getAttributesToRequest()); 1762 if (controls != null) 1763 { 1764 searchRequest.setControls(controls); 1765 } 1766 1767 try 1768 { 1769 final Entry e = i.searchForEntry(searchRequest); 1770 if (e == null) 1771 { 1772 return null; 1773 } 1774 else 1775 { 1776 return decode(e); 1777 } 1778 } 1779 catch (LDAPPersistException lpe) 1780 { 1781 debugException(lpe); 1782 throw lpe; 1783 } 1784 catch (LDAPException le) 1785 { 1786 debugException(le); 1787 throw new LDAPPersistException(le); 1788 } 1789 } 1790 1791 1792 1793 /** 1794 * Performs a search in the directory with an attempt to find all objects of 1795 * the specified type below the given base DN (or below the default parent DN 1796 * if no base DN is specified). Note that this may result in an unindexed 1797 * search, which may be expensive to conduct. Some servers may require 1798 * special permissions of clients wishing to perform unindexed searches. 1799 * 1800 * @param i The connection to use to communicate with the 1801 * directory server. It must not be {@code null}. 1802 * @param baseDN The base DN to use for the search. It may be 1803 * {@code null} if the {@link LDAPObject#defaultParentDN} 1804 * element in the {@code LDAPObject} should be used as the 1805 * base DN. 1806 * @param l The object search result listener that will be used to 1807 * receive objects decoded from entries returned for the 1808 * search. It must not be {@code null}. 1809 * @param controls An optional set of controls to include in the search 1810 * request. It may be empty or {@code null} if no controls 1811 * are needed. 1812 * 1813 * @return The result of the search operation that was processed. 1814 * 1815 * @throws LDAPPersistException If an error occurs while preparing or 1816 * sending the search request. 1817 */ 1818 public SearchResult getAll(final LDAPInterface i, final String baseDN, 1819 final ObjectSearchListener<T> l, 1820 final Control... controls) 1821 throws LDAPPersistException 1822 { 1823 ensureNotNull(i, l); 1824 1825 final String base; 1826 if (baseDN == null) 1827 { 1828 base = handler.getDefaultParentDN().toString(); 1829 } 1830 else 1831 { 1832 base = baseDN; 1833 } 1834 1835 final SearchListenerBridge<T> bridge = new SearchListenerBridge<T>(this, l); 1836 final SearchRequest searchRequest = new SearchRequest(bridge, base, 1837 SearchScope.SUB, DereferencePolicy.NEVER, 0, 0, false, 1838 handler.createBaseFilter(), handler.getAttributesToRequest()); 1839 if (controls != null) 1840 { 1841 searchRequest.setControls(controls); 1842 } 1843 1844 try 1845 { 1846 return i.search(searchRequest); 1847 } 1848 catch (LDAPException le) 1849 { 1850 debugException(le); 1851 throw new LDAPPersistException(le); 1852 } 1853 } 1854}