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.shared.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.shared.i18n.I18n; 029import org.apache.directory.shared.ldap.model.exception.LdapException; 030import org.apache.directory.shared.ldap.model.exception.LdapInvalidAttributeValueException; 031import org.apache.directory.shared.ldap.model.schema.AttributeType; 032import org.apache.directory.shared.ldap.model.schema.LdapComparator; 033import org.apache.directory.shared.ldap.model.schema.comparators.ByteArrayComparator; 034import org.apache.directory.shared.util.Strings; 035 036 037/** 038 * A server side schema aware wrapper around a binary attribute value. 039 * This value wrapper uses schema information to syntax check values, 040 * and to compare them for equality and ordering. It caches results 041 * and invalidates them when the wrapped value changes. 042 * 043 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 044 */ 045public class BinaryValue extends AbstractValue<byte[]> 046{ 047 /** Used for serialization */ 048 public static final long serialVersionUID = 2L; 049 050 /** 051 * Creates a BinaryValue without an initial wrapped value. 052 * 053 * @param attributeType the schema type associated with this BinaryValue 054 */ 055 /* No protection */ BinaryValue( AttributeType attributeType ) 056 { 057 if ( attributeType != null ) 058 { 059 // We must have a Syntax 060 if ( attributeType.getSyntax() == null ) 061 { 062 throw new IllegalArgumentException( I18n.err( I18n.ERR_04445 ) ); 063 } 064 065 if ( attributeType.getSyntax().isHumanReadable() ) 066 { 067 LOG.warn( "Treating a value of a human readible attribute {} as binary: ", attributeType.getName() ); 068 } 069 070 this.attributeType = attributeType; 071 } 072 } 073 074 075 /** 076 * Creates a BinaryValue with an initial wrapped binary value. 077 * 078 * @param value the binary value to wrap which may be null, or a zero length byte array 079 */ 080 public BinaryValue( byte[] value ) 081 { 082 if ( value != null ) 083 { 084 this.wrappedValue = new byte[value.length]; 085 this.normalizedValue = new byte[value.length]; 086 System.arraycopy( value, 0, this.wrappedValue, 0, value.length ); 087 System.arraycopy( value, 0, this.normalizedValue, 0, value.length ); 088 } 089 else 090 { 091 this.wrappedValue = null; 092 this.normalizedValue = null; 093 } 094 } 095 096 097 /** 098 * Creates a BinaryValue with an initial wrapped binary value. 099 * 100 * @param attributeType the schema type associated with this BinaryValue 101 * @param value the binary value to wrap which may be null, or a zero length byte array 102 * @throws LdapInvalidAttributeValueException If the added value is invalid accordingly 103 * to the schema 104 */ 105 public BinaryValue( AttributeType attributeType, byte[] value ) throws LdapInvalidAttributeValueException 106 { 107 this( value ); 108 apply( attributeType ); 109 } 110 111 112 /** 113 * Gets a direct reference to the normalized representation for the 114 * wrapped value of this ServerValue wrapper. Implementations will most 115 * likely leverage the attributeType this value is associated with to 116 * determine how to properly normalize the wrapped value. 117 * 118 * @return the normalized version of the wrapped value 119 */ 120 public byte[] getNormValue() 121 { 122 if ( isNull() ) 123 { 124 return null; 125 } 126 127 byte[] copy = new byte[normalizedValue.length]; 128 System.arraycopy( normalizedValue, 0, copy, 0, normalizedValue.length ); 129 return copy; 130 } 131 132 133 /** 134 * 135 * @see ServerValue#compareTo(Value) 136 */ 137 public int compareTo( Value<byte[]> value ) 138 { 139 if ( isNull() ) 140 { 141 if ( ( value == null ) || value.isNull() ) 142 { 143 return 0; 144 } 145 else 146 { 147 return -1; 148 } 149 } 150 else 151 { 152 if ( ( value == null ) || value.isNull() ) 153 { 154 return 1; 155 } 156 } 157 158 BinaryValue binaryValue = ( BinaryValue ) value; 159 160 if ( attributeType != null ) 161 { 162 try 163 { 164 LdapComparator<byte[]> comparator = getLdapComparator(); 165 166 if ( comparator != null ) 167 { 168 return comparator 169 .compare( getNormReference(), binaryValue.getNormReference() ); 170 } 171 else 172 { 173 return new ByteArrayComparator( null ).compare( getNormReference(), binaryValue 174 .getNormReference() ); 175 } 176 } 177 catch ( LdapException e ) 178 { 179 String msg = I18n.err( I18n.ERR_04443, Arrays.toString( getReference() ), value ); 180 LOG.error( msg, e ); 181 throw new IllegalStateException( msg, e ); 182 } 183 } 184 else 185 { 186 return new ByteArrayComparator( null ).compare( getNormValue(), binaryValue.getNormValue() ); 187 } 188 } 189 190 191 // ----------------------------------------------------------------------- 192 // Object Methods 193 // ----------------------------------------------------------------------- 194 /** 195 * @see Object#hashCode() 196 * @return the instance's hashcode 197 */ 198 public int hashCode() 199 { 200 if ( h == 0 ) 201 { 202 // return zero if the value is null so only one null value can be 203 // stored in an attribute - the string version does the same 204 if ( isNull() ) 205 { 206 return 0; 207 } 208 209 byte[] normalizedValue = getNormReference(); 210 h = Arrays.hashCode( normalizedValue ); 211 } 212 213 return h; 214 } 215 216 217 /** 218 * Checks to see if this BinaryValue equals the supplied object. 219 * 220 * This equals implementation overrides the BinaryValue implementation which 221 * is not schema aware. 222 */ 223 public boolean equals( Object obj ) 224 { 225 if ( this == obj ) 226 { 227 return true; 228 } 229 230 if ( !( obj instanceof BinaryValue ) ) 231 { 232 return false; 233 } 234 235 BinaryValue other = ( BinaryValue ) obj; 236 237 if ( isNull() ) 238 { 239 return other.isNull(); 240 } 241 242 // If we have an attributeType, it must be equal 243 // We should also use the comparator if we have an AT 244 if ( attributeType != null ) 245 { 246 if ( other.attributeType != null ) 247 { 248 if ( !attributeType.equals( other.attributeType ) ) 249 { 250 return false; 251 } 252 } 253 else 254 { 255 other.attributeType = attributeType; 256 } 257 } 258 else if ( other.attributeType != null ) 259 { 260 attributeType = other.attributeType; 261 } 262 263 // Shortcut : if the values are equals, no need to compare 264 // the normalized values 265 if ( Arrays.equals( wrappedValue, other.wrappedValue ) ) 266 { 267 return true; 268 } 269 270 if ( attributeType != null ) 271 { 272 // We have an AttributeType, we eed to use the comparator 273 try 274 { 275 Comparator<byte[]> comparator = ( Comparator<byte[]> ) getLdapComparator(); 276 277 // Compare normalized values 278 if ( comparator == null ) 279 { 280 return Arrays.equals( getNormReference(), other.getNormReference() ); 281 } 282 else 283 { 284 return comparator.compare( getNormReference(), other.getNormReference() ) == 0; 285 } 286 } 287 catch ( LdapException ne ) 288 { 289 return false; 290 } 291 292 } 293 else 294 { 295 // now unlike regular values we have to compare the normalized values 296 return Arrays.equals( getNormReference(), other.getNormReference() ); 297 } 298 } 299 300 301 // ----------------------------------------------------------------------- 302 // Cloneable methods 303 // ----------------------------------------------------------------------- 304 /** 305 * {@inheritDoc} 306 */ 307 public BinaryValue clone() 308 { 309 BinaryValue clone = ( BinaryValue ) super.clone(); 310 311 // We have to copy the byte[], they are just referenced by suoer.clone() 312 if ( normalizedValue != null ) 313 { 314 clone.normalizedValue = new byte[normalizedValue.length]; 315 System.arraycopy( normalizedValue, 0, clone.normalizedValue, 0, normalizedValue.length ); 316 } 317 318 if ( wrappedValue != null ) 319 { 320 clone.wrappedValue = new byte[wrappedValue.length]; 321 System.arraycopy( wrappedValue, 0, clone.wrappedValue, 0, wrappedValue.length ); 322 } 323 324 return clone; 325 } 326 327 328 /** 329 * {@inheritDoc} 330 */ 331 public byte[] getValue() 332 { 333 if ( wrappedValue == null ) 334 { 335 return null; 336 } 337 338 final byte[] copy = new byte[wrappedValue.length]; 339 System.arraycopy( wrappedValue, 0, copy, 0, wrappedValue.length ); 340 341 return copy; 342 } 343 344 345 /** 346 * Tells if the current value is Human Readable 347 * 348 * @return <code>true</code> if the value is HR, <code>false</code> otherwise 349 */ 350 public boolean isHumanReadable() 351 { 352 return false; 353 } 354 355 356 /** 357 * @return The length of the interned value 358 */ 359 public int length() 360 { 361 return wrappedValue != null ? wrappedValue.length : 0; 362 } 363 364 365 /** 366 * Get the wrapped value as a byte[]. This method returns a copy of 367 * the wrapped byte[]. 368 * 369 * @return the wrapped value as a byte[] 370 */ 371 public byte[] getBytes() 372 { 373 return getValue(); 374 } 375 376 377 /** 378 * Get the wrapped value as a String. 379 * 380 * @return the wrapped value as a String 381 */ 382 public String getString() 383 { 384 return Strings.utf8ToString( wrappedValue ); 385 } 386 387 388 /** 389 * Deserialize a BinaryValue. It will return a new BinaryValue instance. 390 * 391 * @param attributeType The AttributeType associated with the Value. Can be null 392 * @param in The input stream 393 * @return A new StringValue instance 394 * @throws IOException If the stream can't be read 395 * @throws ClassNotFoundException If we can't instanciate a BinaryValue 396 */ 397 public static BinaryValue deserialize( ObjectInput in ) throws IOException, ClassNotFoundException 398 { 399 BinaryValue value = new BinaryValue( (AttributeType)null ); 400 value.readExternal( in ); 401 402 return value; 403 } 404 405 406 /** 407 * Deserialize a schema aware BinaryValue. It will return a new BinaryValue instance. 408 * 409 * @param attributeType The AttributeType associated with the Value. Can be null 410 * @param in The input stream 411 * @return A new StringValue instance 412 * @throws IOException If the stream can't be read 413 * @throws ClassNotFoundException If we can't instanciate a BinaryValue 414 */ 415 public static BinaryValue deserialize( AttributeType attributeType, ObjectInput in ) throws IOException, ClassNotFoundException 416 { 417 BinaryValue value = new BinaryValue( attributeType ); 418 value.readExternal( in ); 419 420 return value; 421 } 422 423 424 /** 425 * {@inheritDoc} 426 */ 427 public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException 428 { 429 // Read the BINARY flag 430 boolean isHR = in.readBoolean(); 431 432 if ( isHR ) 433 { 434 throw new IOException( "The serialized value is not a Binary value" ); 435 } 436 // Read the wrapped value, if it's not null 437 int wrappedLength = in.readInt(); 438 439 if ( wrappedLength >= 0 ) 440 { 441 wrappedValue = new byte[wrappedLength]; 442 443 if ( wrappedLength > 0 && in.read( wrappedValue ) == -1 ) 444 { 445 throw new IOException( I18n.err( I18n.ERR_04480_END_OF_STREAM ) ); 446 } 447 } 448 449 // Read the isNormalized flag 450 boolean normalized = in.readBoolean(); 451 452 if ( normalized ) 453 { 454 int normalizedLength = in.readInt(); 455 456 if ( normalizedLength >= 0 ) 457 { 458 normalizedValue = new byte[normalizedLength]; 459 460 if ( normalizedLength > 0 ) 461 { 462 in.read( normalizedValue ); 463 } 464 } 465 } 466 else 467 { 468 // Copy the wrappedValue into the normalizedValue 469 if ( wrappedLength >= 0 ) 470 { 471 normalizedValue = new byte[wrappedLength]; 472 473 System.arraycopy( wrappedValue, 0, normalizedValue, 0, wrappedLength ); 474 } 475 } 476 477 // The hashCoe 478 h = in.readInt(); 479 } 480 481 482 /** 483 * {@inheritDoc} 484 */ 485 public void writeExternal( ObjectOutput out ) throws IOException 486 { 487 // Write the BINARY flag 488 out.writeBoolean( BINARY ); 489 490 // Write the wrapped value, if it's not null 491 if ( wrappedValue != null ) 492 { 493 out.writeInt( wrappedValue.length ); 494 495 if ( wrappedValue.length > 0 ) 496 { 497 out.write( wrappedValue, 0, wrappedValue.length ); 498 } 499 } 500 else 501 { 502 out.writeInt( -1 ); 503 } 504 505 // Write the isNormalized flag 506 if ( attributeType != null ) 507 { 508 out.writeBoolean( true ); 509 510 // Write the normalized value, if not null 511 if ( normalizedValue != null ) 512 { 513 out.writeInt( normalizedValue.length ); 514 515 if ( normalizedValue.length > 0 ) 516 { 517 out.write( normalizedValue, 0, normalizedValue.length ); 518 } 519 } 520 else 521 { 522 out.writeInt( -1 ); 523 } 524 } 525 else 526 { 527 out.writeBoolean( false ); 528 } 529 530 // The hashCode 531 out.writeInt( h ); 532 533 out.flush(); 534 } 535 536 537 /** 538 * Dumps binary in hex with label. 539 * 540 * @see Object#toString() 541 */ 542 public String toString() 543 { 544 if ( wrappedValue == null ) 545 { 546 return "null"; 547 } 548 else if ( wrappedValue.length > 16 ) 549 { 550 // Just dump the first 16 bytes... 551 byte[] copy = new byte[16]; 552 553 System.arraycopy( wrappedValue, 0, copy, 0, 16 ); 554 555 return "'" + Strings.dumpBytes(copy) + "...'"; 556 } 557 else 558 { 559 return "'" + Strings.dumpBytes(wrappedValue) + "'"; 560 } 561 } 562}