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 }