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