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