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