001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.directory.api.ldap.model.entry; 020 021 022import java.io.IOException; 023import java.io.ObjectInput; 024import java.io.ObjectOutput; 025import java.util.Comparator; 026 027import org.apache.directory.api.i18n.I18n; 028import org.apache.directory.api.ldap.model.exception.LdapException; 029import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException; 030import org.apache.directory.api.ldap.model.schema.AttributeType; 031import org.apache.directory.api.ldap.model.schema.MatchingRule; 032import org.apache.directory.api.ldap.model.schema.Normalizer; 033import org.apache.directory.api.util.Serialize; 034import org.apache.directory.api.util.Strings; 035import org.apache.directory.api.util.exception.NotImplementedException; 036 037 038/** 039 * A server side schema aware wrapper around a String attribute value. 040 * This value wrapper uses schema information to syntax check values, 041 * and to compare them for equality and ordering. It caches results 042 * and invalidates them when the user provided value changes. 043 * 044 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 045 */ 046public class StringValue extends AbstractValue<String> 047{ 048 /** Used for serialization */ 049 private static final long serialVersionUID = 2L; 050 051 /** The UTF-8 bytes for this value */ 052 private byte[] bytes; 053 054 055 // ----------------------------------------------------------------------- 056 // Constructors 057 // ----------------------------------------------------------------------- 058 /** 059 * Creates a StringValue without an initial user provided value. 060 * 061 * @param attributeType the schema attribute type associated with this StringValue 062 */ 063 public StringValue( AttributeType attributeType ) 064 { 065 if ( attributeType != null ) 066 { 067 // We must have a Syntax 068 if ( attributeType.getSyntax() == null ) 069 { 070 throw new IllegalArgumentException( I18n.err( I18n.ERR_04445 ) ); 071 } 072 073 if ( !attributeType.getSyntax().isHumanReadable() ) 074 { 075 LOG.warn( "Treating a value of a binary attribute {} as a String: " 076 + "\nthis could cause data corruption!", attributeType.getName() ); 077 } 078 079 this.attributeType = attributeType; 080 } 081 } 082 083 084 /** 085 * Creates a StringValue with an initial user provided String value. 086 * 087 * @param value the value to wrap which can be null 088 */ 089 public StringValue( String value ) 090 { 091 this.upValue = value; 092 this.normalizedValue = value; 093 bytes = Strings.getBytesUtf8( value ); 094 } 095 096 097 /** 098 * Creates a StringValue with an initial user provided String value and a normalized value. 099 * 100 * @param value the user provided value to wrap which can be null 101 * @param normalizedValue the normalized value to wrap which can be null 102 */ 103 public StringValue( String value, String normalizedValue ) 104 { 105 this.upValue = value; 106 this.normalizedValue = normalizedValue; 107 bytes = Strings.getBytesUtf8( normalizedValue ); 108 } 109 110 111 /** 112 * Creates a schema aware StringValue with an initial user provided String value. 113 * 114 * @param attributeType the schema type associated with this StringValue 115 * @param value the value to wrap 116 * @throws LdapInvalidAttributeValueException If the added value is invalid accordingly 117 * to the schema 118 */ 119 public StringValue( AttributeType attributeType, String value ) throws LdapInvalidAttributeValueException 120 { 121 this( value, value ); 122 apply( attributeType ); 123 } 124 125 126 /** 127 * Creates a schema aware StringValue with an initial user provided String value. 128 * 129 * @param attributeType the schema type associated with this StringValue 130 * @param value the value to wrap 131 * @param normValue The normalized form to store 132 * @throws LdapInvalidAttributeValueException If the added value is invalid accordingly 133 * to the schema 134 */ 135 public StringValue( AttributeType attributeType, String value, String normValue ) throws LdapInvalidAttributeValueException 136 { 137 this( value, normValue ); 138 apply( attributeType ); 139 } 140 141 142 // ----------------------------------------------------------------------- 143 // Value<String> Methods 144 // ----------------------------------------------------------------------- 145 /** 146 * {@inheritDoc} 147 */ 148 @Override 149 public String getValue() 150 { 151 // The String is immutable, we can safely return the internal 152 // object without copying it. 153 return upValue; 154 } 155 156 157 /** 158 * {@inheritDoc} 159 */ 160 @Override 161 public String getNormValue() 162 { 163 return normalizedValue; 164 } 165 166 167 // ----------------------------------------------------------------------- 168 // Comparable<String> Methods 169 // ----------------------------------------------------------------------- 170 /** 171 * Compare the current value with a given value 172 * @param value The Value to compare to 173 * @return -1 if the current value is below the given value, 1 if it's abobe, 0 if it's equal 174 * @throws IllegalStateException on failures to extract the comparator, or the 175 * normalizers needed to perform the required comparisons based on the schema 176 */ 177 @Override 178 public int compareTo( Value<String> value ) 179 { 180 if ( isNull() ) 181 { 182 if ( ( value == null ) || value.isNull() ) 183 { 184 return 0; 185 } 186 else 187 { 188 return -1; 189 } 190 } 191 else if ( ( value == null ) || value.isNull() ) 192 { 193 return 1; 194 } 195 196 if ( !( value instanceof StringValue ) ) 197 { 198 String message = I18n.err( I18n.ERR_04128, toString(), value.getClass() ); 199 LOG.error( message ); 200 throw new NotImplementedException( message ); 201 } 202 203 StringValue stringValue = ( StringValue ) value; 204 205 if ( attributeType != null ) 206 { 207 if ( stringValue.getAttributeType() == null ) 208 { 209 return getNormValue().compareTo( stringValue.getNormValue() ); 210 } 211 else 212 { 213 if ( !attributeType.equals( stringValue.getAttributeType() ) ) 214 { 215 String message = I18n.err( I18n.ERR_04128, toString(), value.getClass() ); 216 LOG.error( message ); 217 throw new NotImplementedException( message ); 218 } 219 } 220 } 221 else 222 { 223 return getNormValue().compareTo( stringValue.getNormValue() ); 224 } 225 226 try 227 { 228 return getLdapComparator().compare( getNormValue(), stringValue.getNormValue() ); 229 } 230 catch ( LdapException e ) 231 { 232 String msg = I18n.err( I18n.ERR_04443, this, value ); 233 LOG.error( msg, e ); 234 throw new IllegalStateException( msg, e ); 235 } 236 } 237 238 239 // ----------------------------------------------------------------------- 240 // Cloneable methods 241 // ----------------------------------------------------------------------- 242 /** 243 * {@inheritDoc} 244 */ 245 @Override 246 public StringValue clone() 247 { 248 return ( StringValue ) super.clone(); 249 } 250 251 252 // ----------------------------------------------------------------------- 253 // Object Methods 254 // ----------------------------------------------------------------------- 255 /** 256 * @see Object#hashCode() 257 * @return the instance's hashcode 258 */ 259 @Override 260 public int hashCode() 261 { 262 if ( h == 0 ) 263 { 264 // return zero if the value is null so only one null value can be 265 // stored in an attribute - the binary version does the same 266 if ( isNull() ) 267 { 268 return 0; 269 } 270 271 // If the normalized value is null, will default to user provided 272 // which cannot be null at this point. 273 // If the normalized value is null, will default to user provided 274 // which cannot be null at this point. 275 String normalized = getNormValue(); 276 277 if ( normalized != null ) 278 { 279 h = normalized.hashCode(); 280 } 281 else 282 { 283 h = 17; 284 } 285 } 286 287 return h; 288 } 289 290 291 /** 292 * Two StringValue are equals if their normalized values are equal 293 * 294 * @see Object#equals(Object) 295 */ 296 @Override 297 public boolean equals( Object obj ) 298 { 299 if ( this == obj ) 300 { 301 return true; 302 } 303 304 if ( !( obj instanceof StringValue ) ) 305 { 306 return false; 307 } 308 309 StringValue other = ( StringValue ) obj; 310 311 // First check if we have an attrbuteType. 312 if ( attributeType != null ) 313 { 314 // yes : check for the other value 315 if ( other.attributeType != null ) 316 { 317 if ( attributeType.getOid().equals( other.getAttributeType().getOid() ) ) 318 { 319 // Both AttributeType have the same OID, we can assume they are 320 // equals. We don't check any further, because the unicity of OID 321 // makes it unlikely that the two AT are different. 322 // The values may be both null 323 if ( isNull() ) 324 { 325 return other.isNull(); 326 } 327 328 // Shortcut : if we have an AT for both the values, check the 329 // already normalized values 330 if ( upValue.equals( other.upValue ) ) 331 { 332 return true; 333 } 334 335 // We have an AttributeType, we use the associated comparator 336 try 337 { 338 Comparator<String> comparator = getLdapComparator(); 339 340 // Compare normalized values 341 if ( comparator == null ) 342 { 343 return getNormReference().equals( other.getNormReference() ); 344 } 345 else 346 { 347 return comparator.compare( getNormReference(), other.getNormReference() ) == 0; 348 } 349 } 350 catch ( LdapException ne ) 351 { 352 return false; 353 } 354 } 355 else 356 { 357 // We can't compare two values when the two ATs are different 358 return false; 359 } 360 } 361 else 362 { 363 // We only have one AT : we will assume that both values are for the 364 // same AT. 365 // The values may be both null 366 if ( isNull() ) 367 { 368 return other.isNull(); 369 } 370 371 // We have an AttributeType on the base value, we need to use its comparator 372 try 373 { 374 Comparator<String> comparator = getLdapComparator(); 375 376 // Compare normalized values. We have to normalized the other value, 377 // as it has no AT 378 MatchingRule equality = getAttributeType().getEquality(); 379 380 if ( equality == null ) 381 { 382 // No matching rule : compare the raw values 383 return getNormReference().equals( other.getNormReference() ); 384 } 385 386 Normalizer normalizer = equality.getNormalizer(); 387 388 StringValue otherValue = ( StringValue ) normalizer.normalize( other ); 389 390 if ( comparator == null ) 391 { 392 return getNormReference().equals( otherValue.getNormReference() ); 393 } 394 else 395 { 396 return comparator.compare( getNormReference(), otherValue.getNormReference() ) == 0; 397 } 398 } 399 catch ( LdapException ne ) 400 { 401 return false; 402 } 403 } 404 } 405 else 406 { 407 // No : check for the other value 408 if ( other.attributeType != null ) 409 { 410 // We only have one AT : we will assume that both values are for the 411 // same AT. 412 // The values may be both null 413 if ( isNull() ) 414 { 415 return other.isNull(); 416 } 417 418 try 419 { 420 Comparator<String> comparator = other.getLdapComparator(); 421 422 // Compare normalized values. We have to normalized the other value, 423 // as it has no AT 424 MatchingRule equality = other.getAttributeType().getEquality(); 425 426 if ( equality == null ) 427 { 428 // No matching rule : compare the raw values 429 return getNormReference().equals( other.getNormReference() ); 430 } 431 432 Normalizer normalizer = equality.getNormalizer(); 433 434 StringValue thisValue = ( StringValue ) normalizer.normalize( this ); 435 436 if ( comparator == null ) 437 { 438 return thisValue.getNormReference().equals( other.getNormReference() ); 439 } 440 else 441 { 442 return comparator.compare( thisValue.getNormReference(), other.getNormReference() ) == 0; 443 } 444 } 445 catch ( LdapException ne ) 446 { 447 return false; 448 } 449 } 450 else 451 { 452 // The values may be both null 453 if ( isNull() ) 454 { 455 return other.isNull(); 456 } 457 458 // Now check the normalized values 459 return getNormReference().equals( other.getNormReference() ); 460 } 461 } 462 } 463 464 465 /** 466 * {@inheritDoc} 467 */ 468 @Override 469 public boolean isHumanReadable() 470 { 471 return true; 472 } 473 474 475 /** 476 * @return The length of the interned value 477 */ 478 @Override 479 public int length() 480 { 481 return upValue != null ? upValue.length() : 0; 482 } 483 484 485 /** 486 * Get the user provided value as a byte[]. 487 * @return the user provided value as a byte[] 488 */ 489 @Override 490 public byte[] getBytes() 491 { 492 return bytes; 493 } 494 495 496 /** 497 * Get the user provided value as a String. 498 * 499 * @return the user provided value as a String 500 */ 501 @Override 502 public String getString() 503 { 504 return upValue != null ? upValue : ""; 505 } 506 507 508 /** 509 * Deserialize a StringValue. It will return a new StringValue instance. 510 * 511 * @param in The input stream 512 * @return A new StringValue instance 513 * @throws IOException If the stream can't be read 514 * @throws ClassNotFoundException If we can't instanciate a StringValue 515 */ 516 public static StringValue deserialize( ObjectInput in ) throws IOException, ClassNotFoundException 517 { 518 StringValue value = new StringValue( ( AttributeType ) null ); 519 value.readExternal( in ); 520 521 return value; 522 } 523 524 525 /** 526 * Deserialize a schemaAware StringValue. It will return a new StringValue instance. 527 * 528 * @param attributeType The AttributeType associated with the Value. Can be null 529 * @param in The input stream 530 * @return A new StringValue instance 531 * @throws IOException If the stream can't be read 532 * @throws ClassNotFoundException If we can't instanciate a StringValue 533 */ 534 public static StringValue deserialize( AttributeType attributeType, ObjectInput in ) throws IOException, 535 ClassNotFoundException 536 { 537 StringValue value = new StringValue( attributeType ); 538 value.readExternal( in ); 539 540 return value; 541 } 542 543 544 /** 545 * {@inheritDoc} 546 */ 547 @Override 548 public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException 549 { 550 // Read the STRING flag 551 boolean isHR = in.readBoolean(); 552 553 if ( !isHR ) 554 { 555 throw new IOException( "The serialized value is not a String value" ); 556 } 557 558 // Read the user provided value, if it's not null 559 if ( in.readBoolean() ) 560 { 561 upValue = in.readUTF(); 562 bytes = Strings.getBytesUtf8( upValue ); 563 } 564 565 // Read the isNormalized flag 566 boolean normalized = in.readBoolean(); 567 568 if ( normalized ) 569 { 570 // Read the normalized value, if not null 571 if ( in.readBoolean() ) 572 { 573 normalizedValue = in.readUTF(); 574 } 575 } 576 else 577 { 578 if ( attributeType != null ) 579 { 580 try 581 { 582 MatchingRule equality = attributeType.getEquality(); 583 584 if ( equality == null ) 585 { 586 normalizedValue = upValue; 587 } 588 else 589 { 590 Normalizer normalizer = equality.getNormalizer(); 591 592 if ( normalizer != null ) 593 { 594 normalizedValue = normalizer.normalize( upValue ); 595 } 596 else 597 { 598 normalizedValue = upValue; 599 } 600 } 601 } 602 catch ( LdapException le ) 603 { 604 normalizedValue = upValue; 605 } 606 } 607 else 608 { 609 normalizedValue = upValue; 610 } 611 } 612 613 // The hashCoe 614 h = in.readInt(); 615 } 616 617 618 /** 619 * Serialize the StringValue into a buffer at the given position. 620 * 621 * @param buffer The buffer which will contain the serialized StringValue 622 * @param pos The position in the buffer for the serialized value 623 * @return The new position in the buffer 624 */ 625 public int serialize( byte[] buffer, int pos ) 626 { 627 // Compute the length 628 // The value type, the user provided value presence flag, 629 // the normalizedValue presence flag and the hash length. 630 int length = 1 + 1 + 1 + 4; 631 632 byte[] upValueBytes = null; 633 byte[] normalizedValueBytes = null; 634 635 if ( upValue != null ) 636 { 637 upValueBytes = Strings.getBytesUtf8( upValue ); 638 length += 4 + upValueBytes.length; 639 } 640 641 if ( attributeType != null ) 642 { 643 if ( normalizedValue != null ) 644 { 645 normalizedValueBytes = Strings.getBytesUtf8( normalizedValue ); 646 length += 1 + 4 + normalizedValueBytes.length; 647 } 648 else 649 { 650 length += 1; 651 } 652 } 653 654 // Check that we will be able to store the data in the buffer 655 if ( buffer.length - pos < length ) 656 { 657 throw new ArrayIndexOutOfBoundsException(); 658 } 659 660 // The STRING flag 661 buffer[pos] = Serialize.TRUE; 662 pos++; 663 664 // Write the user provided value, if it's not null 665 if ( upValue != null ) 666 { 667 buffer[pos++] = Serialize.TRUE; 668 pos = Serialize.serialize( upValueBytes, buffer, pos ); 669 } 670 else 671 { 672 buffer[pos++] = Serialize.FALSE; 673 } 674 675 // Write the isNormalized flag 676 if ( attributeType != null ) 677 { 678 // This flag is present to tell that we have a normalized value different 679 // from the upValue 680 681 buffer[pos++] = Serialize.TRUE; 682 683 // Write the normalized value, if not null 684 if ( normalizedValue != null ) 685 { 686 buffer[pos++] = Serialize.TRUE; 687 pos = Serialize.serialize( normalizedValueBytes, buffer, pos ); 688 } 689 else 690 { 691 buffer[pos++] = Serialize.FALSE; 692 } 693 } 694 else 695 { 696 // No normalized value 697 buffer[pos++] = Serialize.FALSE; 698 } 699 700 // Write the hashCode 701 pos = Serialize.serialize( h, buffer, pos ); 702 703 return pos; 704 } 705 706 707 /** 708 * Deserialize a StringValue from a byte[], starting at a given position 709 * 710 * @param buffer The buffer containing the StringValue 711 * @param pos The position in the buffer 712 * @return The new position 713 * @throws IOException If the serialized value is not a StringValue 714 */ 715 public int deserialize( byte[] buffer, int pos ) throws IOException 716 { 717 if ( ( pos < 0 ) || ( pos >= buffer.length ) ) 718 { 719 throw new ArrayIndexOutOfBoundsException(); 720 } 721 722 // Read the STRING flag 723 boolean isHR = Serialize.deserializeBoolean( buffer, pos ); 724 pos++; 725 726 if ( !isHR ) 727 { 728 throw new IOException( "The serialized value is not a String value" ); 729 } 730 731 // Read the user provided value, if it's not null 732 boolean hasWrappedValue = Serialize.deserializeBoolean( buffer, pos ); 733 pos++; 734 735 if ( hasWrappedValue ) 736 { 737 byte[] upValueBytes = Serialize.deserializeBytes( buffer, pos ); 738 pos += 4 + upValueBytes.length; 739 upValue = Strings.utf8ToString( upValueBytes ); 740 } 741 742 // Read the isNormalized flag 743 boolean hasAttributeType = Serialize.deserializeBoolean( buffer, pos ); 744 pos++; 745 746 if ( hasAttributeType ) 747 { 748 // Read the normalized value, if not null 749 boolean hasNormalizedValue = Serialize.deserializeBoolean( buffer, pos ); 750 pos++; 751 752 if ( hasNormalizedValue ) 753 { 754 byte[] normalizedValueBytes = Serialize.deserializeBytes( buffer, pos ); 755 pos += 4 + normalizedValueBytes.length; 756 normalizedValue = Strings.utf8ToString( normalizedValueBytes ); 757 } 758 } 759 else 760 { 761 if ( attributeType != null ) 762 { 763 try 764 { 765 MatchingRule equality = attributeType.getEquality(); 766 767 if ( equality == null ) 768 { 769 normalizedValue = upValue; 770 } 771 else 772 { 773 Normalizer normalizer = equality.getNormalizer(); 774 775 if ( normalizer != null ) 776 { 777 normalizedValue = normalizer.normalize( upValue ); 778 } 779 else 780 { 781 normalizedValue = upValue; 782 } 783 } 784 } 785 catch ( LdapException le ) 786 { 787 normalizedValue = upValue; 788 } 789 } 790 else 791 { 792 normalizedValue = upValue; 793 } 794 } 795 796 // The hashCode 797 h = Serialize.deserializeInt( buffer, pos ); 798 pos += 4; 799 800 return pos; 801 } 802 803 804 /** 805 * {@inheritDoc} 806 */ 807 @Override 808 public void writeExternal( ObjectOutput out ) throws IOException 809 { 810 // Write a boolean for the HR flag 811 out.writeBoolean( STRING ); 812 813 // Write the user provided value, if it's not null 814 if ( upValue != null ) 815 { 816 out.writeBoolean( true ); 817 out.writeUTF( upValue ); 818 } 819 else 820 { 821 out.writeBoolean( false ); 822 } 823 824 // Write the isNormalized flag 825 if ( attributeType != null ) 826 { 827 // This flag is present to tell that we have a normalized value different 828 // from the upValue 829 out.writeBoolean( true ); 830 831 // Write the normalized value, if not null 832 if ( normalizedValue != null ) 833 { 834 out.writeBoolean( true ); 835 out.writeUTF( normalizedValue ); 836 } 837 else 838 { 839 out.writeBoolean( false ); 840 } 841 } 842 else 843 { 844 // No normalized value 845 out.writeBoolean( false ); 846 } 847 848 // Write the hashCode 849 out.writeInt( h ); 850 851 // and flush the data 852 out.flush(); 853 } 854 855 856 /** 857 * @see Object#toString() 858 */ 859 @Override 860 public String toString() 861 { 862 return upValue == null ? "null" : upValue; 863 } 864}