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 * https://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 */
20 package org.apache.directory.api.ldap.model.name;
21
22
23 import java.io.Externalizable;
24 import java.io.IOException;
25 import java.io.ObjectInput;
26 import java.io.ObjectOutput;
27 import java.util.Arrays;
28
29 import org.apache.directory.api.i18n.I18n;
30 import org.apache.directory.api.ldap.model.entry.Value;
31 import org.apache.directory.api.ldap.model.exception.LdapException;
32 import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException;
33 import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException;
34 import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
35 import org.apache.directory.api.ldap.model.schema.AttributeType;
36 import org.apache.directory.api.ldap.model.schema.LdapComparator;
37 import org.apache.directory.api.ldap.model.schema.MatchingRule;
38 import org.apache.directory.api.ldap.model.schema.Normalizer;
39 import org.apache.directory.api.ldap.model.schema.SchemaManager;
40 import org.apache.directory.api.util.Serialize;
41 import org.apache.directory.api.util.Strings;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44
45
46 /**
47 * <p>
48 * A Attribute Type And Value, which is the basis of all Rdn. It contains a
49 * type, and a value. The type must not be case sensitive. Superfluous leading
50 * and trailing spaces MUST have been trimmed before. The value MUST be in UTF8
51 * format, according to RFC 2253. If the type is in OID form, then the value
52 * must be a hexadecimal string prefixed by a '#' character. Otherwise, the
53 * string must respect the RC 2253 grammar.
54 * </p>
55 * <p>
56 * We will also keep a User Provided form of the AVA (Attribute Type And Value),
57 * called upName.
58 * </p>
59 * <p>
60 * This class is immutable
61 * </p>
62 *
63 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
64 */
65 public class Ava implements Externalizable, Cloneable, Comparable<Ava>
66 {
67 /**
68 * Declares the Serial Version Uid.
69 *
70 * @see <a
71 * href="http://c2.com/cgi/wiki?AlwaysDeclareSerialVersionUid">Always
72 * Declare Serial Version Uid</a>
73 */
74 private static final long serialVersionUID = 1L;
75
76 /** The LoggerFactory used by this class */
77 private static final Logger LOG = LoggerFactory.getLogger( Ava.class );
78
79 /** The normalized Name type */
80 /* package protected */ String normType;
81
82 /** The user provided Name type */
83 /* package protected */ String upType;
84
85 /** The value. It can be a String or a byte array */
86 /* package protected */ Value value;
87
88 /** The user provided Ava */
89 /* package protected */ String upName;
90
91 /** The attributeType if the Ava is schemaAware */
92 /* package protected */ AttributeType attributeType;
93
94 /** the schema manager */
95 private transient SchemaManager schemaManager;
96
97 /** The computed hashcode */
98 private volatile int h;
99
100
101 /**
102 * Constructs an empty Ava
103 */
104 public Ava()
105 {
106 this( null );
107 }
108
109
110 /**
111 * Constructs an empty schema aware Ava.
112 *
113 * @param schemaManager The SchemaManager instance
114 */
115 public Ava( SchemaManager schemaManager )
116 {
117 normType = null;
118 upType = null;
119 value = null;
120 upName = "";
121 this.schemaManager = schemaManager;
122 attributeType = null;
123 }
124
125
126 /**
127 * Constructs new Ava using the provided SchemaManager and AVA
128 *
129 * @param schemaManager The SchemaManager instance
130 * @param ava The AVA to copy
131 * @throws LdapInvalidDnException If the Ava is invalid
132 */
133 public Ava( SchemaManager schemaManager, Ava ava ) throws LdapInvalidDnException
134 {
135 upType = ava.upType;
136 this.schemaManager = schemaManager;
137
138 if ( ava.isSchemaAware() )
139 {
140 normType = ava.normType;
141 value = ava.value;
142 attributeType = ava.getAttributeType();
143 }
144 else
145 {
146 if ( schemaManager != null )
147 {
148 attributeType = schemaManager.getAttributeType( ava.normType );
149
150 if ( attributeType != null )
151 {
152 normType = attributeType.getOid();
153
154 try
155 {
156 value = new Value( attributeType, ava.value );
157 }
158 catch ( LdapInvalidAttributeValueException e )
159 {
160 throw new LdapInvalidDnException( e.getResultCode() );
161 }
162 }
163 else
164 {
165 normType = ava.normType;
166 value = ava.value;
167 }
168 }
169 else
170 {
171 normType = ava.normType;
172 value = ava.value;
173 }
174 }
175
176 upName = getEscaped();
177
178 hashCode();
179 }
180
181
182 /**
183 * Construct an Ava containing a binary value.
184 * <p>
185 * Note that the upValue should <b>not</b> be null or empty, or resolve
186 * to an empty string after having trimmed it.
187 *
188 * @param upType The User Provided type
189 * @param upValue The User Provided binary value
190 *
191 * @throws LdapInvalidDnException If the given type or value are invalid
192 */
193 public Ava( String upType, byte[] upValue ) throws LdapInvalidDnException
194 {
195 this( null, upType, upValue );
196 }
197
198
199 /**
200 * Construct a schema aware Ava containing a binary value. The AttributeType
201 * and value will be normalized accordingly to the given SchemaManager.
202 * <p>
203 * Note that the upValue should <b>not</b> be null or empty, or resolve
204 * to an empty string after having trimmed it.
205 *
206 * @param schemaManager The SchemaManager instance
207 * @param upType The User Provided type
208 * @param upValue The User Provided binary value
209 *
210 * @throws LdapInvalidDnException If the given type or value are invalid
211 */
212 public Ava( SchemaManager schemaManager, String upType, byte[] upValue ) throws LdapInvalidDnException
213 {
214 if ( schemaManager != null )
215 {
216 this.schemaManager = schemaManager;
217
218 try
219 {
220 attributeType = schemaManager.lookupAttributeTypeRegistry( upType );
221 }
222 catch ( LdapException le )
223 {
224 String message = I18n.err( I18n.ERR_13600_TYPE_IS_NULL_OR_EMPTY );
225 // Do NOT log the message here. The error may be handled and therefore the log message may polute the log files.
226 // Let the caller log the exception if needed.
227 throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message, le );
228 }
229
230 try
231 {
232 createAva( schemaManager, upType, new Value( attributeType, upValue ) );
233 }
234 catch ( LdapInvalidAttributeValueException liave )
235 {
236 String message = I18n.err( I18n.ERR_13600_TYPE_IS_NULL_OR_EMPTY );
237 throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message, liave );
238 }
239 }
240 else
241 {
242 createAva( upType, new Value( upValue ) );
243 }
244 }
245
246
247 /**
248 * Construct a schema aware Ava containing a binary value. The AttributeType
249 * and value will be normalized accordingly to the given SchemaManager.
250 * <p>
251 * Note that the upValue should <b>not</b> be null or empty, or resolve
252 * to an empty string after having trimmed it.
253 *
254 * @param schemaManager The SchemaManager instance
255 * @param upType The User Provided type
256 * @param upName the User Provided AVA
257 * @param upValue The User Provided binary value
258 *
259 * @throws LdapInvalidDnException If the given type or value are invalid
260 */
261 public Ava( SchemaManager schemaManager, String upType, String upName, byte[] upValue ) throws LdapInvalidDnException
262 {
263 if ( schemaManager != null )
264 {
265 this.schemaManager = schemaManager;
266
267 try
268 {
269 attributeType = schemaManager.lookupAttributeTypeRegistry( upType );
270 }
271 catch ( LdapException le )
272 {
273 String message = I18n.err( I18n.ERR_13600_TYPE_IS_NULL_OR_EMPTY );
274 // Do NOT log the message here. The error may be handled and therefore the log message may polute the log files.
275 // Let the caller log the exception if needed.
276 throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message, le );
277 }
278
279 try
280 {
281 createAva( schemaManager, upType, new Value( attributeType, upValue ) );
282 }
283 catch ( LdapInvalidAttributeValueException liave )
284 {
285 String message = I18n.err( I18n.ERR_13600_TYPE_IS_NULL_OR_EMPTY );
286 throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message, liave );
287 }
288 }
289 else
290 {
291 createAva( upType, new Value( upValue ) );
292 }
293
294 this.upName = upName;
295 }
296
297
298 /**
299 * Construct an Ava with a String value.
300 * <p>
301 * Note that the upValue should <b>not</b> be null or empty, or resolve
302 * to an empty string after having trimmed it.
303 *
304 * @param upType The User Provided type
305 * @param upValue The User Provided String value
306 *
307 * @throws LdapInvalidDnException If the given type or value are invalid
308 */
309 public Ava( String upType, String upValue ) throws LdapInvalidDnException
310 {
311 this( null, upType, upValue );
312 }
313
314
315 /**
316 * Construct a schema aware Ava with a String value.
317 * <p>
318 * Note that the upValue should <b>not</b> be null or empty, or resolve
319 * to an empty string after having trimmed it.
320 *
321 * @param schemaManager The SchemaManager instance
322 * @param upType The User Provided type
323 * @param upValue The User Provided String value
324 *
325 * @throws LdapInvalidDnException If the given type or value are invalid
326 */
327 public Ava( SchemaManager schemaManager, String upType, String upValue ) throws LdapInvalidDnException
328 {
329 if ( schemaManager != null )
330 {
331 this.schemaManager = schemaManager;
332
333 try
334 {
335 attributeType = schemaManager.lookupAttributeTypeRegistry( upType );
336 }
337 catch ( LdapException le )
338 {
339 String message = I18n.err( I18n.ERR_13600_TYPE_IS_NULL_OR_EMPTY );
340 // Do NOT log the message here. The error may be handled and therefore the log message may polute the log files.
341 // Let the caller log the exception if needed.
342 throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message, le );
343 }
344
345 try
346 {
347 createAva( schemaManager, upType, new Value( attributeType, upValue ) );
348 }
349 catch ( LdapInvalidAttributeValueException liave )
350 {
351 String message = I18n.err( I18n.ERR_13600_TYPE_IS_NULL_OR_EMPTY );
352 throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message, liave );
353 }
354 }
355 else
356 {
357 createAva( upType, new Value( upValue ) );
358 }
359 }
360
361
362 /**
363 * Construct a schema aware Ava with a String value.
364 * <p>
365 * Note that the upValue should <b>not</b> be null or empty, or resolve
366 * to an empty string after having trimmed it.
367 *
368 * @param schemaManager The SchemaManager instance
369 * @param upType The User Provided type
370 * @param upName the User provided AVA
371 * @param upValue The User Provided String value
372 *
373 * @throws LdapInvalidDnException If the given type or value are invalid
374 */
375 public Ava( SchemaManager schemaManager, String upType, String upName, String upValue ) throws LdapInvalidDnException
376 {
377 if ( schemaManager != null )
378 {
379 this.schemaManager = schemaManager;
380
381 try
382 {
383 attributeType = schemaManager.lookupAttributeTypeRegistry( upType );
384 }
385 catch ( LdapException le )
386 {
387 String message = I18n.err( I18n.ERR_13600_TYPE_IS_NULL_OR_EMPTY );
388 throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message, le );
389 }
390
391 try
392 {
393 createAva( schemaManager, upType, new Value( attributeType, upValue ) );
394 }
395 catch ( LdapInvalidAttributeValueException liave )
396 {
397 String message = I18n.err( I18n.ERR_13600_TYPE_IS_NULL_OR_EMPTY );
398 throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message, liave );
399 }
400 }
401 else
402 {
403 createAva( upType, new Value( upValue ) );
404 }
405
406 this.upName = upName;
407 }
408
409
410 /**
411 * Construct an Ava. The type and value are normalized :
412 * <ul>
413 * <li> the type is trimmed and lowercased </li>
414 * <li> the value is trimmed </li>
415 * </ul>
416 * <p>
417 * Note that the upValue should <b>not</b> be null or empty, or resolved
418 * to an empty string after having trimmed it.
419 *
420 * @param upType The User Provided type
421 * @param normType The normalized type
422 * @param value The User Provided value
423 * @param upName The User Provided name (may be escaped)
424 *
425 * @throws LdapInvalidDnException If the given type or value are invalid
426 */
427 // WARNING : The protection level is left unspecified intentionally.
428 // We need this method to be visible from the DnParser class, but not
429 // from outside this package.
430 /* Unspecified protection */Ava( String upType, String normType, Value value, String upName )
431 throws LdapInvalidDnException
432 {
433 this( null, upType, normType, value, upName );
434 }
435
436
437 /**
438 * Construct an Ava. The type and value are normalized :
439 * <ul>
440 * <li> the type is trimmed and lowercased </li>
441 * <li> the value is trimmed </li>
442 * </ul>
443 * <p>
444 * Note that the upValue should <b>not</b> be null or empty, or resolved
445 * to an empty string after having trimmed it.
446 *
447 * @param attributeType The AttributeType for this value
448 * @param upType The User Provided type
449 * @param normType The normalized type
450 * @param value The value
451 * @param upName The User Provided name (may be escaped)
452 *
453 * @throws LdapInvalidDnException If the given type or value are invalid
454 */
455 // WARNING : The protection level is left unspecified intentionally.
456 // We need this method to be visible from the DnParser class, but not
457 // from outside this package.
458 /* Unspecified protection */Ava( AttributeType attributeType, String upType, String normType, Value value, String upName )
459 throws LdapInvalidDnException
460 {
461 this.attributeType = attributeType;
462 String upTypeTrimmed = Strings.trim( upType );
463 String normTypeTrimmed = Strings.trim( normType );
464
465 if ( Strings.isEmpty( upTypeTrimmed ) )
466 {
467 if ( Strings.isEmpty( normTypeTrimmed ) )
468 {
469 String message = I18n.err( I18n.ERR_13600_TYPE_IS_NULL_OR_EMPTY );
470 LOG.error( message );
471 throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message );
472 }
473 else
474 {
475 // In this case, we will use the normType instead
476 this.normType = Strings.lowerCaseAscii( normTypeTrimmed );
477 this.upType = normType;
478 }
479 }
480 else if ( Strings.isEmpty( normTypeTrimmed ) )
481 {
482 // In this case, we will use the upType instead
483 this.normType = Strings.lowerCaseAscii( upTypeTrimmed );
484 this.upType = upType;
485 }
486 else
487 {
488 this.normType = Strings.lowerCaseAscii( normTypeTrimmed );
489 this.upType = upType;
490 }
491
492 this.value = value;
493 this.upName = upName;
494 hashCode();
495 }
496
497
498 /**
499 * Construct an Ava. The type and value are normalized :
500 * <ul>
501 * <li> the type is trimmed and lowercased </li>
502 * <li> the value is trimmed </li>
503 * </ul>
504 * <p>
505 * Note that the upValue should <b>not</b> be null or empty, or resolved
506 * to an empty string after having trimmed it.
507 *
508 * @param schemaManager The SchemaManager
509 * @param upType The User Provided type
510 * @param normType The normalized type
511 * @param value The value
512 *
513 * @throws LdapInvalidDnException If the given type or value are invalid
514 */
515 // WARNING : The protection level is left unspecified intentionally.
516 // We need this method to be visible from the DnParser class, but not
517 // from outside this package.
518 /* Unspecified protection */Ava( SchemaManager schemaManager, String upType, String normType, Value value )
519 throws LdapInvalidDnException
520 {
521 StringBuilder sb = new StringBuilder();
522
523 this.upType = upType;
524 this.normType = normType;
525 this.value = value;
526
527 sb.append( upType );
528 sb.append( '=' );
529
530 if ( ( value != null ) && ( value.getString() != null ) )
531 {
532 sb.append( value.getString() );
533 }
534
535 upName = sb.toString();
536
537 if ( schemaManager != null )
538 {
539 apply( schemaManager );
540 }
541
542 hashCode();
543 }
544
545
546 /**
547 * Construct a schema aware Ava. The AttributeType and value will be checked accordingly
548 * to the SchemaManager.
549 * <p>
550 * Note that the upValue should <b>not</b> be null or empty, or resolve
551 * to an empty string after having trimmed it.
552 *
553 * @param schemaManager The SchemaManager instance
554 * @param upType The User Provided type
555 * @param value The value
556 */
557 private void createAva( SchemaManager schemaManager, String upType, Value value )
558 {
559 StringBuilder sb = new StringBuilder();
560
561 normType = attributeType.getOid();
562 this.upType = upType;
563 this.value = value;
564
565 sb.append( upType );
566 sb.append( '=' );
567
568 if ( value != null )
569 {
570 sb.append( Rdn.escapeValue( value.getString() ) );
571 }
572
573 upName = sb.toString();
574
575 hashCode();
576 }
577
578
579 /**
580 * Construct an Ava. The type and value are normalized :
581 * <ul>
582 * <li> the type is trimmed and lowercased </li>
583 * <li> the value is trimmed </li>
584 * </ul>
585 * <p>
586 * Note that the upValue should <b>not</b> be null or empty, or resolved
587 * to an empty string after having trimmed it.
588 *
589 * @param upType The User Provided type
590 * @param upValue The User Provided value
591 *
592 * @throws LdapInvalidDnException If the given type or value are invalid
593 */
594 private void createAva( String upType, Value upValue ) throws LdapInvalidDnException
595 {
596 String upTypeTrimmed = Strings.trim( upType );
597 String normTypeTrimmed = Strings.trim( normType );
598
599 if ( Strings.isEmpty( upTypeTrimmed ) )
600 {
601 if ( Strings.isEmpty( normTypeTrimmed ) )
602 {
603 String message = I18n.err( I18n.ERR_13600_TYPE_IS_NULL_OR_EMPTY );
604 // Do NOT log the message here. The error may be handled and therefore the log message may polute the log files.
605 // Let the caller log the exception if needed.
606 throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message );
607 }
608 else
609 {
610 // In this case, we will use the normType instead
611 this.normType = Strings.lowerCaseAscii( normTypeTrimmed );
612 this.upType = normType;
613 }
614 }
615 else if ( Strings.isEmpty( normTypeTrimmed ) )
616 {
617 // In this case, we will use the upType instead
618 this.normType = Strings.lowerCaseAscii( upTypeTrimmed );
619 this.upType = upType;
620 }
621 else
622 {
623 this.normType = Strings.lowerCaseAscii( normTypeTrimmed );
624 this.upType = upType;
625
626 }
627
628 value = upValue;
629
630 upName = getEscaped();
631
632 hashCode();
633 }
634
635
636 /**
637 * Apply a SchemaManager to the Ava. It will normalize the Ava.<br>
638 * If the Ava already had a SchemaManager, then the new SchemaManager will be
639 * used instead.
640 *
641 * @param schemaManager The SchemaManager instance to use
642 * @throws LdapInvalidDnException If the Ava can't be normalized accordingly
643 * to the given SchemaManager
644 */
645 private void apply( SchemaManager schemaManager ) throws LdapInvalidDnException
646 {
647 if ( schemaManager != null )
648 {
649 this.schemaManager = schemaManager;
650
651 AttributeType tmpAttributeType = null;
652
653 try
654 {
655 tmpAttributeType = schemaManager.lookupAttributeTypeRegistry( normType );
656 }
657 catch ( LdapException le )
658 {
659 if ( schemaManager.isRelaxed() )
660 {
661 // No attribute in the schema, but the schema is relaxed : get out
662 return;
663 }
664 else
665 {
666 String message = I18n.err( I18n.ERR_13600_TYPE_IS_NULL_OR_EMPTY );
667 // Do NOT log the message here. The error may be handled and therefore the log message may polute the log files.
668 // Let the caller log the exception if needed.
669 throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message, le );
670 }
671 }
672
673 if ( this.attributeType == tmpAttributeType )
674 {
675 // No need to normalize again
676 return;
677 }
678 else
679 {
680 this.attributeType = tmpAttributeType;
681 }
682
683 try
684 {
685 value = new Value( tmpAttributeType, value );
686 }
687 catch ( LdapException le )
688 {
689 String message = I18n.err( I18n.ERR_13600_TYPE_IS_NULL_OR_EMPTY );
690 throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message, le );
691 }
692
693 hashCode();
694 }
695 }
696
697
698 /**
699 * Get the normalized type of a Ava
700 *
701 * @return The normalized type
702 */
703 public String getNormType()
704 {
705 return normType;
706 }
707
708
709 /**
710 * Get the user provided type of a Ava
711 *
712 * @return The user provided type
713 */
714 public String getType()
715 {
716 return upType;
717 }
718
719
720 /**
721 * Get the Value of a Ava
722 *
723 * @return The value
724 */
725 public Value getValue()
726 {
727 return value.clone();
728 }
729
730
731 /**
732 * Get the user provided form of this attribute type and value
733 *
734 * @return The user provided form of this ava
735 */
736 public String getName()
737 {
738 return upName;
739 }
740
741
742 /**
743 * @return The Ava as an escaped String
744 */
745 public String getEscaped()
746 {
747 StringBuilder sb = new StringBuilder();
748
749 sb.append( getType() );
750 sb.append( '=' );
751 sb.append( value.getEscaped() );
752
753 return sb.toString();
754 }
755
756
757 /**
758 * Implements the cloning.
759 *
760 * @return a clone of this object
761 */
762 @Override
763 public Ava clone()
764 {
765 try
766 {
767 Ava clone = ( Ava ) super.clone();
768 clone.value = value.clone();
769
770 return clone;
771 }
772 catch ( CloneNotSupportedException cnse )
773 {
774 throw new Error( I18n.err( I18n.ERR_13621_ASSERTION_FAILURE ), cnse );
775 }
776 }
777
778
779 /**
780 * Gets the hashcode of this object.
781 *
782 * @see java.lang.Object#hashCode()
783 * @return The instance hash code
784 */
785 @Override
786 public int hashCode()
787 {
788 if ( h == 0 )
789 {
790 int hTmp = 37;
791
792 hTmp = hTmp * 17 + ( normType != null ? normType.hashCode() : 0 );
793 h = hTmp * 17 + ( value != null ? value.hashCode() : 0 );
794 }
795
796 return h;
797 }
798
799
800 /**
801 * @see Object#equals(Object)
802 */
803 @Override
804 public boolean equals( Object obj )
805 {
806 if ( this == obj )
807 {
808 return true;
809 }
810
811 if ( !( obj instanceof Ava ) )
812 {
813 return false;
814 }
815
816 Ava instance = ( Ava ) obj;
817
818 // Compare the type
819 if ( attributeType == null )
820 {
821 if ( normType == null )
822 {
823 if ( instance.normType != null )
824 {
825 return false;
826 }
827 }
828 else
829 {
830 if ( !normType.equals( instance.normType ) )
831 {
832 return false;
833 }
834 }
835 }
836 else
837 {
838 if ( instance.getAttributeType() == null )
839 {
840 if ( ( schemaManager != null )
841 && !attributeType.equals( schemaManager.getAttributeType( instance.getType() ) ) )
842 {
843 return false;
844 }
845 }
846 else if ( !attributeType.equals( instance.getAttributeType() ) )
847 {
848 return false;
849 }
850 }
851
852 // Compare the values
853 if ( ( value == null ) || value.isNull() )
854 {
855 return ( instance.value == null ) || instance.value.isNull();
856 }
857 else
858 {
859 if ( schemaManager != null )
860 {
861 if ( ( value.getString() != null ) && value.getString().equals( instance.value.getString() ) )
862 {
863 return true;
864 }
865
866 if ( attributeType == null )
867 {
868 attributeType = schemaManager.getAttributeType( normType );
869 }
870
871 MatchingRule equalityMatchingRule = attributeType.getEquality();
872
873 if ( equalityMatchingRule != null )
874 {
875 Normalizer normalizer = equalityMatchingRule.getNormalizer();
876
877 try
878 {
879 return equalityMatchingRule.getLdapComparator().compare( normalizer.normalize( value.getString() ),
880 instance.value.getString() ) == 0;
881 }
882 catch ( LdapException le )
883 {
884 // TODO: is this OK? If the comparison is not reliable without normalization then we should throw exception
885 // instead returning false. Returning false may be misleading and the log message can be easily overlooked.
886 // If the comparison is reliable, this should not really be an error. Maybe use debug or trace instead?
887 LOG.error( I18n.err( I18n.ERR_13620_CANNOT_NORMALIZE_VALUE ), le.getMessage() );
888 return false;
889 }
890 }
891 else
892 {
893 // No Equality MR, use a direct comparison
894 if ( !value.isHumanReadable() )
895 {
896 return Arrays.equals( value.getBytes(), instance.value.getBytes() );
897 }
898 else
899 {
900 return value.getString().equals( instance.value.getString() );
901 }
902 }
903 }
904 else
905 {
906 return value.equals( instance.value );
907 }
908 }
909 }
910
911
912 /**
913 * Serialize the AVA into a buffer at the given position.
914 *
915 * @param buffer The buffer which will contain the serialized Ava
916 * @param pos The position in the buffer for the serialized value
917 * @return The new position in the buffer
918 * @throws IOException Id the serialization failed
919 */
920 public int serialize( byte[] buffer, int pos ) throws IOException
921 {
922 if ( Strings.isEmpty( upName )
923 || Strings.isEmpty( upType )
924 || Strings.isEmpty( normType )
925 || ( value.isNull() ) )
926 {
927 String message;
928
929 if ( Strings.isEmpty( upName ) )
930 {
931 message = I18n.err( I18n.ERR_13616_CANNOT_SERIALIZE_AVA_UPNAME_NULL );
932 }
933 else if ( Strings.isEmpty( upType ) )
934 {
935 message = I18n.err( I18n.ERR_13617_CANNOT_SERIALIZE_AVA_UPTYPE_NULL );
936 }
937 else if ( Strings.isEmpty( normType ) )
938 {
939 message = I18n.err( I18n.ERR_13618_CANNOT_SERIALIZE_AVA_NORMTYPE_NULL );
940 }
941 else
942 {
943 message = I18n.err( I18n.ERR_13619_CANNOT_SERIALIZE_AVA_VALUE_NULL );
944 }
945
946 LOG.error( message );
947 throw new IOException( message );
948 }
949
950 int length = 0;
951
952 // The upName
953 byte[] upNameBytes = null;
954
955 if ( upName != null )
956 {
957 upNameBytes = Strings.getBytesUtf8( upName );
958 length += 1 + 4 + upNameBytes.length;
959 }
960
961 // The upType
962 byte[] upTypeBytes = null;
963
964 if ( upType != null )
965 {
966 upTypeBytes = Strings.getBytesUtf8( upType );
967 length += 1 + 4 + upTypeBytes.length;
968 }
969
970 // Is HR
971 length++;
972
973 // The hash code
974 length += 4;
975
976 // Check that we will be able to store the data in the buffer
977 if ( buffer.length - pos < length )
978 {
979 throw new ArrayIndexOutOfBoundsException();
980 }
981
982 // Write the upName
983 if ( upName != null )
984 {
985 buffer[pos++] = Serialize.TRUE;
986 pos = Serialize.serialize( upNameBytes, buffer, pos );
987 }
988 else
989 {
990 buffer[pos++] = Serialize.FALSE;
991 }
992
993 // Write the upType
994 if ( upType != null )
995 {
996 buffer[pos++] = Serialize.TRUE;
997 pos = Serialize.serialize( upTypeBytes, buffer, pos );
998 }
999 else
1000 {
1001 buffer[pos++] = Serialize.FALSE;
1002 }
1003
1004 // Write the isHR flag
1005 if ( value.isHumanReadable() )
1006 {
1007 buffer[pos++] = Serialize.TRUE;
1008 }
1009 else
1010 {
1011 buffer[pos++] = Serialize.FALSE;
1012 }
1013
1014 // Write the upValue
1015 if ( value.isHumanReadable() )
1016 {
1017 pos = value.serialize( buffer, pos );
1018 }
1019
1020 // Write the hash code
1021 pos = Serialize.serialize( h, buffer, pos );
1022
1023 return pos;
1024 }
1025
1026
1027 /**
1028 * Deserialize an AVA from a byte[], starting at a given position
1029 *
1030 * @param buffer The buffer containing the AVA
1031 * @param pos The position in the buffer
1032 * @return The new position
1033 * @throws IOException If the serialized value is not an AVA
1034 * @throws LdapInvalidAttributeValueException If the serialized AVA is invalid
1035 */
1036 public int deserialize( byte[] buffer, int pos ) throws IOException, LdapInvalidAttributeValueException
1037 {
1038 if ( ( pos < 0 ) || ( pos >= buffer.length ) )
1039 {
1040 throw new ArrayIndexOutOfBoundsException();
1041 }
1042
1043 // Read the upName value, if it's not null
1044 boolean hasUpName = Serialize.deserializeBoolean( buffer, pos );
1045 pos++;
1046
1047 if ( hasUpName )
1048 {
1049 byte[] wrappedValueBytes = Serialize.deserializeBytes( buffer, pos );
1050 pos += 4 + wrappedValueBytes.length;
1051 upName = Strings.utf8ToString( wrappedValueBytes );
1052 }
1053
1054 // Read the upType value, if it's not null
1055 boolean hasUpType = Serialize.deserializeBoolean( buffer, pos );
1056 pos++;
1057
1058 if ( hasUpType )
1059 {
1060 byte[] upTypeBytes = Serialize.deserializeBytes( buffer, pos );
1061 pos += 4 + upTypeBytes.length;
1062 upType = Strings.utf8ToString( upTypeBytes );
1063 }
1064
1065 // Update the AtributeType
1066 if ( schemaManager != null )
1067 {
1068 if ( !Strings.isEmpty( upType ) )
1069 {
1070 attributeType = schemaManager.getAttributeType( upType );
1071 }
1072 else
1073 {
1074 attributeType = schemaManager.getAttributeType( normType );
1075 }
1076 }
1077
1078 if ( attributeType != null )
1079 {
1080 normType = attributeType.getOid();
1081 }
1082 else
1083 {
1084 normType = upType;
1085 }
1086
1087 // Read the isHR flag
1088 boolean isHR = Serialize.deserializeBoolean( buffer, pos );
1089 pos++;
1090
1091 if ( isHR )
1092 {
1093 // Read the upValue
1094 value = Value.createValue( attributeType );
1095 pos = value.deserialize( buffer, pos );
1096 }
1097
1098 // Read the hashCode
1099 h = Serialize.deserializeInt( buffer, pos );
1100 pos += 4;
1101
1102 return pos;
1103 }
1104
1105
1106 /**
1107 *
1108 * An Ava is composed of a type and a value.
1109 * The data are stored following the structure :
1110 * <ul>
1111 * <li>
1112 * <b>upName</b> The User provided ATAV
1113 * </li>
1114 * <li>
1115 * <b>start</b> The position of this ATAV in the Dn
1116 * </li>
1117 * <li>
1118 * <b>length</b> The ATAV length
1119 * </li>
1120 * <li>
1121 * <b>upType</b> The user Provided Type
1122 * </li>
1123 * <li>
1124 * <b>normType</b> The normalized AttributeType
1125 * </li>
1126 * <li>
1127 * <b>isHR</b> Tells if the value is a String or not
1128 * </li>
1129 * </ul>
1130 * <br>
1131 * if the value is a String :
1132 * <ul>
1133 * <li>
1134 * <b>value</b> The value
1135 * </li>
1136 * </ul>
1137 * <br>
1138 * if the value is binary :
1139 * <ul>
1140 * <li>
1141 * <b>valueLength</b>
1142 * </li>
1143 * <li>
1144 * <b>value</b> The value
1145 * </li>
1146 * </ul>
1147 *
1148 * @see Externalizable#readExternal(ObjectInput)
1149 *
1150 * @throws IOException If the Ava can't be written in the stream
1151 */
1152 @Override
1153 public void writeExternal( ObjectOutput out ) throws IOException
1154 {
1155 if ( Strings.isEmpty( upName )
1156 || Strings.isEmpty( upType )
1157 || Strings.isEmpty( normType )
1158 || ( value.isNull() ) )
1159 {
1160 String message;
1161
1162 if ( Strings.isEmpty( upName ) )
1163 {
1164 message = I18n.err( I18n.ERR_13616_CANNOT_SERIALIZE_AVA_UPNAME_NULL );
1165 }
1166 else if ( Strings.isEmpty( upType ) )
1167 {
1168 message = I18n.err( I18n.ERR_13617_CANNOT_SERIALIZE_AVA_UPTYPE_NULL );
1169 }
1170 else if ( Strings.isEmpty( normType ) )
1171 {
1172 message = I18n.err( I18n.ERR_13618_CANNOT_SERIALIZE_AVA_NORMTYPE_NULL );
1173 }
1174 else
1175 {
1176 message = I18n.err( I18n.ERR_13619_CANNOT_SERIALIZE_AVA_VALUE_NULL );
1177 }
1178
1179 LOG.error( message );
1180 throw new IOException( message );
1181 }
1182
1183 if ( upName != null )
1184 {
1185 out.writeBoolean( true );
1186 out.writeUTF( upName );
1187 }
1188 else
1189 {
1190 out.writeBoolean( false );
1191 }
1192
1193 if ( upType != null )
1194 {
1195 out.writeBoolean( true );
1196 out.writeUTF( upType );
1197 }
1198 else
1199 {
1200 out.writeBoolean( false );
1201 }
1202
1203 if ( normType != null )
1204 {
1205 out.writeBoolean( true );
1206 out.writeUTF( normType );
1207 }
1208 else
1209 {
1210 out.writeBoolean( false );
1211 }
1212
1213 boolean isHR = value.isHumanReadable();
1214
1215 out.writeBoolean( isHR );
1216
1217 value.writeExternal( out );
1218
1219 // Write the hashCode
1220 out.writeInt( h );
1221
1222 out.flush();
1223 }
1224
1225
1226 /**
1227 * We read back the data to create a new ATAV. The structure
1228 * read is exposed in the {@link Ava#writeExternal(ObjectOutput)}
1229 * method
1230 *
1231 * @see Externalizable#readExternal(ObjectInput)
1232 *
1233 * @throws IOException If the Ava can't b written to the stream
1234 * @throws ClassNotFoundException If we can't deserialize an Ava from the stream
1235 */
1236 @Override
1237 public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
1238 {
1239 boolean hasUpName = in.readBoolean();
1240
1241 if ( hasUpName )
1242 {
1243 upName = in.readUTF();
1244 }
1245
1246 boolean hasUpType = in.readBoolean();
1247
1248 if ( hasUpType )
1249 {
1250 upType = in.readUTF();
1251 }
1252
1253 boolean hasNormType = in.readBoolean();
1254
1255 if ( hasNormType )
1256 {
1257 normType = in.readUTF();
1258 }
1259
1260 if ( schemaManager != null )
1261 {
1262 if ( !Strings.isEmpty( upType ) )
1263 {
1264 attributeType = schemaManager.getAttributeType( upType );
1265 }
1266 else
1267 {
1268 attributeType = schemaManager.getAttributeType( normType );
1269 }
1270 }
1271
1272 in.readBoolean();
1273
1274 value = Value.deserialize( attributeType, in );
1275
1276 h = in.readInt();
1277 }
1278
1279
1280 /**
1281 * Tells if the Ava is schema aware or not.
1282 *
1283 * @return <tt>true</tt> if the Ava is schema aware
1284 */
1285 public boolean isSchemaAware()
1286 {
1287 return attributeType != null;
1288 }
1289
1290
1291 /**
1292 * @return the attributeType
1293 */
1294 public AttributeType getAttributeType()
1295 {
1296 return attributeType;
1297 }
1298
1299
1300 /* Package protected */ void setAttributeType( SchemaManager schemaManager, String upType )
1301 throws LdapInvalidDnException
1302 {
1303 if ( schemaManager != null )
1304 {
1305 this.schemaManager = schemaManager;
1306
1307 try
1308 {
1309 attributeType = schemaManager.lookupAttributeTypeRegistry( upType );
1310 }
1311 catch ( LdapException le )
1312 {
1313 String message = I18n.err( I18n.ERR_13600_TYPE_IS_NULL_OR_EMPTY );
1314 throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message, le );
1315 }
1316
1317 normType = attributeType.getOid();
1318 }
1319 else
1320 {
1321 String upTypeTrimmed = Strings.trim( upType );
1322
1323 if ( Strings.isEmpty( upTypeTrimmed ) )
1324 {
1325 String message = I18n.err( I18n.ERR_13600_TYPE_IS_NULL_OR_EMPTY );
1326 // Do NOT log the message here. The error may be handled and therefore the log message may polute the log files.
1327 // Let the caller log the exception if needed.
1328 throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message );
1329 }
1330 else
1331 {
1332 // In this case, we will use the upType instead
1333 this.normType = Strings.lowerCaseAscii( upTypeTrimmed );
1334 this.upType = upType;
1335 }
1336 }
1337 }
1338
1339
1340 private int compareValues( Ava that )
1341 {
1342 int comp;
1343
1344 if ( value.isHumanReadable() )
1345 {
1346 comp = value.compareTo( that.value );
1347
1348 return comp;
1349 }
1350 else
1351 {
1352 byte[] bytes1 = value.getBytes();
1353 byte[] bytes2 = that.value.getBytes();
1354
1355 for ( int pos = 0; pos < bytes1.length; pos++ )
1356 {
1357 int v1 = bytes1[pos] & 0x00FF;
1358 int v2 = bytes2[pos] & 0x00FF;
1359
1360 if ( v1 > v2 )
1361 {
1362 return 1;
1363 }
1364 else if ( v2 > v1 )
1365 {
1366 return -1;
1367 }
1368 }
1369
1370 return 0;
1371 }
1372
1373 }
1374
1375
1376 /**
1377 * @see Comparable#compareTo(Object)
1378 */
1379 @Override
1380 public int compareTo( Ava that )
1381 {
1382 if ( that == null )
1383 {
1384 return 1;
1385 }
1386
1387 int comp;
1388
1389 if ( schemaManager == null )
1390 {
1391 // Compare the ATs
1392 comp = normType.compareTo( that.normType );
1393
1394 if ( comp != 0 )
1395 {
1396 return comp;
1397 }
1398
1399 // and compare the values
1400 if ( value == null )
1401 {
1402 if ( that.value == null )
1403 {
1404 return 0;
1405 }
1406 else
1407 {
1408 return -1;
1409 }
1410 }
1411 else
1412 {
1413 if ( that.value == null )
1414 {
1415 return 1;
1416 }
1417 else
1418 {
1419 comp = value.compareTo( ( Value ) that.value );
1420
1421 return comp;
1422 }
1423 }
1424 }
1425 else
1426 {
1427 if ( that.schemaManager == null )
1428 {
1429 // Problem : we will apply the current Ava SchemaManager to the given Ava
1430 try
1431 {
1432 that.apply( schemaManager );
1433 }
1434 catch ( LdapInvalidDnException lide )
1435 {
1436 return 1;
1437 }
1438 }
1439
1440 // First compare the AT OID
1441 comp = attributeType.getOid().compareTo( that.attributeType.getOid() );
1442
1443 if ( comp != 0 )
1444 {
1445 return comp;
1446 }
1447
1448 // Now, compare the two values using the ordering matchingRule comparator, if any
1449 MatchingRule orderingMR = attributeType.getOrdering();
1450
1451 if ( orderingMR != null )
1452 {
1453 LdapComparator<Object> comparator = ( LdapComparator<Object> ) orderingMR.getLdapComparator();
1454
1455 if ( comparator != null )
1456 {
1457 comp = value.compareTo( that.value );
1458
1459 return comp;
1460 }
1461 else
1462 {
1463 comp = compareValues( that );
1464
1465 return comp;
1466 }
1467 }
1468 else
1469 {
1470 comp = compareValues( that );
1471
1472 return comp;
1473 }
1474 }
1475 }
1476
1477
1478 /**
1479 * A String representation of an Ava, as provided by the user.
1480 *
1481 * @return A string representing an Ava
1482 */
1483 @Override
1484 public String toString()
1485 {
1486 return upName;
1487 }
1488 }