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 027 import org.apache.directory.shared.ldap.exception.LdapException; 028 029 import org.apache.directory.shared.i18n.I18n; 030 import org.apache.directory.shared.ldap.NotImplementedException; 031 import org.apache.directory.shared.ldap.schema.AttributeType; 032 import org.apache.directory.shared.ldap.schema.LdapComparator; 033 import org.apache.directory.shared.ldap.schema.Normalizer; 034 import org.apache.directory.shared.ldap.util.StringTools; 035 import org.slf4j.Logger; 036 import org.slf4j.LoggerFactory; 037 038 039 /** 040 * A server side schema aware wrapper around a String 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 * @version $Rev$, $Date$ 047 */ 048 public class StringValue extends AbstractValue<String> 049 { 050 /** Used for serialization */ 051 private static final long serialVersionUID = 2L; 052 053 /** logger for reporting errors that might not be handled properly upstream */ 054 protected static final Logger LOG = LoggerFactory.getLogger( StringValue.class ); 055 056 057 // ----------------------------------------------------------------------- 058 // Constructors 059 // ----------------------------------------------------------------------- 060 /** 061 * Creates a StringValue without an initial wrapped value. 062 */ 063 public StringValue() 064 { 065 normalized = false; 066 valid = null; 067 } 068 069 070 /** 071 * Creates a StringValue without an initial wrapped value. 072 * 073 * @param attributeType the schema type associated with this StringValue 074 */ 075 public StringValue( AttributeType attributeType ) 076 { 077 if ( attributeType == null ) 078 { 079 throw new IllegalArgumentException( I18n.err( I18n.ERR_04442 ) ); 080 } 081 082 if ( attributeType.getSyntax() == null ) 083 { 084 throw new IllegalArgumentException( I18n.err( I18n.ERR_04445 ) ); 085 } 086 087 if ( ! attributeType.getSyntax().isHumanReadable() ) 088 { 089 LOG.warn( "Treating a value of a binary attribute {} as a String: " + 090 "\nthis could cause data corruption!", attributeType.getName() ); 091 } 092 093 this.attributeType = attributeType; 094 } 095 096 097 /** 098 * Creates a StringValue with an initial wrapped String value. 099 * 100 * @param value the value to wrap which can be null 101 */ 102 public StringValue( String value ) 103 { 104 this.wrappedValue = value; 105 normalized = false; 106 valid = null; 107 } 108 109 110 /** 111 * Creates a StringValue with an initial wrapped String value. 112 * 113 * @param attributeType the schema type associated with this StringValue 114 * @param wrapped the value to wrap which can be null 115 */ 116 public StringValue( AttributeType attributeType, String value ) 117 { 118 this( attributeType ); 119 this.wrappedValue = value; 120 } 121 122 123 // ----------------------------------------------------------------------- 124 // Value<String> Methods 125 // ----------------------------------------------------------------------- 126 /** 127 * Get a copy of the stored value. 128 * 129 * @return A copy of the stored value. 130 */ 131 public String get() 132 { 133 // The String is immutable, we can safely return the internal 134 // object without copying it. 135 return wrappedValue; 136 } 137 138 139 /** 140 * Gets the normalized (canonical) representation for the wrapped string. 141 * If the wrapped String is null, null is returned, otherwise the normalized 142 * form is returned. If the normalizedValue is null, then this method 143 * will attempt to generate it from the wrapped value: repeated calls to 144 * this method do not unnecessarily normalize the wrapped value. Only changes 145 * to the wrapped value result in attempts to normalize the wrapped value. 146 * 147 * @return gets the normalized value 148 */ 149 public String getNormalizedValue() 150 { 151 if ( isNull() ) 152 { 153 normalized = true; 154 return null; 155 } 156 157 if ( !normalized ) 158 { 159 try 160 { 161 normalize(); 162 } 163 catch ( LdapException ne ) 164 { 165 String message = "Cannot normalize the value :" + ne.getLocalizedMessage(); 166 LOG.info( message ); 167 normalized = false; 168 } 169 } 170 171 if ( normalizedValue == null ) 172 { 173 return wrappedValue; 174 } 175 176 return normalizedValue; 177 } 178 179 180 /** 181 * Gets a copy of the the normalized (canonical) representation 182 * for the wrapped value. 183 * 184 * @return gets a copy of the normalized value 185 */ 186 public String getNormalizedValueCopy() 187 { 188 return getNormalizedValue(); 189 } 190 191 192 /** 193 * Compute the normalized (canonical) representation for the wrapped string. 194 * If the wrapped String is null, the normalized form will be null too. 195 * 196 * @throws LdapException if the value cannot be properly normalized 197 */ 198 public void normalize() throws LdapException 199 { 200 // If the value is already normalized, get out. 201 if ( normalized ) 202 { 203 return; 204 } 205 206 if ( attributeType != null ) 207 { 208 Normalizer normalizer = getNormalizer(); 209 210 if ( normalizer == null ) 211 { 212 normalizedValue = wrappedValue; 213 } 214 else 215 { 216 normalizedValue = ( String ) normalizer.normalize( wrappedValue ); 217 } 218 219 normalized = true; 220 } 221 } 222 223 224 /** 225 * Normalize the value. For a client String value, applies the given normalizer. 226 * 227 * It supposes that the client has access to the schema in order to select the 228 * appropriate normalizer. 229 * 230 * @param Normalizer The normalizer to apply to the value 231 * @exception LdapException If the value cannot be normalized 232 */ 233 public final void normalize( Normalizer normalizer ) throws LdapException 234 { 235 if ( normalizer != null ) 236 { 237 normalizedValue = (String)normalizer.normalize( wrappedValue ); 238 normalized = true; 239 } 240 } 241 242 243 // ----------------------------------------------------------------------- 244 // Comparable<String> Methods 245 // ----------------------------------------------------------------------- 246 /** 247 * @see ServerValue#compareTo(ServerValue) 248 * @throws IllegalStateException on failures to extract the comparator, or the 249 * normalizers needed to perform the required comparisons based on the schema 250 */ 251 public int compareTo( Value<String> value ) 252 { 253 if ( isNull() ) 254 { 255 if ( ( value == null ) || value.isNull() ) 256 { 257 return 0; 258 } 259 else 260 { 261 return -1; 262 } 263 } 264 else if ( ( value == null ) || value.isNull() ) 265 { 266 return 1; 267 } 268 269 if ( !( value instanceof StringValue ) ) 270 { 271 String message = I18n.err( I18n.ERR_04128, toString(), value.getClass() ); 272 LOG.error( message ); 273 throw new NotImplementedException( message ); 274 } 275 276 StringValue stringValue = ( StringValue ) value; 277 278 if ( attributeType != null ) 279 { 280 if ( stringValue.getAttributeType() == null ) 281 { 282 return getNormalizedValue().compareTo( stringValue.getNormalizedValue() ); 283 } 284 else 285 { 286 if ( !attributeType.equals( stringValue.getAttributeType() ) ) 287 { 288 String message = I18n.err( I18n.ERR_04128, toString(), value.getClass() ); 289 LOG.error( message ); 290 throw new NotImplementedException( message ); 291 } 292 } 293 } 294 else 295 { 296 return getNormalizedValue().compareTo( stringValue.getNormalizedValue() ); 297 } 298 299 try 300 { 301 return getLdapComparator().compare( getNormalizedValue(), stringValue.getNormalizedValue() ); 302 } 303 catch ( LdapException e ) 304 { 305 String msg = I18n.err( I18n.ERR_04443, this, value ); 306 LOG.error( msg, e ); 307 throw new IllegalStateException( msg, e ); 308 } 309 } 310 311 312 // ----------------------------------------------------------------------- 313 // Cloneable methods 314 // ----------------------------------------------------------------------- 315 /** 316 * Get a clone of the Client Value 317 * 318 * @return a copy of the current value 319 */ 320 public StringValue clone() 321 { 322 return (StringValue)super.clone(); 323 } 324 325 326 // ----------------------------------------------------------------------- 327 // Object Methods 328 // ----------------------------------------------------------------------- 329 /** 330 * @see Object#hashCode() 331 * @return the instance's hashcode 332 */ 333 public int hashCode() 334 { 335 // return zero if the value is null so only one null value can be 336 // stored in an attribute - the binary version does the same 337 if ( isNull() ) 338 { 339 if ( attributeType != null ) 340 { 341 // return the OID hashcode if the value is null. 342 return attributeType.getOid().hashCode(); 343 } 344 345 return 0; 346 } 347 348 // If the normalized value is null, will default to wrapped 349 // which cannot be null at this point. 350 // If the normalized value is null, will default to wrapped 351 // which cannot be null at this point. 352 int h = 0; 353 354 String normalized = getNormalizedValue(); 355 356 if ( normalized != null ) 357 { 358 h = normalized.hashCode(); 359 } 360 else 361 { 362 h = 17; 363 } 364 365 // Add the OID hashcode if we have an AttributeType 366 if ( attributeType != null ) 367 { 368 h = h*37 + attributeType.getOid().hashCode(); 369 } 370 371 return h; 372 } 373 374 375 /** 376 * @see Object#equals(Object) 377 * 378 * Two StringValue are equals if their normalized values are equal 379 */ 380 public boolean equals( Object obj ) 381 { 382 if ( this == obj ) 383 { 384 return true; 385 } 386 387 if ( ! ( obj instanceof StringValue ) ) 388 { 389 return false; 390 } 391 392 StringValue other = ( StringValue ) obj; 393 394 if ( this.isNull() ) 395 { 396 return other.isNull(); 397 } 398 399 // If we have an attributeType, it must be equal 400 // We should also use the comparator if we have an AT 401 if ( attributeType != null ) 402 { 403 if ( other.attributeType != null ) 404 { 405 if ( !attributeType.equals( other.attributeType ) ) 406 { 407 return false; 408 } 409 } 410 else 411 { 412 return this.getNormalizedValue().equals( other.getNormalizedValue() ); 413 } 414 } 415 else if ( other.attributeType != null ) 416 { 417 return this.getNormalizedValue().equals( other.getNormalizedValue() ); 418 } 419 420 // Shortcut : compare the values without normalization 421 // If they are equal, we may avoid a normalization. 422 // Note : if two values are equal, then their normalized 423 // value are equal too if their attributeType are equal. 424 if ( getReference().equals( other.getReference() ) ) 425 { 426 return true; 427 } 428 429 if ( attributeType != null ) 430 { 431 try 432 { 433 LdapComparator<String> comparator = getLdapComparator(); 434 435 // Compare normalized values 436 if ( comparator == null ) 437 { 438 return getNormalizedValue().equals( other.getNormalizedValue() ); 439 } 440 else 441 { 442 if ( isNormalized() ) 443 { 444 return comparator.compare( getNormalizedValue(), other.getNormalizedValue() ) == 0; 445 } 446 else 447 { 448 Normalizer normalizer = attributeType.getEquality().getNormalizer(); 449 return comparator.compare( normalizer.normalize( get() ), normalizer.normalize( other.get() ) ) == 0; 450 } 451 } 452 } 453 catch ( LdapException ne ) 454 { 455 return false; 456 } 457 } 458 else 459 { 460 return this.getNormalizedValue().equals( other.getNormalizedValue() ); 461 } 462 } 463 464 465 /** 466 * Tells if the current value is Binary or String 467 * 468 * @return <code>true</code> if the value is Binary, <code>false</code> otherwise 469 */ 470 public boolean isBinary() 471 { 472 return false; 473 } 474 475 476 /** 477 * @return The length of the interned value 478 */ 479 public int length() 480 { 481 return wrappedValue != null ? wrappedValue.length() : 0; 482 } 483 484 485 /** 486 * Get the wrapped value as a byte[]. 487 * @return the wrapped value as a byte[] 488 */ 489 public byte[] getBytes() 490 { 491 return StringTools.getBytesUtf8( wrappedValue ); 492 } 493 494 495 /** 496 * Get the wrapped value as a String. 497 * 498 * @return the wrapped value as a String 499 */ 500 public String getString() 501 { 502 return wrappedValue != null ? wrappedValue : ""; 503 } 504 505 506 /** 507 * @see Externalizable#readExternal(ObjectInput) 508 */ 509 public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException 510 { 511 // Read the wrapped value, if it's not null 512 if ( in.readBoolean() ) 513 { 514 wrappedValue = in.readUTF(); 515 } 516 517 // Read the isNormalized flag 518 normalized = in.readBoolean(); 519 520 if ( normalized ) 521 { 522 // Read the normalized value, if not null 523 if ( in.readBoolean() ) 524 { 525 normalizedValue = in.readUTF(); 526 } 527 } 528 } 529 530 531 /** 532 * @see Externalizable#writeExternal(ObjectOutput) 533 */ 534 public void writeExternal( ObjectOutput out ) throws IOException 535 { 536 // Write the wrapped value, if it's not null 537 if ( wrappedValue != null ) 538 { 539 out.writeBoolean( true ); 540 out.writeUTF( wrappedValue ); 541 } 542 else 543 { 544 out.writeBoolean( false ); 545 } 546 547 // Write the isNormalized flag 548 if ( normalized ) 549 { 550 out.writeBoolean( true ); 551 552 // Write the normalized value, if not null 553 if ( normalizedValue != null ) 554 { 555 out.writeBoolean( true ); 556 out.writeUTF( normalizedValue ); 557 } 558 else 559 { 560 out.writeBoolean( false ); 561 } 562 } 563 else 564 { 565 out.writeBoolean( false ); 566 } 567 568 // and flush the data 569 out.flush(); 570 } 571 572 573 /** 574 * We will write the value and the normalized value, only 575 * if the normalized value is different. 576 * 577 * If the value is empty, a flag is written at the beginning with 578 * the value true, otherwise, a false is written. 579 * 580 * The data will be stored following this structure : 581 * [empty value flag] 582 * [UP value] 583 * [normalized] (will be false if the value can't be normalized) 584 * [same] (a flag set to true if the normalized value equals the UP value) 585 * [Norm value] (the normalized value if different from the UP value) 586 * 587 * @param out the buffer in which we will stored the serialized form of the value 588 * @throws IOException if we can't write into the buffer 589 */ 590 public void serialize( ObjectOutput out ) throws IOException 591 { 592 if ( wrappedValue != null ) 593 { 594 // write a flag indicating that the value is not null 595 out.writeBoolean( true ); 596 597 // Write the data 598 out.writeUTF( wrappedValue ); 599 600 // Normalize the data 601 try 602 { 603 normalize(); 604 out.writeBoolean( true ); 605 606 if ( wrappedValue.equals( normalizedValue ) ) 607 { 608 out.writeBoolean( true ); 609 } 610 else 611 { 612 out.writeBoolean( false ); 613 out.writeUTF( normalizedValue ); 614 } 615 } 616 catch ( LdapException ne ) 617 { 618 // The value can't be normalized, we don't write the 619 // normalized value. 620 normalizedValue = null; 621 out.writeBoolean( false ); 622 } 623 } 624 else 625 { 626 // Write a flag indicating that the value is null 627 out.writeBoolean( false ); 628 } 629 630 out.flush(); 631 } 632 633 634 /** 635 * Deserialize a StringValue. 636 * 637 * @param in the buffer containing the bytes with the serialized value 638 * @throws IOException 639 * @throws ClassNotFoundException 640 */ 641 public void deserialize( ObjectInput in ) throws IOException, ClassNotFoundException 642 { 643 // If the value is null, the flag will be set to false 644 if ( !in.readBoolean() ) 645 { 646 wrappedValue = null; 647 normalizedValue = null; 648 return; 649 } 650 651 // Read the value 652 String wrapped = in.readUTF(); 653 654 wrappedValue = wrapped; 655 656 // Read the normalized flag 657 normalized = in.readBoolean(); 658 659 if ( normalized ) 660 { 661 normalized = true; 662 663 // Read the 'same' flag 664 if ( in.readBoolean() ) 665 { 666 normalizedValue = wrapped; 667 } 668 else 669 { 670 // The normalized value is different. Read it 671 normalizedValue = in.readUTF(); 672 } 673 } 674 } 675 676 677 /** 678 * @see Object#toString() 679 */ 680 public String toString() 681 { 682 return wrappedValue == null ? "null": wrappedValue; 683 } 684 }