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 */
019package org.apache.directory.server.core.shared;
020
021
022import java.io.IOException;
023
024import org.apache.directory.api.ldap.model.constants.SchemaConstants;
025import org.apache.directory.api.ldap.model.entry.Attribute;
026import org.apache.directory.api.ldap.model.entry.DefaultAttribute;
027import org.apache.directory.api.ldap.model.entry.DefaultEntry;
028import org.apache.directory.api.ldap.model.entry.Entry;
029import org.apache.directory.api.ldap.model.exception.LdapException;
030import org.apache.directory.api.ldap.model.exception.LdapOtherException;
031import org.apache.directory.api.ldap.model.name.Dn;
032import org.apache.directory.api.ldap.model.schema.AttributeType;
033import org.apache.directory.api.ldap.model.schema.DitContentRule;
034import org.apache.directory.api.ldap.model.schema.DitStructureRule;
035import org.apache.directory.api.ldap.model.schema.LdapComparator;
036import org.apache.directory.api.ldap.model.schema.LdapSyntax;
037import org.apache.directory.api.ldap.model.schema.MatchingRule;
038import org.apache.directory.api.ldap.model.schema.MatchingRuleUse;
039import org.apache.directory.api.ldap.model.schema.NameForm;
040import org.apache.directory.api.ldap.model.schema.Normalizer;
041import org.apache.directory.api.ldap.model.schema.ObjectClass;
042import org.apache.directory.api.ldap.model.schema.SchemaManager;
043import org.apache.directory.api.ldap.model.schema.SchemaObjectRenderer;
044import org.apache.directory.api.ldap.model.schema.SchemaUtils;
045import org.apache.directory.api.ldap.model.schema.SyntaxChecker;
046import org.apache.directory.api.ldap.model.schema.registries.NormalizerRegistry;
047import org.apache.directory.server.constants.ApacheSchemaConstants;
048import org.apache.directory.server.constants.ServerDNConstants;
049import org.apache.directory.server.core.api.DirectoryService;
050import org.apache.directory.server.core.api.entry.ClonedServerEntry;
051import org.apache.directory.server.core.api.interceptor.context.FilteringOperationContext;
052import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext;
053import org.apache.directory.server.core.api.partition.Partition;
054import org.apache.directory.server.core.api.partition.PartitionTxn;
055
056
057/**
058 * This class manage the Schema's operations. 
059 *
060 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
061 */
062public final class SchemaService
063{
064    /** cached version of the schema subentry with all attributes in it */
065    private static Entry schemaSubentry;
066
067    /** A lock to avid concurrent generation of the SubschemaSubentry */
068    private static Object schemaSubentrLock = new Object();
069
070
071    private SchemaService()
072    {
073    }
074
075
076    /**
077     * Generate the comparators attribute from the registry
078     */
079    private static Attribute generateComparators( SchemaManager schemaManager ) throws LdapException
080    {
081        Attribute attr = new DefaultAttribute(
082            schemaManager.lookupAttributeTypeRegistry( SchemaConstants.COMPARATORS_AT ) );
083
084        for ( LdapComparator<?> comparator : schemaManager.getComparatorRegistry() )
085        {
086            attr.add( SchemaUtils.render( comparator ) );
087        }
088
089        return attr;
090    }
091
092
093    private static Attribute generateNormalizers( SchemaManager schemaManager ) throws LdapException
094    {
095        Attribute attr = new DefaultAttribute(
096            schemaManager.getAttributeType( SchemaConstants.NORMALIZERS_AT ) );
097
098        NormalizerRegistry nr = schemaManager.getNormalizerRegistry();
099
100        for ( Normalizer normalizer : nr )
101        {
102            attr.add( SchemaUtils.render( normalizer ) );
103        }
104
105        return attr;
106    }
107
108
109    private static Attribute generateSyntaxCheckers( SchemaManager schemaManager ) throws LdapException
110    {
111        Attribute attr = new DefaultAttribute(
112            schemaManager.getAttributeType( SchemaConstants.SYNTAX_CHECKERS_AT ) );
113
114        for ( SyntaxChecker syntaxChecker : schemaManager.getSyntaxCheckerRegistry() )
115        {
116            attr.add( SchemaUtils.render( syntaxChecker ) );
117        }
118
119        return attr;
120    }
121
122
123    private static Attribute generateObjectClasses( SchemaManager schemaManager ) throws LdapException
124    {
125        Attribute attr = new DefaultAttribute(
126            schemaManager.getAttributeType( SchemaConstants.OBJECT_CLASSES_AT ) );
127
128        for ( ObjectClass objectClass : schemaManager.getObjectClassRegistry() )
129        {
130            attr.add( SchemaObjectRenderer.SUBSCHEMA_SUBENTRY_RENDERER.render( objectClass ) );
131        }
132
133        return attr;
134    }
135
136
137    private static Attribute generateAttributeTypes( SchemaManager schemaManager ) throws LdapException
138    {
139        Attribute attr = new DefaultAttribute(
140            schemaManager.getAttributeType( SchemaConstants.ATTRIBUTE_TYPES_AT ) );
141
142        for ( AttributeType attributeType : schemaManager.getAttributeTypeRegistry() )
143        {
144            attr.add( SchemaObjectRenderer.SUBSCHEMA_SUBENTRY_RENDERER.render( attributeType ) );
145        }
146
147        return attr;
148    }
149
150
151    private static Attribute generateMatchingRules( SchemaManager schemaManager ) throws LdapException
152    {
153        Attribute attr = new DefaultAttribute(
154            schemaManager.getAttributeType( SchemaConstants.MATCHING_RULES_AT ) );
155
156        for ( MatchingRule matchingRule : schemaManager.getMatchingRuleRegistry() )
157        {
158            attr.add( SchemaObjectRenderer.SUBSCHEMA_SUBENTRY_RENDERER.render( matchingRule ) );
159        }
160
161        return attr;
162    }
163
164
165    private static Attribute generateMatchingRuleUses( SchemaManager schemaManager ) throws LdapException
166    {
167        Attribute attr = new DefaultAttribute(
168            schemaManager.getAttributeType( SchemaConstants.MATCHING_RULE_USE_AT ) );
169
170        for ( MatchingRuleUse matchingRuleUse : schemaManager.getMatchingRuleUseRegistry() )
171        {
172            attr.add( SchemaObjectRenderer.SUBSCHEMA_SUBENTRY_RENDERER.render( matchingRuleUse ) );
173        }
174
175        return attr;
176    }
177
178
179    private static Attribute generateSyntaxes( SchemaManager schemaManager ) throws LdapException
180    {
181        Attribute attr = new DefaultAttribute(
182            schemaManager.getAttributeType( SchemaConstants.LDAP_SYNTAXES_AT ) );
183
184        for ( LdapSyntax syntax : schemaManager.getLdapSyntaxRegistry() )
185        {
186            attr.add( SchemaObjectRenderer.SUBSCHEMA_SUBENTRY_RENDERER.render( syntax ) );
187        }
188
189        return attr;
190    }
191
192
193    private static Attribute generateDitContextRules( SchemaManager schemaManager ) throws LdapException
194    {
195        Attribute attr = new DefaultAttribute(
196            schemaManager.getAttributeType( SchemaConstants.DIT_CONTENT_RULES_AT ) );
197
198        for ( DitContentRule ditContentRule : schemaManager.getDITContentRuleRegistry() )
199        {
200            attr.add( SchemaObjectRenderer.SUBSCHEMA_SUBENTRY_RENDERER.render( ditContentRule ) );
201        }
202
203        return attr;
204    }
205
206
207    private static Attribute generateDitStructureRules( SchemaManager schemaManager ) throws LdapException
208    {
209        Attribute attr = new DefaultAttribute(
210            schemaManager.getAttributeType( SchemaConstants.DIT_STRUCTURE_RULES_AT ) );
211
212        for ( DitStructureRule ditStructureRule : schemaManager.getDITStructureRuleRegistry() )
213        {
214            attr.add( SchemaObjectRenderer.SUBSCHEMA_SUBENTRY_RENDERER.render( ditStructureRule ) );
215        }
216
217        return attr;
218    }
219
220
221    private static Attribute generateNameForms( SchemaManager schemaManager ) throws LdapException
222    {
223        Attribute attr = new DefaultAttribute(
224            schemaManager.getAttributeType( SchemaConstants.NAME_FORMS_AT ) );
225
226        for ( NameForm nameForm : schemaManager.getNameFormRegistry() )
227        {
228            attr.add( SchemaObjectRenderer.SUBSCHEMA_SUBENTRY_RENDERER.render( nameForm ) );
229        }
230
231        return attr;
232    }
233
234
235    /**
236     * Creates the SSSE by extracting all the SchemaObjects from the registries.
237     */
238    private static void generateSchemaSubentry( SchemaManager schemaManager, Entry mods ) throws LdapException
239    {
240        Entry attrs = new DefaultEntry( schemaManager, mods.getDn() );
241
242        // add the objectClass attribute : 'top', 'subschema', 'subentry' and 'apacheSubschema' 
243        attrs.put( SchemaConstants.OBJECT_CLASS_AT,
244            SchemaConstants.TOP_OC,
245            SchemaConstants.SUBSCHEMA_OC,
246            SchemaConstants.SUBENTRY_OC,
247            ApacheSchemaConstants.APACHE_SUBSCHEMA_OC
248            );
249
250        // add the cn attribute as required for the Rdn
251        attrs.put( SchemaConstants.CN_AT, "schema" );
252
253        // generate all the other operational attributes
254        attrs.put( generateComparators( schemaManager ) );
255        attrs.put( generateNormalizers( schemaManager ) );
256        attrs.put( generateSyntaxCheckers( schemaManager ) );
257        attrs.put( generateObjectClasses( schemaManager ) );
258        attrs.put( generateAttributeTypes( schemaManager ) );
259        attrs.put( generateMatchingRules( schemaManager ) );
260        attrs.put( generateMatchingRuleUses( schemaManager ) );
261        attrs.put( generateSyntaxes( schemaManager ) );
262        attrs.put( generateDitContextRules( schemaManager ) );
263        attrs.put( generateDitStructureRules( schemaManager ) );
264        attrs.put( generateNameForms( schemaManager ) );
265        attrs.put( SchemaConstants.SUBTREE_SPECIFICATION_AT, "{}" );
266
267        // -------------------------------------------------------------------
268        // set standard operational attributes for the subentry
269        // -------------------------------------------------------------------
270
271        // Add the createTimestamp
272        Attribute createTimestamp = mods.get( SchemaConstants.CREATE_TIMESTAMP_AT );
273        attrs.put( SchemaConstants.CREATE_TIMESTAMP_AT, createTimestamp.get() );
274
275        // Add the creatorsName
276        attrs.put( SchemaConstants.CREATORS_NAME_AT, ServerDNConstants.ADMIN_SYSTEM_DN );
277
278        // Add the modifyTimestamp
279        Attribute schemaModifyTimestamp = mods.get( ApacheSchemaConstants.SCHEMA_MODIFY_TIMESTAMP_AT );
280        attrs.put( SchemaConstants.MODIFY_TIMESTAMP_AT, schemaModifyTimestamp.get() );
281
282        // Add the modifiersName
283        Attribute schemaModifiersName = mods.get( ApacheSchemaConstants.SCHEMA_MODIFIERS_NAME_AT );
284        attrs.put( SchemaConstants.MODIFIERS_NAME_AT, schemaModifiersName.get() );
285
286        // don't swap out if a request for the subentry is in progress or we
287        // can give back an inconsistent schema back to the client so we block
288        synchronized ( schemaSubentrLock )
289        {
290            schemaSubentry = attrs;
291        }
292    }
293
294
295    private static void addAttribute( Entry attrs, String id ) throws LdapException
296    {
297        Attribute attr = schemaSubentry.get( id );
298
299        if ( ( attr != null ) && ( attr.size() > 0 ) )
300        {
301            attrs.put( attr );
302        }
303    }
304
305
306    /**
307     * {@inheritDoc}
308     */
309    public static Entry getSubschemaEntryImmutable( DirectoryService directoryService ) throws LdapException
310    {
311        synchronized ( schemaSubentrLock )
312        {
313            if ( schemaSubentry == null )
314            {
315                Dn schemaModificationAttributesDn = new Dn( directoryService.getSchemaManager(),
316                    SchemaConstants.SCHEMA_MODIFICATIONS_DN );
317                
318                Partition partition = directoryService.getSchemaPartition();
319                
320                LookupOperationContext lookupContext = new LookupOperationContext( null, schemaModificationAttributesDn );
321                lookupContext.setPartition( partition );
322
323                try ( PartitionTxn partitionTxn = partition.beginReadTransaction() )
324                {
325                    lookupContext.setTransaction( partitionTxn );
326                    
327                    generateSchemaSubentry( directoryService.getSchemaManager(),
328                        directoryService.getSchemaPartition().lookup( lookupContext ) );
329                }
330                catch ( IOException ioe )
331                {
332                    throw new LdapOtherException( ioe.getMessage(), ioe );
333                }
334            }
335
336            return schemaSubentry.clone();
337        }
338    }
339
340
341    /* (non-Javadoc)
342     * @see org.apache.directory.server.core.schema.SchemaService#getSubschemaEntryCloned()
343     */
344    public static Entry getSubschemaEntryCloned( DirectoryService directoryService ) throws LdapException
345    {
346        if ( schemaSubentry == null )
347        {
348            Dn schemaModificationAttributesDn = new Dn( directoryService.getSchemaManager(),
349                SchemaConstants.SCHEMA_MODIFICATIONS_DN );
350
351            Partition partition = directoryService.getSchemaPartition();
352            
353            LookupOperationContext lookupContext = new LookupOperationContext( null, schemaModificationAttributesDn );
354            lookupContext.setPartition( partition );
355
356            try ( PartitionTxn partitionTxn = partition.beginReadTransaction() )
357            {
358                lookupContext.setTransaction( partitionTxn );
359                
360                generateSchemaSubentry(
361                    directoryService.getSchemaManager(),
362                    directoryService.getSchemaPartition().lookup( lookupContext ) );
363            }
364            catch ( IOException ioe )
365            {
366                throw new LdapOtherException( ioe.getMessage(), ioe );
367            }
368        }
369
370        return schemaSubentry.clone();
371    }
372
373
374    /**
375     * {@inheritDoc}
376     */
377    public static Entry getSubschemaEntry( DirectoryService directoryService, FilteringOperationContext operationContext )
378        throws LdapException
379    {
380        SchemaManager schemaManager = directoryService.getSchemaManager();
381
382        Entry attrs = new DefaultEntry( schemaManager, Dn.ROOT_DSE );
383
384        synchronized ( schemaSubentrLock )
385        {
386            // ---------------------------------------------------------------
387            // Check if we need an update by looking at timestamps on disk
388            // ---------------------------------------------------------------
389            Dn schemaModificationAttributesDn = new Dn( directoryService.getSchemaManager(),
390                SchemaConstants.SCHEMA_MODIFICATIONS_DN );
391
392            Partition partition = directoryService.getSchemaPartition();
393            
394            LookupOperationContext lookupContext = new LookupOperationContext( null, schemaModificationAttributesDn, SchemaConstants.ALL_ATTRIBUTES_ARRAY );
395            lookupContext.setPartition( partition );
396
397            try ( PartitionTxn partitionTxn = partition.beginReadTransaction() )
398            {
399                lookupContext.setTransaction( partitionTxn );
400
401                Entry mods =
402                    directoryService.getSchemaPartition().lookup( lookupContext );
403                // TODO enable this optimization at some point but for now it
404                // is causing some problems so I will just turn it off
405                //          Attribute modifyTimeDisk = mods.get( SchemaConstants.MODIFY_TIMESTAMP_AT );
406                //
407                //          Attribute modifyTimeMemory = null;
408                //
409                //            if ( schemaSubentry != null )
410                //            {
411                //                modifyTimeMemory = schemaSubentry.get( SchemaConstants.MODIFY_TIMESTAMP_AT );
412                //                if ( modifyTimeDisk == null && modifyTimeMemory == null )
413                //                {
414                //                    // do nothing!
415                //                }
416                //                else if ( modifyTimeDisk != null && modifyTimeMemory != null )
417                //                {
418                //                    Date disk = DateUtils.getDate( ( String ) modifyTimeDisk.get() );
419                //                    Date mem = DateUtils.getDate( ( String ) modifyTimeMemory.get() );
420                //                    if ( disk.after( mem ) )
421                //                    {
422                //                        generateSchemaSubentry( mods );
423                //                    }
424                //                }
425                //                else
426                //                {
427                //                    generateSchemaSubentry( mods );
428                //                }
429                //            }
430                //            else
431                //            {
432                generateSchemaSubentry( schemaManager, mods );
433            }
434            catch ( IOException ioe )
435            {
436                throw new LdapOtherException( ioe.getMessage(), ioe );
437            }
438
439            // ---------------------------------------------------------------
440            // Prep Work: Transform the attributes to their OID counterpart
441            // ---------------------------------------------------------------
442            addAttribute( attrs, SchemaConstants.COMPARATORS_AT );
443            addAttribute( attrs, SchemaConstants.NORMALIZERS_AT );
444            addAttribute( attrs, SchemaConstants.SYNTAX_CHECKERS_AT );
445            addAttribute( attrs, SchemaConstants.OBJECT_CLASSES_AT );
446            addAttribute( attrs, SchemaConstants.ATTRIBUTE_TYPES_AT );
447            addAttribute( attrs, SchemaConstants.MATCHING_RULES_AT );
448            addAttribute( attrs, SchemaConstants.MATCHING_RULE_USE_AT );
449            addAttribute( attrs, SchemaConstants.LDAP_SYNTAXES_AT );
450            addAttribute( attrs, SchemaConstants.DIT_CONTENT_RULES_AT );
451            addAttribute( attrs, SchemaConstants.DIT_STRUCTURE_RULES_AT );
452            addAttribute( attrs, SchemaConstants.NAME_FORMS_AT );
453            addAttribute( attrs, SchemaConstants.SUBTREE_SPECIFICATION_AT );
454
455            // add the objectClass attribute if needed
456            addAttribute( attrs, SchemaConstants.OBJECT_CLASS_AT );
457
458            // add the cn attribute as required for the Rdn
459            addAttribute( attrs, SchemaConstants.CN_AT );
460
461            // -------------------------------------------------------------------
462            // set standard operational attributes for the subentry
463            // -------------------------------------------------------------------
464            addAttribute( attrs, SchemaConstants.CREATE_TIMESTAMP_AT );
465            addAttribute( attrs, SchemaConstants.CREATORS_NAME_AT );
466            addAttribute( attrs, SchemaConstants.MODIFY_TIMESTAMP_AT );
467            addAttribute( attrs, SchemaConstants.MODIFIERS_NAME_AT );
468            addAttribute( attrs, SchemaConstants.ENTRY_UUID_AT );
469            addAttribute( attrs, SchemaConstants.ENTRY_DN_AT );
470        }
471
472        return new ClonedServerEntry( attrs );
473    }
474}