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