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