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      * @return The SchemaObject extensions, as a Map of [extension, values]
481      */
482     public Map<String, List<String>> getExtensions()
483     {
484         return extensions;
485     }
486 
487 
488     /**
489      * Add an extension with its values
490      * @param key The extension key
491      * @param values The associated values
492      */
493     public void addExtension( String key, String... values )
494     {
495         if ( locked )
496         {
497             throw new UnsupportedOperationException( I18n.err( I18n.ERR_04441, getName() ) );
498         }
499 
500         if ( !isReadOnly )
501         {
502             List<String> valueList = new ArrayList<String>();
503             
504             for ( String value : values )
505             {
506                 valueList.add( value );
507             }
508             
509             extensions.put( key, valueList );
510         }
511     }
512 
513 
514     /**
515      * Add an extension with its values
516      * @param key The extension key
517      * @param values The associated values
518      */
519     public void addExtension( String key, List<String> values )
520     {
521         if ( locked )
522         {
523             throw new UnsupportedOperationException( I18n.err( I18n.ERR_04441, getName() ) );
524         }
525 
526         if ( !isReadOnly )
527         {
528             extensions.put( key, values );
529         }
530     }
531 
532 
533     /**
534      * Add an extensions with their values. (Actually do a copy)
535      * 
536      * @param extensions The extensions map
537      */
538     public void setExtensions( Map<String, List<String>> extensions )
539     {
540         if ( locked )
541         {
542             throw new UnsupportedOperationException( I18n.err( I18n.ERR_04441, getName() ) );
543         }
544 
545         if ( !isReadOnly && ( extensions != null ) )
546         {
547             this.extensions = new HashMap<String, List<String>>();
548 
549             for ( Map.Entry<String, List<String>> entry : extensions.entrySet() )
550             {
551                 List<String> values = new ArrayList<String>();
552 
553                 for ( String value : entry.getValue() )
554                 {
555                     values.add( value );
556                 }
557 
558                 this.extensions.put( entry.getKey(), values );
559             }
560 
561         }
562     }
563 
564 
565     /**
566      * The SchemaObject type :
567      * <ul>
568      *   <li> AttributeType
569      *   <li> DitCOntentRule
570      *   <li> DitStructureRule
571      *   <li> LdapComparator (specific to ADS)
572      *   <li> LdapSyntaxe
573      *   <li> MatchingRule
574      *   <li> MatchingRuleUse
575      *   <li> NameForm
576      *   <li> Normalizer (specific to ADS)
577      *   <li> ObjectClass
578      *   <li> SyntaxChecker (specific to ADS)
579      * </ul>
580      * 
581      * @return the SchemaObject type
582      */
583     public SchemaObjectType getObjectType()
584     {
585         return objectType;
586     }
587 
588 
589     /**
590      * Gets the name of the schema this SchemaObject is associated with.
591      *
592      * @return the name of the schema associated with this schemaObject
593      */
594     public String getSchemaName()
595     {
596         return schemaName;
597     }
598 
599 
600     /**
601      * Sets the name of the schema this SchemaObject is associated with.
602      * 
603      * @param schemaName the new schema name
604      */
605     public void setSchemaName( String schemaName )
606     {
607         if ( locked )
608         {
609             throw new UnsupportedOperationException( I18n.err( I18n.ERR_04441, getName() ) );
610         }
611 
612         if ( !isReadOnly )
613         {
614             this.schemaName = schemaName;
615         }
616     }
617 
618 
619     /**
620      * This method is final to forbid the inherited classes to implement
621      * it. This has been done for performances reasons : the hashcode should
622      * be computed only once, and stored locally.
623      * 
624      * The hashcode is currently computed in the lock() method, which is a hack
625      * that should be fixed.
626      * 
627      * @return {@inheritDoc}
628      */
629     @Override
630     public final int hashCode()
631     {
632         return h;
633     }
634 
635 
636     /**
637      * @{@inheritDoc}
638      */
639     @Override
640     public boolean equals( Object o1 )
641     {
642         if ( this == o1 )
643         {
644             return true;
645         }
646 
647         if ( !( o1 instanceof AbstractSchemaObject ) )
648         {
649             return false;
650         }
651 
652         AbstractSchemaObject that = ( AbstractSchemaObject ) o1;
653 
654         // Two schemaObject are equals if their oid is equal,
655         // their ObjectType is equal, their names are equals
656         // their schema name is the same, all their flags are equals,
657         // the description is the same and their extensions are equals
658         if ( !compareOid( oid, that.oid ) )
659         {
660             return false;
661         }
662 
663         // Compare the names
664         if ( names == null )
665         {
666             if ( that.names != null )
667             {
668                 return false;
669             }
670         }
671         else if ( that.names == null )
672         {
673             return false;
674         }
675         else
676         {
677             int nbNames = 0;
678 
679             for ( String name : names )
680             {
681                 if ( !that.names.contains( name ) )
682                 {
683                     return false;
684                 }
685 
686                 nbNames++;
687             }
688 
689             if ( nbNames != names.size() )
690             {
691                 return false;
692             }
693         }
694 
695         if ( schemaName == null )
696         {
697             if ( that.schemaName != null )
698             {
699                 return false;
700             }
701         }
702         else
703         {
704             if ( !schemaName.equalsIgnoreCase( that.schemaName ) )
705             {
706                 return false;
707             }
708         }
709 
710         if ( objectType != that.objectType )
711         {
712             return false;
713         }
714 
715         if ( extensions != null )
716         {
717             if ( that.extensions == null )
718             {
719                 return false;
720             }
721             else
722             {
723                 for ( String key : extensions.keySet() )
724                 {
725                     if ( !that.extensions.containsKey( key ) )
726                     {
727                         return false;
728                     }
729 
730                     List<String> thisValues = extensions.get( key );
731                     List<String> thatValues = that.extensions.get( key );
732 
733                     if ( thisValues != null )
734                     {
735                         if ( thatValues == null )
736                         {
737                             return false;
738                         }
739                         else
740                         {
741                             if ( thisValues.size() != thatValues.size() )
742                             {
743                                 return false;
744                             }
745 
746                             // TODO compare the values
747                         }
748                     }
749                     else if ( thatValues != null )
750                     {
751                         return false;
752                     }
753                 }
754             }
755         }
756         else if ( that.extensions != null )
757         {
758             return false;
759         }
760 
761         if ( this.isEnabled != that.isEnabled )
762         {
763             return false;
764         }
765 
766         if ( this.isObsolete != that.isObsolete )
767         {
768             return false;
769         }
770 
771         if ( this.isReadOnly != that.isReadOnly )
772         {
773             return false;
774         }
775 
776         if ( this.description == null )
777         {
778             return that.description == null;
779         }
780         else
781         {
782             return this.description.equalsIgnoreCase( that.description );
783         }
784     }
785 
786 
787     /**
788      * Copy the current SchemaObject on place
789      *
790      * @return The copied SchemaObject
791      */
792     public abstract SchemaObject copy();
793 
794 
795     /**
796      * Compare two oids, and return true if they are both null or equal.
797      *
798      * @param oid1 the first OID
799      * @param oid2 the second OID
800      * @return <code>true</code> if both OIDs are null or equal
801      */
802     protected boolean compareOid( String oid1, String oid2 )
803     {
804         if ( oid1 == null )
805         {
806             return oid2 == null;
807         }
808         else
809         {
810             return oid1.equals( oid2 );
811         }
812     }
813 
814 
815     /**
816      * {@inheritDoc}
817      */
818     public SchemaObject copy( SchemaObject original )
819     {
820         // copy the description
821         description = original.getDescription();
822 
823         // copy the flags
824         isEnabled = original.isEnabled();
825         isObsolete = original.isObsolete();
826         isReadOnly = original.isReadOnly();
827 
828         // copy the names
829         names = new ArrayList<String>();
830 
831         for ( String name : original.getNames() )
832         {
833             names.add( name );
834         }
835 
836         // copy the extensions
837         extensions = new HashMap<String, List<String>>();
838 
839         for ( String key : original.getExtensions().keySet() )
840         {
841             List<String> extensionValues = original.getExtensions().get( key );
842 
843             List<String> cloneExtension = new ArrayList<String>();
844 
845             for ( String value : extensionValues )
846             {
847                 cloneExtension.add( value );
848             }
849 
850             extensions.put( key, cloneExtension );
851         }
852 
853         // The SchemaName
854         schemaName = original.getSchemaName();
855 
856         // The specification
857         specification = original.getSpecification();
858 
859         return this;
860     }
861 
862 
863     /**
864      * Clear the current SchemaObject : remove all the references to other objects,
865      * and all the Maps.
866      */
867     public void clear()
868     {
869         // Clear the extensions
870         for ( String extension : extensions.keySet() )
871         {
872             List<String> extensionList = extensions.get( extension );
873 
874             extensionList.clear();
875         }
876 
877         extensions.clear();
878 
879         // Clear the names
880         names.clear();
881     }
882     
883     
884     public void unlock()
885     {
886         locked = false;
887     }
888 
889 
890     /**
891      * {@inheritDoc}
892      */
893     public final void lock()
894     {
895         if ( locked )
896         {
897             return;
898         }
899 
900         h = 37;
901 
902         // The OID
903         h += h * 17 + oid.hashCode();
904 
905         // The SchemaObject type
906         h += h * 17 + objectType.getValue();
907 
908         // The Names, if any
909         if ( ( names != null ) && ( names.size() != 0 ) )
910         {
911             for ( String name : names )
912             {
913                 h += h * 17 + name.hashCode();
914             }
915         }
916 
917         // The schemaName if any
918         if ( schemaName != null )
919         {
920             h += h * 17 + schemaName.hashCode();
921         }
922 
923         h += h * 17 + ( isEnabled ? 1 : 0 );
924         h += h * 17 + ( isReadOnly ? 1 : 0 );
925 
926         // The description, if any
927         if ( description != null )
928         {
929             h += h * 17 + description.hashCode();
930         }
931 
932         // The extensions, if any
933         for ( String key : extensions.keySet() )
934         {
935             h += h * 17 + key.hashCode();
936 
937             List<String> values = extensions.get( key );
938 
939             if ( values != null )
940             {
941                 for ( String value : values )
942                 {
943                     h += h * 17 + value.hashCode();
944                 }
945             }
946         }
947 
948         locked = true;
949     }
950 }