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