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 */
020 package org.apache.directory.shared.ldap.schema;
021
022
023 import java.util.ArrayList;
024 import java.util.Collections;
025 import java.util.HashMap;
026 import java.util.HashSet;
027 import java.util.List;
028 import java.util.Map;
029 import java.util.Set;
030
031 import org.apache.directory.shared.i18n.I18n;
032 import org.apache.directory.shared.ldap.exception.LdapException;
033 import org.apache.directory.shared.ldap.schema.registries.Registries;
034 import org.apache.directory.shared.ldap.util.StringTools;
035
036
037 /**
038 * Most schema objects have some common attributes. This class
039 * contains the minimum set of properties exposed by a SchemaObject.<br>
040 * We have 11 types of SchemaObjects :
041 * <li> AttributeType
042 * <li> DitCOntentRule
043 * <li> DitStructureRule
044 * <li> LdapComparator (specific to ADS)
045 * <li> LdapSyntaxe
046 * <li> MatchingRule
047 * <li> MatchingRuleUse
048 * <li> NameForm
049 * <li> Normalizer (specific to ADS)
050 * <li> ObjectClass
051 * <li> SyntaxChecker (specific to ADS)
052 * <br>
053 * <br>
054 * This class provides accessors and setters for the following attributes,
055 * which are common to all those SchemaObjects :
056 * <li>oid : The numeric OID
057 * <li>description : The SchemaObject description
058 * <li>obsolete : Tells if the schema object is obsolete
059 * <li>extensions : The extensions, a key/Values map
060 * <li>schemaObjectType : The SchemaObject type (see upper)
061 * <li>schema : The schema the SchemaObject is associated with (it's an extension).
062 * Can be null
063 * <li>isEnabled : The SchemaObject status (it's related to the schema status)
064 * <li>isReadOnly : Tells if the SchemaObject can be modified or not
065 * <br><br>
066 * Some of those attributes are not used by some Schema elements, even if they should
067 * have been used. Here is the list :
068 * <b>name</b> : LdapSyntax, Comparator, Normalizer, SyntaxChecker
069 * <b>numericOid</b> : DitStructureRule,
070 * <b>obsolete</b> : LdapSyntax, Comparator, Normalizer, SyntaxChecker
071 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
072 * @version $Rev: 885381 $
073 */
074 public abstract class AbstractSchemaObject implements SchemaObject
075 {
076 /** The serialVersionUID */
077 public static final long serialVersionUID = 1L;
078
079 /** The SchemaObject numeric OID */
080 protected String oid;
081
082 /** The optional names for this SchemaObject */
083 protected List<String> names;
084
085 /** Whether or not this SchemaObject is enabled */
086 protected boolean isEnabled = true;
087
088 /** Whether or not this SchemaObject can be modified */
089 protected boolean isReadOnly = false;
090
091 /** Whether or not this SchemaObject is obsolete */
092 protected boolean isObsolete = false;
093
094 /** A short description of this SchemaObject */
095 protected String description;
096
097 /** The SchemaObject specification */
098 protected String specification;
099
100 /** The name of the schema this object is associated with */
101 protected String schemaName;
102
103 /** The SchemaObjectType */
104 protected SchemaObjectType objectType;
105
106 /** A map containing the list of supported extensions */
107 protected Map<String, List<String>> extensions;
108
109 /** A locked to avoid modifications when set to true */
110 protected volatile boolean locked;
111
112 /** The hashcoe for this schemaObject */
113 private int h;
114
115
116 /**
117 * A constructor for a SchemaObject instance. It must be
118 * invoked by the inherited class.
119 *
120 * @param objectType The SchemaObjectType to create
121 */
122 protected AbstractSchemaObject( SchemaObjectType objectType, String oid )
123 {
124 this.objectType = objectType;
125 this.oid = oid;
126 extensions = new HashMap<String, List<String>>();
127 names = new ArrayList<String>();
128 }
129
130
131 /**
132 * Constructor used when a generic reusable SchemaObject is assigned an
133 * OID after being instantiated.
134 *
135 * @param objectType The SchemaObjectType to create
136 */
137 protected AbstractSchemaObject( SchemaObjectType objectType )
138 {
139 this.objectType = objectType;
140 extensions = new HashMap<String, List<String>>();
141 names = new ArrayList<String>();
142 }
143
144
145 /**
146 * Gets usually what is the numeric object identifier assigned to this
147 * SchemaObject. All schema objects except for MatchingRuleUses have an OID
148 * assigned specifically to then. A MatchingRuleUse's OID really is the OID
149 * of it's MatchingRule and not specific to the MatchingRuleUse. This
150 * effects how MatchingRuleUse objects are maintained by the system.
151 *
152 * @return an OID for this SchemaObject or its MatchingRule if this
153 * SchemaObject is a MatchingRuleUse object
154 */
155 public String getOid()
156 {
157 return oid;
158 }
159
160
161 /**
162 * A special method used when renaming an SchemaObject: we may have to
163 * change it's OID
164 * @param oid The new OID
165 */
166 public void setOid( String oid )
167 {
168 if ( locked )
169 {
170 throw new UnsupportedOperationException( I18n.err( I18n.ERR_04441, getName() ) );
171 }
172
173 this.oid = oid;
174 }
175
176
177 /**
178 * Gets short names for this SchemaObject if any exists for it, otherwise,
179 * returns an empty list.
180 *
181 * @return the names for this SchemaObject
182 */
183 public List<String> getNames()
184 {
185 if ( names != null )
186 {
187 return Collections.unmodifiableList( names );
188 }
189 else
190 {
191 return Collections.emptyList();
192 }
193 }
194
195
196 /**
197 * Gets the first name in the set of short names for this SchemaObject if
198 * any exists for it.
199 *
200 * @return the first of the names for this SchemaObject or the oid
201 * if one does not exist
202 */
203 public String getName()
204 {
205 if ( ( names != null ) && ( names.size() != 0 ) )
206 {
207 return names.get( 0 );
208 }
209 else
210 {
211 return oid;
212 }
213 }
214
215
216 /**
217 * Inject this SchemaObject to the given registries, updating the references to
218 * other SchemaObject
219 *
220 * @param errors The errors we got
221 * @param registries The Registries
222 */
223 public void addToRegistries( List<Throwable> errors, Registries registries ) throws LdapException
224 {
225 // do nothing
226 }
227
228
229 /**
230 * Remove this SchemaObject from the given registries, updating the references to
231 * other SchemaObject
232 *
233 * @param errors The errors we got
234 * @param registries The Registries
235 */
236 public void removeFromRegistries( List<Throwable> errors, Registries registries ) throws LdapException
237 {
238 // do nothing
239 }
240
241
242 /**
243 * Inject the Registries into the SchemaObject
244 *
245 * @param registries The Registries
246 */
247 public void setRegistries( Registries registries )
248 {
249 // do nothing
250 }
251
252
253 /**
254 * Add a new name to the list of names for this SchemaObject. The name
255 * is lowercased and trimmed.
256 *
257 * @param names The names to add
258 */
259 public void addName( String... names )
260 {
261 if ( locked )
262 {
263 throw new UnsupportedOperationException( I18n.err( I18n.ERR_04441, getName() ) );
264 }
265
266 if ( !isReadOnly )
267 {
268 // We must avoid duplicated names, as names are case insensitive
269 Set<String> lowerNames = new HashSet<String>();
270
271 // Fills a set with all the existing names
272 for ( String name : this.names )
273 {
274 lowerNames.add( StringTools.toLowerCase( name ) );
275 }
276
277 for ( String name : names )
278 {
279 if ( name != null )
280 {
281 String lowerName = StringTools.toLowerCase( name );
282 // Check that the lower cased names is not already present
283 if ( !lowerNames.contains( lowerName ) )
284 {
285 this.names.add( name );
286 lowerNames.add( lowerName );
287 }
288 }
289 }
290 }
291 }
292
293
294 /**
295 * Sets the list of names for this SchemaObject. The names are
296 * lowercased and trimmed.
297 *
298 * @param names The list of names. Can be empty
299 */
300 public void setNames( List<String> names )
301 {
302 if ( locked )
303 {
304 throw new UnsupportedOperationException( I18n.err( I18n.ERR_04441, getName() ) );
305 }
306
307 if ( names == null )
308 {
309 return;
310 }
311
312 if ( !isReadOnly )
313 {
314 this.names = new ArrayList<String>( names.size() );
315
316 for ( String name : names )
317 {
318 if ( name != null )
319 {
320 this.names.add( name );
321 }
322 }
323 }
324 }
325
326
327 /**
328 * Sets the list of names for this SchemaObject. The names are
329 * lowercased and trimmed.
330 *
331 * @param names The list of names.
332 */
333 public void setNames( String... names )
334 {
335 if ( locked )
336 {
337 throw new UnsupportedOperationException( I18n.err( I18n.ERR_04441, getName() ) );
338 }
339
340 if ( names == null )
341 {
342 return;
343 }
344
345 if ( !isReadOnly )
346 {
347 for ( String name : names )
348 {
349 if ( name != null )
350 {
351 this.names.add( name );
352 }
353 }
354 }
355 }
356
357
358 /**
359 * Gets a short description about this SchemaObject.
360 *
361 * @return a short description about this SchemaObject
362 */
363 public String getDescription()
364 {
365 return description;
366 }
367
368
369 /**
370 * Sets the SchemaObject's description
371 *
372 * @param description The SchemaObject's description
373 */
374 public void setDescription( String description )
375 {
376 if ( locked )
377 {
378 throw new UnsupportedOperationException( I18n.err( I18n.ERR_04441, getName() ) );
379 }
380
381 if ( !isReadOnly )
382 {
383 this.description = description;
384 }
385 }
386
387
388 /**
389 * Gets the SchemaObject specification.
390 *
391 * @return the SchemaObject specification
392 */
393 public String getSpecification()
394 {
395 return specification;
396 }
397
398
399 /**
400 * Sets the SchemaObject's specification
401 *
402 * @param specification The SchemaObject's specification
403 */
404 public void setSpecification( String specification )
405 {
406 if ( locked )
407 {
408 throw new UnsupportedOperationException( I18n.err( I18n.ERR_04441, getName() ) );
409 }
410
411 if ( !isReadOnly )
412 {
413 this.specification = specification;
414 }
415 }
416
417
418 /**
419 * Tells if this SchemaObject is enabled.
420 *
421 * @param schemaEnabled the associated schema status
422 * @return true if the SchemaObject is enabled, or if it depends on
423 * an enabled schema
424 */
425 public boolean isEnabled()
426 {
427 return isEnabled;
428 }
429
430
431 /**
432 * Tells if this SchemaObject is disabled.
433 *
434 * @return true if the SchemaObject is disabled
435 */
436 public boolean isDisabled()
437 {
438 return !isEnabled;
439 }
440
441
442 /**
443 * Sets the SchemaObject state, either enabled or disabled.
444 *
445 * @param enabled The current SchemaObject state
446 */
447 public void setEnabled( boolean enabled )
448 {
449 if ( !isReadOnly )
450 {
451 isEnabled = enabled;
452 }
453 }
454
455
456 /**
457 * Tells if this SchemaObject is ReadOnly.
458 *
459 * @return true if the SchemaObject is not modifiable
460 */
461 public boolean isReadOnly()
462 {
463 return isReadOnly;
464 }
465
466
467 /**
468 * Sets the SchemaObject readOnly flag
469 *
470 * @param enabled The current SchemaObject ReadOnly status
471 */
472 public void setReadOnly( boolean isReadOnly )
473 {
474 if ( locked )
475 {
476 throw new UnsupportedOperationException( I18n.err( I18n.ERR_04441, getName() ) );
477 }
478
479 this.isReadOnly = isReadOnly;
480 }
481
482
483 /**
484 * Gets whether or not this SchemaObject has been inactivated. All
485 * SchemaObjects except Syntaxes allow for this parameter within their
486 * definition. For Syntaxes this property should always return false in
487 * which case it is never included in the description.
488 *
489 * @return true if inactive, false if active
490 */
491 public boolean isObsolete()
492 {
493 return isObsolete;
494 }
495
496
497 /**
498 * Sets the Obsolete flag.
499 *
500 * @param obsolete The Obsolete flag state
501 */
502 public void setObsolete( boolean obsolete )
503 {
504 if ( locked )
505 {
506 throw new UnsupportedOperationException( I18n.err( I18n.ERR_04441, getName() ) );
507 }
508
509 if ( !isReadOnly )
510 {
511 this.isObsolete = obsolete;
512 }
513 }
514
515
516 /**
517 * @return The SchemaObject extensions, as a Map of [extension, values]
518 */
519 public Map<String, List<String>> getExtensions()
520 {
521 return extensions;
522 }
523
524
525 /**
526 * Add an extension with its values
527 * @param key The extension key
528 * @param values The associated values
529 */
530 public void addExtension( String key, List<String> values )
531 {
532 if ( locked )
533 {
534 throw new UnsupportedOperationException( I18n.err( I18n.ERR_04441, getName() ) );
535 }
536
537 if ( !isReadOnly )
538 {
539 extensions.put( key, values );
540 }
541 }
542
543
544 /**
545 * Add an extensions with their values. (Actually do a copy)
546 *
547 * @param key The extension key
548 * @param values The associated values
549 */
550 public void setExtensions( Map<String, List<String>> extensions )
551 {
552 if ( locked )
553 {
554 throw new UnsupportedOperationException( I18n.err( I18n.ERR_04441, getName() ) );
555 }
556
557 if ( !isReadOnly && ( extensions != null ) )
558 {
559 this.extensions = new HashMap<String, List<String>>();
560
561 for ( String key : extensions.keySet() )
562 {
563 List<String> values = new ArrayList<String>();
564
565 for ( String value : extensions.get( key ) )
566 {
567 values.add( value );
568 }
569
570 this.extensions.put( key, values );
571 }
572
573 }
574 }
575
576
577 /**
578 * The SchemaObject type :
579 * <li> AttributeType
580 * <li> DitCOntentRule
581 * <li> DitStructureRule
582 * <li> LdapComparator (specific to ADS)
583 * <li> LdapSyntaxe
584 * <li> MatchingRule
585 * <li> MatchingRuleUse
586 * <li> NameForm
587 * <li> Normalizer (specific to ADS)
588 * <li> ObjectClass
589 * <li> SyntaxChecker (specific to ADS)
590 *
591 * @return the SchemaObject type
592 */
593 public SchemaObjectType getObjectType()
594 {
595 return objectType;
596 }
597
598
599 /**
600 * Gets the name of the schema this SchemaObject is associated with.
601 *
602 * @return the name of the schema associated with this schemaObject
603 */
604 public String getSchemaName()
605 {
606 return schemaName;
607 }
608
609
610 /**
611 * Sets the name of the schema this SchemaObject is associated with.
612 *
613 * @param schemaName the new schema name
614 */
615 public void setSchemaName( String schemaName )
616 {
617 if ( locked )
618 {
619 throw new UnsupportedOperationException( I18n.err( I18n.ERR_04441, getName() ) );
620 }
621
622 if ( !isReadOnly )
623 {
624 this.schemaName = schemaName;
625 }
626 }
627
628
629 /**
630 * @see Object#hashCode()
631 */
632 public int hashCode()
633 {
634 return h;
635 }
636
637
638 /**
639 * @see Object#equals(Object)
640 */
641 public boolean equals( Object o1 )
642 {
643 if ( this == o1 )
644 {
645 return true;
646 }
647
648 if ( !( o1 instanceof AbstractSchemaObject ) )
649 {
650 return false;
651 }
652
653 AbstractSchemaObject that = ( AbstractSchemaObject ) o1;
654
655 // Two schemaObject are equals if their oid is equal,
656 // their ObjectType is equal, their names are equals
657 // their schema name is the same, all their flags are equals,
658 // the description is the same and their extensions are equals
659 if ( !compareOid( oid, that.oid ) )
660 {
661 return false;
662 }
663
664 // Compare the names
665 if ( names == null )
666 {
667 if ( that.names != null )
668 {
669 return false;
670 }
671 }
672 else if ( that.names == null )
673 {
674 return false;
675 }
676 else
677 {
678 int nbNames = 0;
679
680 for ( String name : names )
681 {
682 if ( !that.names.contains( name ) )
683 {
684 return false;
685 }
686
687 nbNames++;
688 }
689
690 if ( nbNames != names.size() )
691 {
692 return false;
693 }
694 }
695
696 if ( schemaName == null )
697 {
698 if ( that.schemaName != null )
699 {
700 return false;
701 }
702 }
703 else
704 {
705 if ( !schemaName.equalsIgnoreCase( that.schemaName ) )
706 {
707 return false;
708 }
709 }
710
711 if ( objectType != that.objectType )
712 {
713 return false;
714 }
715
716 if ( extensions != null )
717 {
718 if ( that.extensions == null )
719 {
720 return false;
721 }
722 else
723 {
724 for ( String key : extensions.keySet() )
725 {
726 if ( !that.extensions.containsKey( key ) )
727 {
728 return false;
729 }
730
731 List<String> thisValues = extensions.get( key );
732 List<String> thatValues = that.extensions.get( key );
733
734 if ( thisValues != null )
735 {
736 if ( thatValues == null )
737 {
738 return false;
739 }
740 else
741 {
742 if ( thisValues.size() != thatValues.size() )
743 {
744 return false;
745 }
746
747 // TODO compare the values
748 }
749 }
750 else if ( thatValues != null )
751 {
752 return false;
753 }
754 }
755 }
756 }
757 else if ( that.extensions != null )
758 {
759 return false;
760 }
761
762 if ( this.isEnabled != that.isEnabled )
763 {
764 return false;
765 }
766
767 if ( this.isObsolete != that.isObsolete )
768 {
769 return false;
770 }
771
772 if ( this.isReadOnly != that.isReadOnly )
773 {
774 return false;
775 }
776
777 if ( this.description == null )
778 {
779 return that.description == null;
780 }
781 else
782 {
783 return this.description.equalsIgnoreCase( that.description );
784 }
785 }
786
787
788 /**
789 * Register the given SchemaObject into the given registries' globalOidRegistry
790 *
791 * @param schemaObject the SchemaObject we want to register
792 * @param registries The registries in which we want it to be stored
793 * @throws LdapException If the OID is invalid
794 */
795 public void registerOid( SchemaObject schemaObject, Registries registries ) throws LdapException
796 {
797 // Add the SchemaObject into the globalOidRegistry
798 registries.getGlobalOidRegistry().register( schemaObject );
799 }
800
801
802 /**
803 * Copy the current SchemaObject on place
804 *
805 * @return The copied SchemaObject
806 */
807 public abstract SchemaObject copy();
808
809
810 /**
811 * Compare two oids, and return true if they are both null or
812 * equals
813 */
814 protected boolean compareOid( String oid1, String oid2 )
815 {
816 if ( oid1 == null )
817 {
818 return oid2 == null;
819 }
820 else
821 {
822 return oid1.equals( oid2 );
823 }
824 }
825
826
827 /**
828 * Copy a SchemaObject.
829 *
830 * @return A copy of the current SchemaObject
831 */
832 public SchemaObject copy( SchemaObject original )
833 {
834 // copy the description
835 description = original.getDescription();
836
837 // copy the flags
838 isEnabled = original.isEnabled();
839 isObsolete = original.isObsolete();
840 isReadOnly = original.isReadOnly();
841
842 // copy the names
843 names = new ArrayList<String>();
844
845 for ( String name : original.getNames() )
846 {
847 names.add( name );
848 }
849
850 // copy the extensions
851 extensions = new HashMap<String, List<String>>();
852
853 for ( String key : original.getExtensions().keySet() )
854 {
855 List<String> extensionValues = original.getExtensions().get( key );
856
857 List<String> cloneExtension = new ArrayList<String>();
858
859 for ( String value : extensionValues )
860 {
861 cloneExtension.add( value );
862 }
863
864 extensions.put( key, cloneExtension );
865 }
866
867 // The SchemaName
868 schemaName = original.getSchemaName();
869
870 // The specification
871 specification = original.getSpecification();
872
873 return this;
874 }
875
876
877 /**
878 * Clear the current SchemaObject : remove all the references to other objects,
879 * and all the Maps.
880 */
881 public void clear()
882 {
883 // Clear the extensions
884 for ( String extension : extensions.keySet() )
885 {
886 List<String> extensionList = extensions.get( extension );
887
888 extensionList.clear();
889 }
890
891 extensions.clear();
892
893 // Clear the names
894 names.clear();
895 }
896
897
898 /**
899 * {@inheritDoc}
900 */
901 public final void lock()
902 {
903 if ( locked )
904 {
905 return;
906 }
907
908 h = 37;
909
910 // The OID
911 h += h * 17 + oid.hashCode();
912
913 // The SchemaObject type
914 h += h * 17 + objectType.getValue();
915
916 // The Names, if any
917 if ( ( names != null ) && ( names.size() != 0 ) )
918 {
919 for ( String name : names )
920 {
921 h += h * 17 + name.hashCode();
922 }
923 }
924
925 // The schemaName if any
926 if ( schemaName != null )
927 {
928 h += h * 17 + schemaName.hashCode();
929 }
930
931 h += h * 17 + ( isEnabled ? 1 : 0 );
932 h += h * 17 + ( isReadOnly ? 1 : 0 );
933
934 // The description, if any
935 if ( description != null )
936 {
937 h += h * 17 + description.hashCode();
938 }
939
940 // The extensions, if any
941 for ( String key : extensions.keySet() )
942 {
943 h += h * 17 + key.hashCode();
944
945 List<String> values = extensions.get( key );
946
947 if ( values != null )
948 {
949 for ( String value : values )
950 {
951 h += h * 17 + value.hashCode();
952 }
953 }
954 }
955
956 locked = true;
957 }
958 }