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