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.server.core.schema;
021
022
023import java.util.HashMap;
024import java.util.HashSet;
025import java.util.List;
026import java.util.Map;
027import java.util.Set;
028
029import org.apache.directory.api.ldap.model.constants.SchemaConstants;
030import org.apache.directory.api.ldap.model.entry.Attribute;
031import org.apache.directory.api.ldap.model.entry.Modification;
032import org.apache.directory.api.ldap.model.exception.LdapException;
033import org.apache.directory.api.ldap.model.exception.LdapUnwillingToPerformException;
034import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
035import org.apache.directory.api.ldap.model.schema.AttributeType;
036import org.apache.directory.api.ldap.model.schema.DitContentRule;
037import org.apache.directory.api.ldap.model.schema.DitStructureRule;
038import org.apache.directory.api.ldap.model.schema.LdapSyntax;
039import org.apache.directory.api.ldap.model.schema.MatchingRule;
040import org.apache.directory.api.ldap.model.schema.MatchingRuleUse;
041import org.apache.directory.api.ldap.model.schema.NameForm;
042import org.apache.directory.api.ldap.model.schema.ObjectClass;
043import org.apache.directory.api.ldap.model.schema.SchemaManager;
044import org.apache.directory.api.ldap.model.schema.parsers.LdapComparatorDescription;
045import org.apache.directory.api.ldap.model.schema.parsers.NormalizerDescription;
046import org.apache.directory.api.ldap.model.schema.parsers.SyntaxCheckerDescription;
047import org.apache.directory.api.util.Strings;
048import org.apache.directory.server.core.api.DirectoryService;
049import org.apache.directory.server.core.api.DnFactory;
050import org.apache.directory.server.core.api.InterceptorEnum;
051import org.apache.directory.server.core.api.OperationEnum;
052import org.apache.directory.server.core.api.interceptor.Interceptor;
053import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext;
054import org.apache.directory.server.core.api.schema.DescriptionParsers;
055import org.apache.directory.server.i18n.I18n;
056import org.slf4j.Logger;
057import org.slf4j.LoggerFactory;
058
059
060/**
061 *
062 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
063 */
064public class SchemaSubentryManager
065{
066    /** A logger for this class */
067    private static final Logger LOG = LoggerFactory.getLogger( SchemaSubentryManager.class );
068
069    // indices of handlers and object ids into arrays
070    private static final int COMPARATOR_INDEX = 0;
071    private static final int NORMALIZER_INDEX = 1;
072    private static final int SYNTAX_CHECKER_INDEX = 2;
073    private static final int SYNTAX_INDEX = 3;
074    private static final int MATCHING_RULE_INDEX = 4;
075    private static final int ATTRIBUTE_TYPE_INDEX = 5;
076    private static final int OBJECT_CLASS_INDEX = 6;
077    private static final int MATCHING_RULE_USE_INDEX = 7;
078    private static final int DIT_STRUCTURE_RULE_INDEX = 8;
079    private static final int DIT_CONTENT_RULE_INDEX = 9;
080    private static final int NAME_FORM_INDEX = 10;
081
082    private static final Set<String> VALID_OU_VALUES = new HashSet<>();
083
084    /** The schemaManager */
085    private final SchemaManager schemaManager;
086
087    private final SchemaSubentryModifier subentryModifier;
088
089    /** The description parsers */
090    private final DescriptionParsers parsers;
091
092    /**
093     * Maps the OID of a subschemaSubentry operational attribute to the index of
094     * the handler in the schemaObjectHandlers array.
095     */
096    private final Map<String, Integer> opAttr2handlerIndex = new HashMap<>( 11 );
097    private static final String CASCADING_ERROR =
098        "Cascading has not yet been implemented: standard operation is in effect.";
099
100    static
101    {
102        VALID_OU_VALUES.add( Strings.toLowerCaseAscii( SchemaConstants.NORMALIZERS_AT ) );
103        VALID_OU_VALUES.add( Strings.toLowerCaseAscii( SchemaConstants.COMPARATORS_AT ) );
104        VALID_OU_VALUES.add( Strings.toLowerCaseAscii( SchemaConstants.SYNTAX_CHECKERS_AT ) );
105        VALID_OU_VALUES.add( Strings.toLowerCaseAscii( SchemaConstants.SYNTAXES ) );
106        VALID_OU_VALUES.add( Strings.toLowerCaseAscii( SchemaConstants.MATCHING_RULES_AT ) );
107        VALID_OU_VALUES.add( Strings.toLowerCaseAscii( SchemaConstants.MATCHING_RULE_USE_AT ) );
108        VALID_OU_VALUES.add( Strings.toLowerCaseAscii( SchemaConstants.ATTRIBUTE_TYPES_AT ) );
109        VALID_OU_VALUES.add( Strings.toLowerCaseAscii( SchemaConstants.OBJECT_CLASSES_AT ) );
110        VALID_OU_VALUES.add( Strings.toLowerCaseAscii( SchemaConstants.NAME_FORMS_AT ) );
111        VALID_OU_VALUES.add( Strings.toLowerCaseAscii( SchemaConstants.DIT_CONTENT_RULES_AT ) );
112        VALID_OU_VALUES.add( Strings.toLowerCaseAscii( SchemaConstants.DIT_STRUCTURE_RULES_AT ) );
113    }
114
115
116    public SchemaSubentryManager( SchemaManager schemaManager, DnFactory dnFactory )
117        throws LdapException
118    {
119        this.schemaManager = schemaManager;
120        this.subentryModifier = new SchemaSubentryModifier( schemaManager, dnFactory );
121        this.parsers = new DescriptionParsers( schemaManager );
122
123        String comparatorsOid = schemaManager.getAttributeTypeRegistry().getOidByName( SchemaConstants.COMPARATORS_AT );
124        opAttr2handlerIndex.put( comparatorsOid, COMPARATOR_INDEX );
125
126        String normalizersOid = schemaManager.getAttributeTypeRegistry().getOidByName( SchemaConstants.NORMALIZERS_AT );
127        opAttr2handlerIndex.put( normalizersOid, NORMALIZER_INDEX );
128
129        String syntaxCheckersOid = schemaManager.getAttributeTypeRegistry().getOidByName(
130            SchemaConstants.SYNTAX_CHECKERS_AT );
131        opAttr2handlerIndex.put( syntaxCheckersOid, SYNTAX_CHECKER_INDEX );
132
133        String ldapSyntaxesOid = schemaManager.getAttributeTypeRegistry().getOidByName(
134            SchemaConstants.LDAP_SYNTAXES_AT );
135        opAttr2handlerIndex.put( ldapSyntaxesOid, SYNTAX_INDEX );
136
137        String matchingRulesOid = schemaManager.getAttributeTypeRegistry().getOidByName(
138            SchemaConstants.MATCHING_RULES_AT );
139        opAttr2handlerIndex.put( matchingRulesOid, MATCHING_RULE_INDEX );
140
141        String attributeTypesOid = schemaManager.getAttributeTypeRegistry().getOidByName(
142            SchemaConstants.ATTRIBUTE_TYPES_AT );
143        opAttr2handlerIndex.put( attributeTypesOid, ATTRIBUTE_TYPE_INDEX );
144
145        String objectClassesOid = schemaManager.getAttributeTypeRegistry().getOidByName(
146            SchemaConstants.OBJECT_CLASSES_AT );
147        opAttr2handlerIndex.put( objectClassesOid, OBJECT_CLASS_INDEX );
148
149        String matchingRuleUseOid = schemaManager.getAttributeTypeRegistry().getOidByName(
150            SchemaConstants.MATCHING_RULE_USE_AT );
151        opAttr2handlerIndex.put( matchingRuleUseOid, MATCHING_RULE_USE_INDEX );
152
153        String ditStructureRulesOid = schemaManager.getAttributeTypeRegistry().getOidByName(
154            SchemaConstants.DIT_STRUCTURE_RULES_AT );
155        opAttr2handlerIndex.put( ditStructureRulesOid, DIT_STRUCTURE_RULE_INDEX );
156
157        String ditContentRulesOid = schemaManager.getAttributeTypeRegistry().getOidByName(
158            SchemaConstants.DIT_CONTENT_RULES_AT );
159        opAttr2handlerIndex.put( ditContentRulesOid, DIT_CONTENT_RULE_INDEX );
160
161        String nameFormsOid = schemaManager.getAttributeTypeRegistry().getOidByName( SchemaConstants.NAME_FORMS_AT );
162        opAttr2handlerIndex.put( nameFormsOid, NAME_FORM_INDEX );
163    }
164
165
166    /**
167     * Find the next interceptor in an operation's list of interceptors, assuming that
168     * we are already processing an operation, and we have stopped in a specific
169     * interceptor.<br>
170     * For instance, if the list of all the interceptors is : <br>
171     * [A, B, C, D, E, F]<br>
172     * and we ave two operations op1 and op2 with the following interceptors list : <br>
173     * op1 -> [A, D, F]<br>
174     * op2 -> [B, C, E]<br>
175     * then assuming that we have stopped at D, then op1.next -> F and op2.next -> E.
176     */
177    private Interceptor findNextInterceptor( OperationEnum operation, DirectoryService directoryService )
178    {
179        Interceptor interceptor = null;
180
181        List<Interceptor> allInterceptors = directoryService.getInterceptors();
182        List<String> operationInterceptors = directoryService.getInterceptors( operation );
183        int position = 0;
184        String addInterceptor = operationInterceptors.get( position );
185
186        for ( Interceptor inter : allInterceptors )
187        {
188            String interName = inter.getName();
189
190            if ( interName.equals( InterceptorEnum.SCHEMA_INTERCEPTOR.getName() ) )
191            {
192                // Found, get out
193                position++;
194
195                if ( position < operationInterceptors.size() )
196                {
197                    interceptor = directoryService.getInterceptor( operationInterceptors.get( position ) );
198                }
199
200                break;
201            }
202
203            if ( interName.equals( addInterceptor ) )
204            {
205                position++;
206                addInterceptor = operationInterceptors.get( position );
207            }
208        }
209
210        return interceptor;
211    }
212
213
214    /**
215     * Find the position in the operation's list knowing the inteceptor name.
216     */
217    private int findPosition( OperationEnum operation, Interceptor interceptor, DirectoryService directoryService )
218    {
219        int position = 1;
220
221        List<String> interceptors = directoryService.getInterceptors( operation );
222
223        String interceptorName = interceptor.getName();
224
225        for ( String name : interceptors )
226        {
227            if ( name.equals( interceptorName ) )
228            {
229                break;
230            }
231
232            position++;
233        }
234
235        return position;
236    }
237
238
239    /**
240     * Update the SubschemaSubentry with all the modifications
241     * 
242     * @param modifyContext The Modification context
243     * @param doCascadeModify If we should recursively apply the modification
244     * @throws LdapException If the schema modification failed
245     */
246    public void modifySchemaSubentry( ModifyOperationContext modifyContext, boolean doCascadeModify )
247        throws LdapException
248    {
249        DirectoryService directoryService = modifyContext.getSession().getDirectoryService();
250
251        // Compute the next interceptor for the Add and Delete operation, starting from
252        // the schemaInterceptor. We also need to get the position of this next interceptor
253        // in the operation's list.
254        Interceptor nextAdd = findNextInterceptor( OperationEnum.ADD, directoryService );
255        int positionAdd = findPosition( OperationEnum.ADD, nextAdd, directoryService );
256        Interceptor nextDelete = findNextInterceptor( OperationEnum.DELETE, directoryService );
257        int positionDelete = findPosition( OperationEnum.DELETE, nextDelete, directoryService );
258
259        for ( Modification mod : modifyContext.getModItems() )
260        {
261            String opAttrOid = schemaManager.getAttributeTypeRegistry().getOidByName( mod.getAttribute().getId() );
262
263            Attribute serverAttribute = mod.getAttribute();
264
265            switch ( mod.getOperation() )
266            {
267                case ADD_ATTRIBUTE:
268                    modifyAddOperation( nextAdd, positionAdd, modifyContext, opAttrOid, serverAttribute,
269                        doCascadeModify );
270                    break;
271
272                case REMOVE_ATTRIBUTE:
273                    modifyRemoveOperation( nextDelete, positionDelete, modifyContext, opAttrOid, serverAttribute );
274                    break;
275
276                case REPLACE_ATTRIBUTE:
277                    // a hack to allow entryCSN modification
278                    if ( directoryService.getAtProvider().getEntryCSN().equals( serverAttribute.getAttributeType() ) )
279                    {
280                        break;
281                    }
282
283                    throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM,
284                        I18n.err( I18n.ERR_283 ) );
285
286                default:
287                    throw new IllegalStateException( I18n.err( I18n.ERR_284, mod.getOperation() ) );
288            }
289        }
290    }
291
292
293    /**
294     * Handles the modify remove operation on the subschemaSubentry for schema entities.
295     * 
296     * @param opAttrOid the numeric id of the operational attribute modified
297     * @param mods the attribute with the modifications
298     * to effect all dependents on the changed entity
299     * @throws Exception if there are problems updating the registries and the
300     * schema partition
301     */
302    private void modifyRemoveOperation( Interceptor nextInterceptor, int position,
303        ModifyOperationContext modifyContext, String opAttrOid,
304        Attribute mods ) throws LdapException
305    {
306        int index = opAttr2handlerIndex.get( opAttrOid );
307
308        switch ( index )
309        {
310            case COMPARATOR_INDEX :
311                LdapComparatorDescription[] comparatorDescriptions = parsers.parseComparators( mods );
312
313                for ( LdapComparatorDescription comparatorDescription : comparatorDescriptions )
314                {
315                    subentryModifier.delete( nextInterceptor, position, modifyContext, comparatorDescription );
316                }
317
318                break;
319
320            case NORMALIZER_INDEX :
321                NormalizerDescription[] normalizerDescriptions = parsers.parseNormalizers( mods );
322
323                for ( NormalizerDescription normalizerDescription : normalizerDescriptions )
324                {
325                    subentryModifier.delete( nextInterceptor, position, modifyContext, normalizerDescription );
326                }
327
328                break;
329
330            case SYNTAX_CHECKER_INDEX :
331                SyntaxCheckerDescription[] syntaxCheckerDescriptions = parsers.parseSyntaxCheckers( mods );
332
333                for ( SyntaxCheckerDescription syntaxCheckerDescription : syntaxCheckerDescriptions )
334                {
335                    subentryModifier.delete( nextInterceptor, position, modifyContext, syntaxCheckerDescription );
336                }
337
338                break;
339
340            case SYNTAX_INDEX :
341                LdapSyntax[] syntaxes = parsers.parseLdapSyntaxes( mods );
342
343                for ( LdapSyntax syntax : syntaxes )
344                {
345                    subentryModifier.deleteSchemaObject( nextInterceptor, position, modifyContext, syntax );
346                }
347
348                break;
349
350            case MATCHING_RULE_INDEX :
351                MatchingRule[] mrs = parsers.parseMatchingRules( mods );
352
353                for ( MatchingRule mr : mrs )
354                {
355                    subentryModifier.deleteSchemaObject( nextInterceptor, position, modifyContext, mr );
356                }
357
358                break;
359
360            case ATTRIBUTE_TYPE_INDEX :
361                AttributeType[] ats = parsers.parseAttributeTypes( mods );
362
363                for ( AttributeType at : ats )
364                {
365                    subentryModifier.deleteSchemaObject( nextInterceptor, position, modifyContext, at );
366                }
367
368                break;
369
370            case OBJECT_CLASS_INDEX :
371                ObjectClass[] ocs = parsers.parseObjectClasses( mods );
372
373                for ( ObjectClass oc : ocs )
374                {
375                    subentryModifier.deleteSchemaObject( nextInterceptor, position, modifyContext, oc );
376                }
377
378                break;
379
380            case MATCHING_RULE_USE_INDEX :
381                MatchingRuleUse[] mrus = parsers.parseMatchingRuleUses( mods );
382
383                for ( MatchingRuleUse mru : mrus )
384                {
385                    subentryModifier.deleteSchemaObject( nextInterceptor, position, modifyContext, mru );
386                }
387
388                break;
389
390            case DIT_STRUCTURE_RULE_INDEX :
391                DitStructureRule[] dsrs = parsers.parseDitStructureRules( mods );
392
393                for ( DitStructureRule dsr : dsrs )
394                {
395                    subentryModifier.deleteSchemaObject( nextInterceptor, position, modifyContext, dsr );
396                }
397
398                break;
399
400            case DIT_CONTENT_RULE_INDEX :
401                DitContentRule[] dcrs = parsers.parseDitContentRules( mods );
402
403                for ( DitContentRule dcr : dcrs )
404                {
405                    subentryModifier.deleteSchemaObject( nextInterceptor, position, modifyContext, dcr );
406                }
407
408                break;
409
410            case NAME_FORM_INDEX :
411                NameForm[] nfs = parsers.parseNameForms( mods );
412
413                for ( NameForm nf : nfs )
414                {
415                    subentryModifier.deleteSchemaObject( nextInterceptor, position, modifyContext, nf );
416                }
417
418                break;
419
420            default:
421                throw new IllegalStateException( I18n.err( I18n.ERR_285, index ) );
422        }
423    }
424
425
426    /**
427     * Handles the modify add operation on the subschemaSubentry for schema entities.
428     * 
429     * @param opAttrOid the numeric id of the operational attribute modified
430     * @param mods the attribute with the modifications
431     * @param doCascadeModify determines if a cascading operation should be performed
432     * to effect all dependents on the changed entity
433     * @throws Exception if there are problems updating the registries and the
434     * schema partition
435     */
436    private void modifyAddOperation( Interceptor nextInterceptor, int position, ModifyOperationContext modifyContext,
437        String opAttrOid,
438        Attribute mods, boolean doCascadeModify ) throws LdapException
439    {
440        if ( doCascadeModify )
441        {
442            LOG.error( CASCADING_ERROR );
443        }
444
445        int index = opAttr2handlerIndex.get( opAttrOid );
446
447        switch ( index )
448        {
449            case COMPARATOR_INDEX :
450                LdapComparatorDescription[] comparatorDescriptions = parsers.parseComparators( mods );
451
452                for ( LdapComparatorDescription comparatorDescription : comparatorDescriptions )
453                {
454                    subentryModifier.add( nextInterceptor, position, modifyContext, comparatorDescription );
455                }
456
457                break;
458
459            case NORMALIZER_INDEX :
460                NormalizerDescription[] normalizerDescriptions = parsers.parseNormalizers( mods );
461
462                for ( NormalizerDescription normalizerDescription : normalizerDescriptions )
463                {
464                    subentryModifier.add( nextInterceptor, position, modifyContext, normalizerDescription );
465                }
466
467                break;
468
469            case SYNTAX_CHECKER_INDEX :
470                SyntaxCheckerDescription[] syntaxCheckerDescriptions = parsers.parseSyntaxCheckers( mods );
471
472                for ( SyntaxCheckerDescription syntaxCheckerDescription : syntaxCheckerDescriptions )
473                {
474                    subentryModifier.add( nextInterceptor, position, modifyContext, syntaxCheckerDescription );
475                }
476
477                break;
478
479            case SYNTAX_INDEX :
480                LdapSyntax[] syntaxes = parsers.parseLdapSyntaxes( mods );
481
482                for ( LdapSyntax syntax : syntaxes )
483                {
484                    subentryModifier.addSchemaObject( nextInterceptor, position, modifyContext, syntax );
485                }
486
487                break;
488
489            case MATCHING_RULE_INDEX :
490                MatchingRule[] mrs = parsers.parseMatchingRules( mods );
491
492                for ( MatchingRule mr : mrs )
493                {
494                    subentryModifier.addSchemaObject( nextInterceptor, position, modifyContext, mr );
495                }
496
497                break;
498
499            case ATTRIBUTE_TYPE_INDEX :
500                AttributeType[] ats = parsers.parseAttributeTypes( mods );
501
502                for ( AttributeType at : ats )
503                {
504                    subentryModifier.addSchemaObject( nextInterceptor, position, modifyContext, at );
505                }
506
507                break;
508
509            case OBJECT_CLASS_INDEX :
510                ObjectClass[] ocs = parsers.parseObjectClasses( mods );
511
512                for ( ObjectClass oc : ocs )
513                {
514                    subentryModifier.addSchemaObject( nextInterceptor, position, modifyContext, oc );
515                }
516
517                break;
518
519            case MATCHING_RULE_USE_INDEX :
520                MatchingRuleUse[] mrus = parsers.parseMatchingRuleUses( mods );
521
522                for ( MatchingRuleUse mru : mrus )
523                {
524                    subentryModifier.addSchemaObject( nextInterceptor, position, modifyContext, mru );
525                }
526
527                break;
528
529            case DIT_STRUCTURE_RULE_INDEX :
530                DitStructureRule[] dsrs = parsers.parseDitStructureRules( mods );
531
532                for ( DitStructureRule dsr : dsrs )
533                {
534                    subentryModifier.addSchemaObject( nextInterceptor, position, modifyContext, dsr );
535                }
536
537                break;
538
539            case DIT_CONTENT_RULE_INDEX :
540                DitContentRule[] dcrs = parsers.parseDitContentRules( mods );
541
542                for ( DitContentRule dcr : dcrs )
543                {
544                    subentryModifier.addSchemaObject( nextInterceptor, position, modifyContext, dcr );
545                }
546
547                break;
548
549            case NAME_FORM_INDEX :
550                NameForm[] nfs = parsers.parseNameForms( mods );
551
552                for ( NameForm nf : nfs )
553                {
554                    subentryModifier.addSchemaObject( nextInterceptor, position, modifyContext, nf );
555                }
556
557                break;
558
559            default:
560                throw new IllegalStateException( I18n.err( I18n.ERR_285, index ) );
561        }
562    }
563}