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 */ 019 package org.apache.directory.shared.ldap.entry; 020 021 022 import java.io.Externalizable; 023 import java.io.IOException; 024 import java.io.ObjectInput; 025 import java.io.ObjectOutput; 026 import java.util.Arrays; 027 import java.util.Comparator; 028 029 import org.apache.directory.shared.ldap.exception.LdapException; 030 031 import org.apache.directory.shared.i18n.I18n; 032 import org.apache.directory.shared.ldap.schema.AttributeType; 033 import org.apache.directory.shared.ldap.schema.LdapComparator; 034 import org.apache.directory.shared.ldap.schema.Normalizer; 035 import org.apache.directory.shared.ldap.schema.comparators.ByteArrayComparator; 036 import org.apache.directory.shared.ldap.util.StringTools; 037 import org.slf4j.Logger; 038 import org.slf4j.LoggerFactory; 039 040 041 /** 042 * A server side schema aware wrapper around a binary attribute value. 043 * This value wrapper uses schema information to syntax check values, 044 * and to compare them for equality and ordering. It caches results 045 * and invalidates them when the wrapped value changes. 046 * 047 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 048 * @version $Rev$, $Date$ 049 */ 050 public class BinaryValue extends AbstractValue<byte[]> 051 { 052 /** Used for serialization */ 053 protected static final long serialVersionUID = 2L; 054 055 /** logger for reporting errors that might not be handled properly upstream */ 056 protected static final Logger LOG = LoggerFactory.getLogger( BinaryValue.class ); 057 058 /** 059 * Creates a BinaryValue without an initial wrapped value. 060 * 061 * @param attributeType the schema type associated with this BinaryValue 062 */ 063 public BinaryValue() 064 { 065 wrappedValue = null; 066 normalized = false; 067 valid = null; 068 normalizedValue = null; 069 } 070 071 072 /** 073 * Creates a BinaryValue without an initial wrapped value. 074 * 075 * @param attributeType the schema type associated with this BinaryValue 076 */ 077 public BinaryValue( AttributeType attributeType ) 078 { 079 if ( attributeType == null ) 080 { 081 throw new IllegalArgumentException( I18n.err( I18n.ERR_04442 ) ); 082 } 083 084 if ( attributeType.getSyntax() == null ) 085 { 086 throw new IllegalArgumentException( I18n.err( I18n.ERR_04445 ) ); 087 } 088 089 if ( attributeType.getSyntax().isHumanReadable() ) 090 { 091 LOG.warn( "Treating a value of a human readible attribute {} as binary: ", attributeType.getName() ); 092 } 093 094 this.attributeType = attributeType; 095 } 096 097 098 /** 099 * Creates a BinaryValue with an initial wrapped binary value. 100 * 101 * @param attributeType the schema type associated with this BinaryValue 102 * @param value the binary value to wrap which may be null, or a zero length byte array 103 */ 104 public BinaryValue( byte[] value ) 105 { 106 if ( value != null ) 107 { 108 this.wrappedValue = new byte[ value.length ]; 109 System.arraycopy( value, 0, this.wrappedValue, 0, value.length ); 110 } 111 else 112 { 113 this.wrappedValue = null; 114 } 115 116 normalized = false; 117 valid = null; 118 normalizedValue = null; 119 } 120 121 122 /** 123 * Creates a BinaryValue with an initial wrapped binary value. 124 * 125 * @param attributeType the schema type associated with this BinaryValue 126 * @param value the binary value to wrap which may be null, or a zero length byte array 127 */ 128 public BinaryValue( AttributeType attributeType, byte[] value ) 129 { 130 this( attributeType ); 131 this.wrappedValue = value; 132 } 133 134 135 /** 136 * Gets a direct reference to the normalized representation for the 137 * wrapped value of this ServerValue wrapper. Implementations will most 138 * likely leverage the attributeType this value is associated with to 139 * determine how to properly normalize the wrapped value. 140 * 141 * @return the normalized version of the wrapped value 142 * @throws LdapException if schema entity resolution fails or normalization fails 143 */ 144 public byte[] getNormalizedValueCopy() 145 { 146 if ( isNull() ) 147 { 148 return null; 149 } 150 151 if ( !normalized ) 152 { 153 try 154 { 155 normalize(); 156 } 157 catch ( LdapException ne ) 158 { 159 String message = "Cannot normalize the value :" + ne.getLocalizedMessage(); 160 LOG.warn( message ); 161 normalized = false; 162 } 163 } 164 165 if ( normalizedValue != null ) 166 { 167 byte[] copy = new byte[ normalizedValue.length ]; 168 System.arraycopy( normalizedValue, 0, copy, 0, normalizedValue.length ); 169 return copy; 170 } 171 else 172 { 173 return StringTools.EMPTY_BYTES; 174 } 175 } 176 177 178 /** 179 * Gets the normalized (canonical) representation for the wrapped string. 180 * If the wrapped String is null, null is returned, otherwise the normalized 181 * form is returned. If no the normalizedValue is null, then this method 182 * will attempt to generate it from the wrapped value: repeated calls to 183 * this method do not unnecessarily normalize the wrapped value. Only changes 184 * to the wrapped value result in attempts to normalize the wrapped value. 185 * 186 * @return a reference to the normalized version of the wrapped value 187 */ 188 public byte[] getNormalizedValueReference() 189 { 190 if ( isNull() ) 191 { 192 return null; 193 } 194 195 if ( !isNormalized() ) 196 { 197 try 198 { 199 normalize(); 200 } 201 catch ( LdapException ne ) 202 { 203 String message = "Cannot normalize the value :" + ne.getLocalizedMessage(); 204 LOG.warn( message ); 205 normalized = false; 206 } 207 } 208 209 if ( normalizedValue != null ) 210 { 211 return normalizedValue; 212 } 213 else 214 { 215 return wrappedValue; 216 } 217 } 218 219 220 /** 221 * Gets the normalized (canonical) representation for the wrapped byte[]. 222 * If the wrapped byte[] is null, null is returned, otherwise the normalized 223 * form is returned. If the normalizedValue is null, then this method 224 * will attempt to generate it from the wrapped value: repeated calls to 225 * this method do not unnecessarily normalize the wrapped value. Only changes 226 * to the wrapped value result in attempts to normalize the wrapped value. 227 * 228 * @return gets the normalized value 229 */ 230 public byte[] getNormalizedValue() 231 { 232 return getNormalizedValueCopy(); 233 } 234 235 236 /** 237 * Normalize the value. For a client String value, applies the given normalizer. 238 * 239 * It supposes that the client has access to the schema in order to select the 240 * appropriate normalizer. 241 * 242 * @param Normalizer The normalizer to apply to the value 243 * @exception LdapException If the value cannot be normalized 244 */ 245 public final void normalize( Normalizer normalizer ) throws LdapException 246 { 247 if ( normalizer != null ) 248 { 249 if ( wrappedValue == null ) 250 { 251 normalizedValue = wrappedValue; 252 normalized = true; 253 same = true; 254 } 255 else 256 { 257 normalizedValue = normalizer.normalize( this ).getBytes(); 258 normalized = true; 259 same = Arrays.equals( wrappedValue, normalizedValue ); 260 } 261 } 262 else 263 { 264 normalizedValue = wrappedValue; 265 normalized = false; 266 same = true; 267 } 268 } 269 270 271 /** 272 * {@inheritDoc} 273 */ 274 public void normalize() throws LdapException 275 { 276 if ( isNormalized() ) 277 { 278 // Bypass the normalization if it has already been done. 279 return; 280 } 281 282 if ( attributeType != null ) 283 { 284 Normalizer normalizer = getNormalizer(); 285 normalize( normalizer ); 286 } 287 else 288 { 289 normalizedValue = wrappedValue; 290 normalized = true; 291 same = true; 292 } 293 } 294 295 296 /** 297 * 298 * @see ServerValue#compareTo(ServerValue) 299 * @throws IllegalStateException on failures to extract the comparator, or the 300 * normalizers needed to perform the required comparisons based on the schema 301 */ 302 public int compareTo( Value<byte[]> value ) 303 { 304 if ( isNull() ) 305 { 306 if ( ( value == null ) || value.isNull() ) 307 { 308 return 0; 309 } 310 else 311 { 312 return -1; 313 } 314 } 315 else 316 { 317 if ( ( value == null ) || value.isNull() ) 318 { 319 return 1; 320 } 321 } 322 323 BinaryValue binaryValue = ( BinaryValue ) value; 324 325 if ( attributeType != null ) 326 { 327 try 328 { 329 LdapComparator<byte[]> comparator = getLdapComparator(); 330 331 if ( comparator != null ) 332 { 333 return comparator 334 .compare( getNormalizedValueReference(), binaryValue.getNormalizedValueReference() ); 335 } 336 else 337 { 338 return new ByteArrayComparator( null ).compare( getNormalizedValueReference(), binaryValue 339 .getNormalizedValueReference() ); 340 } 341 } 342 catch ( LdapException e ) 343 { 344 String msg = I18n.err( I18n.ERR_04443, Arrays.toString( getReference() ), value ); 345 LOG.error( msg, e ); 346 throw new IllegalStateException( msg, e ); 347 } 348 } 349 else 350 { 351 return new ByteArrayComparator( null ).compare( getNormalizedValue(), binaryValue.getNormalizedValue() ); 352 } 353 } 354 355 356 // ----------------------------------------------------------------------- 357 // Object Methods 358 // ----------------------------------------------------------------------- 359 360 361 /** 362 * @see Object#hashCode() 363 * @return the instance's hashcode 364 */ 365 public int hashCode() 366 { 367 // return zero if the value is null so only one null value can be 368 // stored in an attribute - the string version does the same 369 if ( isNull() ) 370 { 371 return 0; 372 } 373 374 byte[] normalizedValue = getNormalizedValueReference(); 375 int h = Arrays.hashCode( normalizedValue ); 376 377 return h; 378 } 379 380 381 /** 382 * Checks to see if this BinaryValue equals the supplied object. 383 * 384 * This equals implementation overrides the BinaryValue implementation which 385 * is not schema aware. 386 * @throws IllegalStateException on failures to extract the comparator, or the 387 * normalizers needed to perform the required comparisons based on the schema 388 */ 389 public boolean equals( Object obj ) 390 { 391 if ( this == obj ) 392 { 393 return true; 394 } 395 396 if ( ! ( obj instanceof BinaryValue ) ) 397 { 398 return false; 399 } 400 401 BinaryValue other = ( BinaryValue ) obj; 402 403 if ( isNull() ) 404 { 405 return other.isNull(); 406 } 407 408 // If we have an attributeType, it must be equal 409 // We should also use the comparator if we have an AT 410 if ( attributeType != null ) 411 { 412 if ( other.attributeType != null ) 413 { 414 if ( !attributeType.equals( other.attributeType ) ) 415 { 416 return false; 417 } 418 } 419 else 420 { 421 other.attributeType = attributeType; 422 } 423 } 424 else if ( other.attributeType != null ) 425 { 426 attributeType = other.attributeType; 427 } 428 429 // Shortcut : if the values are equals, no need to compare 430 // the normalized values 431 if ( Arrays.equals( wrappedValue, other.wrappedValue ) ) 432 { 433 return true; 434 } 435 436 if ( attributeType != null ) 437 { 438 // We have an AttributeType, we eed to use the comparator 439 try 440 { 441 Comparator<byte[]> comparator = (Comparator<byte[]>)getLdapComparator(); 442 443 // Compare normalized values 444 if ( comparator == null ) 445 { 446 return Arrays.equals( getNormalizedValueReference(), other.getNormalizedValueReference() ); 447 } 448 else 449 { 450 return comparator.compare( getNormalizedValueReference(), other.getNormalizedValueReference() ) == 0; 451 } 452 } 453 catch ( LdapException ne ) 454 { 455 return false; 456 } 457 458 } 459 else 460 { 461 // now unlike regular values we have to compare the normalized values 462 return Arrays.equals( getNormalizedValueReference(), other.getNormalizedValueReference() ); 463 } 464 } 465 466 467 // ----------------------------------------------------------------------- 468 // Private Helper Methods (might be put into abstract base class) 469 // ----------------------------------------------------------------------- 470 /** 471 * @return a copy of the current value 472 */ 473 public BinaryValue clone() 474 { 475 BinaryValue clone = (BinaryValue)super.clone(); 476 477 if ( normalizedValue != null ) 478 { 479 clone.normalizedValue = new byte[ normalizedValue.length ]; 480 System.arraycopy( normalizedValue, 0, clone.normalizedValue, 0, normalizedValue.length ); 481 } 482 483 if ( wrappedValue != null ) 484 { 485 clone.wrappedValue = new byte[ wrappedValue.length ]; 486 System.arraycopy( wrappedValue, 0, clone.wrappedValue, 0, wrappedValue.length ); 487 } 488 489 return clone; 490 } 491 492 493 /** 494 * {@inheritDoc} 495 */ 496 public byte[] get() 497 { 498 if ( wrappedValue == null ) 499 { 500 return null; 501 } 502 503 final byte[] copy = new byte[ wrappedValue.length ]; 504 System.arraycopy( wrappedValue, 0, copy, 0, wrappedValue.length ); 505 506 return copy; 507 } 508 509 510 /** 511 * Tells if the current value is Binary or String 512 * 513 * @return <code>true</code> if the value is Binary, <code>false</code> otherwise 514 */ 515 public boolean isBinary() 516 { 517 return true; 518 } 519 520 521 /** 522 * @return The length of the interned value 523 */ 524 public int length() 525 { 526 return wrappedValue != null ? wrappedValue.length : 0; 527 } 528 529 530 /** 531 * Get the wrapped value as a byte[]. This method returns a copy of 532 * the wrapped byte[]. 533 * 534 * @return the wrapped value as a byte[] 535 */ 536 public byte[] getBytes() 537 { 538 return get(); 539 } 540 541 542 /** 543 * Get the wrapped value as a String. 544 * 545 * @return the wrapped value as a String 546 */ 547 public String getString() 548 { 549 return StringTools.utf8ToString( wrappedValue ); 550 } 551 552 553 /** 554 * @see Externalizable#readExternal(ObjectInput) 555 */ 556 public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException 557 { 558 // Read the wrapped value, if it's not null 559 int wrappedLength = in.readInt(); 560 561 if ( wrappedLength >= 0 ) 562 { 563 wrappedValue = new byte[wrappedLength]; 564 565 if ( wrappedLength > 0 ) 566 { 567 in.read( wrappedValue ); 568 } 569 } 570 571 // Read the isNormalized flag 572 normalized = in.readBoolean(); 573 574 if ( normalized ) 575 { 576 int normalizedLength = in.readInt(); 577 578 if ( normalizedLength >= 0 ) 579 { 580 normalizedValue = new byte[normalizedLength]; 581 582 if ( normalizedLength > 0 ) 583 { 584 in.read( normalizedValue ); 585 } 586 } 587 } 588 } 589 590 591 /** 592 * @see Externalizable#writeExternal(ObjectOutput) 593 */ 594 public void writeExternal( ObjectOutput out ) throws IOException 595 { 596 // Write the wrapped value, if it's not null 597 if ( wrappedValue != null ) 598 { 599 out.writeInt( wrappedValue.length ); 600 601 if ( wrappedValue.length > 0 ) 602 { 603 out.write( wrappedValue, 0, wrappedValue.length ); 604 } 605 } 606 else 607 { 608 out.writeInt( -1 ); 609 } 610 611 // Write the isNormalized flag 612 if ( normalized ) 613 { 614 out.writeBoolean( true ); 615 616 // Write the normalized value, if not null 617 if ( normalizedValue != null ) 618 { 619 out.writeInt( normalizedValue.length ); 620 621 if ( normalizedValue.length > 0 ) 622 { 623 out.write( normalizedValue, 0, normalizedValue.length ); 624 } 625 } 626 else 627 { 628 out.writeInt( -1 ); 629 } 630 } 631 else 632 { 633 out.writeBoolean( false ); 634 } 635 } 636 637 638 /** 639 * We will write the value and the normalized value, only 640 * if the normalized value is different. 641 * 642 * If the value is empty, a flag is written at the beginning with 643 * the value true, otherwise, a false is written. 644 * 645 * The data will be stored following this structure : 646 * [length] the wrapped length. Can be -1, if wrapped is null 647 * [value length] 648 * [UP value] if not empty 649 * [normalized] (will be false if the value can't be normalized) 650 * [same] (a flag set to true if the normalized value equals the UP value) 651 * [Norm value] (the normalized value if different from the UP value, and not empty) 652 * 653 * @param out the buffer in which we will stored the serialized form of the value 654 * @throws IOException if we can't write into the buffer 655 */ 656 public void serialize( ObjectOutput out ) throws IOException 657 { 658 if ( wrappedValue != null ) 659 { 660 // write a the wrapped length 661 out.writeInt( wrappedValue.length ); 662 663 // Write the data if not empty 664 if ( wrappedValue.length > 0 ) 665 { 666 // The data 667 out.write( wrappedValue ); 668 669 // Normalize the data 670 try 671 { 672 normalize(); 673 674 if ( !normalized ) 675 { 676 // We may not have a normalizer. Just get out 677 // after having writen the flag 678 out.writeBoolean( false ); 679 } 680 else 681 { 682 // Write a flag indicating that the data has been normalized 683 out.writeBoolean( true ); 684 685 if ( Arrays.equals( getReference(), normalizedValue ) ) 686 { 687 // Write the 'same = true' flag 688 out.writeBoolean( true ); 689 } 690 else 691 { 692 // Write the 'same = false' flag 693 out.writeBoolean( false ); 694 695 // Write the normalized value length 696 out.writeInt( normalizedValue.length ); 697 698 if ( normalizedValue.length > 0 ) 699 { 700 // Write the normalized value if not empty 701 out.write( normalizedValue ); 702 } 703 } 704 } 705 } 706 catch ( LdapException ne ) 707 { 708 // The value can't be normalized, we don't write the 709 // normalized value. 710 normalizedValue = null; 711 out.writeBoolean( false ); 712 } 713 } 714 } 715 else 716 { 717 // Write -1 indicating that the value is null 718 out.writeInt( -1 ); 719 } 720 } 721 722 723 /** 724 * 725 * Deserialize a BinaryValue. 726 * 727 * @param in the buffer containing the bytes with the serialized value 728 * @throws IOException 729 * @throws ClassNotFoundException 730 */ 731 public void deserialize( ObjectInput in ) throws IOException, ClassNotFoundException 732 { 733 // The UP value length 734 int wrappedLength = in.readInt(); 735 736 if ( wrappedLength == -1 ) 737 { 738 // If the value is null, the length will be set to -1 739 same = true; 740 wrappedValue = null; 741 } 742 else if ( wrappedLength == 0 ) 743 { 744 wrappedValue = StringTools.EMPTY_BYTES; 745 same = true; 746 normalized = true; 747 normalizedValue = wrappedValue; 748 } 749 else 750 { 751 wrappedValue = new byte[wrappedLength]; 752 753 // Read the data 754 in.readFully( wrappedValue ); 755 756 // Check if we have a normalized value 757 normalized = in.readBoolean(); 758 759 if ( normalized ) 760 { 761 // Read the 'same' flag 762 same = in.readBoolean(); 763 764 if ( !same ) 765 { 766 // Read the normalizedvalue length 767 int normalizedLength = in.readInt(); 768 769 if ( normalizedLength > 0 ) 770 { 771 normalizedValue = new byte[normalizedLength]; 772 773 // Read the normalized value 774 in.read( normalizedValue, 0, normalizedLength ); 775 } 776 else 777 { 778 normalizedValue = StringTools.EMPTY_BYTES; 779 } 780 } 781 else 782 { 783 normalizedValue = new byte[wrappedLength]; 784 System.arraycopy( wrappedValue, 0, normalizedValue, 0, wrappedLength ); 785 } 786 } 787 } 788 } 789 790 791 /** 792 * Dumps binary in hex with label. 793 * 794 * @see Object#toString() 795 */ 796 public String toString() 797 { 798 if ( wrappedValue == null ) 799 { 800 return "null"; 801 } 802 else if ( wrappedValue.length > 16 ) 803 { 804 // Just dump the first 16 bytes... 805 byte[] copy = new byte[16]; 806 807 System.arraycopy( wrappedValue, 0, copy, 0, 16 ); 808 809 return "'" + StringTools.dumpBytes( copy ) + "...'"; 810 } 811 else 812 { 813 return "'" + StringTools.dumpBytes( wrappedValue ) + "'"; 814 } 815 } 816 }