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