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}