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