001/* 002 * Copyright 2007-2017 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-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; 022 023 024 025import java.math.BigInteger; 026import java.util.ArrayList; 027import java.util.Arrays; 028import java.util.Collection; 029import java.util.Collections; 030import java.util.Date; 031import java.util.HashSet; 032import java.util.Iterator; 033import java.util.LinkedHashMap; 034import java.util.List; 035import java.util.Map; 036import java.util.Set; 037import java.util.StringTokenizer; 038 039import com.unboundid.asn1.ASN1OctetString; 040import com.unboundid.ldap.matchingrules.MatchingRule; 041import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition; 042import com.unboundid.ldap.sdk.schema.Schema; 043import com.unboundid.ldif.LDIFException; 044import com.unboundid.ldif.LDIFReader; 045import com.unboundid.ldif.LDIFRecord; 046import com.unboundid.ldif.LDIFWriter; 047import com.unboundid.util.ByteStringBuffer; 048import com.unboundid.util.Mutable; 049import com.unboundid.util.NotExtensible; 050import com.unboundid.util.ThreadSafety; 051import com.unboundid.util.ThreadSafetyLevel; 052 053import static com.unboundid.ldap.sdk.LDAPMessages.*; 054import static com.unboundid.util.Debug.*; 055import static com.unboundid.util.StaticUtils.*; 056import static com.unboundid.util.Validator.*; 057 058 059 060/** 061 * This class provides a data structure for holding information about an LDAP 062 * entry. An entry contains a distinguished name (DN) and a set of attributes. 063 * An entry can be created from these components, and it can also be created 064 * from its LDIF representation as described in 065 * <A HREF="http://www.ietf.org/rfc/rfc2849.txt">RFC 2849</A>. For example: 066 * <BR><BR> 067 * <PRE> 068 * Entry entry = new Entry( 069 * "dn: dc=example,dc=com", 070 * "objectClass: top", 071 * "objectClass: domain", 072 * "dc: example"); 073 * </PRE> 074 * <BR><BR> 075 * This class also provides methods for retrieving the LDIF representation of 076 * an entry, either as a single string or as an array of strings that make up 077 * the LDIF lines. 078 * <BR><BR> 079 * The {@link Entry#diff} method may be used to obtain the set of differences 080 * between two entries, and to retrieve a list of {@link Modification} objects 081 * that can be used to modify one entry so that it contains the same set of 082 * data as another. The {@link Entry#applyModifications} method may be used to 083 * apply a set of modifications to an entry. 084 * <BR><BR> 085 * Entry objects are mutable, and the DN, set of attributes, and individual 086 * attribute values can be altered. 087 */ 088@Mutable() 089@NotExtensible() 090@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 091public class Entry 092 implements LDIFRecord 093{ 094 /** 095 * The serial version UID for this serializable class. 096 */ 097 private static final long serialVersionUID = -4438809025903729197L; 098 099 100 101 // The parsed DN for this entry. 102 private volatile DN parsedDN; 103 104 // The set of attributes for this entry. 105 private final LinkedHashMap<String,Attribute> attributes; 106 107 // The schema to use for this entry. 108 private final Schema schema; 109 110 // The DN for this entry. 111 private String dn; 112 113 114 115 /** 116 * Creates a new entry that wraps the provided entry. 117 * 118 * @param e The entry to be wrapped. 119 */ 120 protected Entry(final Entry e) 121 { 122 parsedDN = e.parsedDN; 123 attributes = e.attributes; 124 schema = e.schema; 125 dn = e.dn; 126 } 127 128 129 130 /** 131 * Creates a new entry with the provided DN and no attributes. 132 * 133 * @param dn The DN for this entry. It must not be {@code null}. 134 */ 135 public Entry(final String dn) 136 { 137 this(dn, (Schema) null); 138 } 139 140 141 142 /** 143 * Creates a new entry with the provided DN and no attributes. 144 * 145 * @param dn The DN for this entry. It must not be {@code null}. 146 * @param schema The schema to use for operations involving this entry. It 147 * may be {@code null} if no schema is available. 148 */ 149 public Entry(final String dn, final Schema schema) 150 { 151 ensureNotNull(dn); 152 153 this.dn = dn; 154 this.schema = schema; 155 156 attributes = new LinkedHashMap<String,Attribute>(); 157 } 158 159 160 161 /** 162 * Creates a new entry with the provided DN and no attributes. 163 * 164 * @param dn The DN for this entry. It must not be {@code null}. 165 */ 166 public Entry(final DN dn) 167 { 168 this(dn, (Schema) null); 169 } 170 171 172 173 /** 174 * Creates a new entry with the provided DN and no attributes. 175 * 176 * @param dn The DN for this entry. It must not be {@code null}. 177 * @param schema The schema to use for operations involving this entry. It 178 * may be {@code null} if no schema is available. 179 */ 180 public Entry(final DN dn, final Schema schema) 181 { 182 ensureNotNull(dn); 183 184 parsedDN = dn; 185 this.dn = parsedDN.toString(); 186 this.schema = schema; 187 188 attributes = new LinkedHashMap<String,Attribute>(); 189 } 190 191 192 193 /** 194 * Creates a new entry with the provided DN and set of attributes. 195 * 196 * @param dn The DN for this entry. It must not be {@code null}. 197 * @param attributes The set of attributes for this entry. It must not be 198 * {@code null}. 199 */ 200 public Entry(final String dn, final Attribute... attributes) 201 { 202 this(dn, null, attributes); 203 } 204 205 206 207 /** 208 * Creates a new entry with the provided DN and set of attributes. 209 * 210 * @param dn The DN for this entry. It must not be {@code null}. 211 * @param schema The schema to use for operations involving this entry. 212 * It may be {@code null} if no schema is available. 213 * @param attributes The set of attributes for this entry. It must not be 214 * {@code null}. 215 */ 216 public Entry(final String dn, final Schema schema, 217 final Attribute... attributes) 218 { 219 ensureNotNull(dn, attributes); 220 221 this.dn = dn; 222 this.schema = schema; 223 224 this.attributes = new LinkedHashMap<String,Attribute>(attributes.length); 225 for (final Attribute a : attributes) 226 { 227 final String name = toLowerCase(a.getName()); 228 final Attribute attr = this.attributes.get(name); 229 if (attr == null) 230 { 231 this.attributes.put(name, a); 232 } 233 else 234 { 235 this.attributes.put(name, Attribute.mergeAttributes(attr, a)); 236 } 237 } 238 } 239 240 241 242 /** 243 * Creates a new entry with the provided DN and set of attributes. 244 * 245 * @param dn The DN for this entry. It must not be {@code null}. 246 * @param attributes The set of attributes for this entry. It must not be 247 * {@code null}. 248 */ 249 public Entry(final DN dn, final Attribute... attributes) 250 { 251 this(dn, null, attributes); 252 } 253 254 255 256 /** 257 * Creates a new entry with the provided DN and set of attributes. 258 * 259 * @param dn The DN for this entry. It must not be {@code null}. 260 * @param schema The schema to use for operations involving this entry. 261 * It may be {@code null} if no schema is available. 262 * @param attributes The set of attributes for this entry. It must not be 263 * {@code null}. 264 */ 265 public Entry(final DN dn, final Schema schema, final Attribute... attributes) 266 { 267 ensureNotNull(dn, attributes); 268 269 parsedDN = dn; 270 this.dn = parsedDN.toString(); 271 this.schema = schema; 272 273 this.attributes = new LinkedHashMap<String,Attribute>(attributes.length); 274 for (final Attribute a : attributes) 275 { 276 final String name = toLowerCase(a.getName()); 277 final Attribute attr = this.attributes.get(name); 278 if (attr == null) 279 { 280 this.attributes.put(name, a); 281 } 282 else 283 { 284 this.attributes.put(name, Attribute.mergeAttributes(attr, a)); 285 } 286 } 287 } 288 289 290 291 /** 292 * Creates a new entry with the provided DN and set of attributes. 293 * 294 * @param dn The DN for this entry. It must not be {@code null}. 295 * @param attributes The set of attributes for this entry. It must not be 296 * {@code null}. 297 */ 298 public Entry(final String dn, final Collection<Attribute> attributes) 299 { 300 this(dn, null, attributes); 301 } 302 303 304 305 /** 306 * Creates a new entry with the provided DN and set of attributes. 307 * 308 * @param dn The DN for this entry. It must not be {@code null}. 309 * @param schema The schema to use for operations involving this entry. 310 * It may be {@code null} if no schema is available. 311 * @param attributes The set of attributes for this entry. It must not be 312 * {@code null}. 313 */ 314 public Entry(final String dn, final Schema schema, 315 final Collection<Attribute> attributes) 316 { 317 ensureNotNull(dn, attributes); 318 319 this.dn = dn; 320 this.schema = schema; 321 322 this.attributes = new LinkedHashMap<String,Attribute>(attributes.size()); 323 for (final Attribute a : attributes) 324 { 325 final String name = toLowerCase(a.getName()); 326 final Attribute attr = this.attributes.get(name); 327 if (attr == null) 328 { 329 this.attributes.put(name, a); 330 } 331 else 332 { 333 this.attributes.put(name, Attribute.mergeAttributes(attr, a)); 334 } 335 } 336 } 337 338 339 340 /** 341 * Creates a new entry with the provided DN and set of attributes. 342 * 343 * @param dn The DN for this entry. It must not be {@code null}. 344 * @param attributes The set of attributes for this entry. It must not be 345 * {@code null}. 346 */ 347 public Entry(final DN dn, final Collection<Attribute> attributes) 348 { 349 this(dn, null, attributes); 350 } 351 352 353 354 /** 355 * Creates a new entry with the provided DN and set of attributes. 356 * 357 * @param dn The DN for this entry. It must not be {@code null}. 358 * @param schema The schema to use for operations involving this entry. 359 * It may be {@code null} if no schema is available. 360 * @param attributes The set of attributes for this entry. It must not be 361 * {@code null}. 362 */ 363 public Entry(final DN dn, final Schema schema, 364 final Collection<Attribute> attributes) 365 { 366 ensureNotNull(dn, attributes); 367 368 parsedDN = dn; 369 this.dn = parsedDN.toString(); 370 this.schema = schema; 371 372 this.attributes = new LinkedHashMap<String,Attribute>(attributes.size()); 373 for (final Attribute a : attributes) 374 { 375 final String name = toLowerCase(a.getName()); 376 final Attribute attr = this.attributes.get(name); 377 if (attr == null) 378 { 379 this.attributes.put(name, a); 380 } 381 else 382 { 383 this.attributes.put(name, Attribute.mergeAttributes(attr, a)); 384 } 385 } 386 } 387 388 389 390 /** 391 * Creates a new entry from the provided LDIF representation. 392 * 393 * @param entryLines The set of lines that comprise an LDIF representation 394 * of the entry. It must not be {@code null} or empty. 395 * 396 * @throws LDIFException If the provided lines cannot be decoded as an entry 397 * in LDIF format. 398 */ 399 public Entry(final String... entryLines) 400 throws LDIFException 401 { 402 this(null, entryLines); 403 } 404 405 406 407 /** 408 * Creates a new entry from the provided LDIF representation. 409 * 410 * @param schema The schema to use for operations involving this entry. 411 * It may be {@code null} if no schema is available. 412 * @param entryLines The set of lines that comprise an LDIF representation 413 * of the entry. It must not be {@code null} or empty. 414 * 415 * @throws LDIFException If the provided lines cannot be decoded as an entry 416 * in LDIF format. 417 */ 418 public Entry(final Schema schema, final String... entryLines) 419 throws LDIFException 420 { 421 final Entry e = LDIFReader.decodeEntry(false, schema, entryLines); 422 423 this.schema = schema; 424 425 dn = e.dn; 426 parsedDN = e.parsedDN; 427 attributes = e.attributes; 428 } 429 430 431 432 /** 433 * Retrieves the DN for this entry. 434 * 435 * @return The DN for this entry. 436 */ 437 public final String getDN() 438 { 439 return dn; 440 } 441 442 443 444 /** 445 * Specifies the DN for this entry. 446 * 447 * @param dn The DN for this entry. It must not be {@code null}. 448 */ 449 public void setDN(final String dn) 450 { 451 ensureNotNull(dn); 452 453 this.dn = dn; 454 parsedDN = null; 455 } 456 457 458 459 /** 460 * Specifies the DN for this entry. 461 * 462 * @param dn The DN for this entry. It must not be {@code null}. 463 */ 464 public void setDN(final DN dn) 465 { 466 ensureNotNull(dn); 467 468 parsedDN = dn; 469 this.dn = parsedDN.toString(); 470 } 471 472 473 474 /** 475 * Retrieves the parsed DN for this entry. 476 * 477 * @return The parsed DN for this entry. 478 * 479 * @throws LDAPException If the DN string cannot be parsed as a valid DN. 480 */ 481 public final DN getParsedDN() 482 throws LDAPException 483 { 484 if (parsedDN == null) 485 { 486 parsedDN = new DN(dn, schema); 487 } 488 489 return parsedDN; 490 } 491 492 493 494 /** 495 * Retrieves the RDN for this entry. 496 * 497 * @return The RDN for this entry, or {@code null} if the DN is the null DN. 498 * 499 * @throws LDAPException If the DN string cannot be parsed as a valid DN. 500 */ 501 public final RDN getRDN() 502 throws LDAPException 503 { 504 return getParsedDN().getRDN(); 505 } 506 507 508 509 /** 510 * Retrieves the parent DN for this entry. 511 * 512 * @return The parent DN for this entry, or {@code null} if there is no 513 * parent. 514 * 515 * @throws LDAPException If the DN string cannot be parsed as a valid DN. 516 */ 517 public final DN getParentDN() 518 throws LDAPException 519 { 520 if (parsedDN == null) 521 { 522 parsedDN = new DN(dn, schema); 523 } 524 525 return parsedDN.getParent(); 526 } 527 528 529 530 /** 531 * Retrieves the parent DN for this entry as a string. 532 * 533 * @return The parent DN for this entry as a string, or {@code null} if there 534 * is no parent. 535 * 536 * @throws LDAPException If the DN string cannot be parsed as a valid DN. 537 */ 538 public final String getParentDNString() 539 throws LDAPException 540 { 541 if (parsedDN == null) 542 { 543 parsedDN = new DN(dn, schema); 544 } 545 546 final DN parentDN = parsedDN.getParent(); 547 if (parentDN == null) 548 { 549 return null; 550 } 551 else 552 { 553 return parentDN.toString(); 554 } 555 } 556 557 558 559 /** 560 * Retrieves the schema that will be used for this entry, if any. 561 * 562 * @return The schema that will be used for this entry, or {@code null} if 563 * no schema was provided. 564 */ 565 protected Schema getSchema() 566 { 567 return schema; 568 } 569 570 571 572 /** 573 * Indicates whether this entry contains the specified attribute. 574 * 575 * @param attributeName The name of the attribute for which to make the 576 * determination. It must not be {@code null}. 577 * 578 * @return {@code true} if this entry contains the specified attribute, or 579 * {@code false} if not. 580 */ 581 public final boolean hasAttribute(final String attributeName) 582 { 583 return hasAttribute(attributeName, schema); 584 } 585 586 587 588 /** 589 * Indicates whether this entry contains the specified attribute. 590 * 591 * @param attributeName The name of the attribute for which to make the 592 * determination. It must not be {@code null}. 593 * @param schema The schema to use to determine whether there may be 594 * alternate names for the specified attribute. It may 595 * be {@code null} if no schema is available. 596 * 597 * @return {@code true} if this entry contains the specified attribute, or 598 * {@code false} if not. 599 */ 600 public final boolean hasAttribute(final String attributeName, 601 final Schema schema) 602 { 603 ensureNotNull(attributeName); 604 605 if (attributes.containsKey(toLowerCase(attributeName))) 606 { 607 return true; 608 } 609 610 if (schema != null) 611 { 612 final String baseName; 613 final String options; 614 final int semicolonPos = attributeName.indexOf(';'); 615 if (semicolonPos > 0) 616 { 617 baseName = attributeName.substring(0, semicolonPos); 618 options = toLowerCase(attributeName.substring(semicolonPos)); 619 } 620 else 621 { 622 baseName = attributeName; 623 options = ""; 624 } 625 626 final AttributeTypeDefinition at = schema.getAttributeType(baseName); 627 if (at != null) 628 { 629 if (attributes.containsKey(toLowerCase(at.getOID()) + options)) 630 { 631 return true; 632 } 633 634 for (final String name : at.getNames()) 635 { 636 if (attributes.containsKey(toLowerCase(name) + options)) 637 { 638 return true; 639 } 640 } 641 } 642 } 643 644 return false; 645 } 646 647 648 649 /** 650 * Indicates whether this entry contains the specified attribute. It will 651 * only return {@code true} if this entry contains an attribute with the same 652 * name and exact set of values. 653 * 654 * @param attribute The attribute for which to make the determination. It 655 * must not be {@code null}. 656 * 657 * @return {@code true} if this entry contains the specified attribute, or 658 * {@code false} if not. 659 */ 660 public final boolean hasAttribute(final Attribute attribute) 661 { 662 ensureNotNull(attribute); 663 664 final String lowerName = toLowerCase(attribute.getName()); 665 final Attribute attr = attributes.get(lowerName); 666 return ((attr != null) && attr.equals(attribute)); 667 } 668 669 670 671 /** 672 * Indicates whether this entry contains an attribute with the given name and 673 * value. 674 * 675 * @param attributeName The name of the attribute for which to make the 676 * determination. It must not be {@code null}. 677 * @param attributeValue The value for which to make the determination. It 678 * must not be {@code null}. 679 * 680 * @return {@code true} if this entry contains an attribute with the 681 * specified name and value, or {@code false} if not. 682 */ 683 public final boolean hasAttributeValue(final String attributeName, 684 final String attributeValue) 685 { 686 ensureNotNull(attributeName, attributeValue); 687 688 final Attribute attr = attributes.get(toLowerCase(attributeName)); 689 return ((attr != null) && attr.hasValue(attributeValue)); 690 } 691 692 693 694 /** 695 * Indicates whether this entry contains an attribute with the given name and 696 * value. 697 * 698 * @param attributeName The name of the attribute for which to make the 699 * determination. It must not be {@code null}. 700 * @param attributeValue The value for which to make the determination. It 701 * must not be {@code null}. 702 * @param matchingRule The matching rule to use to make the determination. 703 * It must not be {@code null}. 704 * 705 * @return {@code true} if this entry contains an attribute with the 706 * specified name and value, or {@code false} if not. 707 */ 708 public final boolean hasAttributeValue(final String attributeName, 709 final String attributeValue, 710 final MatchingRule matchingRule) 711 { 712 ensureNotNull(attributeName, attributeValue); 713 714 final Attribute attr = attributes.get(toLowerCase(attributeName)); 715 return ((attr != null) && attr.hasValue(attributeValue, matchingRule)); 716 } 717 718 719 720 /** 721 * Indicates whether this entry contains an attribute with the given name and 722 * value. 723 * 724 * @param attributeName The name of the attribute for which to make the 725 * determination. It must not be {@code null}. 726 * @param attributeValue The value for which to make the determination. It 727 * must not be {@code null}. 728 * 729 * @return {@code true} if this entry contains an attribute with the 730 * specified name and value, or {@code false} if not. 731 */ 732 public final boolean hasAttributeValue(final String attributeName, 733 final byte[] attributeValue) 734 { 735 ensureNotNull(attributeName, attributeValue); 736 737 final Attribute attr = attributes.get(toLowerCase(attributeName)); 738 return ((attr != null) && attr.hasValue(attributeValue)); 739 } 740 741 742 743 /** 744 * Indicates whether this entry contains an attribute with the given name and 745 * value. 746 * 747 * @param attributeName The name of the attribute for which to make the 748 * determination. It must not be {@code null}. 749 * @param attributeValue The value for which to make the determination. It 750 * must not be {@code null}. 751 * @param matchingRule The matching rule to use to make the determination. 752 * It must not be {@code null}. 753 * 754 * @return {@code true} if this entry contains an attribute with the 755 * specified name and value, or {@code false} if not. 756 */ 757 public final boolean hasAttributeValue(final String attributeName, 758 final byte[] attributeValue, 759 final MatchingRule matchingRule) 760 { 761 ensureNotNull(attributeName, attributeValue); 762 763 final Attribute attr = attributes.get(toLowerCase(attributeName)); 764 return ((attr != null) && attr.hasValue(attributeValue, matchingRule)); 765 } 766 767 768 769 /** 770 * Indicates whether this entry contains the specified object class. 771 * 772 * @param objectClassName The name of the object class for which to make the 773 * determination. It must not be {@code null}. 774 * 775 * @return {@code true} if this entry contains the specified object class, or 776 * {@code false} if not. 777 */ 778 public final boolean hasObjectClass(final String objectClassName) 779 { 780 return hasAttributeValue("objectClass", objectClassName); 781 } 782 783 784 785 /** 786 * Retrieves the set of attributes contained in this entry. 787 * 788 * @return The set of attributes contained in this entry. 789 */ 790 public final Collection<Attribute> getAttributes() 791 { 792 return Collections.unmodifiableCollection(attributes.values()); 793 } 794 795 796 797 /** 798 * Retrieves the attribute with the specified name. 799 * 800 * @param attributeName The name of the attribute to retrieve. It must not 801 * be {@code null}. 802 * 803 * @return The requested attribute from this entry, or {@code null} if the 804 * specified attribute is not present in this entry. 805 */ 806 public final Attribute getAttribute(final String attributeName) 807 { 808 return getAttribute(attributeName, schema); 809 } 810 811 812 813 /** 814 * Retrieves the attribute with the specified name. 815 * 816 * @param attributeName The name of the attribute to retrieve. It must not 817 * be {@code null}. 818 * @param schema The schema to use to determine whether there may be 819 * alternate names for the specified attribute. It may 820 * be {@code null} if no schema is available. 821 * 822 * @return The requested attribute from this entry, or {@code null} if the 823 * specified attribute is not present in this entry. 824 */ 825 public final Attribute getAttribute(final String attributeName, 826 final Schema schema) 827 { 828 ensureNotNull(attributeName); 829 830 Attribute a = attributes.get(toLowerCase(attributeName)); 831 if ((a == null) && (schema != null)) 832 { 833 final String baseName; 834 final String options; 835 final int semicolonPos = attributeName.indexOf(';'); 836 if (semicolonPos > 0) 837 { 838 baseName = attributeName.substring(0, semicolonPos); 839 options = toLowerCase(attributeName.substring(semicolonPos)); 840 } 841 else 842 { 843 baseName = attributeName; 844 options = ""; 845 } 846 847 final AttributeTypeDefinition at = schema.getAttributeType(baseName); 848 if (at == null) 849 { 850 return null; 851 } 852 853 a = attributes.get(toLowerCase(at.getOID() + options)); 854 if (a == null) 855 { 856 for (final String name : at.getNames()) 857 { 858 a = attributes.get(toLowerCase(name) + options); 859 if (a != null) 860 { 861 return a; 862 } 863 } 864 } 865 866 return a; 867 } 868 else 869 { 870 return a; 871 } 872 } 873 874 875 876 /** 877 * Retrieves the list of attributes with the given base name and all of the 878 * specified options. 879 * 880 * @param baseName The base name (without any options) for the attribute to 881 * retrieve. It must not be {@code null}. 882 * @param options The set of options that should be included in the 883 * attributes that are returned. It may be empty or 884 * {@code null} if all attributes with the specified base 885 * name should be returned, regardless of the options that 886 * they contain (if any). 887 * 888 * @return The list of attributes with the given base name and all of the 889 * specified options. It may be empty if there are no attributes 890 * with the specified base name and set of options. 891 */ 892 public final List<Attribute> getAttributesWithOptions(final String baseName, 893 final Set<String> options) 894 { 895 ensureNotNull(baseName); 896 897 final ArrayList<Attribute> attrList = new ArrayList<Attribute>(10); 898 899 for (final Attribute a : attributes.values()) 900 { 901 if (a.getBaseName().equalsIgnoreCase(baseName)) 902 { 903 if ((options == null) || options.isEmpty()) 904 { 905 attrList.add(a); 906 } 907 else 908 { 909 boolean allFound = true; 910 for (final String option : options) 911 { 912 if (! a.hasOption(option)) 913 { 914 allFound = false; 915 break; 916 } 917 } 918 919 if (allFound) 920 { 921 attrList.add(a); 922 } 923 } 924 } 925 } 926 927 return Collections.unmodifiableList(attrList); 928 } 929 930 931 932 /** 933 * Retrieves the value for the specified attribute, if available. If the 934 * attribute has more than one value, then the first value will be returned. 935 * 936 * @param attributeName The name of the attribute for which to retrieve the 937 * value. It must not be {@code null}. 938 * 939 * @return The value for the specified attribute, or {@code null} if that 940 * attribute is not available. 941 */ 942 public String getAttributeValue(final String attributeName) 943 { 944 ensureNotNull(attributeName); 945 946 final Attribute a = attributes.get(toLowerCase(attributeName)); 947 if (a == null) 948 { 949 return null; 950 } 951 else 952 { 953 return a.getValue(); 954 } 955 } 956 957 958 959 /** 960 * Retrieves the value for the specified attribute as a byte array, if 961 * available. If the attribute has more than one value, then the first value 962 * will be returned. 963 * 964 * @param attributeName The name of the attribute for which to retrieve the 965 * value. It must not be {@code null}. 966 * 967 * @return The value for the specified attribute as a byte array, or 968 * {@code null} if that attribute is not available. 969 */ 970 public byte[] getAttributeValueBytes(final String attributeName) 971 { 972 ensureNotNull(attributeName); 973 974 final Attribute a = attributes.get(toLowerCase(attributeName)); 975 if (a == null) 976 { 977 return null; 978 } 979 else 980 { 981 return a.getValueByteArray(); 982 } 983 } 984 985 986 987 /** 988 * Retrieves the value for the specified attribute as a Boolean, if available. 989 * If the attribute has more than one value, then the first value will be 990 * returned. Values of "true", "t", "yes", "y", "on", and "1" will be 991 * interpreted as {@code TRUE}. Values of "false", "f", "no", "n", "off", and 992 * "0" will be interpreted as {@code FALSE}. 993 * 994 * @param attributeName The name of the attribute for which to retrieve the 995 * value. It must not be {@code null}. 996 * 997 * @return The Boolean value parsed from the specified attribute, or 998 * {@code null} if that attribute is not available or the value 999 * cannot be parsed as a Boolean. 1000 */ 1001 public Boolean getAttributeValueAsBoolean(final String attributeName) 1002 { 1003 ensureNotNull(attributeName); 1004 1005 final Attribute a = attributes.get(toLowerCase(attributeName)); 1006 if (a == null) 1007 { 1008 return null; 1009 } 1010 else 1011 { 1012 return a.getValueAsBoolean(); 1013 } 1014 } 1015 1016 1017 1018 /** 1019 * Retrieves the value for the specified attribute as a Date, formatted using 1020 * the generalized time syntax, if available. If the attribute has more than 1021 * one value, then the first value will be returned. 1022 * 1023 * @param attributeName The name of the attribute for which to retrieve the 1024 * value. It must not be {@code null}. 1025 * 1026 * @return The Date value parsed from the specified attribute, or 1027 * {@code null} if that attribute is not available or the value 1028 * cannot be parsed as a Date. 1029 */ 1030 public Date getAttributeValueAsDate(final String attributeName) 1031 { 1032 ensureNotNull(attributeName); 1033 1034 final Attribute a = attributes.get(toLowerCase(attributeName)); 1035 if (a == null) 1036 { 1037 return null; 1038 } 1039 else 1040 { 1041 return a.getValueAsDate(); 1042 } 1043 } 1044 1045 1046 1047 /** 1048 * Retrieves the value for the specified attribute as a DN, if available. If 1049 * the attribute has more than one value, then the first value will be 1050 * returned. 1051 * 1052 * @param attributeName The name of the attribute for which to retrieve the 1053 * value. It must not be {@code null}. 1054 * 1055 * @return The DN value parsed from the specified attribute, or {@code null} 1056 * if that attribute is not available or the value cannot be parsed 1057 * as a DN. 1058 */ 1059 public DN getAttributeValueAsDN(final String attributeName) 1060 { 1061 ensureNotNull(attributeName); 1062 1063 final Attribute a = attributes.get(toLowerCase(attributeName)); 1064 if (a == null) 1065 { 1066 return null; 1067 } 1068 else 1069 { 1070 return a.getValueAsDN(); 1071 } 1072 } 1073 1074 1075 1076 /** 1077 * Retrieves the value for the specified attribute as an Integer, if 1078 * available. If the attribute has more than one value, then the first value 1079 * will be returned. 1080 * 1081 * @param attributeName The name of the attribute for which to retrieve the 1082 * value. It must not be {@code null}. 1083 * 1084 * @return The Integer value parsed from the specified attribute, or 1085 * {@code null} if that attribute is not available or the value 1086 * cannot be parsed as an Integer. 1087 */ 1088 public Integer getAttributeValueAsInteger(final String attributeName) 1089 { 1090 ensureNotNull(attributeName); 1091 1092 final Attribute a = attributes.get(toLowerCase(attributeName)); 1093 if (a == null) 1094 { 1095 return null; 1096 } 1097 else 1098 { 1099 return a.getValueAsInteger(); 1100 } 1101 } 1102 1103 1104 1105 /** 1106 * Retrieves the value for the specified attribute as a Long, if available. 1107 * If the attribute has more than one value, then the first value will be 1108 * returned. 1109 * 1110 * @param attributeName The name of the attribute for which to retrieve the 1111 * value. It must not be {@code null}. 1112 * 1113 * @return The Long value parsed from the specified attribute, or 1114 * {@code null} if that attribute is not available or the value 1115 * cannot be parsed as a Long. 1116 */ 1117 public Long getAttributeValueAsLong(final String attributeName) 1118 { 1119 ensureNotNull(attributeName); 1120 1121 final Attribute a = attributes.get(toLowerCase(attributeName)); 1122 if (a == null) 1123 { 1124 return null; 1125 } 1126 else 1127 { 1128 return a.getValueAsLong(); 1129 } 1130 } 1131 1132 1133 1134 /** 1135 * Retrieves the set of values for the specified attribute, if available. 1136 * 1137 * @param attributeName The name of the attribute for which to retrieve the 1138 * values. It must not be {@code null}. 1139 * 1140 * @return The set of values for the specified attribute, or {@code null} if 1141 * that attribute is not available. 1142 */ 1143 public String[] getAttributeValues(final String attributeName) 1144 { 1145 ensureNotNull(attributeName); 1146 1147 final Attribute a = attributes.get(toLowerCase(attributeName)); 1148 if (a == null) 1149 { 1150 return null; 1151 } 1152 else 1153 { 1154 return a.getValues(); 1155 } 1156 } 1157 1158 1159 1160 /** 1161 * Retrieves the set of values for the specified attribute as byte arrays, if 1162 * available. 1163 * 1164 * @param attributeName The name of the attribute for which to retrieve the 1165 * values. It must not be {@code null}. 1166 * 1167 * @return The set of values for the specified attribute as byte arrays, or 1168 * {@code null} if that attribute is not available. 1169 */ 1170 public byte[][] getAttributeValueByteArrays(final String attributeName) 1171 { 1172 ensureNotNull(attributeName); 1173 1174 final Attribute a = attributes.get(toLowerCase(attributeName)); 1175 if (a == null) 1176 { 1177 return null; 1178 } 1179 else 1180 { 1181 return a.getValueByteArrays(); 1182 } 1183 } 1184 1185 1186 1187 /** 1188 * Retrieves the "objectClass" attribute from the entry, if available. 1189 * 1190 * @return The "objectClass" attribute from the entry, or {@code null} if 1191 * that attribute not available. 1192 */ 1193 public final Attribute getObjectClassAttribute() 1194 { 1195 return getAttribute("objectClass"); 1196 } 1197 1198 1199 1200 /** 1201 * Retrieves the values of the "objectClass" attribute from the entry, if 1202 * available. 1203 * 1204 * @return The values of the "objectClass" attribute from the entry, or 1205 * {@code null} if that attribute is not available. 1206 */ 1207 public final String[] getObjectClassValues() 1208 { 1209 return getAttributeValues("objectClass"); 1210 } 1211 1212 1213 1214 /** 1215 * Adds the provided attribute to this entry. If this entry already contains 1216 * an attribute with the same name, then their values will be merged. 1217 * 1218 * @param attribute The attribute to be added. It must not be {@code null}. 1219 * 1220 * @return {@code true} if the entry was updated, or {@code false} because 1221 * the specified attribute already existed with all provided values. 1222 */ 1223 public boolean addAttribute(final Attribute attribute) 1224 { 1225 ensureNotNull(attribute); 1226 1227 final String lowerName = toLowerCase(attribute.getName()); 1228 final Attribute attr = attributes.get(lowerName); 1229 if (attr == null) 1230 { 1231 attributes.put(lowerName, attribute); 1232 return true; 1233 } 1234 else 1235 { 1236 final Attribute newAttr = Attribute.mergeAttributes(attr, attribute); 1237 attributes.put(lowerName, newAttr); 1238 return (attr.getRawValues().length != newAttr.getRawValues().length); 1239 } 1240 } 1241 1242 1243 1244 /** 1245 * Adds the specified attribute value to this entry, if it is not already 1246 * present. 1247 * 1248 * @param attributeName The name for the attribute to be added. It must 1249 * not be {@code null}. 1250 * @param attributeValue The value for the attribute to be added. It must 1251 * not be {@code null}. 1252 * 1253 * @return {@code true} if the entry was updated, or {@code false} because 1254 * the specified attribute already existed with the given value. 1255 */ 1256 public boolean addAttribute(final String attributeName, 1257 final String attributeValue) 1258 { 1259 ensureNotNull(attributeName, attributeValue); 1260 return addAttribute(new Attribute(attributeName, schema, attributeValue)); 1261 } 1262 1263 1264 1265 /** 1266 * Adds the specified attribute value to this entry, if it is not already 1267 * present. 1268 * 1269 * @param attributeName The name for the attribute to be added. It must 1270 * not be {@code null}. 1271 * @param attributeValue The value for the attribute to be added. It must 1272 * not be {@code null}. 1273 * 1274 * @return {@code true} if the entry was updated, or {@code false} because 1275 * the specified attribute already existed with the given value. 1276 */ 1277 public boolean addAttribute(final String attributeName, 1278 final byte[] attributeValue) 1279 { 1280 ensureNotNull(attributeName, attributeValue); 1281 return addAttribute(new Attribute(attributeName, schema, attributeValue)); 1282 } 1283 1284 1285 1286 /** 1287 * Adds the provided attribute to this entry. If this entry already contains 1288 * an attribute with the same name, then their values will be merged. 1289 * 1290 * @param attributeName The name for the attribute to be added. It must 1291 * not be {@code null}. 1292 * @param attributeValues The value for the attribute to be added. It must 1293 * not be {@code null}. 1294 * 1295 * @return {@code true} if the entry was updated, or {@code false} because 1296 * the specified attribute already existed with all provided values. 1297 */ 1298 public boolean addAttribute(final String attributeName, 1299 final String... attributeValues) 1300 { 1301 ensureNotNull(attributeName, attributeValues); 1302 return addAttribute(new Attribute(attributeName, schema, attributeValues)); 1303 } 1304 1305 1306 1307 /** 1308 * Adds the provided attribute to this entry. If this entry already contains 1309 * an attribute with the same name, then their values will be merged. 1310 * 1311 * @param attributeName The name for the attribute to be added. It must 1312 * not be {@code null}. 1313 * @param attributeValues The value for the attribute to be added. It must 1314 * not be {@code null}. 1315 * 1316 * @return {@code true} if the entry was updated, or {@code false} because 1317 * the specified attribute already existed with all provided values. 1318 */ 1319 public boolean addAttribute(final String attributeName, 1320 final byte[]... attributeValues) 1321 { 1322 ensureNotNull(attributeName, attributeValues); 1323 return addAttribute(new Attribute(attributeName, schema, attributeValues)); 1324 } 1325 1326 1327 1328 /** 1329 * Adds the provided attribute to this entry. If this entry already contains 1330 * an attribute with the same name, then their values will be merged. 1331 * 1332 * @param attributeName The name for the attribute to be added. It must 1333 * not be {@code null}. 1334 * @param attributeValues The value for the attribute to be added. It must 1335 * not be {@code null}. 1336 * 1337 * @return {@code true} if the entry was updated, or {@code false} because 1338 * the specified attribute already existed with all provided values. 1339 */ 1340 public boolean addAttribute(final String attributeName, 1341 final Collection<String> attributeValues) 1342 { 1343 ensureNotNull(attributeName, attributeValues); 1344 return addAttribute(new Attribute(attributeName, schema, attributeValues)); 1345 } 1346 1347 1348 1349 /** 1350 * Removes the specified attribute from this entry. 1351 * 1352 * @param attributeName The name of the attribute to remove. It must not be 1353 * {@code null}. 1354 * 1355 * @return {@code true} if the attribute was removed from the entry, or 1356 * {@code false} if it was not present. 1357 */ 1358 public boolean removeAttribute(final String attributeName) 1359 { 1360 ensureNotNull(attributeName); 1361 1362 if (schema == null) 1363 { 1364 return (attributes.remove(toLowerCase(attributeName)) != null); 1365 } 1366 else 1367 { 1368 final Attribute a = getAttribute(attributeName, schema); 1369 if (a == null) 1370 { 1371 return false; 1372 } 1373 else 1374 { 1375 attributes.remove(toLowerCase(a.getName())); 1376 return true; 1377 } 1378 } 1379 } 1380 1381 1382 1383 /** 1384 * Removes the specified attribute value from this entry if it is present. If 1385 * it is the last value for the attribute, then the entire attribute will be 1386 * removed. If the specified value is not present, then no change will be 1387 * made. 1388 * 1389 * @param attributeName The name of the attribute from which to remove the 1390 * value. It must not be {@code null}. 1391 * @param attributeValue The value to remove from the attribute. It must 1392 * not be {@code null}. 1393 * 1394 * @return {@code true} if the attribute value was removed from the entry, or 1395 * {@code false} if it was not present. 1396 */ 1397 public boolean removeAttributeValue(final String attributeName, 1398 final String attributeValue) 1399 { 1400 return removeAttributeValue(attributeName, attributeValue, null); 1401 } 1402 1403 1404 1405 /** 1406 * Removes the specified attribute value from this entry if it is present. If 1407 * it is the last value for the attribute, then the entire attribute will be 1408 * removed. If the specified value is not present, then no change will be 1409 * made. 1410 * 1411 * @param attributeName The name of the attribute from which to remove the 1412 * value. It must not be {@code null}. 1413 * @param attributeValue The value to remove from the attribute. It must 1414 * not be {@code null}. 1415 * @param matchingRule The matching rule to use for the attribute. It may 1416 * be {@code null} to use the matching rule associated 1417 * with the attribute. 1418 * 1419 * @return {@code true} if the attribute value was removed from the entry, or 1420 * {@code false} if it was not present. 1421 */ 1422 public boolean removeAttributeValue(final String attributeName, 1423 final String attributeValue, 1424 final MatchingRule matchingRule) 1425 { 1426 ensureNotNull(attributeName, attributeValue); 1427 1428 final Attribute attr = getAttribute(attributeName, schema); 1429 if (attr == null) 1430 { 1431 return false; 1432 } 1433 else 1434 { 1435 final String lowerName = toLowerCase(attr.getName()); 1436 final Attribute newAttr = Attribute.removeValues(attr, 1437 new Attribute(attributeName, attributeValue), matchingRule); 1438 if (newAttr.hasValue()) 1439 { 1440 attributes.put(lowerName, newAttr); 1441 } 1442 else 1443 { 1444 attributes.remove(lowerName); 1445 } 1446 1447 return (attr.getRawValues().length != newAttr.getRawValues().length); 1448 } 1449 } 1450 1451 1452 1453 /** 1454 * Removes the specified attribute value from this entry if it is present. If 1455 * it is the last value for the attribute, then the entire attribute will be 1456 * removed. If the specified value is not present, then no change will be 1457 * made. 1458 * 1459 * @param attributeName The name of the attribute from which to remove the 1460 * value. It must not be {@code null}. 1461 * @param attributeValue The value to remove from the attribute. It must 1462 * not be {@code null}. 1463 * 1464 * @return {@code true} if the attribute value was removed from the entry, or 1465 * {@code false} if it was not present. 1466 */ 1467 public boolean removeAttributeValue(final String attributeName, 1468 final byte[] attributeValue) 1469 { 1470 return removeAttributeValue(attributeName, attributeValue, null); 1471 } 1472 1473 1474 1475 /** 1476 * Removes the specified attribute value from this entry if it is present. If 1477 * it is the last value for the attribute, then the entire attribute will be 1478 * removed. If the specified value is not present, then no change will be 1479 * made. 1480 * 1481 * @param attributeName The name of the attribute from which to remove the 1482 * value. It must not be {@code null}. 1483 * @param attributeValue The value to remove from the attribute. It must 1484 * not be {@code null}. 1485 * @param matchingRule The matching rule to use for the attribute. It may 1486 * be {@code null} to use the matching rule associated 1487 * with the attribute. 1488 * 1489 * @return {@code true} if the attribute value was removed from the entry, or 1490 * {@code false} if it was not present. 1491 */ 1492 public boolean removeAttributeValue(final String attributeName, 1493 final byte[] attributeValue, 1494 final MatchingRule matchingRule) 1495 { 1496 ensureNotNull(attributeName, attributeValue); 1497 1498 final Attribute attr = getAttribute(attributeName, schema); 1499 if (attr == null) 1500 { 1501 return false; 1502 } 1503 else 1504 { 1505 final String lowerName = toLowerCase(attr.getName()); 1506 final Attribute newAttr = Attribute.removeValues(attr, 1507 new Attribute(attributeName, attributeValue), matchingRule); 1508 if (newAttr.hasValue()) 1509 { 1510 attributes.put(lowerName, newAttr); 1511 } 1512 else 1513 { 1514 attributes.remove(lowerName); 1515 } 1516 1517 return (attr.getRawValues().length != newAttr.getRawValues().length); 1518 } 1519 } 1520 1521 1522 1523 /** 1524 * Removes the specified attribute values from this entry if they are present. 1525 * If the attribute does not have any remaining values, then the entire 1526 * attribute will be removed. If any of the provided values are not present, 1527 * then they will be ignored. 1528 * 1529 * @param attributeName The name of the attribute from which to remove the 1530 * values. It must not be {@code null}. 1531 * @param attributeValues The set of values to remove from the attribute. 1532 * It must not be {@code null}. 1533 * 1534 * @return {@code true} if any attribute values were removed from the entry, 1535 * or {@code false} none of them were present. 1536 */ 1537 public boolean removeAttributeValues(final String attributeName, 1538 final String... attributeValues) 1539 { 1540 ensureNotNull(attributeName, attributeValues); 1541 1542 final Attribute attr = getAttribute(attributeName, schema); 1543 if (attr == null) 1544 { 1545 return false; 1546 } 1547 else 1548 { 1549 final String lowerName = toLowerCase(attr.getName()); 1550 final Attribute newAttr = Attribute.removeValues(attr, 1551 new Attribute(attributeName, attributeValues)); 1552 if (newAttr.hasValue()) 1553 { 1554 attributes.put(lowerName, newAttr); 1555 } 1556 else 1557 { 1558 attributes.remove(lowerName); 1559 } 1560 1561 return (attr.getRawValues().length != newAttr.getRawValues().length); 1562 } 1563 } 1564 1565 1566 1567 /** 1568 * Removes the specified attribute values from this entry if they are present. 1569 * If the attribute does not have any remaining values, then the entire 1570 * attribute will be removed. If any of the provided values are not present, 1571 * then they will be ignored. 1572 * 1573 * @param attributeName The name of the attribute from which to remove the 1574 * values. It must not be {@code null}. 1575 * @param attributeValues The set of values to remove from the attribute. 1576 * It must not be {@code null}. 1577 * 1578 * @return {@code true} if any attribute values were removed from the entry, 1579 * or {@code false} none of them were present. 1580 */ 1581 public boolean removeAttributeValues(final String attributeName, 1582 final byte[]... attributeValues) 1583 { 1584 ensureNotNull(attributeName, attributeValues); 1585 1586 final Attribute attr = getAttribute(attributeName, schema); 1587 if (attr == null) 1588 { 1589 return false; 1590 } 1591 else 1592 { 1593 final String lowerName = toLowerCase(attr.getName()); 1594 final Attribute newAttr = Attribute.removeValues(attr, 1595 new Attribute(attributeName, attributeValues)); 1596 if (newAttr.hasValue()) 1597 { 1598 attributes.put(lowerName, newAttr); 1599 } 1600 else 1601 { 1602 attributes.remove(lowerName); 1603 } 1604 1605 return (attr.getRawValues().length != newAttr.getRawValues().length); 1606 } 1607 } 1608 1609 1610 1611 /** 1612 * Adds the provided attribute to this entry, replacing any existing set of 1613 * values for the associated attribute. 1614 * 1615 * @param attribute The attribute to be included in this entry. It must not 1616 * be {@code null}. 1617 */ 1618 public void setAttribute(final Attribute attribute) 1619 { 1620 ensureNotNull(attribute); 1621 1622 final String lowerName; 1623 final Attribute a = getAttribute(attribute.getName(), schema); 1624 if (a == null) 1625 { 1626 lowerName = toLowerCase(attribute.getName()); 1627 } 1628 else 1629 { 1630 lowerName = toLowerCase(a.getName()); 1631 } 1632 1633 attributes.put(lowerName, attribute); 1634 } 1635 1636 1637 1638 /** 1639 * Adds the provided attribute to this entry, replacing any existing set of 1640 * values for the associated attribute. 1641 * 1642 * @param attributeName The name to use for the attribute. It must not be 1643 * {@code null}. 1644 * @param attributeValue The value to use for the attribute. It must not be 1645 * {@code null}. 1646 */ 1647 public void setAttribute(final String attributeName, 1648 final String attributeValue) 1649 { 1650 ensureNotNull(attributeName, attributeValue); 1651 setAttribute(new Attribute(attributeName, schema, attributeValue)); 1652 } 1653 1654 1655 1656 /** 1657 * Adds the provided attribute to this entry, replacing any existing set of 1658 * values for the associated attribute. 1659 * 1660 * @param attributeName The name to use for the attribute. It must not be 1661 * {@code null}. 1662 * @param attributeValue The value to use for the attribute. It must not be 1663 * {@code null}. 1664 */ 1665 public void setAttribute(final String attributeName, 1666 final byte[] attributeValue) 1667 { 1668 ensureNotNull(attributeName, attributeValue); 1669 setAttribute(new Attribute(attributeName, schema, attributeValue)); 1670 } 1671 1672 1673 1674 /** 1675 * Adds the provided attribute to this entry, replacing any existing set of 1676 * values for the associated attribute. 1677 * 1678 * @param attributeName The name to use for the attribute. It must not be 1679 * {@code null}. 1680 * @param attributeValues The set of values to use for the attribute. It 1681 * must not be {@code null}. 1682 */ 1683 public void setAttribute(final String attributeName, 1684 final String... attributeValues) 1685 { 1686 ensureNotNull(attributeName, attributeValues); 1687 setAttribute(new Attribute(attributeName, schema, attributeValues)); 1688 } 1689 1690 1691 1692 /** 1693 * Adds the provided attribute to this entry, replacing any existing set of 1694 * values for the associated attribute. 1695 * 1696 * @param attributeName The name to use for the attribute. It must not be 1697 * {@code null}. 1698 * @param attributeValues The set of values to use for the attribute. It 1699 * must not be {@code null}. 1700 */ 1701 public void setAttribute(final String attributeName, 1702 final byte[]... attributeValues) 1703 { 1704 ensureNotNull(attributeName, attributeValues); 1705 setAttribute(new Attribute(attributeName, schema, attributeValues)); 1706 } 1707 1708 1709 1710 /** 1711 * Adds the provided attribute to this entry, replacing any existing set of 1712 * values for the associated attribute. 1713 * 1714 * @param attributeName The name to use for the attribute. It must not be 1715 * {@code null}. 1716 * @param attributeValues The set of values to use for the attribute. It 1717 * must not be {@code null}. 1718 */ 1719 public void setAttribute(final String attributeName, 1720 final Collection<String> attributeValues) 1721 { 1722 ensureNotNull(attributeName, attributeValues); 1723 setAttribute(new Attribute(attributeName, schema, attributeValues)); 1724 } 1725 1726 1727 1728 /** 1729 * Indicates whether this entry falls within the range of the provided search 1730 * base DN and scope. 1731 * 1732 * @param baseDN The base DN for which to make the determination. It must 1733 * not be {@code null}. 1734 * @param scope The scope for which to make the determination. It must not 1735 * be {@code null}. 1736 * 1737 * @return {@code true} if this entry is within the range of the provided 1738 * base and scope, or {@code false} if not. 1739 * 1740 * @throws LDAPException If a problem occurs while making the determination. 1741 */ 1742 public boolean matchesBaseAndScope(final String baseDN, 1743 final SearchScope scope) 1744 throws LDAPException 1745 { 1746 return getParsedDN().matchesBaseAndScope(new DN(baseDN), scope); 1747 } 1748 1749 1750 1751 /** 1752 * Indicates whether this entry falls within the range of the provided search 1753 * base DN and scope. 1754 * 1755 * @param baseDN The base DN for which to make the determination. It must 1756 * not be {@code null}. 1757 * @param scope The scope for which to make the determination. It must not 1758 * be {@code null}. 1759 * 1760 * @return {@code true} if this entry is within the range of the provided 1761 * base and scope, or {@code false} if not. 1762 * 1763 * @throws LDAPException If a problem occurs while making the determination. 1764 */ 1765 public boolean matchesBaseAndScope(final DN baseDN, final SearchScope scope) 1766 throws LDAPException 1767 { 1768 return getParsedDN().matchesBaseAndScope(baseDN, scope); 1769 } 1770 1771 1772 1773 /** 1774 * Retrieves a set of modifications that can be applied to the source entry in 1775 * order to make it match the target entry. The diff will be generated in 1776 * reversible form (i.e., the same as calling 1777 * {@code diff(sourceEntry, targetEntry, ignoreRDN, true, attributes)}. 1778 * 1779 * @param sourceEntry The source entry for which the set of modifications 1780 * should be generated. 1781 * @param targetEntry The target entry, which is what the source entry 1782 * should look like if the returned modifications are 1783 * applied. 1784 * @param ignoreRDN Indicates whether to ignore differences in the RDNs 1785 * of the provided entries. If this is {@code false}, 1786 * then the resulting set of modifications may include 1787 * changes to the RDN attribute. If it is {@code true}, 1788 * then differences in the entry DNs will be ignored. 1789 * @param attributes The set of attributes to be compared. If this is 1790 * {@code null} or empty, then all attributes will be 1791 * compared. Note that if a list of attributes is 1792 * specified, then matching will be performed only 1793 * against the attribute base name and any differences in 1794 * attribute options will be ignored. 1795 * 1796 * @return A set of modifications that can be applied to the source entry in 1797 * order to make it match the target entry. 1798 */ 1799 public static List<Modification> diff(final Entry sourceEntry, 1800 final Entry targetEntry, 1801 final boolean ignoreRDN, 1802 final String... attributes) 1803 { 1804 return diff(sourceEntry, targetEntry, ignoreRDN, true, attributes); 1805 } 1806 1807 1808 1809 /** 1810 * Retrieves a set of modifications that can be applied to the source entry in 1811 * order to make it match the target entry. 1812 * 1813 * @param sourceEntry The source entry for which the set of modifications 1814 * should be generated. 1815 * @param targetEntry The target entry, which is what the source entry 1816 * should look like if the returned modifications are 1817 * applied. 1818 * @param ignoreRDN Indicates whether to ignore differences in the RDNs 1819 * of the provided entries. If this is {@code false}, 1820 * then the resulting set of modifications may include 1821 * changes to the RDN attribute. If it is {@code true}, 1822 * then differences in the entry DNs will be ignored. 1823 * @param reversible Indicates whether to generate the diff in reversible 1824 * form. In reversible form, only the ADD or DELETE 1825 * modification types will be used so that source entry 1826 * could be reconstructed from the target and the 1827 * resulting modifications. In non-reversible form, only 1828 * the REPLACE modification type will be used. Attempts 1829 * to apply the modifications obtained when using 1830 * reversible form are more likely to fail if the entry 1831 * has been modified since the source and target forms 1832 * were obtained. 1833 * @param attributes The set of attributes to be compared. If this is 1834 * {@code null} or empty, then all attributes will be 1835 * compared. Note that if a list of attributes is 1836 * specified, then matching will be performed only 1837 * against the attribute base name and any differences in 1838 * attribute options will be ignored. 1839 * 1840 * @return A set of modifications that can be applied to the source entry in 1841 * order to make it match the target entry. 1842 */ 1843 public static List<Modification> diff(final Entry sourceEntry, 1844 final Entry targetEntry, 1845 final boolean ignoreRDN, 1846 final boolean reversible, 1847 final String... attributes) 1848 { 1849 HashSet<String> compareAttrs = null; 1850 if ((attributes != null) && (attributes.length > 0)) 1851 { 1852 compareAttrs = new HashSet<String>(attributes.length); 1853 for (final String s : attributes) 1854 { 1855 compareAttrs.add(toLowerCase(Attribute.getBaseName(s))); 1856 } 1857 } 1858 1859 final LinkedHashMap<String,Attribute> sourceOnlyAttrs = 1860 new LinkedHashMap<String,Attribute>(); 1861 final LinkedHashMap<String,Attribute> targetOnlyAttrs = 1862 new LinkedHashMap<String,Attribute>(); 1863 final LinkedHashMap<String,Attribute> commonAttrs = 1864 new LinkedHashMap<String,Attribute>(); 1865 1866 for (final Map.Entry<String,Attribute> e : 1867 sourceEntry.attributes.entrySet()) 1868 { 1869 final String lowerName = toLowerCase(e.getKey()); 1870 if ((compareAttrs != null) && 1871 (! compareAttrs.contains(Attribute.getBaseName(lowerName)))) 1872 { 1873 continue; 1874 } 1875 1876 sourceOnlyAttrs.put(lowerName, e.getValue()); 1877 commonAttrs.put(lowerName, e.getValue()); 1878 } 1879 1880 for (final Map.Entry<String,Attribute> e : 1881 targetEntry.attributes.entrySet()) 1882 { 1883 final String lowerName = toLowerCase(e.getKey()); 1884 if ((compareAttrs != null) && 1885 (! compareAttrs.contains(Attribute.getBaseName(lowerName)))) 1886 { 1887 continue; 1888 } 1889 1890 1891 if (sourceOnlyAttrs.remove(lowerName) == null) 1892 { 1893 // It wasn't in the set of source attributes, so it must be a 1894 // target-only attribute. 1895 targetOnlyAttrs.put(lowerName,e.getValue()); 1896 } 1897 } 1898 1899 for (final String lowerName : sourceOnlyAttrs.keySet()) 1900 { 1901 commonAttrs.remove(lowerName); 1902 } 1903 1904 RDN sourceRDN = null; 1905 RDN targetRDN = null; 1906 if (ignoreRDN) 1907 { 1908 try 1909 { 1910 sourceRDN = sourceEntry.getRDN(); 1911 } 1912 catch (Exception e) 1913 { 1914 debugException(e); 1915 } 1916 1917 try 1918 { 1919 targetRDN = targetEntry.getRDN(); 1920 } 1921 catch (Exception e) 1922 { 1923 debugException(e); 1924 } 1925 } 1926 1927 final ArrayList<Modification> mods = new ArrayList<Modification>(10); 1928 1929 for (final Attribute a : sourceOnlyAttrs.values()) 1930 { 1931 if (reversible) 1932 { 1933 ASN1OctetString[] values = a.getRawValues(); 1934 if ((sourceRDN != null) && (sourceRDN.hasAttribute(a.getName()))) 1935 { 1936 final ArrayList<ASN1OctetString> newValues = 1937 new ArrayList<ASN1OctetString>(values.length); 1938 for (final ASN1OctetString value : values) 1939 { 1940 if (! sourceRDN.hasAttributeValue(a.getName(), value.getValue())) 1941 { 1942 newValues.add(value); 1943 } 1944 } 1945 1946 if (newValues.isEmpty()) 1947 { 1948 continue; 1949 } 1950 else 1951 { 1952 values = new ASN1OctetString[newValues.size()]; 1953 newValues.toArray(values); 1954 } 1955 } 1956 1957 mods.add(new Modification(ModificationType.DELETE, a.getName(), 1958 values)); 1959 } 1960 else 1961 { 1962 mods.add(new Modification(ModificationType.REPLACE, a.getName())); 1963 } 1964 } 1965 1966 for (final Attribute a : targetOnlyAttrs.values()) 1967 { 1968 ASN1OctetString[] values = a.getRawValues(); 1969 if ((targetRDN != null) && (targetRDN.hasAttribute(a.getName()))) 1970 { 1971 final ArrayList<ASN1OctetString> newValues = 1972 new ArrayList<ASN1OctetString>(values.length); 1973 for (final ASN1OctetString value : values) 1974 { 1975 if (! targetRDN.hasAttributeValue(a.getName(), value.getValue())) 1976 { 1977 newValues.add(value); 1978 } 1979 } 1980 1981 if (newValues.isEmpty()) 1982 { 1983 continue; 1984 } 1985 else 1986 { 1987 values = new ASN1OctetString[newValues.size()]; 1988 newValues.toArray(values); 1989 } 1990 } 1991 1992 if (reversible) 1993 { 1994 mods.add(new Modification(ModificationType.ADD, a.getName(), values)); 1995 } 1996 else 1997 { 1998 mods.add(new Modification(ModificationType.REPLACE, a.getName(), 1999 values)); 2000 } 2001 } 2002 2003 for (final Attribute sourceAttr : commonAttrs.values()) 2004 { 2005 final Attribute targetAttr = 2006 targetEntry.getAttribute(sourceAttr.getName()); 2007 if (sourceAttr.equals(targetAttr)) 2008 { 2009 continue; 2010 } 2011 2012 if (reversible || 2013 ((targetRDN != null) && targetRDN.hasAttribute(targetAttr.getName()))) 2014 { 2015 final ASN1OctetString[] sourceValueArray = sourceAttr.getRawValues(); 2016 final LinkedHashMap<ASN1OctetString,ASN1OctetString> sourceValues = 2017 new LinkedHashMap<ASN1OctetString,ASN1OctetString>( 2018 sourceValueArray.length); 2019 for (final ASN1OctetString s : sourceValueArray) 2020 { 2021 try 2022 { 2023 sourceValues.put(sourceAttr.getMatchingRule().normalize(s), s); 2024 } 2025 catch (final Exception e) 2026 { 2027 debugException(e); 2028 sourceValues.put(s, s); 2029 } 2030 } 2031 2032 final ASN1OctetString[] targetValueArray = targetAttr.getRawValues(); 2033 final LinkedHashMap<ASN1OctetString,ASN1OctetString> targetValues = 2034 new LinkedHashMap<ASN1OctetString,ASN1OctetString>( 2035 targetValueArray.length); 2036 for (final ASN1OctetString s : targetValueArray) 2037 { 2038 try 2039 { 2040 targetValues.put(sourceAttr.getMatchingRule().normalize(s), s); 2041 } 2042 catch (final Exception e) 2043 { 2044 debugException(e); 2045 targetValues.put(s, s); 2046 } 2047 } 2048 2049 final Iterator<Map.Entry<ASN1OctetString,ASN1OctetString>> 2050 sourceIterator = sourceValues.entrySet().iterator(); 2051 while (sourceIterator.hasNext()) 2052 { 2053 final Map.Entry<ASN1OctetString,ASN1OctetString> e = 2054 sourceIterator.next(); 2055 if (targetValues.remove(e.getKey()) != null) 2056 { 2057 sourceIterator.remove(); 2058 } 2059 else if ((sourceRDN != null) && 2060 sourceRDN.hasAttributeValue(sourceAttr.getName(), 2061 e.getValue().getValue())) 2062 { 2063 sourceIterator.remove(); 2064 } 2065 } 2066 2067 final Iterator<Map.Entry<ASN1OctetString,ASN1OctetString>> 2068 targetIterator = targetValues.entrySet().iterator(); 2069 while (targetIterator.hasNext()) 2070 { 2071 final Map.Entry<ASN1OctetString,ASN1OctetString> e = 2072 targetIterator.next(); 2073 if ((targetRDN != null) && 2074 targetRDN.hasAttributeValue(targetAttr.getName(), 2075 e.getValue().getValue())) 2076 { 2077 targetIterator.remove(); 2078 } 2079 } 2080 2081 final ArrayList<ASN1OctetString> addValues = 2082 new ArrayList<ASN1OctetString>(targetValues.values()); 2083 final ArrayList<ASN1OctetString> delValues = 2084 new ArrayList<ASN1OctetString>(sourceValues.values()); 2085 2086 if (! addValues.isEmpty()) 2087 { 2088 final ASN1OctetString[] addArray = 2089 new ASN1OctetString[addValues.size()]; 2090 mods.add(new Modification(ModificationType.ADD, targetAttr.getName(), 2091 addValues.toArray(addArray))); 2092 } 2093 2094 if (! delValues.isEmpty()) 2095 { 2096 final ASN1OctetString[] delArray = 2097 new ASN1OctetString[delValues.size()]; 2098 mods.add(new Modification(ModificationType.DELETE, 2099 sourceAttr.getName(), delValues.toArray(delArray))); 2100 } 2101 } 2102 else 2103 { 2104 mods.add(new Modification(ModificationType.REPLACE, 2105 targetAttr.getName(), targetAttr.getRawValues())); 2106 } 2107 } 2108 2109 return mods; 2110 } 2111 2112 2113 2114 /** 2115 * Merges the contents of all provided entries so that the resulting entry 2116 * will contain all attribute values present in at least one of the entries. 2117 * 2118 * @param entries The set of entries to be merged. At least one entry must 2119 * be provided. 2120 * 2121 * @return An entry containing all attribute values present in at least one 2122 * of the entries. 2123 */ 2124 public static Entry mergeEntries(final Entry... entries) 2125 { 2126 ensureNotNull(entries); 2127 ensureTrue(entries.length > 0); 2128 2129 final Entry newEntry = entries[0].duplicate(); 2130 2131 for (int i=1; i < entries.length; i++) 2132 { 2133 for (final Attribute a : entries[i].attributes.values()) 2134 { 2135 newEntry.addAttribute(a); 2136 } 2137 } 2138 2139 return newEntry; 2140 } 2141 2142 2143 2144 /** 2145 * Intersects the contents of all provided entries so that the resulting 2146 * entry will contain only attribute values present in all of the provided 2147 * entries. 2148 * 2149 * @param entries The set of entries to be intersected. At least one entry 2150 * must be provided. 2151 * 2152 * @return An entry containing only attribute values contained in all of the 2153 * provided entries. 2154 */ 2155 public static Entry intersectEntries(final Entry... entries) 2156 { 2157 ensureNotNull(entries); 2158 ensureTrue(entries.length > 0); 2159 2160 final Entry newEntry = entries[0].duplicate(); 2161 2162 for (final Attribute a : entries[0].attributes.values()) 2163 { 2164 final String name = a.getName(); 2165 for (final byte[] v : a.getValueByteArrays()) 2166 { 2167 for (int i=1; i < entries.length; i++) 2168 { 2169 if (! entries[i].hasAttributeValue(name, v)) 2170 { 2171 newEntry.removeAttributeValue(name, v); 2172 break; 2173 } 2174 } 2175 } 2176 } 2177 2178 return newEntry; 2179 } 2180 2181 2182 2183 /** 2184 * Creates a duplicate of the provided entry with the given set of 2185 * modifications applied to it. 2186 * 2187 * @param entry The entry to be modified. It must not be 2188 * {@code null}. 2189 * @param lenient Indicates whether to exhibit a lenient behavior for 2190 * the modifications, which will cause it to ignore 2191 * problems like trying to add values that already 2192 * exist or to remove nonexistent attributes or values. 2193 * @param modifications The set of modifications to apply to the entry. It 2194 * must not be {@code null} or empty. 2195 * 2196 * @return An updated version of the entry with the requested modifications 2197 * applied. 2198 * 2199 * @throws LDAPException If a problem occurs while attempting to apply the 2200 * modifications. 2201 */ 2202 public static Entry applyModifications(final Entry entry, 2203 final boolean lenient, 2204 final Modification... modifications) 2205 throws LDAPException 2206 { 2207 ensureNotNull(entry, modifications); 2208 ensureFalse(modifications.length == 0); 2209 2210 return applyModifications(entry, lenient, Arrays.asList(modifications)); 2211 } 2212 2213 2214 2215 /** 2216 * Creates a duplicate of the provided entry with the given set of 2217 * modifications applied to it. 2218 * 2219 * @param entry The entry to be modified. It must not be 2220 * {@code null}. 2221 * @param lenient Indicates whether to exhibit a lenient behavior for 2222 * the modifications, which will cause it to ignore 2223 * problems like trying to add values that already 2224 * exist or to remove nonexistent attributes or values. 2225 * @param modifications The set of modifications to apply to the entry. It 2226 * must not be {@code null} or empty. 2227 * 2228 * @return An updated version of the entry with the requested modifications 2229 * applied. 2230 * 2231 * @throws LDAPException If a problem occurs while attempting to apply the 2232 * modifications. 2233 */ 2234 public static Entry applyModifications(final Entry entry, 2235 final boolean lenient, 2236 final List<Modification> modifications) 2237 throws LDAPException 2238 { 2239 ensureNotNull(entry, modifications); 2240 ensureFalse(modifications.isEmpty()); 2241 2242 final Entry e = entry.duplicate(); 2243 final ArrayList<String> errors = 2244 new ArrayList<String>(modifications.size()); 2245 ResultCode resultCode = null; 2246 2247 // Get the RDN for the entry to ensure that RDN modifications are not 2248 // allowed. 2249 RDN rdn = null; 2250 try 2251 { 2252 rdn = entry.getRDN(); 2253 } 2254 catch (final LDAPException le) 2255 { 2256 debugException(le); 2257 } 2258 2259 for (final Modification m : modifications) 2260 { 2261 final String name = m.getAttributeName(); 2262 final byte[][] values = m.getValueByteArrays(); 2263 switch (m.getModificationType().intValue()) 2264 { 2265 case ModificationType.ADD_INT_VALUE: 2266 if (lenient) 2267 { 2268 e.addAttribute(m.getAttribute()); 2269 } 2270 else 2271 { 2272 if (values.length == 0) 2273 { 2274 errors.add(ERR_ENTRY_APPLY_MODS_ADD_NO_VALUES.get(name)); 2275 } 2276 2277 for (int i=0; i < values.length; i++) 2278 { 2279 if (! e.addAttribute(name, values[i])) 2280 { 2281 if (resultCode == null) 2282 { 2283 resultCode = ResultCode.ATTRIBUTE_OR_VALUE_EXISTS; 2284 } 2285 errors.add(ERR_ENTRY_APPLY_MODS_ADD_EXISTING.get( 2286 m.getValues()[i], name)); 2287 } 2288 } 2289 } 2290 break; 2291 2292 case ModificationType.DELETE_INT_VALUE: 2293 if (values.length == 0) 2294 { 2295 final boolean removed = e.removeAttribute(name); 2296 if (! (lenient || removed)) 2297 { 2298 if (resultCode == null) 2299 { 2300 resultCode = ResultCode.NO_SUCH_ATTRIBUTE; 2301 } 2302 errors.add(ERR_ENTRY_APPLY_MODS_DELETE_NONEXISTENT_ATTR.get( 2303 name)); 2304 } 2305 } 2306 else 2307 { 2308 for (int i=0; i < values.length; i++) 2309 { 2310 final boolean removed = e.removeAttributeValue(name, values[i]); 2311 if (! (lenient || removed)) 2312 { 2313 if (resultCode == null) 2314 { 2315 resultCode = ResultCode.NO_SUCH_ATTRIBUTE; 2316 } 2317 errors.add(ERR_ENTRY_APPLY_MODS_DELETE_NONEXISTENT_VALUE.get( 2318 m.getValues()[i], name)); 2319 } 2320 } 2321 } 2322 break; 2323 2324 case ModificationType.REPLACE_INT_VALUE: 2325 if (values.length == 0) 2326 { 2327 e.removeAttribute(name); 2328 } 2329 else 2330 { 2331 e.setAttribute(m.getAttribute()); 2332 } 2333 break; 2334 2335 case ModificationType.INCREMENT_INT_VALUE: 2336 final Attribute a = e.getAttribute(name); 2337 if ((a == null) || (! a.hasValue())) 2338 { 2339 errors.add(ERR_ENTRY_APPLY_MODS_INCREMENT_NO_SUCH_ATTR.get(name)); 2340 continue; 2341 } 2342 2343 if (a.size() > 1) 2344 { 2345 errors.add(ERR_ENTRY_APPLY_MODS_INCREMENT_NOT_SINGLE_VALUED.get( 2346 name)); 2347 continue; 2348 } 2349 2350 if ((rdn != null) && rdn.hasAttribute(name)) 2351 { 2352 final String msg = 2353 ERR_ENTRY_APPLY_MODS_TARGETS_RDN.get(entry.getDN()); 2354 if (! errors.contains(msg)) 2355 { 2356 errors.add(msg); 2357 } 2358 2359 if (resultCode == null) 2360 { 2361 resultCode = ResultCode.NOT_ALLOWED_ON_RDN; 2362 } 2363 continue; 2364 } 2365 2366 final BigInteger currentValue; 2367 try 2368 { 2369 currentValue = new BigInteger(a.getValue()); 2370 } 2371 catch (NumberFormatException nfe) 2372 { 2373 debugException(nfe); 2374 errors.add( 2375 ERR_ENTRY_APPLY_MODS_INCREMENT_ENTRY_VALUE_NOT_INTEGER.get( 2376 name, a.getValue())); 2377 continue; 2378 } 2379 2380 if (values.length == 0) 2381 { 2382 errors.add(ERR_ENTRY_APPLY_MODS_INCREMENT_NO_MOD_VALUES.get(name)); 2383 continue; 2384 } 2385 else if (values.length > 1) 2386 { 2387 errors.add(ERR_ENTRY_APPLY_MODS_INCREMENT_MULTIPLE_MOD_VALUES.get( 2388 name)); 2389 continue; 2390 } 2391 2392 final BigInteger incrementValue; 2393 final String incrementValueStr = m.getValues()[0]; 2394 try 2395 { 2396 incrementValue = new BigInteger(incrementValueStr); 2397 } 2398 catch (NumberFormatException nfe) 2399 { 2400 debugException(nfe); 2401 errors.add(ERR_ENTRY_APPLY_MODS_INCREMENT_MOD_VALUE_NOT_INTEGER.get( 2402 name, incrementValueStr)); 2403 continue; 2404 } 2405 2406 final BigInteger newValue = currentValue.add(incrementValue); 2407 e.setAttribute(name, newValue.toString()); 2408 break; 2409 2410 default: 2411 errors.add(ERR_ENTRY_APPLY_MODS_UNKNOWN_TYPE.get( 2412 String.valueOf(m.getModificationType()))); 2413 break; 2414 } 2415 } 2416 2417 2418 // Make sure that the entry still has all of the RDN attribute values. 2419 if (rdn != null) 2420 { 2421 final String[] rdnAttrs = rdn.getAttributeNames(); 2422 final byte[][] rdnValues = rdn.getByteArrayAttributeValues(); 2423 for (int i=0; i < rdnAttrs.length; i++) 2424 { 2425 if (! e.hasAttributeValue(rdnAttrs[i], rdnValues[i])) 2426 { 2427 errors.add(ERR_ENTRY_APPLY_MODS_TARGETS_RDN.get(entry.getDN())); 2428 if (resultCode == null) 2429 { 2430 resultCode = ResultCode.NOT_ALLOWED_ON_RDN; 2431 } 2432 break; 2433 } 2434 } 2435 } 2436 2437 2438 if (errors.isEmpty()) 2439 { 2440 return e; 2441 } 2442 2443 if (resultCode == null) 2444 { 2445 resultCode = ResultCode.CONSTRAINT_VIOLATION; 2446 } 2447 2448 throw new LDAPException(resultCode, 2449 ERR_ENTRY_APPLY_MODS_FAILURE.get(e.getDN(), 2450 concatenateStrings(errors))); 2451 } 2452 2453 2454 2455 /** 2456 * Creates a duplicate of the provided entry with the appropriate changes for 2457 * a modify DN operation. Any corresponding changes to the set of attribute 2458 * values (to ensure that the new RDN values are present in the entry, and 2459 * optionally to remove the old RDN values from the entry) will also be 2460 * applied. 2461 * 2462 * @param entry The entry to be renamed. It must not be 2463 * {@code null}. 2464 * @param newRDN The new RDN to use for the entry. It must not be 2465 * {@code null}. 2466 * @param deleteOldRDN Indicates whether attribute values that were present 2467 * in the old RDN but are no longer present in the new 2468 * DN should be removed from the entry. 2469 * 2470 * @return A new entry that is a duplicate of the provided entry, except with 2471 * any necessary changes for the modify DN. 2472 * 2473 * @throws LDAPException If a problem is encountered during modify DN 2474 * processing. 2475 */ 2476 public static Entry applyModifyDN(final Entry entry, final String newRDN, 2477 final boolean deleteOldRDN) 2478 throws LDAPException 2479 { 2480 return applyModifyDN(entry, newRDN, deleteOldRDN, null); 2481 } 2482 2483 2484 2485 /** 2486 * Creates a duplicate of the provided entry with the appropriate changes for 2487 * a modify DN operation. Any corresponding changes to the set of attribute 2488 * values (to ensure that the new RDN values are present in the entry, and 2489 * optionally to remove the old RDN values from the entry) will also be 2490 * applied. 2491 * 2492 * @param entry The entry to be renamed. It must not be 2493 * {@code null}. 2494 * @param newRDN The new RDN to use for the entry. It must not be 2495 * {@code null}. 2496 * @param deleteOldRDN Indicates whether attribute values that were present 2497 * in the old RDN but are no longer present in the new 2498 * DN should be removed from the entry. 2499 * @param newSuperiorDN The new superior DN for the entry. If this is 2500 * {@code null}, then the entry will remain below its 2501 * existing parent. If it is non-{@code null}, then 2502 * the resulting DN will be a concatenation of the new 2503 * RDN and the new superior DN. 2504 * 2505 * @return A new entry that is a duplicate of the provided entry, except with 2506 * any necessary changes for the modify DN. 2507 * 2508 * @throws LDAPException If a problem is encountered during modify DN 2509 * processing. 2510 */ 2511 public static Entry applyModifyDN(final Entry entry, final String newRDN, 2512 final boolean deleteOldRDN, 2513 final String newSuperiorDN) 2514 throws LDAPException 2515 { 2516 ensureNotNull(entry); 2517 ensureNotNull(newRDN); 2518 2519 // Parse all of the necessary elements from the request. 2520 final DN parsedOldDN = entry.getParsedDN(); 2521 final RDN parsedOldRDN = parsedOldDN.getRDN(); 2522 final DN parsedOldSuperiorDN = parsedOldDN.getParent(); 2523 2524 final RDN parsedNewRDN = new RDN(newRDN); 2525 2526 final DN parsedNewSuperiorDN; 2527 if (newSuperiorDN == null) 2528 { 2529 parsedNewSuperiorDN = parsedOldSuperiorDN; 2530 } 2531 else 2532 { 2533 parsedNewSuperiorDN = new DN(newSuperiorDN); 2534 } 2535 2536 // Duplicate the provided entry and update it with the new DN. 2537 final Entry newEntry = entry.duplicate(); 2538 if (parsedNewSuperiorDN == null) 2539 { 2540 // This should only happen if the provided entry has a zero-length DN. 2541 // It's extremely unlikely that a directory server would permit this 2542 // change, but we'll go ahead and process it. 2543 newEntry.setDN(new DN(parsedNewRDN)); 2544 } 2545 else 2546 { 2547 newEntry.setDN(new DN(parsedNewRDN, parsedNewSuperiorDN)); 2548 } 2549 2550 // If deleteOldRDN is true, then remove any values present in the old RDN 2551 // that are not present in the new RDN. 2552 if (deleteOldRDN && (parsedOldRDN != null)) 2553 { 2554 final String[] oldNames = parsedOldRDN.getAttributeNames(); 2555 final byte[][] oldValues = parsedOldRDN.getByteArrayAttributeValues(); 2556 for (int i=0; i < oldNames.length; i++) 2557 { 2558 if (! parsedNewRDN.hasAttributeValue(oldNames[i], oldValues[i])) 2559 { 2560 newEntry.removeAttributeValue(oldNames[i], oldValues[i]); 2561 } 2562 } 2563 } 2564 2565 // Add any values present in the new RDN that were not present in the old 2566 // RDN. 2567 final String[] newNames = parsedNewRDN.getAttributeNames(); 2568 final byte[][] newValues = parsedNewRDN.getByteArrayAttributeValues(); 2569 for (int i=0; i < newNames.length; i++) 2570 { 2571 if ((parsedOldRDN == null) || 2572 (! parsedOldRDN.hasAttributeValue(newNames[i], newValues[i]))) 2573 { 2574 newEntry.addAttribute(newNames[i], newValues[i]); 2575 } 2576 } 2577 2578 return newEntry; 2579 } 2580 2581 2582 2583 /** 2584 * Generates a hash code for this entry. 2585 * 2586 * @return The generated hash code for this entry. 2587 */ 2588 @Override() 2589 public int hashCode() 2590 { 2591 int hashCode = 0; 2592 try 2593 { 2594 hashCode += getParsedDN().hashCode(); 2595 } 2596 catch (LDAPException le) 2597 { 2598 debugException(le); 2599 hashCode += dn.hashCode(); 2600 } 2601 2602 for (final Attribute a : attributes.values()) 2603 { 2604 hashCode += a.hashCode(); 2605 } 2606 2607 return hashCode; 2608 } 2609 2610 2611 2612 /** 2613 * Indicates whether the provided object is equal to this entry. The provided 2614 * object will only be considered equal to this entry if it is an entry with 2615 * the same DN and set of attributes. 2616 * 2617 * @param o The object for which to make the determination. 2618 * 2619 * @return {@code true} if the provided object is considered equal to this 2620 * entry, or {@code false} if not. 2621 */ 2622 @Override() 2623 public boolean equals(final Object o) 2624 { 2625 if (o == null) 2626 { 2627 return false; 2628 } 2629 2630 if (o == this) 2631 { 2632 return true; 2633 } 2634 2635 if (! (o instanceof Entry)) 2636 { 2637 return false; 2638 } 2639 2640 final Entry e = (Entry) o; 2641 2642 try 2643 { 2644 final DN thisDN = getParsedDN(); 2645 final DN thatDN = e.getParsedDN(); 2646 if (! thisDN.equals(thatDN)) 2647 { 2648 return false; 2649 } 2650 } 2651 catch (LDAPException le) 2652 { 2653 debugException(le); 2654 if (! dn.equals(e.dn)) 2655 { 2656 return false; 2657 } 2658 } 2659 2660 if (attributes.size() != e.attributes.size()) 2661 { 2662 return false; 2663 } 2664 2665 for (final Attribute a : attributes.values()) 2666 { 2667 if (! e.hasAttribute(a)) 2668 { 2669 return false; 2670 } 2671 } 2672 2673 return true; 2674 } 2675 2676 2677 2678 /** 2679 * Creates a new entry that is a duplicate of this entry. 2680 * 2681 * @return A new entry that is a duplicate of this entry. 2682 */ 2683 public Entry duplicate() 2684 { 2685 return new Entry(dn, schema, attributes.values()); 2686 } 2687 2688 2689 2690 /** 2691 * Retrieves an LDIF representation of this entry, with each attribute value 2692 * on a separate line. Long lines will not be wrapped. 2693 * 2694 * @return An LDIF representation of this entry. 2695 */ 2696 public final String[] toLDIF() 2697 { 2698 return toLDIF(0); 2699 } 2700 2701 2702 2703 /** 2704 * Retrieves an LDIF representation of this entry, with each attribute value 2705 * on a separate line. Long lines will be wrapped at the specified column. 2706 * 2707 * @param wrapColumn The column at which long lines should be wrapped. A 2708 * value less than or equal to two indicates that no 2709 * wrapping should be performed. 2710 * 2711 * @return An LDIF representation of this entry. 2712 */ 2713 public final String[] toLDIF(final int wrapColumn) 2714 { 2715 List<String> ldifLines = new ArrayList<String>(2*attributes.size()); 2716 encodeNameAndValue("dn", new ASN1OctetString(dn), ldifLines); 2717 2718 for (final Attribute a : attributes.values()) 2719 { 2720 final String name = a.getName(); 2721 for (final ASN1OctetString value : a.getRawValues()) 2722 { 2723 encodeNameAndValue(name, value, ldifLines); 2724 } 2725 } 2726 2727 if (wrapColumn > 2) 2728 { 2729 ldifLines = LDIFWriter.wrapLines(wrapColumn, ldifLines); 2730 } 2731 2732 final String[] lineArray = new String[ldifLines.size()]; 2733 ldifLines.toArray(lineArray); 2734 return lineArray; 2735 } 2736 2737 2738 2739 /** 2740 * Encodes the provided name and value and adds the result to the provided 2741 * list of lines. This will handle the case in which the encoded name and 2742 * value includes comments about the base64-decoded representation of the 2743 * provided value. 2744 * 2745 * @param name The attribute name to be encoded. 2746 * @param value The attribute value to be encoded. 2747 * @param lines The list of lines to be updated. 2748 */ 2749 private static void encodeNameAndValue(final String name, 2750 final ASN1OctetString value, 2751 final List<String> lines) 2752 { 2753 final String line = LDIFWriter.encodeNameAndValue(name, value); 2754 if (LDIFWriter.commentAboutBase64EncodedValues() && 2755 line.startsWith(name + "::")) 2756 { 2757 final StringTokenizer tokenizer = new StringTokenizer(line, "\r\n"); 2758 while (tokenizer.hasMoreTokens()) 2759 { 2760 lines.add(tokenizer.nextToken()); 2761 } 2762 } 2763 else 2764 { 2765 lines.add(line); 2766 } 2767 } 2768 2769 2770 2771 /** 2772 * Appends an LDIF representation of this entry to the provided buffer. Long 2773 * lines will not be wrapped. 2774 * 2775 * @param buffer The buffer to which the LDIF representation of this entry 2776 * should be written. 2777 */ 2778 public final void toLDIF(final ByteStringBuffer buffer) 2779 { 2780 toLDIF(buffer, 0); 2781 } 2782 2783 2784 2785 /** 2786 * Appends an LDIF representation of this entry to the provided buffer. 2787 * 2788 * @param buffer The buffer to which the LDIF representation of this 2789 * entry should be written. 2790 * @param wrapColumn The column at which long lines should be wrapped. A 2791 * value less than or equal to two indicates that no 2792 * wrapping should be performed. 2793 */ 2794 public final void toLDIF(final ByteStringBuffer buffer, final int wrapColumn) 2795 { 2796 LDIFWriter.encodeNameAndValue("dn", new ASN1OctetString(dn), buffer, 2797 wrapColumn); 2798 buffer.append(EOL_BYTES); 2799 2800 for (final Attribute a : attributes.values()) 2801 { 2802 final String name = a.getName(); 2803 for (final ASN1OctetString value : a.getRawValues()) 2804 { 2805 LDIFWriter.encodeNameAndValue(name, value, buffer, wrapColumn); 2806 buffer.append(EOL_BYTES); 2807 } 2808 } 2809 } 2810 2811 2812 2813 /** 2814 * Retrieves an LDIF-formatted string representation of this entry. No 2815 * wrapping will be performed, and no extra blank lines will be added. 2816 * 2817 * @return An LDIF-formatted string representation of this entry. 2818 */ 2819 public final String toLDIFString() 2820 { 2821 final StringBuilder buffer = new StringBuilder(); 2822 toLDIFString(buffer, 0); 2823 return buffer.toString(); 2824 } 2825 2826 2827 2828 /** 2829 * Retrieves an LDIF-formatted string representation of this entry. No 2830 * extra blank lines will be added. 2831 * 2832 * @param wrapColumn The column at which long lines should be wrapped. A 2833 * value less than or equal to two indicates that no 2834 * wrapping should be performed. 2835 * 2836 * @return An LDIF-formatted string representation of this entry. 2837 */ 2838 public final String toLDIFString(final int wrapColumn) 2839 { 2840 final StringBuilder buffer = new StringBuilder(); 2841 toLDIFString(buffer, wrapColumn); 2842 return buffer.toString(); 2843 } 2844 2845 2846 2847 /** 2848 * Appends an LDIF-formatted string representation of this entry to the 2849 * provided buffer. No wrapping will be performed, and no extra blank lines 2850 * will be added. 2851 * 2852 * @param buffer The buffer to which to append the LDIF representation of 2853 * this entry. 2854 */ 2855 public final void toLDIFString(final StringBuilder buffer) 2856 { 2857 toLDIFString(buffer, 0); 2858 } 2859 2860 2861 2862 /** 2863 * Appends an LDIF-formatted string representation of this entry to the 2864 * provided buffer. No extra blank lines will be added. 2865 * 2866 * @param buffer The buffer to which to append the LDIF representation 2867 * of this entry. 2868 * @param wrapColumn The column at which long lines should be wrapped. A 2869 * value less than or equal to two indicates that no 2870 * wrapping should be performed. 2871 */ 2872 public final void toLDIFString(final StringBuilder buffer, 2873 final int wrapColumn) 2874 { 2875 LDIFWriter.encodeNameAndValue("dn", new ASN1OctetString(dn), buffer, 2876 wrapColumn); 2877 buffer.append(EOL); 2878 2879 for (final Attribute a : attributes.values()) 2880 { 2881 final String name = a.getName(); 2882 for (final ASN1OctetString value : a.getRawValues()) 2883 { 2884 LDIFWriter.encodeNameAndValue(name, value, buffer, wrapColumn); 2885 buffer.append(EOL); 2886 } 2887 } 2888 } 2889 2890 2891 2892 /** 2893 * Retrieves a string representation of this entry. 2894 * 2895 * @return A string representation of this entry. 2896 */ 2897 @Override() 2898 public final String toString() 2899 { 2900 final StringBuilder buffer = new StringBuilder(); 2901 toString(buffer); 2902 return buffer.toString(); 2903 } 2904 2905 2906 2907 /** 2908 * Appends a string representation of this entry to the provided buffer. 2909 * 2910 * @param buffer The buffer to which to append the string representation of 2911 * this entry. 2912 */ 2913 public void toString(final StringBuilder buffer) 2914 { 2915 buffer.append("Entry(dn='"); 2916 buffer.append(dn); 2917 buffer.append("', attributes={"); 2918 2919 final Iterator<Attribute> iterator = attributes.values().iterator(); 2920 2921 while (iterator.hasNext()) 2922 { 2923 iterator.next().toString(buffer); 2924 if (iterator.hasNext()) 2925 { 2926 buffer.append(", "); 2927 } 2928 } 2929 2930 buffer.append("})"); 2931 } 2932}