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