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 javax.naming.NamingException;
032    
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    
110        /**
111         * A constructor for a SchemaObject instance. It must be 
112         * invoked by the inherited class.
113         * 
114         * @param objectType The SchemaObjectType to create
115         */
116        protected AbstractSchemaObject( SchemaObjectType objectType, String oid )
117        {
118            this.objectType = objectType;
119            this.oid = oid;
120            extensions = new HashMap<String, List<String>>();
121            names = new ArrayList<String>();
122        }
123    
124    
125        /**
126         * Constructor used when a generic reusable SchemaObject is assigned an
127         * OID after being instantiated.
128         * 
129         * @param objectType The SchemaObjectType to create
130         */
131        protected AbstractSchemaObject( SchemaObjectType objectType )
132        {
133            this.objectType = objectType;
134            extensions = new HashMap<String, List<String>>();
135            names = new ArrayList<String>();
136        }
137    
138    
139        /**
140         * Gets usually what is the numeric object identifier assigned to this
141         * SchemaObject. All schema objects except for MatchingRuleUses have an OID
142         * assigned specifically to then. A MatchingRuleUse's OID really is the OID
143         * of it's MatchingRule and not specific to the MatchingRuleUse. This
144         * effects how MatchingRuleUse objects are maintained by the system.
145         * 
146         * @return an OID for this SchemaObject or its MatchingRule if this
147         *         SchemaObject is a MatchingRuleUse object
148         */
149        public String getOid()
150        {
151            return oid;
152        }
153    
154    
155        /**
156         * A special method used when renaming an SchemaObject: we may have to
157         * change it's OID
158         * @param oid The new OID
159         */
160        public void setOid( String oid )
161        {
162            this.oid = oid;
163        }
164    
165    
166        /**
167         * Gets short names for this SchemaObject if any exists for it, otherwise,
168         * returns an empty list.
169         * 
170         * @return the names for this SchemaObject
171         */
172        public List<String> getNames()
173        {
174            if ( names != null )
175            {
176                return Collections.unmodifiableList( names );
177            }
178            else
179            {
180                return Collections.emptyList();
181            }
182        }
183    
184    
185        /**
186         * Gets the first name in the set of short names for this SchemaObject if
187         * any exists for it.
188         * 
189         * @return the first of the names for this SchemaObject or the oid
190         * if one does not exist
191         */
192        public String getName()
193        {
194            if ( ( names != null ) && ( names.size() != 0 ) )
195            {
196                return names.get( 0 );
197            }
198            else
199            {
200                return oid;
201            }
202        }
203    
204    
205        /**
206         * Inject this SchemaObject to the given registries, updating the references to
207         * other SchemaObject
208         *
209         * @param errors The errors we got
210         * @param registries The Registries
211         */
212        public void addToRegistries( List<Throwable> errors, Registries registries ) throws NamingException
213        {
214            // do nothing
215        }
216    
217    
218        /**
219         * Remove this SchemaObject from the given registries, updating the references to
220         * other SchemaObject
221         *
222         * @param errors The errors we got
223         * @param registries The Registries
224         */
225        public void removeFromRegistries( List<Throwable> errors, Registries registries ) throws NamingException
226        {
227            // do nothing
228        }
229    
230    
231        /**
232         * Inject the Registries into the SchemaObject
233         *
234         * @param registries The Registries
235         */
236        public void setRegistries( Registries registries )
237        {
238            // do nothing
239        }
240    
241    
242        /**
243         * Add a new name to the list of names for this SchemaObject. The name
244         * is lowercased and trimmed.
245         *  
246         * @param names The names to add
247         */
248        public void addName( String... names )
249        {
250            if ( !isReadOnly )
251            {
252                // We must avoid duplicated names, as names are case insensitive
253                Set<String> lowerNames = new HashSet<String>();
254    
255                // Fills a set with all the existing names
256                for ( String name : this.names )
257                {
258                    lowerNames.add( StringTools.toLowerCase( name ) );
259                }
260    
261                for ( String name : names )
262                {
263                    if ( name != null )
264                    {
265                        String lowerName = StringTools.toLowerCase( name );
266                        // Check that the lower cased names is not already present
267                        if ( !lowerNames.contains( lowerName ) )
268                        {
269                            this.names.add( name );
270                            lowerNames.add( lowerName );
271                        }
272                    }
273                }
274            }
275        }
276    
277    
278        /**
279         * Sets the list of names for this SchemaObject. The names are
280         * lowercased and trimmed.
281         *  
282         * @param names The list of names. Can be empty
283         */
284        public void setNames( List<String> names )
285        {
286            if ( names == null )
287            {
288                return;
289            }
290    
291            if ( !isReadOnly )
292            {
293                this.names = new ArrayList<String>( names.size() );
294    
295                for ( String name : names )
296                {
297                    if ( name != null )
298                    {
299                        this.names.add( name );
300                    }
301                }
302            }
303        }
304    
305    
306        /**
307         * Sets the list of names for this SchemaObject. The names are
308         * lowercased and trimmed.
309         *  
310         * @param names The list of names.
311         */
312        public void setNames( String... names )
313        {
314            if ( names == null )
315            {
316                return;
317            }
318    
319            if ( !isReadOnly )
320            {
321                for ( String name : names )
322                {
323                    if ( name != null )
324                    {
325                        this.names.add( name );
326                    }
327                }
328            }
329        }
330    
331    
332        /**
333         * Gets a short description about this SchemaObject.
334         * 
335         * @return a short description about this SchemaObject
336         */
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        public void setDescription( String description )
349        {
350            if ( !isReadOnly )
351            {
352                this.description = description;
353            }
354        }
355    
356    
357        /**
358         * Gets the SchemaObject specification.
359         * 
360         * @return the SchemaObject specification
361         */
362        public String getSpecification()
363        {
364            return specification;
365        }
366    
367    
368        /**
369         * Sets the SchemaObject's specification
370         * 
371         * @param specification The SchemaObject's specification
372         */
373        public void setSpecification( String specification )
374        {
375            if ( !isReadOnly )
376            {
377                this.specification = specification;
378            }
379        }
380    
381    
382        /**
383         * Tells if this SchemaObject is enabled.
384         *  
385         * @param schemaEnabled the associated schema status
386         * @return true if the SchemaObject is enabled, or if it depends on 
387         * an enabled schema
388         */
389        public boolean isEnabled()
390        {
391            return isEnabled;
392        }
393    
394    
395        /**
396         * Tells if this SchemaObject is disabled.
397         *  
398         * @return true if the SchemaObject is disabled
399         */
400        public boolean isDisabled()
401        {
402            return !isEnabled;
403        }
404    
405    
406        /**
407         * Sets the SchemaObject state, either enabled or disabled.
408         * 
409         * @param enabled The current SchemaObject state
410         */
411        public void setEnabled( boolean enabled )
412        {
413            if ( !isReadOnly )
414            {
415                isEnabled = enabled;
416            }
417        }
418    
419    
420        /**
421         * Tells if this SchemaObject is ReadOnly.
422         *  
423         * @return true if the SchemaObject is not modifiable
424         */
425        public boolean isReadOnly()
426        {
427            return isReadOnly;
428        }
429    
430    
431        /**
432         * Sets the SchemaObject readOnly flag
433         * 
434         * @param enabled The current SchemaObject ReadOnly status
435         */
436        public void setReadOnly( boolean isReadOnly )
437        {
438            this.isReadOnly = isReadOnly;
439        }
440    
441    
442        /**
443         * Gets whether or not this SchemaObject has been inactivated. All
444         * SchemaObjects except Syntaxes allow for this parameter within their
445         * definition. For Syntaxes this property should always return false in
446         * which case it is never included in the description.
447         * 
448         * @return true if inactive, false if active
449         */
450        public boolean isObsolete()
451        {
452            return isObsolete;
453        }
454    
455    
456        /**
457         * Sets the Obsolete flag.
458         * 
459         * @param obsolete The Obsolete flag state
460         */
461        public void setObsolete( boolean obsolete )
462        {
463            if ( !isReadOnly )
464            {
465                this.isObsolete = obsolete;
466            }
467        }
468    
469    
470        /**
471         * @return The SchemaObject extensions, as a Map of [extension, values]
472         */
473        public Map<String, List<String>> getExtensions()
474        {
475            return extensions;
476        }
477    
478    
479        /**
480         * Add an extension with its values
481         * @param key The extension key
482         * @param values The associated values
483         */
484        public void addExtension( String key, List<String> values )
485        {
486            if ( !isReadOnly )
487            {
488                extensions.put( key, values );
489            }
490        }
491    
492    
493        /**
494         * Add an extensions with their values. (Actually do a copy)
495         * 
496         * @param key The extension key
497         * @param values The associated values
498         */
499        public void setExtensions( Map<String, List<String>> extensions )
500        {
501            if ( !isReadOnly && ( extensions != null ) )
502            {
503                this.extensions = new HashMap<String, List<String>>();
504    
505                for ( String key : extensions.keySet() )
506                {
507                    List<String> values = new ArrayList<String>();
508    
509                    for ( String value : extensions.get( key ) )
510                    {
511                        values.add( value );
512                    }
513    
514                    this.extensions.put( key, values );
515                }
516    
517            }
518        }
519    
520    
521        /**
522         * The SchemaObject type :
523         * <li> AttributeType
524         * <li> DitCOntentRule
525         * <li> DitStructureRule
526         * <li> LdapComparator (specific to ADS)
527         * <li> LdapSyntaxe
528         * <li> MatchingRule
529         * <li> MatchingRuleUse
530         * <li> NameForm
531         * <li> Normalizer (specific to ADS)
532         * <li> ObjectClass
533         * <li> SyntaxChecker (specific to ADS)
534         * 
535         * @return the SchemaObject type
536         */
537        public SchemaObjectType getObjectType()
538        {
539            return objectType;
540        }
541    
542    
543        /**
544         * Gets the name of the schema this SchemaObject is associated with.
545         *
546         * @return the name of the schema associated with this schemaObject
547         */
548        public String getSchemaName()
549        {
550            return schemaName;
551        }
552    
553    
554        /**
555         * Sets the name of the schema this SchemaObject is associated with.
556         * 
557         * @param schemaName the new schema name
558         */
559        public void setSchemaName( String schemaName )
560        {
561            if ( !isReadOnly )
562            {
563                this.schemaName = schemaName;
564            }
565        }
566    
567    
568        /**
569         * @see Object#hashCode()
570         */
571        public int hashCode()
572        {
573            int h = 37;
574    
575            // The OID
576            h += h * 17 + oid.hashCode();
577    
578            // The SchemaObject type
579            h += h * 17 + objectType.getValue();
580    
581            // The Names, if any
582            if ( ( names != null ) && ( names.size() != 0 ) )
583            {
584                for ( String name : names )
585                {
586                    h += h * 17 + name.hashCode();
587                }
588            }
589    
590            // The schemaName if any
591            if ( schemaName != null )
592            {
593                h += h * 17 + schemaName.hashCode();
594            }
595    
596            h += h * 17 + ( isEnabled ? 1 : 0 );
597            h += h * 17 + ( isReadOnly ? 1 : 0 );
598    
599            // The description, if any
600            if ( description != null )
601            {
602                h += h * 17 + description.hashCode();
603            }
604    
605            // The extensions, if any
606            for ( String key : extensions.keySet() )
607            {
608                h += h * 17 + key.hashCode();
609    
610                List<String> values = extensions.get( key );
611    
612                if ( values != null )
613                {
614                    for ( String value : values )
615                    {
616                        h += h * 17 + value.hashCode();
617                    }
618                }
619            }
620    
621            return h;
622        }
623    
624    
625        /**
626         * @see Object#equals(Object)
627         */
628        public boolean equals( Object o1 )
629        {
630            if ( this == o1 )
631            {
632                return true;
633            }
634    
635            if ( !( o1 instanceof AbstractSchemaObject ) )
636            {
637                return false;
638            }
639    
640            AbstractSchemaObject that = ( AbstractSchemaObject ) o1;
641    
642            // Two schemaObject are equals if their oid is equal,
643            // their ObjectType is equal, their names are equals
644            // their schema name is the same, all their flags are equals,
645            // the description is the same and their extensions are equals
646            if ( !compareOid( oid, that.oid ) )
647            {
648                return false;
649            }
650    
651            // Compare the names
652            if ( names == null )
653            {
654                if ( that.names != null )
655                {
656                    return false;
657                }
658            }
659            else if ( that.names == null )
660            {
661                return false;
662            }
663            else
664            {
665                int nbNames = 0;
666    
667                for ( String name : names )
668                {
669                    if ( !that.names.contains( name ) )
670                    {
671                        return false;
672                    }
673    
674                    nbNames++;
675                }
676    
677                if ( nbNames != names.size() )
678                {
679                    return false;
680                }
681            }
682    
683            if ( schemaName == null )
684            {
685                if ( that.schemaName != null )
686                {
687                    return false;
688                }
689            }
690            else
691            {
692                if ( !schemaName.equalsIgnoreCase( that.schemaName ) )
693                {
694                    return false;
695                }
696            }
697    
698            if ( objectType != that.objectType )
699            {
700                return false;
701            }
702    
703            if ( extensions != null )
704            {
705                if ( that.extensions == null )
706                {
707                    return false;
708                }
709                else
710                {
711                    for ( String key : extensions.keySet() )
712                    {
713                        if ( !that.extensions.containsKey( key ) )
714                        {
715                            return false;
716                        }
717    
718                        List<String> thisValues = extensions.get( key );
719                        List<String> thatValues = that.extensions.get( key );
720    
721                        if ( thisValues != null )
722                        {
723                            if ( thatValues == null )
724                            {
725                                return false;
726                            }
727                            else
728                            {
729                                if ( thisValues.size() != thatValues.size() )
730                                {
731                                    return false;
732                                }
733    
734                                // TODO compare the values
735                            }
736                        }
737                        else if ( thatValues != null )
738                        {
739                            return false;
740                        }
741                    }
742                }
743            }
744            else if ( that.extensions != null )
745            {
746                return false;
747            }
748    
749            if ( this.isEnabled != that.isEnabled )
750            {
751                return false;
752            }
753    
754            if ( this.isObsolete != that.isObsolete )
755            {
756                return false;
757            }
758    
759            if ( this.isReadOnly != that.isReadOnly )
760            {
761                return false;
762            }
763    
764            if ( this.description == null )
765            {
766                return that.description == null;
767            }
768            else
769            {
770                return this.description.equalsIgnoreCase( that.description );
771            }
772        }
773    
774    
775        /**
776         * Register the given SchemaObject into the given registries' globalOidRegistry
777         *
778         * @param schemaObject the SchemaObject we want to register
779         * @param registries The registries in which we want it to be stored
780         * @throws NamingException If the OID is invalid
781         */
782        public void registerOid( SchemaObject schemaObject, Registries registries ) throws NamingException
783        {
784            // Add the SchemaObject into the globalOidRegistry
785            registries.getGlobalOidRegistry().register( schemaObject );
786        }
787    
788    
789        /**
790         * Copy the current SchemaObject on place
791         *
792         * @return The copied SchemaObject
793         */
794        public abstract SchemaObject copy();
795    
796    
797        /**
798         * Compare two oids, and return true if they are both null or
799         * equals
800         */
801        protected boolean compareOid( String oid1, String oid2 )
802        {
803            if ( oid1 == null )
804            {
805                return oid2 == null;
806            }
807            else
808            {
809                return oid1.equals( oid2 );
810            }
811        }
812    
813    
814        /**
815         * Copy a SchemaObject.
816         * 
817         * @return A copy of the current SchemaObject
818         */
819        public SchemaObject copy( SchemaObject original )
820        {
821            // copy the description
822            description = original.getDescription();
823    
824            // copy the flags
825            isEnabled = original.isEnabled();
826            isObsolete = original.isObsolete();
827            isReadOnly = original.isReadOnly();
828    
829            // copy the names
830            names = new ArrayList<String>();
831    
832            for ( String name : original.getNames() )
833            {
834                names.add( name );
835            }
836    
837            // copy the extensions
838            extensions = new HashMap<String, List<String>>();
839    
840            for ( String key : original.getExtensions().keySet() )
841            {
842                List<String> extensionValues = original.getExtensions().get( key );
843    
844                List<String> cloneExtension = new ArrayList<String>();
845    
846                for ( String value : extensionValues )
847                {
848                    cloneExtension.add( value );
849                }
850    
851                extensions.put( key, cloneExtension );
852            }
853    
854            // The SchemaName
855            schemaName = original.getSchemaName();
856    
857            // The specification
858            specification = original.getSpecification();
859    
860            return this;
861        }
862    
863    
864        /**
865         * Clear the current SchemaObject : remove all the references to other objects, 
866         * and all the Maps. 
867         */
868        public void clear()
869        {
870            // Clear the extensions
871            for ( String extension : extensions.keySet() )
872            {
873                List<String> extensionList = extensions.get( extension );
874    
875                extensionList.clear();
876            }
877    
878            extensions.clear();
879    
880            // Clear the names
881            names.clear();
882        }
883    }