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