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.Arrays;
27 import java.util.Comparator;
28
29 import javax.naming.NamingException;
30
31 import org.apache.directory.shared.ldap.NotImplementedException;
32 import org.apache.directory.shared.ldap.entry.Value;
33 import org.apache.directory.shared.ldap.entry.client.ClientBinaryValue;
34 import org.apache.directory.shared.ldap.schema.AttributeType;
35 import org.apache.directory.shared.ldap.schema.ByteArrayComparator;
36 import org.apache.directory.shared.ldap.schema.MatchingRule;
37 import org.apache.directory.shared.ldap.schema.Normalizer;
38 import org.apache.directory.shared.ldap.util.StringTools;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42
43 /**
44 * A server side schema aware wrapper around a binary attribute value.
45 * This value wrapper uses schema information to syntax check values,
46 * and to compare them for equality and ordering. It caches results
47 * and invalidates them when the wrapped value changes.
48 *
49 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
50 * @version $Rev$, $Date$
51 */
52 public class ServerBinaryValue extends ClientBinaryValue
53 {
54 /** Used for serialization */
55 private static final long serialVersionUID = 2L;
56
57 /** logger for reporting errors that might not be handled properly upstream */
58 private static final Logger LOG = LoggerFactory.getLogger( ServerBinaryValue.class );
59
60 /** used to dynamically lookup the attributeType when/if deserializing */
61 //@SuppressWarnings ( { "FieldCanBeLocal", "UnusedDeclaration" } )
62 //private final String oid;
63
64 /** reference to the attributeType which is not serialized */
65 private transient AttributeType attributeType;
66
67 /** A flag set if the normalized data is different from the wrapped data */
68 private transient boolean same;
69
70
71 // -----------------------------------------------------------------------
72 // utility methods
73 // -----------------------------------------------------------------------
74 /**
75 * Utility method to get some logs if an assert fails
76 */
77 protected String logAssert( String message )
78 {
79 LOG.error( message );
80 return message;
81 }
82
83
84 /**
85 * Check the attributeType member. It should not be null,
86 * and it should contains a syntax.
87 */
88 protected String checkAttributeType( AttributeType attributeType )
89 {
90 try
91 {
92 if ( attributeType == null )
93 {
94 return "The AttributeType parameter should not be null";
95 }
96
97 if ( attributeType.getSyntax() == null )
98 {
99 return "There is no Syntax associated with this attributeType";
100 }
101
102 return null;
103 }
104 catch ( NamingException ne )
105 {
106 return "This AttributeType is incorrect";
107 }
108 }
109
110
111 // -----------------------------------------------------------------------
112 // Constructors
113 // -----------------------------------------------------------------------
114 /**
115 * Creates a ServerBinaryValue without an initial wrapped value.
116 *
117 * @param attributeType the schema type associated with this ServerBinaryValue
118 */
119 public ServerBinaryValue( AttributeType attributeType )
120 {
121 super();
122
123 if ( attributeType == null )
124 {
125 throw new IllegalArgumentException( "The AttributeType parameter should not be null" );
126 }
127
128 try
129 {
130 if ( attributeType.getSyntax() == null )
131 {
132 throw new IllegalArgumentException( "There is no Syntax associated with this attributeType" );
133 }
134
135 if ( attributeType.getSyntax().isHumanReadable() )
136 {
137 LOG.warn( "Treating a value of a human readible attribute {} as binary: ", attributeType.getName() );
138 }
139 }
140 catch( NamingException e )
141 {
142 LOG.error( "Failed to resolve syntax for attributeType {}", attributeType, e );
143 }
144
145 this.attributeType = attributeType;
146 }
147
148
149 /**
150 * Creates a ServerBinaryValue with an initial wrapped binary value.
151 *
152 * @param attributeType the schema type associated with this ServerBinaryValue
153 * @param wrapped the binary value to wrap which may be null, or a zero length byte array
154 */
155 public ServerBinaryValue( AttributeType attributeType, byte[] wrapped )
156 {
157 this( attributeType );
158 this.wrapped = wrapped;
159 }
160
161
162 /**
163 * Creates a ServerStringValue with an initial wrapped String value and
164 * a normalized value.
165 *
166 * @param attributeType the schema type associated with this ServerStringValue
167 * @param wrapped the value to wrap which can be null
168 * @param normalizedValue the normalized value
169 */
170 /** No protection */
171 ServerBinaryValue( AttributeType attributeType, byte[] wrapped, byte[] normalizedValue, boolean same, boolean valid )
172 {
173 super( wrapped );
174 this.normalized = true;
175 this.attributeType = attributeType;
176 this.normalizedValue = normalizedValue;
177 this.valid = valid;
178 this.same = same;
179 //this.oid = attributeType.getOid();
180 }
181
182
183 // -----------------------------------------------------------------------
184 // ServerValue<byte[]> Methods
185 // -----------------------------------------------------------------------
186 public void normalize() throws NamingException
187 {
188 if ( isNormalized() )
189 {
190 // Bypass the normalization if it has already been done.
191 return;
192 }
193
194 if ( getReference() != null )
195 {
196 Normalizer normalizer = getNormalizer();
197
198 if ( normalizer == null )
199 {
200 normalizedValue = getCopy();
201 setNormalized( false );
202 }
203 else
204 {
205 normalizedValue = ( byte[] ) normalizer.normalize( getCopy() );
206 setNormalized( true );
207 }
208
209 if ( Arrays.equals( super.getReference(), normalizedValue ) )
210 {
211 same = true;
212 }
213 else
214 {
215 same = false;
216 }
217 }
218 else
219 {
220 normalizedValue = null;
221 same = true;
222 setNormalized( false );
223 }
224 }
225
226
227 /**
228 * Gets the normalized (cannonical) representation for the wrapped string.
229 * If the wrapped String is null, null is returned, otherwise the normalized
230 * form is returned. If no the normalizedValue is null, then this method
231 * will attempt to generate it from the wrapped value: repeated calls to
232 * this method do not unnecessarily normalize the wrapped value. Only changes
233 * to the wrapped value result in attempts to normalize the wrapped value.
234 *
235 * @return a reference to the normalized version of the wrapped value
236 */
237 public byte[] getNormalizedValueReference()
238 {
239 if ( isNull() )
240 {
241 return null;
242 }
243
244 if ( !isNormalized() )
245 {
246 try
247 {
248 normalize();
249 }
250 catch ( NamingException ne )
251 {
252 String message = "Cannot normalize the value :" + ne.getMessage();
253 LOG.warn( message );
254 normalized = false;
255 }
256 }
257
258 return normalizedValue;
259 }
260
261
262 /**
263 * Gets the normalized (canonical) representation for the wrapped byte[].
264 * If the wrapped byte[] is null, null is returned, otherwise the normalized
265 * form is returned. If no the normalizedValue is null, then this method
266 * will attempt to generate it from the wrapped value: repeated calls to
267 * this method do not unnecessarily normalize the wrapped value. Only changes
268 * to the wrapped value result in attempts to normalize the wrapped value.
269 *
270 * @return gets the normalized value
271 */
272 public byte[] getNormalizedValue()
273 {
274 if ( isNull() )
275 {
276 return null;
277 }
278
279 if ( !normalized )
280 {
281 try
282 {
283 normalize();
284 }
285 catch ( NamingException ne )
286 {
287 String message = "Cannot normalize the value :" + ne.getMessage();
288 LOG.warn( message );
289 normalized = false;
290 }
291 }
292
293 return normalizedValue;
294 }
295
296
297 /**
298 * Gets a direct reference to the normalized representation for the
299 * wrapped value of this ServerValue wrapper. Implementations will most
300 * likely leverage the attributeType this value is associated with to
301 * determine how to properly normalize the wrapped value.
302 *
303 * @return the normalized version of the wrapped value
304 */
305 public byte[] getNormalizedValueCopy()
306 {
307 if ( isNull() )
308 {
309 return null;
310 }
311
312 if ( normalizedValue == null )
313 {
314 try
315 {
316 normalize();
317 }
318 catch ( NamingException ne )
319 {
320 String message = "Cannot normalize the value :" + ne.getMessage();
321 LOG.warn( message );
322 normalized = false;
323 }
324 }
325
326 if ( normalizedValue != null )
327 {
328 byte[] copy = new byte[ normalizedValue.length ];
329 System.arraycopy( normalizedValue, 0, copy, 0, normalizedValue.length );
330 return copy;
331 }
332 else
333 {
334 return null;
335 }
336 }
337
338
339 /**
340 * Uses the syntaxChecker associated with the attributeType to check if the
341 * value is valid. Repeated calls to this method do not attempt to re-check
342 * the syntax of the wrapped value every time if the wrapped value does not
343 * change. Syntax checks only result on the first check, and when the wrapped
344 * value changes.
345 *
346 * @see Value#isValid()
347 */
348 public final boolean isValid()
349 {
350 if ( valid != null )
351 {
352 return valid;
353 }
354
355 try
356 {
357 valid = attributeType.getSyntax().getSyntaxChecker().isValidSyntax( getReference() );
358 }
359 catch ( NamingException ne )
360 {
361 String message = "Cannot check the syntax : " + ne.getMessage();
362 LOG.error( message );
363 valid = false;
364 }
365
366 return valid;
367 }
368
369
370 /**
371 * @return Tells if the wrapped value and the normalized value are the same
372 */
373 public final boolean isSame()
374 {
375 return same;
376 }
377
378
379 /**
380 *
381 * @see Value#compareTo(Value)
382 * @throws IllegalStateException on failures to extract the comparator, or the
383 * normalizers needed to perform the required comparisons based on the schema
384 */
385 public int compareTo( Value<byte[]> value )
386 {
387 if ( isNull() )
388 {
389 if ( ( value == null ) || value.isNull() )
390 {
391 return 0;
392 }
393 else
394 {
395 return -1;
396 }
397 }
398 else
399 {
400 if ( ( value == null ) || value.isNull() )
401 {
402 return 1;
403 }
404 }
405
406 if ( value instanceof ServerBinaryValue )
407 {
408 ServerBinaryValue binaryValue = ( ServerBinaryValue ) value;
409
410 try
411 {
412 Comparator<? super Value<byte[]>> comparator = getComparator();
413
414 if ( comparator != null )
415 {
416 return getComparator().compare( getNormalizedValueReference(), binaryValue.getNormalizedValueReference() );
417 }
418 else
419 {
420 return ByteArrayComparator.INSTANCE.compare( getNormalizedValueReference(),
421 binaryValue.getNormalizedValueReference() );
422 }
423 }
424 catch ( NamingException e )
425 {
426 String msg = "Failed to compare normalized values for " + Arrays.toString( getReference() )
427 + " and " + value;
428 LOG.error( msg, e );
429 throw new IllegalStateException( msg, e );
430 }
431 }
432
433 String message = "I don't really know how to compare anything other " +
434 "than ServerBinaryValues at this point in time.";
435 LOG.error( message );
436 throw new NotImplementedException( message );
437 }
438
439
440 /**
441 * Get the associated AttributeType
442 * @return The AttributeType
443 */
444 public AttributeType getAttributeType()
445 {
446 return attributeType;
447 }
448
449
450 /**
451 * Check if the value is stored into an instance of the given
452 * AttributeType, or one of its ascendant.
453 *
454 * For instance, if the Value is associated with a CommonName,
455 * checking for Name will match.
456 *
457 * @param attributeType The AttributeType we are looking at
458 * @return <code>true</code> if the value is associated with the given
459 * attributeType or one of its ascendant
460 */
461 public boolean instanceOf( AttributeType attributeType ) throws NamingException
462 {
463 if ( this.attributeType.equals( attributeType ) )
464 {
465 return true;
466 }
467
468 return this.attributeType.isDescentantOf( attributeType );
469 }
470
471
472 // -----------------------------------------------------------------------
473 // Object Methods
474 // -----------------------------------------------------------------------
475 /**
476 * @see Object#hashCode()
477 * @return the instance's hash code
478 */
479 public int hashCode()
480 {
481 // return zero if the value is null so only one null value can be
482 // stored in an attribute - the string version does the same
483 if ( isNull() )
484 {
485 return 0;
486 }
487
488 return Arrays.hashCode( getNormalizedValueReference() );
489 }
490
491
492 /**
493 * Checks to see if this ServerBinaryValue equals the supplied object.
494 *
495 * This equals implementation overrides the BinaryValue implementation which
496 * is not schema aware.
497 * @throws IllegalStateException on failures to extract the comparator, or the
498 * normalizers needed to perform the required comparisons based on the schema
499 */
500 public boolean equals( Object obj )
501 {
502 if ( this == obj )
503 {
504 return true;
505 }
506
507 if ( ! ( obj instanceof ServerBinaryValue ) )
508 {
509 return false;
510 }
511
512 ServerBinaryValue other = ( ServerBinaryValue ) obj;
513
514 if ( !attributeType.equals( other.attributeType ) )
515 {
516 return false;
517 }
518
519 if ( isNull() )
520 {
521 return other.isNull();
522 }
523
524 // Shortcut : if the values are equals, no need to compare
525 // the normalized values
526 if ( Arrays.equals( wrapped, other.get() ) )
527 {
528 return true;
529 }
530 else
531 {
532 try
533 {
534 Comparator<byte[]> comparator = getComparator();
535
536 // Compare normalized values
537 if ( comparator == null )
538 {
539 return Arrays.equals( getNormalizedValueReference(), other.getNormalizedValueReference() );
540 }
541 else
542 {
543 return comparator.compare( getNormalizedValueReference(), other.getNormalizedValueReference() ) == 0;
544 }
545 }
546 catch ( NamingException ne )
547 {
548 return false;
549 }
550 }
551 }
552
553
554 // -----------------------------------------------------------------------
555 // Private Helper Methods (might be put into abstract base class)
556 // -----------------------------------------------------------------------
557 /**
558 * Find a matchingRule to use for normalization and comparison. If an equality
559 * matchingRule cannot be found it checks to see if other matchingRules are
560 * available: SUBSTR, and ORDERING. If a matchingRule cannot be found null is
561 * returned.
562 *
563 * @return a matchingRule or null if one cannot be found for the attributeType
564 * @throws NamingException if resolution of schema entities fail
565 */
566 private MatchingRule getMatchingRule() throws NamingException
567 {
568 MatchingRule mr = attributeType.getEquality();
569
570 if ( mr == null )
571 {
572 mr = attributeType.getOrdering();
573 }
574
575 if ( mr == null )
576 {
577 mr = attributeType.getSubstr();
578 }
579
580 return mr;
581 }
582
583
584 /**
585 * Gets a normalizer using getMatchingRule() to resolve the matchingRule
586 * that the normalizer is extracted from.
587 *
588 * @return a normalizer associated with the attributeType or null if one cannot be found
589 * @throws NamingException if resolution of schema entities fail
590 */
591 private Normalizer getNormalizer() throws NamingException
592 {
593 MatchingRule mr = getMatchingRule();
594
595 if ( mr == null )
596 {
597 return null;
598 }
599
600 return mr.getNormalizer();
601 }
602
603
604 /**
605 * Gets a comparator using getMatchingRule() to resolve the matching
606 * that the comparator is extracted from.
607 *
608 * @return a comparator associated with the attributeType or null if one cannot be found
609 * @throws NamingException if resolution of schema entities fail
610 */
611 private Comparator getComparator() throws NamingException
612 {
613 MatchingRule mr = getMatchingRule();
614
615 if ( mr == null )
616 {
617 return null;
618 }
619
620 return mr.getComparator();
621 }
622
623
624 /**
625 * @return a copy of the current value
626 */
627 public ServerBinaryValue clone()
628 {
629 ServerBinaryValue clone = (ServerBinaryValue)super.clone();
630
631 if ( normalizedValue != null )
632 {
633 clone.normalizedValue = new byte[ normalizedValue.length ];
634 System.arraycopy( normalizedValue, 0, clone.normalizedValue, 0, normalizedValue.length );
635 }
636
637 return clone;
638 }
639
640
641 /**
642 * @see Externalizable#writeExternal(ObjectOutput)
643 *
644 * We can't use this method for a ServerBinaryValue, as we have to feed the value
645 * with an AttributeType object
646 */
647 public void writeExternal( ObjectOutput out ) throws IOException
648 {
649 throw new IllegalStateException( "Cannot use standard serialization for a ServerStringValue" );
650 }
651
652
653 /**
654 * We will write the value and the normalized value, only
655 * if the normalized value is different.
656 *
657 * If the value is empty, a flag is written at the beginning with
658 * the value true, otherwise, a false is written.
659 *
660 * The data will be stored following this structure :
661 * [length] the wrapped length. Can be -1, if wrapped is null
662 * [value length]
663 * [UP value] if not empty
664 * [normalized] (will be false if the value can't be normalized)
665 * [same] (a flag set to true if the normalized value equals the UP value)
666 * [Norm value] (the normalized value if different from the UP value, and not empty)
667 *
668 * @param out the buffer in which we will stored the serialized form of the value
669 * @throws IOException if we can't write into the buffer
670 */
671 public void serialize( ObjectOutput out ) throws IOException
672 {
673 if ( wrapped != null )
674 {
675 // write a the wrapped length
676 out.writeInt( wrapped.length );
677
678 // Write the data if not empty
679 if ( wrapped.length > 0 )
680 {
681 // The data
682 out.write( wrapped );
683
684 // Normalize the data
685 try
686 {
687 normalize();
688
689 if ( !normalized )
690 {
691 // We may not have a normalizer. Just get out
692 // after having writen the flag
693 out.writeBoolean( false );
694 }
695 else
696 {
697 // Write a flag indicating that the data has been normalized
698 out.writeBoolean( true );
699
700 if ( Arrays.equals( getReference(), normalizedValue ) )
701 {
702 // Write the 'same = true' flag
703 out.writeBoolean( true );
704 }
705 else
706 {
707 // Write the 'same = false' flag
708 out.writeBoolean( false );
709
710 // Write the normalized value length
711 out.write( normalizedValue.length );
712
713 if ( normalizedValue.length > 0 )
714 {
715 // Write the normalized value if not empty
716 out.write( normalizedValue );
717 }
718 }
719 }
720 }
721 catch ( NamingException ne )
722 {
723 // The value can't be normalized, we don't write the
724 // normalized value.
725 normalizedValue = null;
726 out.writeBoolean( false );
727 }
728 }
729 }
730 else
731 {
732 // Write -1 indicating that the value is null
733 out.writeInt( -1 );
734 }
735 }
736
737
738 /**
739 * @see Externalizable#readExternal(ObjectInput)
740 *
741 * We can't use this method for a ServerBinaryValue, as we have to feed the value
742 * with an AttributeType object
743 */
744 public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
745 {
746 throw new IllegalStateException( "Cannot use standard serialization for a ServerStringValue" );
747 }
748
749
750 /**
751 *
752 * Deserialize a ServerBinaryValue.
753 *
754 * @param in the buffer containing the bytes with the serialized value
755 * @throws IOException
756 * @throws ClassNotFoundException
757 */
758 public void deserialize( ObjectInput in ) throws IOException, ClassNotFoundException
759 {
760 // The UP value length
761 int wrappedLength = in.readInt();
762
763 if ( wrappedLength == -1 )
764 {
765 // If the value is null, the length will be set to -1
766 same = true;
767 wrapped = null;
768 }
769 else if ( wrappedLength == 0 )
770 {
771 wrapped = StringTools.EMPTY_BYTES;
772 same = true;
773 normalized = true;
774 normalizedValue = wrapped;
775 }
776 else
777 {
778 wrapped = new byte[wrappedLength];
779
780 // Read the data
781 in.readFully( wrapped );
782
783 // Check if we have a normalized value
784 normalized = in.readBoolean();
785
786 if ( normalized )
787 {
788 // Read the 'same' flag
789 same = in.readBoolean();
790
791 if ( !same )
792 {
793 // Read the normalizedvalue length
794 int normalizedLength = in.readInt();
795
796 if ( normalizedLength > 0 )
797 {
798 normalizedValue = new byte[normalizedLength];
799
800 // Read the normalized value
801 in.read( normalizedValue, 0, normalizedLength );
802 }
803 else
804 {
805 normalizedValue = StringTools.EMPTY_BYTES;
806 }
807 }
808 else
809 {
810 normalizedValue = new byte[wrappedLength];
811 System.arraycopy( wrapped, 0, normalizedValue, 0, wrappedLength );
812 }
813 }
814 }
815 }
816 }