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    package org.apache.directory.server.core.schema;
020    
021    
022    import java.util.HashSet;
023    import java.util.Set;
024    
025    import org.apache.directory.server.constants.ApacheSchemaConstants;
026    import org.apache.directory.server.constants.ServerDNConstants;
027    import org.apache.directory.server.core.interceptor.context.LookupOperationContext;
028    import org.apache.directory.shared.ldap.constants.SchemaConstants;
029    import org.apache.directory.shared.ldap.entry.DefaultServerAttribute;
030    import org.apache.directory.shared.ldap.entry.DefaultServerEntry;
031    import org.apache.directory.shared.ldap.entry.EntryAttribute;
032    import org.apache.directory.shared.ldap.entry.ServerEntry;
033    import org.apache.directory.shared.ldap.exception.LdapException;
034    import org.apache.directory.shared.ldap.name.DN;
035    import org.apache.directory.shared.ldap.schema.AttributeType;
036    import org.apache.directory.shared.ldap.schema.DITContentRule;
037    import org.apache.directory.shared.ldap.schema.DITStructureRule;
038    import org.apache.directory.shared.ldap.schema.LdapComparator;
039    import org.apache.directory.shared.ldap.schema.LdapSyntax;
040    import org.apache.directory.shared.ldap.schema.MatchingRule;
041    import org.apache.directory.shared.ldap.schema.MatchingRuleUse;
042    import org.apache.directory.shared.ldap.schema.NameForm;
043    import org.apache.directory.shared.ldap.schema.Normalizer;
044    import org.apache.directory.shared.ldap.schema.ObjectClass;
045    import org.apache.directory.shared.ldap.schema.SchemaManager;
046    import org.apache.directory.shared.ldap.schema.SchemaUtils;
047    import org.apache.directory.shared.ldap.schema.SyntaxChecker;
048    import org.apache.directory.shared.ldap.schema.registries.NormalizerRegistry;
049    
050    
051    /**
052     * This class manage the Schema's operations. 
053     *
054     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
055     * @version $Rev$, $Date$
056     */
057    public class DefaultSchemaService implements SchemaService
058    {
059        private static final String[] EMPTY_STRING_ARRAY = new String[0];
060    
061        /** cached version of the schema subentry with all attributes in it */
062        private ServerEntry schemaSubentry;
063        private final Object lock = new Object();
064    
065        /** a handle on the schema partition */
066        private SchemaPartition schemaPartition;
067    
068        /** the normalized name for the schema modification attributes */
069        private DN schemaModificationAttributesDN;
070        
071        /** A lock to avid concurrent generation of the SubschemaSubentry */
072        private Object schemaSubentrLock = new Object();
073    
074        
075        public DefaultSchemaService() throws Exception
076        {
077            schemaPartition = new SchemaPartition();
078        }
079        
080        
081    
082        /* (non-Javadoc)
083         * @see org.apache.directory.server.core.schema.SchemaService#isSchemaSubentry(java.lang.String)
084         */
085        public boolean isSchemaSubentry( String dnString ) throws LdapException
086        {
087            if ( ServerDNConstants.CN_SCHEMA_DN.equalsIgnoreCase( dnString ) ||
088                ServerDNConstants.CN_SCHEMA_DN_NORMALIZED.equalsIgnoreCase( dnString ) )
089            {
090                return true;
091            }
092    
093            DN dn = new DN( dnString ).normalize( schemaPartition.getSchemaManager().getNormalizerMapping() );
094            return dn.getNormName().equals( ServerDNConstants.CN_SCHEMA_DN_NORMALIZED );
095        }
096    
097    
098        public final SchemaManager getSchemaManager()
099        {
100            return schemaPartition.getSchemaManager();
101        }
102        
103        
104        /* (non-Javadoc)
105         * @see org.apache.directory.server.core.schema.SchemaService#getSchemaPartition()
106         */
107        public SchemaPartition getSchemaPartition()
108        {
109            return schemaPartition;
110        }
111        
112        
113        /* (non-Javadoc)
114         * @see org.apache.directory.server.core.schema.SchemaService#setSchemaPartition(org.apache.directory.server.core.schema.SchemaPartition)
115         */
116        public void setSchemaPartition( SchemaPartition schemaPartition )
117        {
118            this.schemaPartition = schemaPartition;
119        }
120    
121    
122        /**
123         * Generate the comparators attribute from the registry
124         */
125        private EntryAttribute generateComparators() throws LdapException
126        {
127            EntryAttribute attr = new DefaultServerAttribute( 
128                getSchemaManager().lookupAttributeTypeRegistry( SchemaConstants.COMPARATORS_AT ) );
129    
130            for ( LdapComparator<?> comparator : getSchemaManager().getComparatorRegistry() )
131            {
132                attr.add( SchemaUtils.render( comparator ) );
133            }
134    
135            return attr;
136        }
137    
138    
139        private EntryAttribute generateNormalizers() throws LdapException
140        {
141            EntryAttribute attr = new DefaultServerAttribute( 
142                getSchemaManager().lookupAttributeTypeRegistry( SchemaConstants.NORMALIZERS_AT ) );
143    
144            NormalizerRegistry nr = getSchemaManager().getNormalizerRegistry();
145            
146            for ( Normalizer normalizer : nr )
147            {
148                attr.add( SchemaUtils.render( normalizer ) );
149            }
150    
151            return attr;
152        }
153    
154    
155        private EntryAttribute generateSyntaxCheckers() throws LdapException
156        {
157            EntryAttribute attr = new DefaultServerAttribute( 
158                getSchemaManager().lookupAttributeTypeRegistry( SchemaConstants.SYNTAX_CHECKERS_AT ) );
159    
160            for ( SyntaxChecker syntaxChecker : getSchemaManager().getSyntaxCheckerRegistry() )
161            {
162                attr.add( SchemaUtils.render( syntaxChecker ) );
163            }
164            
165            return attr;
166        }
167    
168    
169        private EntryAttribute generateObjectClasses() throws LdapException
170        {
171            EntryAttribute attr = new DefaultServerAttribute( 
172                getSchemaManager().lookupAttributeTypeRegistry( SchemaConstants.OBJECT_CLASSES_AT ) );
173    
174            for ( ObjectClass objectClass : getSchemaManager().getObjectClassRegistry() )
175            {
176                attr.add( SchemaUtils.render( objectClass ).toString() );
177            }
178    
179            return attr;
180        }
181    
182    
183        private EntryAttribute generateAttributeTypes() throws LdapException
184        {
185            EntryAttribute attr = new DefaultServerAttribute( 
186                getSchemaManager().lookupAttributeTypeRegistry( SchemaConstants.ATTRIBUTE_TYPES_AT ) );
187    
188            for ( AttributeType attributeType : getSchemaManager().getAttributeTypeRegistry() )
189            {
190                attr.add( SchemaUtils.render( attributeType ).toString() );
191            }
192    
193            return attr;
194        }
195    
196    
197        private EntryAttribute generateMatchingRules() throws LdapException
198        {
199            EntryAttribute attr = new DefaultServerAttribute( 
200                getSchemaManager().lookupAttributeTypeRegistry( SchemaConstants.MATCHING_RULES_AT ) );
201    
202            for ( MatchingRule matchingRule : getSchemaManager().getMatchingRuleRegistry() )
203            {
204                attr.add( SchemaUtils.render( matchingRule ).toString() );
205            }
206    
207            return attr;
208        }
209    
210    
211        private EntryAttribute generateMatchingRuleUses() throws LdapException
212        {
213            EntryAttribute attr = new DefaultServerAttribute( 
214                getSchemaManager().lookupAttributeTypeRegistry( SchemaConstants.MATCHING_RULE_USE_AT ) );
215    
216            for ( MatchingRuleUse matchingRuleUse : getSchemaManager().getMatchingRuleUseRegistry() )
217            {
218                attr.add( SchemaUtils.render( matchingRuleUse ).toString() );
219            }
220    
221            return attr;
222        }
223    
224    
225        private EntryAttribute generateSyntaxes() throws LdapException
226        {
227            EntryAttribute attr = new DefaultServerAttribute( 
228                getSchemaManager().lookupAttributeTypeRegistry( SchemaConstants.LDAP_SYNTAXES_AT ) );
229    
230            for ( LdapSyntax syntax : getSchemaManager().getLdapSyntaxRegistry() )
231            {
232                attr.add( SchemaUtils.render( syntax ).toString() );
233            }
234    
235            return attr;
236        }
237    
238    
239        private EntryAttribute generateDitContextRules() throws LdapException
240        {
241            EntryAttribute attr = new DefaultServerAttribute( 
242                getSchemaManager().lookupAttributeTypeRegistry( SchemaConstants.DIT_CONTENT_RULES_AT ) );
243    
244            for ( DITContentRule ditContentRule : getSchemaManager().getDITContentRuleRegistry() )
245            {
246                attr.add( SchemaUtils.render( ditContentRule ).toString() );
247            }
248            
249            return attr;
250        }
251    
252    
253        private EntryAttribute generateDitStructureRules() throws LdapException
254        {
255            EntryAttribute attr = new DefaultServerAttribute( 
256                getSchemaManager().lookupAttributeTypeRegistry( SchemaConstants.DIT_STRUCTURE_RULES_AT ) );
257    
258            for ( DITStructureRule ditStructureRule : getSchemaManager().getDITStructureRuleRegistry() )
259            {
260                attr.add( SchemaUtils.render( ditStructureRule ).toString() );
261            }
262            
263            return attr;
264        }
265    
266    
267        private EntryAttribute generateNameForms() throws LdapException
268        {
269            EntryAttribute attr = new DefaultServerAttribute( 
270                getSchemaManager().lookupAttributeTypeRegistry( SchemaConstants.NAME_FORMS_AT ) );
271    
272            for ( NameForm nameForm : getSchemaManager().getNameFormRegistry() )
273            {
274                attr.add( SchemaUtils.render( nameForm ).toString() );
275            }
276            
277            return attr;
278        }
279    
280    
281        /**
282         * Creates the SSSE by extracting all the SchemaObjects from the registries.
283         */
284        private void generateSchemaSubentry( ServerEntry mods ) throws LdapException
285        {
286            ServerEntry attrs = new DefaultServerEntry( getSchemaManager(), mods.getDn() );
287    
288            // add the objectClass attribute : 'top', 'subschema', 'subentry' and 'apacheSubschema' 
289            attrs.put( SchemaConstants.OBJECT_CLASS_AT, 
290                SchemaConstants.TOP_OC,
291                SchemaConstants.SUBSCHEMA_OC,
292                SchemaConstants.SUBENTRY_OC,
293                ApacheSchemaConstants.APACHE_SUBSCHEMA_OC
294                );
295    
296            // add the cn attribute as required for the RDN
297            attrs.put( SchemaConstants.CN_AT, "schema" );
298    
299            // generate all the other operational attributes
300            attrs.put( generateComparators() );
301            attrs.put( generateNormalizers() );
302            attrs.put( generateSyntaxCheckers() );
303            attrs.put( generateObjectClasses() );
304            attrs.put( generateAttributeTypes() );
305            attrs.put( generateMatchingRules() );
306            attrs.put( generateMatchingRuleUses() );
307            attrs.put( generateSyntaxes() );
308            attrs.put( generateDitContextRules() );
309            attrs.put( generateDitStructureRules() );
310            attrs.put( generateNameForms() );
311            attrs.put( SchemaConstants.SUBTREE_SPECIFICATION_AT, "{}" );
312    
313            // -------------------------------------------------------------------
314            // set standard operational attributes for the subentry
315            // -------------------------------------------------------------------
316    
317            // Add the createTimestamp
318            EntryAttribute createTimestamp = mods.get( SchemaConstants.CREATE_TIMESTAMP_AT );
319            attrs.put( SchemaConstants.CREATE_TIMESTAMP_AT, createTimestamp.get() );
320    
321            // Add the creatorsName
322            attrs.put( SchemaConstants.CREATORS_NAME_AT, ServerDNConstants.ADMIN_SYSTEM_DN );
323    
324            // Add the modifyTimestamp
325            EntryAttribute schemaModifyTimestamp = mods.get( ApacheSchemaConstants.SCHEMA_MODIFY_TIMESTAMP_AT );
326            attrs.put( SchemaConstants.MODIFY_TIMESTAMP_AT, schemaModifyTimestamp.get() );
327    
328            // Add the modifiersName
329            EntryAttribute schemaModifiersName = mods.get( ApacheSchemaConstants.SCHEMA_MODIFIERS_NAME_AT );
330            attrs.put( SchemaConstants.MODIFIERS_NAME_AT, schemaModifiersName.get() );
331    
332            // don't swap out if a request for the subentry is in progress or we
333            // can give back an inconsistent schema back to the client so we block
334            synchronized ( lock )
335            {
336                schemaSubentry = attrs;
337            }
338        }
339    
340    
341        private void addAttribute( ServerEntry attrs, String id ) throws LdapException
342        {
343            EntryAttribute attr = schemaSubentry.get( id );
344    
345            if ( attr != null )
346            {
347                attrs.put( attr );
348            }
349        }
350    
351    
352        /**
353         * {@inheritDoc}
354         */
355        public ServerEntry getSubschemaEntryImmutable() throws Exception
356        {
357            synchronized ( schemaSubentrLock )
358            {
359                if ( schemaSubentry == null )
360                {
361                    generateSchemaSubentry( schemaPartition.lookup(
362                            new LookupOperationContext( null, schemaModificationAttributesDN ) ) );
363                }
364        
365                return ( ServerEntry ) schemaSubentry.clone();
366            }
367        }
368        
369        
370        /**
371         * Initializes the SchemaService
372         *
373         * @throws Exception If the initializaion fails
374         */
375        public void initialize() throws Exception
376        {
377            try
378            {
379                schemaModificationAttributesDN = new DN( ServerDNConstants.SCHEMA_MODIFICATIONS_DN );
380                schemaModificationAttributesDN.normalize( 
381                    getSchemaManager().getNormalizerMapping() );
382            }
383            catch ( LdapException e )
384            {
385                throw new RuntimeException( e );
386            }
387        }
388    
389    
390        /* (non-Javadoc)
391         * @see org.apache.directory.server.core.schema.SchemaService#getSubschemaEntryCloned()
392         */
393        public ServerEntry getSubschemaEntryCloned() throws Exception
394        {
395            if ( schemaSubentry == null )
396            {
397                generateSchemaSubentry( schemaPartition.lookup(
398                        new LookupOperationContext( null, schemaModificationAttributesDN ) ) );
399            }
400    
401            return ( ServerEntry ) schemaSubentry.clone();
402        }
403    
404    
405        /**
406         * {@inheritDoc}
407         */
408        public ServerEntry getSubschemaEntry( String[] ids ) throws Exception
409        {
410            if ( ids == null )
411            {
412                ids = EMPTY_STRING_ARRAY;
413            }
414    
415            Set<String> setOids = new HashSet<String>();
416            ServerEntry attrs = new DefaultServerEntry( getSchemaManager(), DN.EMPTY_DN );
417            boolean returnAllOperationalAttributes = false;
418    
419            synchronized( lock )
420            {
421                // ---------------------------------------------------------------
422                // Check if we need an update by looking at timestamps on disk
423                // ---------------------------------------------------------------
424    
425                ServerEntry mods = 
426                    schemaPartition.lookup( 
427                        new LookupOperationContext( null, schemaModificationAttributesDN ) );
428                
429    // @todo enable this optimization at some point but for now it
430    // is causing some problems so I will just turn it off
431    //          Attribute modifyTimeDisk = mods.get( SchemaConstants.MODIFY_TIMESTAMP_AT );
432    //
433    //          Attribute modifyTimeMemory = null;
434    //
435    //            if ( schemaSubentry != null )
436    //            {
437    //                modifyTimeMemory = schemaSubentry.get( SchemaConstants.MODIFY_TIMESTAMP_AT );
438    //                if ( modifyTimeDisk == null && modifyTimeMemory == null )
439    //                {
440    //                    // do nothing!
441    //                }
442    //                else if ( modifyTimeDisk != null && modifyTimeMemory != null )
443    //                {
444    //                    Date disk = DateUtils.getDate( ( String ) modifyTimeDisk.get() );
445    //                    Date mem = DateUtils.getDate( ( String ) modifyTimeMemory.get() );
446    //                    if ( disk.after( mem ) )
447    //                    {
448    //                        generateSchemaSubentry( mods );
449    //                    }
450    //                }
451    //                else
452    //                {
453    //                    generateSchemaSubentry( mods );
454    //                }
455    //            }
456    //            else
457    //            {
458                    generateSchemaSubentry( mods );
459    //            }
460    
461                // ---------------------------------------------------------------
462                // Prep Work: Transform the attributes to their OID counterpart
463                // ---------------------------------------------------------------
464    
465                for ( String id:ids )
466                {
467                    // Check whether the set contains a plus, and use it below to include all
468                    // operational attributes.  Due to RFC 3673, and issue DIREVE-228 in JIRA
469                    if ( SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES.equals( id ) )
470                    {
471                        returnAllOperationalAttributes = true;
472                    }
473                    else if ( SchemaConstants.ALL_USER_ATTRIBUTES.equals(  id ) )
474                    {
475                        setOids.add( id );
476                    }
477                    else
478                    {
479                        setOids.add( getSchemaManager().getAttributeTypeRegistry().getOidByName( id ) );
480                    }
481                }
482    
483                if ( returnAllOperationalAttributes || setOids.contains( SchemaConstants.COMPARATORS_AT_OID ) )
484                {
485                    addAttribute( attrs, SchemaConstants.COMPARATORS_AT );
486                }
487    
488                if ( returnAllOperationalAttributes || setOids.contains( SchemaConstants.NORMALIZERS_AT_OID ) )
489                {
490                    addAttribute( attrs, SchemaConstants.NORMALIZERS_AT );
491                }
492    
493                if ( returnAllOperationalAttributes || setOids.contains( SchemaConstants.SYNTAX_CHECKERS_AT_OID ) )
494                {
495                    addAttribute( attrs, SchemaConstants.SYNTAX_CHECKERS_AT );
496                }
497    
498                if ( returnAllOperationalAttributes || setOids.contains( SchemaConstants.OBJECT_CLASSES_AT_OID ) )
499                {
500                    addAttribute( attrs, SchemaConstants.OBJECT_CLASSES_AT );
501                }
502    
503                if ( returnAllOperationalAttributes || setOids.contains( SchemaConstants.ATTRIBUTE_TYPES_AT_OID ) )
504                {
505                    addAttribute( attrs, SchemaConstants.ATTRIBUTE_TYPES_AT );
506                }
507    
508                if ( returnAllOperationalAttributes || setOids.contains( SchemaConstants.MATCHING_RULES_AT_OID ) )
509                {
510                    addAttribute( attrs, SchemaConstants.MATCHING_RULES_AT );
511                }
512    
513                if ( returnAllOperationalAttributes || setOids.contains( SchemaConstants.MATCHING_RULE_USE_AT_OID ) )
514                {
515                    addAttribute( attrs, SchemaConstants.MATCHING_RULE_USE_AT );
516                }
517    
518                if ( returnAllOperationalAttributes || setOids.contains( SchemaConstants.LDAP_SYNTAXES_AT_OID ) )
519                {
520                    addAttribute( attrs, SchemaConstants.LDAP_SYNTAXES_AT );
521                }
522    
523                if ( returnAllOperationalAttributes || setOids.contains( SchemaConstants.DIT_CONTENT_RULES_AT_OID ) )
524                {
525                    addAttribute( attrs, SchemaConstants.DIT_CONTENT_RULES_AT );
526                }
527    
528                if ( returnAllOperationalAttributes || setOids.contains( SchemaConstants.DIT_STRUCTURE_RULES_AT_OID ) )
529                {
530                    addAttribute( attrs, SchemaConstants.DIT_STRUCTURE_RULES_AT );
531                }
532    
533                if ( returnAllOperationalAttributes || setOids.contains( SchemaConstants.NAME_FORMS_AT_OID ) )
534                {
535                    addAttribute( attrs, SchemaConstants.NAME_FORMS_AT );
536                }
537    
538                if ( returnAllOperationalAttributes || setOids.contains( SchemaConstants.SUBTREE_SPECIFICATION_AT_OID ) )
539                {
540                    addAttribute( attrs, SchemaConstants.SUBTREE_SPECIFICATION_AT );
541                }
542    
543                int minSetSize = 0;
544                
545                if ( setOids.contains( SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES ) )
546                {
547                    minSetSize++;
548                }
549    
550                if ( setOids.contains( SchemaConstants.ALL_USER_ATTRIBUTES ) )
551                {
552                    minSetSize++;
553                }
554    
555                if ( setOids.contains( SchemaConstants.REF_AT_OID ) )
556                {
557                    minSetSize++;
558                }
559    
560                // add the objectClass attribute
561                if ( setOids.contains( SchemaConstants.ALL_USER_ATTRIBUTES ) ||
562                     setOids.contains( SchemaConstants.OBJECT_CLASS_AT_OID ) ||
563                     setOids.size() == minSetSize )
564                {
565                    addAttribute( attrs, SchemaConstants.OBJECT_CLASS_AT );
566                }
567    
568                // add the cn attribute as required for the RDN
569                if ( setOids.contains( SchemaConstants.ALL_USER_ATTRIBUTES ) ||
570                     setOids.contains( SchemaConstants.CN_AT_OID ) ||
571                     setOids.size() == minSetSize )
572                {
573                    addAttribute( attrs, SchemaConstants.CN_AT );
574                }
575    
576                // -------------------------------------------------------------------
577                // set standard operational attributes for the subentry
578                // -------------------------------------------------------------------
579    
580    
581                if ( returnAllOperationalAttributes || setOids.contains( SchemaConstants.CREATE_TIMESTAMP_AT_OID ) )
582                {
583                    addAttribute( attrs, SchemaConstants.CREATE_TIMESTAMP_AT );
584                }
585    
586                if ( returnAllOperationalAttributes || setOids.contains( SchemaConstants.CREATORS_NAME_AT_OID ) )
587                {
588                    addAttribute( attrs, SchemaConstants.CREATORS_NAME_AT );
589                }
590    
591                if ( returnAllOperationalAttributes || setOids.contains( SchemaConstants.MODIFY_TIMESTAMP_AT_OID ) )
592                {
593                    addAttribute( attrs, SchemaConstants.MODIFY_TIMESTAMP_AT );
594                }
595    
596                if ( returnAllOperationalAttributes || setOids.contains( SchemaConstants.MODIFIERS_NAME_AT_OID ) )
597                {
598                    addAttribute( attrs, SchemaConstants.MODIFIERS_NAME_AT );
599                }
600            }
601    
602            return attrs;
603        }
604    }