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.Arrays; 026import java.util.Comparator; 027 028import org.apache.directory.api.i18n.I18n; 029import org.apache.directory.api.ldap.model.exception.LdapException; 030import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException; 031import org.apache.directory.api.ldap.model.schema.AttributeType; 032import org.apache.directory.api.ldap.model.schema.LdapComparator; 033import org.apache.directory.api.ldap.model.schema.MatchingRule; 034import org.apache.directory.api.ldap.model.schema.Normalizer; 035import org.apache.directory.api.ldap.model.schema.comparators.ByteArrayComparator; 036import org.apache.directory.api.util.Strings; 037 038 039/** 040 * A server side schema aware wrapper around a binary attribute value. 041 * This value wrapper uses schema information to syntax check values, 042 * and to compare them for equality and ordering. It caches results 043 * and invalidates them when the user provided value changes. 044 * 045 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 046 */ 047public class BinaryValue extends AbstractValue<byte[]> 048{ 049 /** Used for serialization */ 050 public static final long serialVersionUID = 2L; 051 052 053 /** 054 * Creates a BinaryValue without an initial user provided value. 055 * 056 * @param attributeType the schema type associated with this BinaryValue 057 */ 058 /* No protection */BinaryValue( AttributeType attributeType ) 059 { 060 if ( attributeType != null ) 061 { 062 // We must have a Syntax 063 if ( attributeType.getSyntax() == null ) 064 { 065 throw new IllegalArgumentException( I18n.err( I18n.ERR_04445 ) ); 066 } 067 068 if ( attributeType.getSyntax().isHumanReadable() ) 069 { 070 LOG.warn( "Treating a value of a human readible attribute {} as binary: ", attributeType.getName() ); 071 } 072 073 this.attributeType = attributeType; 074 } 075 } 076 077 078 /** 079 * Creates a BinaryValue with an initial user provided binary value. 080 * 081 * @param value the binary value to wrap which may be null, or a zero length byte array 082 */ 083 public BinaryValue( byte[] value ) 084 { 085 if ( value != null ) 086 { 087 this.upValue = new byte[value.length]; 088 this.normalizedValue = new byte[value.length]; 089 System.arraycopy( value, 0, this.upValue, 0, value.length ); 090 System.arraycopy( value, 0, this.normalizedValue, 0, value.length ); 091 } 092 else 093 { 094 this.upValue = null; 095 this.normalizedValue = null; 096 } 097 } 098 099 100 /** 101 * Creates a BinaryValue with an initial user provided binary value. 102 * 103 * @param attributeType the schema type associated with this BinaryValue 104 * @param value the binary value to wrap which may be null, or a zero length byte array 105 * @throws LdapInvalidAttributeValueException If the added value is invalid accordingly 106 * to the schema 107 */ 108 public BinaryValue( AttributeType attributeType, byte[] value ) throws LdapInvalidAttributeValueException 109 { 110 this( value ); 111 apply( attributeType ); 112 } 113 114 115 /** 116 * Gets a direct reference to the normalized representation for the 117 * user provided value of this ServerValue wrapper. Implementations will most 118 * likely leverage the attributeType this value is associated with to 119 * determine how to properly normalize the user provided value. 120 * 121 * @return the normalized version of the user provided value 122 */ 123 @Override 124 public byte[] getNormValue() 125 { 126 if ( isNull() ) 127 { 128 return null; 129 } 130 131 byte[] copy = new byte[normalizedValue.length]; 132 System.arraycopy( normalizedValue, 0, copy, 0, normalizedValue.length ); 133 return copy; 134 } 135 136 137 /** 138 * Compare the current value with a provided one 139 * 140 * @param value The value we want to compare to 141 * @return -1 if the current is below the provided one, 1 if it's above, 0 if they are equal 142 */ 143 @Override 144 public int compareTo( Value<byte[]> value ) 145 { 146 if ( isNull() ) 147 { 148 if ( ( value == null ) || value.isNull() ) 149 { 150 return 0; 151 } 152 else 153 { 154 return -1; 155 } 156 } 157 else 158 { 159 if ( ( value == null ) || value.isNull() ) 160 { 161 return 1; 162 } 163 } 164 165 BinaryValue binaryValue = ( BinaryValue ) value; 166 167 if ( attributeType != null ) 168 { 169 try 170 { 171 LdapComparator<byte[]> comparator = getLdapComparator(); 172 173 if ( comparator != null ) 174 { 175 return comparator 176 .compare( getNormReference(), binaryValue.getNormReference() ); 177 } 178 else 179 { 180 return new ByteArrayComparator( null ).compare( getNormReference(), binaryValue 181 .getNormReference() ); 182 } 183 } 184 catch ( LdapException e ) 185 { 186 String msg = I18n.err( I18n.ERR_04443, Arrays.toString( getReference() ), value ); 187 LOG.error( msg, e ); 188 throw new IllegalStateException( msg, e ); 189 } 190 } 191 else 192 { 193 return new ByteArrayComparator( null ).compare( getNormValue(), binaryValue.getNormValue() ); 194 } 195 } 196 197 198 // ----------------------------------------------------------------------- 199 // Object Methods 200 // ----------------------------------------------------------------------- 201 /** 202 * @see Object#hashCode() 203 * @return the instance's hashcode 204 */ 205 @Override 206 public int hashCode() 207 { 208 if ( h == 0 ) 209 { 210 // return zero if the value is null so only one null value can be 211 // stored in an attribute - the string version does the same 212 if ( isNull() ) 213 { 214 return 0; 215 } 216 217 byte[] normalizedValue = getNormReference(); 218 h = Arrays.hashCode( normalizedValue ); 219 } 220 221 return h; 222 } 223 224 225 /** 226 * Checks to see if this BinaryValue equals the supplied object. 227 * 228 * This equals implementation overrides the BinaryValue implementation which 229 * is not schema aware. 230 */ 231 @Override 232 public boolean equals( Object obj ) 233 { 234 if ( this == obj ) 235 { 236 return true; 237 } 238 239 if ( !( obj instanceof BinaryValue ) ) 240 { 241 return false; 242 } 243 244 BinaryValue other = ( BinaryValue ) obj; 245 246 // First check if we have an attrbuteType. 247 if ( attributeType != null ) 248 { 249 // yes : check for the other value 250 if ( other.attributeType != null ) 251 { 252 if ( attributeType.getOid().equals( other.getAttributeType().getOid() ) ) 253 { 254 // Both AttributeType have the same OID, we can assume they are 255 // equals. We don't check any further, because the unicity of OID 256 // makes it unlikely that the two AT are different. 257 // The values may be both null 258 if ( isNull() ) 259 { 260 return other.isNull(); 261 } 262 263 // Shortcut : if we have an AT for both the values, check the 264 // already normalized values 265 if ( Arrays.equals( upValue, other.upValue ) ) 266 { 267 return true; 268 } 269 270 // We have an AttributeType, we use the associated comparator 271 try 272 { 273 Comparator<byte[]> comparator = getLdapComparator(); 274 275 // Compare normalized values 276 if ( comparator == null ) 277 { 278 return Arrays.equals( getNormReference(), other.getNormReference() ); 279 } 280 else 281 { 282 return comparator.compare( getNormReference(), other.getNormReference() ) == 0; 283 } 284 } 285 catch ( LdapException ne ) 286 { 287 return false; 288 } 289 } 290 else 291 { 292 // We can't compare two values when the two ATs are different 293 return false; 294 } 295 } 296 else 297 { 298 // We only have one AT : we will assume that both values are for the 299 // same AT. 300 // The values may be both null 301 if ( isNull() ) 302 { 303 return other.isNull(); 304 } 305 306 // We have an AttributeType on the base value, we need to use its comparator 307 try 308 { 309 Comparator<byte[]> comparator = getLdapComparator(); 310 311 // Compare normalized values. We have to normalized the other value, 312 // as it has no AT 313 MatchingRule equality = getAttributeType().getEquality(); 314 315 if ( equality == null ) 316 { 317 // No matching rule : compare the raw values 318 return Arrays.equals( getNormReference(), other.getNormReference() ); 319 } 320 321 Normalizer normalizer = equality.getNormalizer(); 322 323 BinaryValue otherValue = ( BinaryValue ) normalizer.normalize( other ); 324 325 if ( comparator == null ) 326 { 327 return Arrays.equals( getNormReference(), otherValue.getNormReference() ); 328 } 329 else 330 { 331 return comparator.compare( getNormReference(), otherValue.getNormReference() ) == 0; 332 } 333 } 334 catch ( LdapException ne ) 335 { 336 return false; 337 } 338 } 339 } 340 else 341 { 342 // No : check for the other value 343 if ( other.attributeType != null ) 344 { 345 // We only have one AT : we will assume that both values are for the 346 // same AT. 347 // The values may be both null 348 if ( isNull() ) 349 { 350 return other.isNull(); 351 } 352 353 try 354 { 355 Comparator<byte[]> comparator = other.getLdapComparator(); 356 357 // Compare normalized values. We have to normalized the other value, 358 // as it has no AT 359 MatchingRule equality = other.getAttributeType().getEquality(); 360 361 if ( equality == null ) 362 { 363 // No matching rule : compare the raw values 364 return Arrays.equals( getNormReference(), other.getNormReference() ); 365 } 366 367 Normalizer normalizer = equality.getNormalizer(); 368 369 BinaryValue thisValue = ( BinaryValue ) normalizer.normalize( this ); 370 371 if ( comparator == null ) 372 { 373 return Arrays.equals( thisValue.getNormReference(), other.getNormReference() ); 374 } 375 else 376 { 377 return comparator.compare( thisValue.getNormReference(), other.getNormReference() ) == 0; 378 } 379 } 380 catch ( LdapException ne ) 381 { 382 return false; 383 } 384 } 385 else 386 { 387 // The values may be both null 388 if ( isNull() ) 389 { 390 return other.isNull(); 391 } 392 393 // Now check the normalized values 394 return Arrays.equals( getNormReference(), other.getNormReference() ); 395 } 396 } 397 } 398 399 400 // ----------------------------------------------------------------------- 401 // Cloneable methods 402 // ----------------------------------------------------------------------- 403 /** 404 * {@inheritDoc} 405 */ 406 @Override 407 public BinaryValue clone() 408 { 409 BinaryValue clone = ( BinaryValue ) super.clone(); 410 411 // We have to copy the byte[], they are just referenced by suoer.clone() 412 if ( normalizedValue != null ) 413 { 414 clone.normalizedValue = new byte[normalizedValue.length]; 415 System.arraycopy( normalizedValue, 0, clone.normalizedValue, 0, normalizedValue.length ); 416 } 417 418 if ( upValue != null ) 419 { 420 clone.upValue = new byte[upValue.length]; 421 System.arraycopy( upValue, 0, clone.upValue, 0, upValue.length ); 422 } 423 424 return clone; 425 } 426 427 428 /** 429 * {@inheritDoc} 430 */ 431 @Override 432 public byte[] getValue() 433 { 434 if ( upValue == null ) 435 { 436 return null; 437 } 438 439 final byte[] copy = new byte[upValue.length]; 440 System.arraycopy( upValue, 0, copy, 0, upValue.length ); 441 442 return copy; 443 } 444 445 446 /** 447 * Tells if the current value is Human Readable 448 * 449 * @return <code>true</code> if the value is HR, <code>false</code> otherwise 450 */ 451 @Override 452 public boolean isHumanReadable() 453 { 454 return false; 455 } 456 457 458 /** 459 * @return The length of the interned value 460 */ 461 @Override 462 public int length() 463 { 464 return upValue != null ? upValue.length : 0; 465 } 466 467 468 /** 469 * Get the user provided value as a byte[]. This method returns a copy of 470 * the user provided byte[]. 471 * 472 * @return the user provided value as a byte[] 473 */ 474 @Override 475 public byte[] getBytes() 476 { 477 return getValue(); 478 } 479 480 481 /** 482 * Get the user provided value as a String. 483 * 484 * @return the user provided value as a String 485 */ 486 @Override 487 public String getString() 488 { 489 return Strings.utf8ToString( upValue ); 490 } 491 492 493 /** 494 * Deserialize a BinaryValue. It will return a new BinaryValue instance. 495 * 496 * @param in The input stream 497 * @return A new StringValue instance 498 * @throws IOException If the stream can't be read 499 * @throws ClassNotFoundException If we can't instanciate a BinaryValue 500 */ 501 public static BinaryValue deserialize( ObjectInput in ) throws IOException, ClassNotFoundException 502 { 503 BinaryValue value = new BinaryValue( ( AttributeType ) null ); 504 value.readExternal( in ); 505 506 return value; 507 } 508 509 510 /** 511 * Deserialize a schema aware BinaryValue. It will return a new BinaryValue instance. 512 * 513 * @param attributeType The AttributeType associated with the Value. Can be null 514 * @param in The input stream 515 * @return A new StringValue instance 516 * @throws IOException If the stream can't be read 517 * @throws ClassNotFoundException If we can't instanciate a BinaryValue 518 */ 519 public static BinaryValue deserialize( AttributeType attributeType, ObjectInput in ) throws IOException, 520 ClassNotFoundException 521 { 522 BinaryValue value = new BinaryValue( attributeType ); 523 value.readExternal( in ); 524 525 return value; 526 } 527 528 529 /** 530 * {@inheritDoc} 531 */ 532 @Override 533 public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException 534 { 535 // Read the BINARY flag 536 boolean isHR = in.readBoolean(); 537 538 if ( isHR ) 539 { 540 throw new IOException( "The serialized value is not a Binary value" ); 541 } 542 // Read the user provided value, if it's not null 543 int upLength = in.readInt(); 544 545 if ( upLength >= 0 ) 546 { 547 upValue = new byte[upLength]; 548 549 in.readFully( upValue ); 550 } 551 552 // Read the isNormalized flag 553 boolean normalized = in.readBoolean(); 554 555 if ( normalized ) 556 { 557 int normalizedLength = in.readInt(); 558 559 if ( normalizedLength >= 0 ) 560 { 561 normalizedValue = new byte[normalizedLength]; 562 563 in.readFully( normalizedValue ); 564 } 565 } 566 else 567 { 568 if ( attributeType != null ) 569 { 570 try 571 { 572 normalizedValue = attributeType.getEquality().getNormalizer().normalize( this ).getBytes(); 573 MatchingRule equality = attributeType.getEquality(); 574 575 if ( equality == null ) 576 { 577 if ( upLength >= 0 ) 578 { 579 normalizedValue = new byte[upLength]; 580 581 System.arraycopy( upValue, 0, normalizedValue, 0, upLength ); 582 } 583 } 584 else 585 { 586 Normalizer normalizer = equality.getNormalizer(); 587 588 if ( normalizer != null ) 589 { 590 normalizedValue = normalizer.normalize( this ).getBytes(); 591 } 592 else 593 { 594 if ( upLength >= 0 ) 595 { 596 normalizedValue = new byte[upLength]; 597 598 System.arraycopy( upValue, 0, normalizedValue, 0, upLength ); 599 } 600 } 601 } 602 } 603 catch ( LdapException le ) 604 { 605 // Copy the upValue into the normalizedValue 606 if ( upLength >= 0 ) 607 { 608 normalizedValue = new byte[upLength]; 609 610 System.arraycopy( upValue, 0, normalizedValue, 0, upLength ); 611 } 612 } 613 } 614 else 615 { 616 // Copy the upValue into the normalizedValue 617 if ( upLength >= 0 ) 618 { 619 normalizedValue = new byte[upLength]; 620 621 System.arraycopy( upValue, 0, normalizedValue, 0, upLength ); 622 } 623 } 624 } 625 626 // The hashCoe 627 h = in.readInt(); 628 } 629 630 631 /** 632 * {@inheritDoc} 633 */ 634 @Override 635 public void writeExternal( ObjectOutput out ) throws IOException 636 { 637 // Write the BINARY flag 638 out.writeBoolean( BINARY ); 639 640 // Write the user provided value, if it's not null 641 if ( upValue != null ) 642 { 643 out.writeInt( upValue.length ); 644 645 if ( upValue.length > 0 ) 646 { 647 out.write( upValue, 0, upValue.length ); 648 } 649 } 650 else 651 { 652 out.writeInt( -1 ); 653 } 654 655 // Write the isNormalized flag 656 if ( attributeType != null ) 657 { 658 out.writeBoolean( true ); 659 660 // Write the normalized value, if not null 661 if ( normalizedValue != null ) 662 { 663 out.writeInt( normalizedValue.length ); 664 665 if ( normalizedValue.length > 0 ) 666 { 667 out.write( normalizedValue, 0, normalizedValue.length ); 668 } 669 } 670 else 671 { 672 out.writeInt( -1 ); 673 } 674 } 675 else 676 { 677 out.writeBoolean( false ); 678 } 679 680 // The hashCode 681 out.writeInt( h ); 682 683 out.flush(); 684 } 685 686 687 /** 688 * Dumps binary in hex with label. 689 * 690 * @see Object#toString() 691 */ 692 @Override 693 public String toString() 694 { 695 if ( upValue == null ) 696 { 697 return "null"; 698 } 699 else if ( upValue.length > 16 ) 700 { 701 // Just dump the first 16 bytes... 702 byte[] copy = new byte[16]; 703 704 System.arraycopy( upValue, 0, copy, 0, 16 ); 705 706 return Strings.dumpBytes( copy ) + "..."; 707 } 708 else 709 { 710 return Strings.dumpBytes( upValue ); 711 } 712 } 713}