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