1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.directory.server.core.entry;
20
21
22 import java.io.Externalizable;
23 import java.io.IOException;
24 import java.io.ObjectInput;
25 import java.io.ObjectOutput;
26 import java.util.Comparator;
27
28 import javax.naming.NamingException;
29
30 import org.apache.directory.shared.ldap.NotImplementedException;
31 import org.apache.directory.shared.ldap.entry.Value;
32 import org.apache.directory.shared.ldap.entry.client.ClientStringValue;
33 import org.apache.directory.shared.ldap.schema.AttributeType;
34 import org.apache.directory.shared.ldap.schema.MatchingRule;
35 import org.apache.directory.shared.ldap.schema.Normalizer;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38
39
40 /**
41 * A server side schema aware wrapper around a String attribute value.
42 * This value wrapper uses schema information to syntax check values,
43 * and to compare them for equality and ordering. It caches results
44 * and invalidates them when the wrapped value changes.
45 *
46 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
47 * @version $Rev$, $Date$
48 */
49 public class ServerStringValue extends ClientStringValue
50 {
51 /** Used for serialization */
52 private static final long serialVersionUID = 2L;
53
54 /** logger for reporting errors that might not be handled properly upstream */
55 private static final Logger LOG = LoggerFactory.getLogger( ServerStringValue.class );
56
57 /** reference to the attributeType which is not serialized */
58 private transient AttributeType attributeType;
59
60
61 // -----------------------------------------------------------------------
62 // utility methods
63 // -----------------------------------------------------------------------
64 /**
65 * Utility method to get some logs if an assert fails
66 */
67 protected String logAssert( String message )
68 {
69 LOG.error( message );
70 return message;
71 }
72
73
74 /**
75 * Check the attributeType member. It should not be null,
76 * and it should contains a syntax.
77 */
78 protected String checkAttributeType( AttributeType attributeType )
79 {
80 try
81 {
82 if ( attributeType == null )
83 {
84 return "The AttributeType parameter should not be null";
85 }
86
87 if ( attributeType.getSyntax() == null )
88 {
89 return "There is no Syntax associated with this attributeType";
90 }
91
92 return null;
93 }
94 catch ( NamingException ne )
95 {
96 return "This AttributeType is incorrect";
97 }
98 }
99
100
101 // -----------------------------------------------------------------------
102 // Constructors
103 // -----------------------------------------------------------------------
104 /**
105 * Creates a ServerStringValue without an initial wrapped value.
106 *
107 * @param attributeType the schema type associated with this ServerStringValue
108 */
109 public ServerStringValue( AttributeType attributeType )
110 {
111 super();
112
113 if ( attributeType == null )
114 {
115 throw new IllegalArgumentException( "The AttributeType parameter should not be null" );
116 }
117
118 try
119 {
120 if ( attributeType.getSyntax() == null )
121 {
122 throw new IllegalArgumentException( "There is no Syntax associated with this attributeType" );
123 }
124
125 if ( ! attributeType.getSyntax().isHumanReadable() )
126 {
127 LOG.warn( "Treating a value of a binary attribute {} as a String: " +
128 "\nthis could cause data corruption!", attributeType.getName() );
129 }
130 }
131 catch( NamingException e )
132 {
133 LOG.error( "Failed to resolve syntax for attributeType {}", attributeType, e );
134 }
135
136 this.attributeType = attributeType;
137 }
138
139
140 /**
141 * Creates a ServerStringValue with an initial wrapped String value.
142 *
143 * @param attributeType the schema type associated with this ServerStringValue
144 * @param wrapped the value to wrap which can be null
145 */
146 public ServerStringValue( AttributeType attributeType, String wrapped )
147 {
148 this( attributeType );
149 this.wrapped = wrapped;
150 }
151
152
153 /**
154 * Creates a ServerStringValue with an initial wrapped String value and
155 * a normalized value.
156 *
157 * @param attributeType the schema type associated with this ServerStringValue
158 * @param wrapped the value to wrap which can be null
159 * @param normalizedValue the normalized value
160 */
161 /** No protection */ ServerStringValue( AttributeType attributeType, String wrapped, String normalizedValue, boolean valid )
162 {
163 super( wrapped );
164 this.normalized = true;
165 this.attributeType = attributeType;
166 this.normalizedValue = normalizedValue;
167 this.valid = valid;
168 }
169
170
171 // -----------------------------------------------------------------------
172 // Value<String> Methods, overloaded
173 // -----------------------------------------------------------------------
174 /**
175 * @return a copy of the current value
176 */
177 public ServerStringValue clone()
178 {
179 ServerStringValue clone = (ServerStringValue)super.clone();
180
181 return clone;
182 }
183
184
185
186
187 // -----------------------------------------------------------------------
188 // ServerValue<String> Methods
189 // -----------------------------------------------------------------------
190 /**
191 * Compute the normalized (canonical) representation for the wrapped string.
192 * If the wrapped String is null, the normalized form will be null too.
193 *
194 * @throws NamingException if the value cannot be properly normalized
195 */
196 public void normalize() throws NamingException
197 {
198 // If the value is already normalized, get out.
199 if ( normalized )
200 {
201 return;
202 }
203
204 Normalizer normalizer = getNormalizer();
205
206 if ( normalizer == null )
207 {
208 normalizedValue = wrapped;
209 }
210 else
211 {
212 normalizedValue = ( String ) normalizer.normalize( wrapped );
213 }
214
215 normalized = true;
216 }
217
218
219 /**
220 * Gets the normalized (canonical) representation for the wrapped string.
221 * If the wrapped String is null, null is returned, otherwise the normalized
222 * form is returned. If no the normalizedValue is null, then this method
223 * will attempt to generate it from the wrapped value: repeated calls to
224 * this method do not unnecessarily normalize the wrapped value. Only changes
225 * to the wrapped value result in attempts to normalize the wrapped value.
226 *
227 * @return gets the normalized value
228 * @throws NamingException if the value cannot be properly normalized
229 */
230 public String getNormalizedValue()
231 {
232 if ( isNull() )
233 {
234 normalized = true;
235 return null;
236 }
237
238 if ( !normalized )
239 {
240 try
241 {
242 normalize();
243 }
244 catch ( NamingException ne )
245 {
246 String message = "Cannot normalize the value :" + ne.getMessage();
247 LOG.warn( message );
248 normalized = false;
249 }
250 }
251
252 return normalizedValue;
253 }
254
255
256 /**
257 * Uses the syntaxChecker associated with the attributeType to check if the
258 * value is valid. Repeated calls to this method do not attempt to re-check
259 * the syntax of the wrapped value every time if the wrapped value does not
260 * change. Syntax checks only result on the first check, and when the wrapped
261 * value changes.
262 *
263 * @see Value#isValid()
264 */
265 public final boolean isValid()
266 {
267 if ( valid != null )
268 {
269 return valid;
270 }
271
272 try
273 {
274 valid = attributeType.getSyntax().getSyntaxChecker().isValidSyntax( get() );
275 }
276 catch ( NamingException ne )
277 {
278 String message = "Cannot check the syntax : " + ne.getMessage();
279 LOG.error( message );
280 valid = false;
281 }
282
283 return valid;
284 }
285
286
287 /**
288 * @see Value#compareTo(Object)
289 * @throws IllegalStateException on failures to extract the comparator, or the
290 * normalizers needed to perform the required comparisons based on the schema
291 */
292 public int compareTo( Value<String> value )
293 {
294 if ( isNull() )
295 {
296 if ( ( value == null ) || value.isNull() )
297 {
298 return 0;
299 }
300 else
301 {
302 return -1;
303 }
304 }
305 else if ( ( value == null ) || value.isNull() )
306 {
307 return 1;
308 }
309
310 if ( value instanceof ServerStringValue )
311 {
312 ServerStringValue stringValue = ( ServerStringValue ) value;
313
314 // Normalizes the compared value
315 try
316 {
317 stringValue.normalize();
318 }
319 catch ( NamingException ne )
320 {
321 String message = "Cannot normalize the wrapped value " + stringValue;
322 LOG.error( message );
323 }
324
325 // Normalizes the value
326 try
327 {
328 normalize();
329 }
330 catch ( NamingException ne )
331 {
332 String message = "Cannot normalize the wrapped value " + this;
333 LOG.error( message );
334 }
335
336 try
337 {
338 //noinspection unchecked
339 return getComparator().compare( getNormalizedValue(), stringValue.getNormalizedValue() );
340 }
341 catch ( NamingException e )
342 {
343 String msg = "Failed to compare normalized values for " + this + " and " + value;
344 LOG.error( msg, e );
345 throw new IllegalStateException( msg, e );
346 }
347 }
348
349 String message = "I don't know what to do if value is not a ServerStringValue";
350 LOG.error( message );
351 throw new NotImplementedException( message );
352 }
353
354
355 /**
356 * Get the associated AttributeType
357 * @return The AttributeType
358 */
359 public AttributeType getAttributeType()
360 {
361 return attributeType;
362 }
363
364
365 /**
366 * Check if the value is stored into an instance of the given
367 * AttributeType, or one of its ascendant.
368 *
369 * For instance, if the Value is associated with a CommonName,
370 * checking for Name will match.
371 *
372 * @param attributeType The AttributeType we are looking at
373 * @return <code>true</code> if the value is associated with the given
374 * attributeType or one of its ascendant
375 */
376 public boolean instanceOf( AttributeType attributeType ) throws NamingException
377 {
378 if ( this.attributeType.equals( attributeType ) )
379 {
380 return true;
381 }
382
383 return this.attributeType.isDescentantOf( attributeType );
384 }
385
386
387 // -----------------------------------------------------------------------
388 // Object Methods
389 // -----------------------------------------------------------------------
390 /**
391 * Checks to see if this ServerStringValue equals the supplied object.
392 *
393 * This equals implementation overrides the StringValue implementation which
394 * is not schema aware.
395 *
396 * Two ServerStringValues are equal if they have the same AttributeType,
397 * they are both null, their value are equal or their normalized value
398 * are equal. If the AttributeType has a comparator, we use it to
399 * compare both values.
400 * @throws IllegalStateException on failures to extract the comparator, or the
401 * normalizers needed to perform the required comparisons based on the schema
402 */
403 public boolean equals( Object obj )
404 {
405 if ( this == obj )
406 {
407 return true;
408 }
409
410 if ( ! ( obj instanceof ServerStringValue ) )
411 {
412 return false;
413 }
414
415 ServerStringValue other = ( ServerStringValue ) obj;
416
417 if ( !attributeType.equals( other.attributeType ) )
418 {
419 return false;
420 }
421
422 if ( isNull() )
423 {
424 return other.isNull();
425 }
426
427 // Shortcut : compare the values without normalization
428 // If they are equal, we may avoid a normalization.
429 // Note : if two values are equal, then their normalized
430 // value are equal too if their attributeType are equal.
431 if ( get().equals( other.get() ) )
432 {
433 return true;
434 }
435 else
436 {
437 try
438 {
439 Comparator<String> comparator = getComparator();
440
441 // Compare normalized values
442 if ( comparator == null )
443 {
444 return getNormalizedValue().equals( other.getNormalizedValue() );
445 }
446 else
447 {
448 return comparator.compare( getNormalizedValue(), other.getNormalizedValue() ) == 0;
449 }
450 }
451 catch ( NamingException ne )
452 {
453 return false;
454 }
455 }
456 }
457
458
459 // -----------------------------------------------------------------------
460 // Private Helper Methods (might be put into abstract base class)
461 // -----------------------------------------------------------------------
462
463
464 /**
465 * Find a matchingRule to use for normalization and comparison. If an equality
466 * matchingRule cannot be found it checks to see if other matchingRules are
467 * available: SUBSTR, and ORDERING. If a matchingRule cannot be found null is
468 * returned.
469 *
470 * @return a matchingRule or null if one cannot be found for the attributeType
471 * @throws NamingException if resolution of schema entities fail
472 */
473 private MatchingRule getMatchingRule() throws NamingException
474 {
475 MatchingRule mr = attributeType.getEquality();
476
477 if ( mr == null )
478 {
479 mr = attributeType.getOrdering();
480 }
481
482 if ( mr == null )
483 {
484 mr = attributeType.getSubstr();
485 }
486
487 return mr;
488 }
489
490
491 /**
492 * Gets a normalizer using getMatchingRule() to resolve the matchingRule
493 * that the normalizer is extracted from.
494 *
495 * @return a normalizer associated with the attributeType or null if one cannot be found
496 * @throws NamingException if resolution of schema entities fail
497 */
498 private Normalizer getNormalizer() throws NamingException
499 {
500 MatchingRule mr = getMatchingRule();
501
502 if ( mr == null )
503 {
504 return null;
505 }
506
507 return mr.getNormalizer();
508 }
509
510
511 /**
512 * Implement the hashCode method.
513 *
514 * @see Object#hashCode()
515 * @throws IllegalStateException on failures to extract the comparator, or the
516 * normalizers needed to perform the required comparisons based on the schema
517 * @return the instance's hash code
518 */
519 public int hashCode()
520 {
521 // return the OID hashcode if the value is null.
522 if ( isNull() )
523 {
524 return attributeType.getOid().hashCode();
525 }
526
527 // If the normalized value is null, will default to wrapped
528 // which cannot be null at this point.
529 int h = 17;
530
531 String normalized = getNormalizedValue();
532
533 if ( normalized != null )
534 {
535 h = h*37 + normalized.hashCode();
536 }
537
538 // Add the OID hashcode
539 h = h*37 + attributeType.getOid().hashCode();
540
541 return h;
542 }
543
544
545 /**
546 * Gets a comparator using getMatchingRule() to resolve the matching
547 * that the comparator is extracted from.
548 *
549 * @return a comparator associated with the attributeType or null if one cannot be found
550 * @throws NamingException if resolution of schema entities fail
551 */
552 private Comparator getComparator() throws NamingException
553 {
554 MatchingRule mr = getMatchingRule();
555
556 if ( mr == null )
557 {
558 return null;
559 }
560
561 return mr.getComparator();
562 }
563
564
565 /**
566 * @see Externalizable#writeExternal(ObjectOutput)
567 *
568 * We can't use this method for a ServerStringValue, as we have to feed the value
569 * with an AttributeType object
570 */
571 public void writeExternal( ObjectOutput out ) throws IOException
572 {
573 throw new IllegalStateException( "Cannot use standard serialization for a ServerStringValue" );
574 }
575
576
577 /**
578 * We will write the value and the normalized value, only
579 * if the normalized value is different.
580 *
581 * If the value is empty, a flag is written at the beginning with
582 * the value true, otherwise, a false is written.
583 *
584 * The data will be stored following this structure :
585 * [empty value flag]
586 * [UP value]
587 * [normalized] (will be false if the value can't be normalized)
588 * [same] (a flag set to true if the normalized value equals the UP value)
589 * [Norm value] (the normalized value if different from the UP value)
590 *
591 * @param out the buffer in which we will stored the serialized form of the value
592 * @throws IOException if we can't write into the buffer
593 */
594 public void serialize( ObjectOutput out ) throws IOException
595 {
596 if ( wrapped != null )
597 {
598 // write a flag indicating that the value is not null
599 out.writeBoolean( true );
600
601 // Write the data
602 out.writeUTF( wrapped );
603
604 // Normalize the data
605 try
606 {
607 normalize();
608 out.writeBoolean( true );
609
610 if ( wrapped.equals( normalizedValue ) )
611 {
612 out.writeBoolean( true );
613 }
614 else
615 {
616 out.writeBoolean( false );
617 out.writeUTF( normalizedValue );
618 }
619 }
620 catch ( NamingException ne )
621 {
622 // The value can't be normalized, we don't write the
623 // normalized value.
624 normalizedValue = null;
625 out.writeBoolean( false );
626 }
627 }
628 else
629 {
630 // Write a flag indicating that the value is null
631 out.writeBoolean( false );
632 }
633
634 out.flush();
635 }
636
637
638 /**
639 * @see Externalizable#readExternal(ObjectInput)
640 *
641 * We can't use this method for a ServerStringValue, as we have to feed the value
642 * with an AttributeType object
643 */
644 public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
645 {
646 throw new IllegalStateException( "Cannot use standard serialization for a ServerStringValue" );
647 }
648
649
650 /**
651 *
652 * Deserialize a ServerStringValue.
653 *
654 * @param in the buffer containing the bytes with the serialized value
655 * @throws IOException
656 * @throws ClassNotFoundException
657 */
658 public void deserialize( ObjectInput in ) throws IOException, ClassNotFoundException
659 {
660 // If the value is null, the flag will be set to false
661 if ( !in.readBoolean() )
662 {
663 set( null );
664 normalizedValue = null;
665 return;
666 }
667
668 // Read the value
669 String wrapped = in.readUTF();
670
671 set( wrapped );
672
673 // Read the normalized flag
674 normalized = in.readBoolean();
675
676 if ( normalized )
677 {
678 normalized = true;
679
680 // Read the 'same' flag
681 if ( in.readBoolean() )
682 {
683 normalizedValue = wrapped;
684 }
685 else
686 {
687 // The normalized value is different. Read it
688 normalizedValue = in.readUTF();
689 }
690 }
691 }
692 }