001/* 002 * Copyright 2007-2014 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2014 UnboundID Corp. 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU General Public License (GPLv2 only) 010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 011 * as published by the Free Software Foundation. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Public License for more details. 017 * 018 * You should have received a copy of the GNU General Public License 019 * along with this program; if not, see <http://www.gnu.org/licenses>. 020 */ 021package com.unboundid.ldap.sdk; 022 023 024 025import java.io.Serializable; 026import java.util.ArrayList; 027 028import com.unboundid.asn1.ASN1Buffer; 029import com.unboundid.asn1.ASN1BufferSequence; 030import com.unboundid.asn1.ASN1BufferSet; 031import com.unboundid.asn1.ASN1Element; 032import com.unboundid.asn1.ASN1Enumerated; 033import com.unboundid.asn1.ASN1Exception; 034import com.unboundid.asn1.ASN1OctetString; 035import com.unboundid.asn1.ASN1Sequence; 036import com.unboundid.asn1.ASN1Set; 037import com.unboundid.asn1.ASN1StreamReader; 038import com.unboundid.asn1.ASN1StreamReaderSet; 039import com.unboundid.ldap.matchingrules.CaseIgnoreStringMatchingRule; 040import com.unboundid.util.Base64; 041import com.unboundid.util.NotMutable; 042import com.unboundid.util.ThreadSafety; 043import com.unboundid.util.ThreadSafetyLevel; 044 045import static com.unboundid.ldap.sdk.LDAPMessages.*; 046import static com.unboundid.util.Debug.*; 047import static com.unboundid.util.StaticUtils.*; 048import static com.unboundid.util.Validator.*; 049 050 051 052/** 053 * This class provides a data structure for holding information about an LDAP 054 * modification, which describes a change to apply to an attribute. A 055 * modification includes the following elements: 056 * <UL> 057 * <LI>A modification type, which describes the type of change to apply.</LI> 058 * <LI>An attribute name, which specifies which attribute should be 059 * updated.</LI> 060 * <LI>An optional set of values to use for the modification.</LI> 061 * </UL> 062 */ 063@NotMutable() 064@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 065public final class Modification 066 implements Serializable 067{ 068 /** 069 * The value array that will be used when the modification should not have any 070 * values. 071 */ 072 private static final ASN1OctetString[] NO_VALUES = new ASN1OctetString[0]; 073 074 075 076 /** 077 * The byte array value array that will be used when the modification does not 078 * have any values. 079 */ 080 private static final byte[][] NO_BYTE_VALUES = new byte[0][]; 081 082 083 084 /** 085 * The serial version UID for this serializable class. 086 */ 087 private static final long serialVersionUID = 5170107037390858876L; 088 089 090 091 // The set of values for this modification. 092 private final ASN1OctetString[] values; 093 094 // The modification type for this modification. 095 private final ModificationType modificationType; 096 097 // The name of the attribute to target with this modification. 098 private final String attributeName; 099 100 101 102 /** 103 * Creates a new LDAP modification with the provided modification type and 104 * attribute name. It will not have any values. 105 * 106 * @param modificationType The modification type for this modification. 107 * @param attributeName The name of the attribute to target with this 108 * modification. It must not be {@code null}. 109 */ 110 public Modification(final ModificationType modificationType, 111 final String attributeName) 112 { 113 ensureNotNull(attributeName); 114 115 this.modificationType = modificationType; 116 this.attributeName = attributeName; 117 118 values = NO_VALUES; 119 } 120 121 122 123 /** 124 * Creates a new LDAP modification with the provided information. 125 * 126 * @param modificationType The modification type for this modification. 127 * @param attributeName The name of the attribute to target with this 128 * modification. It must not be {@code null}. 129 * @param attributeValue The attribute value for this modification. It 130 * must not be {@code null}. 131 */ 132 public Modification(final ModificationType modificationType, 133 final String attributeName, final String attributeValue) 134 { 135 ensureNotNull(attributeName, attributeValue); 136 137 this.modificationType = modificationType; 138 this.attributeName = attributeName; 139 140 values = new ASN1OctetString[] { new ASN1OctetString(attributeValue) }; 141 } 142 143 144 145 /** 146 * Creates a new LDAP modification with the provided information. 147 * 148 * @param modificationType The modification type for this modification. 149 * @param attributeName The name of the attribute to target with this 150 * modification. It must not be {@code null}. 151 * @param attributeValue The attribute value for this modification. It 152 * must not be {@code null}. 153 */ 154 public Modification(final ModificationType modificationType, 155 final String attributeName, final byte[] attributeValue) 156 { 157 ensureNotNull(attributeName, attributeValue); 158 159 this.modificationType = modificationType; 160 this.attributeName = attributeName; 161 162 values = new ASN1OctetString[] { new ASN1OctetString(attributeValue) }; 163 } 164 165 166 167 /** 168 * Creates a new LDAP modification with the provided information. 169 * 170 * @param modificationType The modification type for this modification. 171 * @param attributeName The name of the attribute to target with this 172 * modification. It must not be {@code null}. 173 * @param attributeValues The set of attribute value for this modification. 174 * It must not be {@code null}. 175 */ 176 public Modification(final ModificationType modificationType, 177 final String attributeName, 178 final String... attributeValues) 179 { 180 ensureNotNull(attributeName, attributeValues); 181 182 this.modificationType = modificationType; 183 this.attributeName = attributeName; 184 185 values = new ASN1OctetString[attributeValues.length]; 186 for (int i=0; i < values.length; i++) 187 { 188 values[i] = new ASN1OctetString(attributeValues[i]); 189 } 190 } 191 192 193 194 /** 195 * Creates a new LDAP modification with the provided information. 196 * 197 * @param modificationType The modification type for this modification. 198 * @param attributeName The name of the attribute to target with this 199 * modification. It must not be {@code null}. 200 * @param attributeValues The set of attribute value for this modification. 201 * It must not be {@code null}. 202 */ 203 public Modification(final ModificationType modificationType, 204 final String attributeName, 205 final byte[]... attributeValues) 206 { 207 ensureNotNull(attributeName, attributeValues); 208 209 this.modificationType = modificationType; 210 this.attributeName = attributeName; 211 212 values = new ASN1OctetString[attributeValues.length]; 213 for (int i=0; i < values.length; i++) 214 { 215 values[i] = new ASN1OctetString(attributeValues[i]); 216 } 217 } 218 219 220 221 /** 222 * Creates a new LDAP modification with the provided information. 223 * 224 * @param modificationType The modification type for this modification. 225 * @param attributeName The name of the attribute to target with this 226 * modification. It must not be {@code null}. 227 * @param attributeValues The set of attribute value for this modification. 228 * It must not be {@code null}. 229 */ 230 public Modification(final ModificationType modificationType, 231 final String attributeName, 232 final ASN1OctetString[] attributeValues) 233 { 234 this.modificationType = modificationType; 235 this.attributeName = attributeName; 236 values = attributeValues; 237 } 238 239 240 241 /** 242 * Retrieves the modification type for this modification. 243 * 244 * @return The modification type for this modification. 245 */ 246 public ModificationType getModificationType() 247 { 248 return modificationType; 249 } 250 251 252 253 /** 254 * Retrieves the attribute for this modification. 255 * 256 * @return The attribute for this modification. 257 */ 258 public Attribute getAttribute() 259 { 260 return new Attribute(attributeName, 261 CaseIgnoreStringMatchingRule.getInstance(), values); 262 } 263 264 265 266 /** 267 * Retrieves the name of the attribute to target with this modification. 268 * 269 * @return The name of the attribute to target with this modification. 270 */ 271 public String getAttributeName() 272 { 273 return attributeName; 274 } 275 276 277 278 /** 279 * Indicates whether this modification has at least one value. 280 * 281 * @return {@code true} if this modification has one or more values, or 282 * {@code false} if not. 283 */ 284 public boolean hasValue() 285 { 286 return (values.length > 0); 287 } 288 289 290 291 /** 292 * Retrieves the set of values for this modification as an array of strings. 293 * 294 * @return The set of values for this modification as an array of strings. 295 */ 296 public String[] getValues() 297 { 298 if (values.length == 0) 299 { 300 return NO_STRINGS; 301 } 302 else 303 { 304 final String[] stringValues = new String[values.length]; 305 for (int i=0; i < values.length; i++) 306 { 307 stringValues[i] = values[i].stringValue(); 308 } 309 310 return stringValues; 311 } 312 } 313 314 315 316 /** 317 * Retrieves the set of values for this modification as an array of byte 318 * arrays. 319 * 320 * @return The set of values for this modification as an array of byte 321 * arrays. 322 */ 323 public byte[][] getValueByteArrays() 324 { 325 if (values.length == 0) 326 { 327 return NO_BYTE_VALUES; 328 } 329 else 330 { 331 final byte[][] byteValues = new byte[values.length][]; 332 for (int i=0; i < values.length; i++) 333 { 334 byteValues[i] = values[i].getValue(); 335 } 336 337 return byteValues; 338 } 339 } 340 341 342 343 /** 344 * Retrieves the set of values for this modification as an array of ASN.1 345 * octet strings. 346 * 347 * @return The set of values for this modification as an array of ASN.1 octet 348 * strings. 349 */ 350 public ASN1OctetString[] getRawValues() 351 { 352 return values; 353 } 354 355 356 357 /** 358 * Writes an ASN.1-encoded representation of this modification to the provided 359 * ASN.1 buffer. 360 * 361 * @param buffer The ASN.1 buffer to which the encoded representation should 362 * be written. 363 */ 364 public void writeTo(final ASN1Buffer buffer) 365 { 366 final ASN1BufferSequence modSequence = buffer.beginSequence(); 367 buffer.addEnumerated(modificationType.intValue()); 368 369 final ASN1BufferSequence attrSequence = buffer.beginSequence(); 370 buffer.addOctetString(attributeName); 371 372 final ASN1BufferSet valueSet = buffer.beginSet(); 373 for (final ASN1OctetString v : values) 374 { 375 buffer.addElement(v); 376 } 377 valueSet.end(); 378 attrSequence.end(); 379 modSequence.end(); 380 } 381 382 383 384 /** 385 * Encodes this modification to an ASN.1 sequence suitable for use in the LDAP 386 * protocol. 387 * 388 * @return An ASN.1 sequence containing the encoded value. 389 */ 390 public ASN1Sequence encode() 391 { 392 final ASN1Element[] attrElements = 393 { 394 new ASN1OctetString(attributeName), 395 new ASN1Set(values) 396 }; 397 398 final ASN1Element[] modificationElements = 399 { 400 new ASN1Enumerated(modificationType.intValue()), 401 new ASN1Sequence(attrElements) 402 }; 403 404 return new ASN1Sequence(modificationElements); 405 } 406 407 408 409 /** 410 * Reads and decodes an LDAP modification from the provided ASN.1 stream 411 * reader. 412 * 413 * @param reader The ASN.1 stream reader from which to read the 414 * modification. 415 * 416 * @return The decoded modification. 417 * 418 * @throws LDAPException If a problem occurs while trying to read or decode 419 * the modification. 420 */ 421 public static Modification readFrom(final ASN1StreamReader reader) 422 throws LDAPException 423 { 424 try 425 { 426 ensureNotNull(reader.beginSequence()); 427 final ModificationType modType = 428 ModificationType.valueOf(reader.readEnumerated()); 429 430 ensureNotNull(reader.beginSequence()); 431 final String attrName = reader.readString(); 432 433 final ArrayList<ASN1OctetString> valueList = 434 new ArrayList<ASN1OctetString>(5); 435 final ASN1StreamReaderSet valueSet = reader.beginSet(); 436 while (valueSet.hasMoreElements()) 437 { 438 valueList.add(new ASN1OctetString(reader.readBytes())); 439 } 440 441 final ASN1OctetString[] values = new ASN1OctetString[valueList.size()]; 442 valueList.toArray(values); 443 444 return new Modification(modType, attrName, values); 445 } 446 catch (Exception e) 447 { 448 debugException(e); 449 throw new LDAPException(ResultCode.DECODING_ERROR, 450 ERR_MOD_CANNOT_DECODE.get(getExceptionMessage(e)), e); 451 } 452 } 453 454 455 456 /** 457 * Decodes the provided ASN.1 sequence as an LDAP modification. 458 * 459 * @param modificationSequence The ASN.1 sequence to decode as an LDAP 460 * modification. It must not be {@code null}. 461 * 462 * @return The decoded LDAP modification. 463 * 464 * @throws LDAPException If a problem occurs while trying to decode the 465 * provided ASN.1 sequence as an LDAP modification. 466 */ 467 public static Modification decode(final ASN1Sequence modificationSequence) 468 throws LDAPException 469 { 470 ensureNotNull(modificationSequence); 471 472 final ASN1Element[] modificationElements = modificationSequence.elements(); 473 if (modificationElements.length != 2) 474 { 475 throw new LDAPException(ResultCode.DECODING_ERROR, 476 ERR_MOD_DECODE_INVALID_ELEMENT_COUNT.get( 477 modificationElements.length)); 478 } 479 480 final int modType; 481 try 482 { 483 final ASN1Enumerated typeEnumerated = 484 ASN1Enumerated.decodeAsEnumerated(modificationElements[0]); 485 modType = typeEnumerated.intValue(); 486 } 487 catch (final ASN1Exception ae) 488 { 489 debugException(ae); 490 throw new LDAPException(ResultCode.DECODING_ERROR, 491 ERR_MOD_DECODE_CANNOT_PARSE_MOD_TYPE.get(getExceptionMessage(ae)), 492 ae); 493 } 494 495 final ASN1Sequence attrSequence; 496 try 497 { 498 attrSequence = ASN1Sequence.decodeAsSequence(modificationElements[1]); 499 } 500 catch (final ASN1Exception ae) 501 { 502 debugException(ae); 503 throw new LDAPException(ResultCode.DECODING_ERROR, 504 ERR_MOD_DECODE_CANNOT_PARSE_ATTR.get(getExceptionMessage(ae)), ae); 505 } 506 507 final ASN1Element[] attrElements = attrSequence.elements(); 508 if (attrElements.length != 2) 509 { 510 throw new LDAPException(ResultCode.DECODING_ERROR, 511 ERR_MOD_DECODE_INVALID_ATTR_ELEMENT_COUNT.get( 512 attrElements.length)); 513 } 514 515 final String attrName = 516 ASN1OctetString.decodeAsOctetString(attrElements[0]).stringValue(); 517 518 final ASN1Set valueSet; 519 try 520 { 521 valueSet = ASN1Set.decodeAsSet(attrElements[1]); 522 } 523 catch (final ASN1Exception ae) 524 { 525 debugException(ae); 526 throw new LDAPException(ResultCode.DECODING_ERROR, 527 ERR_MOD_DECODE_CANNOT_PARSE_ATTR_VALUE_SET.get( 528 getExceptionMessage(ae)), ae); 529 } 530 531 final ASN1Element[] valueElements = valueSet.elements(); 532 final ASN1OctetString[] values = new ASN1OctetString[valueElements.length]; 533 for (int i=0; i < values.length; i++) 534 { 535 values[i] = ASN1OctetString.decodeAsOctetString(valueElements[i]); 536 } 537 538 return new Modification(ModificationType.valueOf(modType), attrName, 539 values); 540 } 541 542 543 544 /** 545 * Calculates a hash code for this LDAP modification. 546 * 547 * @return The generated hash code for this LDAP modification. 548 */ 549 @Override() 550 public int hashCode() 551 { 552 int hashCode = modificationType.intValue() + 553 toLowerCase(attributeName).hashCode(); 554 555 for (final ASN1OctetString value : values) 556 { 557 hashCode += value.hashCode(); 558 } 559 560 return hashCode; 561 } 562 563 564 565 /** 566 * Indicates whether the provided object is equal to this LDAP modification. 567 * The provided object will only be considered equal if it is an LDAP 568 * modification with the same modification type, attribute name, and set of 569 * values as this LDAP modification. 570 * 571 * @param o The object for which to make the determination. 572 * 573 * @return {@code true} if the provided object is equal to this modification, 574 * or {@code false} if not. 575 */ 576 @Override() 577 public boolean equals(final Object o) 578 { 579 if (o == null) 580 { 581 return false; 582 } 583 584 if (o == this) 585 { 586 return true; 587 } 588 589 if (! (o instanceof Modification)) 590 { 591 return false; 592 } 593 594 final Modification mod = (Modification) o; 595 if (modificationType != mod.modificationType) 596 { 597 return false; 598 } 599 600 if (! attributeName.equalsIgnoreCase(mod.attributeName)) 601 { 602 return false; 603 } 604 605 if (values.length != mod.values.length) 606 { 607 return false; 608 } 609 610 // Look at the values using a byte-for-byte matching. 611 for (final ASN1OctetString value : values) 612 { 613 boolean found = false; 614 for (int j = 0; j < mod.values.length; j++) 615 { 616 if (value.equalsIgnoreType(mod.values[j])) 617 { 618 found = true; 619 break; 620 } 621 } 622 623 if (!found) 624 { 625 return false; 626 } 627 } 628 629 // If we've gotten here, then we can consider the object equal to this LDAP 630 // modification. 631 return true; 632 } 633 634 635 636 /** 637 * Retrieves a string representation of this LDAP modification. 638 * 639 * @return A string representation of this LDAP modification. 640 */ 641 @Override() 642 public String toString() 643 { 644 final StringBuilder buffer = new StringBuilder(); 645 toString(buffer); 646 return buffer.toString(); 647 } 648 649 650 651 /** 652 * Appends a string representation of this LDAP modification to the provided 653 * buffer. 654 * 655 * @param buffer The buffer to which to append the string representation of 656 * this LDAP modification. 657 */ 658 public void toString(final StringBuilder buffer) 659 { 660 buffer.append("LDAPModification(type="); 661 662 switch (modificationType.intValue()) 663 { 664 case 0: 665 buffer.append("add"); 666 break; 667 case 1: 668 buffer.append("delete"); 669 break; 670 case 2: 671 buffer.append("replace"); 672 break; 673 case 3: 674 buffer.append("increment"); 675 break; 676 default: 677 buffer.append(modificationType); 678 break; 679 } 680 681 buffer.append(", attr="); 682 buffer.append(attributeName); 683 684 if (values.length == 0) 685 { 686 buffer.append(", values={"); 687 } 688 else if (needsBase64Encoding()) 689 { 690 buffer.append(", base64Values={'"); 691 692 for (int i=0; i < values.length; i++) 693 { 694 if (i > 0) 695 { 696 buffer.append("', '"); 697 } 698 699 buffer.append(Base64.encode(values[i].getValue())); 700 } 701 702 buffer.append('\''); 703 } 704 else 705 { 706 buffer.append(", values={'"); 707 708 for (int i=0; i < values.length; i++) 709 { 710 if (i > 0) 711 { 712 buffer.append("', '"); 713 } 714 715 buffer.append(values[i].stringValue()); 716 } 717 718 buffer.append('\''); 719 } 720 721 buffer.append("})"); 722 } 723 724 725 726 /** 727 * Indicates whether this modification needs to be base64-encoded when 728 * represented as LDIF. 729 * 730 * @return {@code true} if this modification needs to be base64-encoded when 731 * represented as LDIF, or {@code false} if not. 732 */ 733 private boolean needsBase64Encoding() 734 { 735 for (final ASN1OctetString s : values) 736 { 737 if (Attribute.needsBase64Encoding(s.getValue())) 738 { 739 return true; 740 } 741 } 742 743 return false; 744 } 745}