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.api.schema.registries.synchronizers; 021 022 023import java.util.List; 024import java.util.Map; 025import java.util.Set; 026 027import org.apache.directory.api.ldap.model.constants.MetaSchemaConstants; 028import org.apache.directory.api.ldap.model.constants.SchemaConstants; 029import org.apache.directory.api.ldap.model.entry.Attribute; 030import org.apache.directory.api.ldap.model.entry.Entry; 031import org.apache.directory.api.ldap.model.entry.Modification; 032import org.apache.directory.api.ldap.model.entry.ModificationOperation; 033import org.apache.directory.api.ldap.model.entry.Value; 034import org.apache.directory.api.ldap.model.exception.LdapException; 035import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException; 036import org.apache.directory.api.ldap.model.exception.LdapUnwillingToPerformException; 037import org.apache.directory.api.ldap.model.message.ResultCodeEnum; 038import org.apache.directory.api.ldap.model.name.Dn; 039import org.apache.directory.api.ldap.model.name.Rdn; 040import org.apache.directory.api.ldap.model.schema.AttributeType; 041import org.apache.directory.api.ldap.model.schema.SchemaManager; 042import org.apache.directory.api.ldap.model.schema.registries.Schema; 043import org.apache.directory.api.ldap.schema.loader.SchemaEntityFactory; 044import org.apache.directory.api.util.Strings; 045import org.apache.directory.server.core.api.entry.ServerEntryUtils; 046import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext; 047import org.apache.directory.server.i18n.I18n; 048import org.slf4j.Logger; 049import org.slf4j.LoggerFactory; 050 051 052/** 053 * This class handle modifications made on a global schema. Modifications made 054 * on SchemaObjects are handled by the specific shcemaObject synchronizers. 055 * 056 * TODO poorly implemented - revisit the SchemaChangeHandler for this puppy 057 * and do it right. 058 * 059 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 060 */ 061public class SchemaSynchronizer implements RegistrySynchronizer 062{ 063 /** A logger for this class */ 064 private static final Logger LOG = LoggerFactory.getLogger( SchemaSynchronizer.class ); 065 066 private final SchemaEntityFactory factory; 067 068 private final SchemaManager schemaManager; 069 070 /** The m-disable AttributeType */ 071 private final AttributeType disabledAT; 072 073 /** The CN attributeType */ 074 private final AttributeType cnAT; 075 076 /** The m-dependencies AttributeType */ 077 private final AttributeType dependenciesAT; 078 079 /** A static Dn referencing ou=schema */ 080 private final Dn ouSchemaDn; 081 082 083 /** 084 * Creates and initializes a new instance of Schema synchronizer 085 * 086 * @param schemaManager The server schemaManager 087 * @throws Exception If something went wrong 088 */ 089 public SchemaSynchronizer( SchemaManager schemaManager ) throws Exception 090 { 091 this.schemaManager = schemaManager; 092 disabledAT = schemaManager.lookupAttributeTypeRegistry( MetaSchemaConstants.M_DISABLED_AT ); 093 factory = new SchemaEntityFactory(); 094 cnAT = schemaManager.lookupAttributeTypeRegistry( SchemaConstants.CN_AT ); 095 dependenciesAT = schemaManager.lookupAttributeTypeRegistry( MetaSchemaConstants.M_DEPENDENCIES_AT ); 096 097 ouSchemaDn = new Dn( schemaManager, SchemaConstants.OU_SCHEMA ); 098 } 099 100 101 /** 102 * The only modification done on a schema element is on the m-disabled 103 * attributeType 104 * 105 * Depending in the existence of this attribute in the previous entry, we will 106 * have to update the entry or not. 107 */ 108 @Override 109 public boolean modify( ModifyOperationContext modifyContext, Entry targetEntry, boolean cascade ) 110 throws LdapException 111 { 112 Entry entry = modifyContext.getEntry(); 113 List<Modification> mods = modifyContext.getModItems(); 114 boolean hasModification = SCHEMA_UNCHANGED; 115 116 // Check if the entry has a m-disabled attribute 117 Attribute disabledInEntry = entry.get( disabledAT ); 118 Modification disabledModification = ServerEntryUtils.getModificationItem( mods, disabledAT ); 119 120 // The attribute might be present, but that does not mean we will change it. 121 // If it's absent, and if we have it in the previous entry, that mean we want 122 // to enable the schema 123 if ( disabledModification != null ) 124 { 125 // We are trying to modify the m-disabled attribute. 126 ModificationOperation modification = disabledModification.getOperation(); 127 Attribute attribute = disabledModification.getAttribute(); 128 129 hasModification = modifyDisable( modifyContext, modification, attribute, disabledInEntry ); 130 } 131 else if ( disabledInEntry != null ) 132 { 133 hasModification = modifyDisable( modifyContext, ModificationOperation.REMOVE_ATTRIBUTE, null, 134 disabledInEntry ); 135 } 136 137 return hasModification; 138 } 139 140 141 /** 142 * {@inheritDoc} 143 */ 144 @Override 145 public void moveAndRename( Dn oriChildName, Dn newParentName, Rdn newRn, boolean deleteOldRn, Entry entry, 146 boolean cascaded ) throws LdapException 147 { 148 // Not implemented yet 149 } 150 151 152 /** 153 * Handles the addition of a metaSchema object to the schema partition. 154 * 155 * @param entry the attributes of the new metaSchema object 156 * @throws LdapException If the add failed 157 */ 158 @Override 159 public void add( Entry entry ) throws LdapException 160 { 161 Dn dn = entry.getDn(); 162 Dn parentDn = dn.getParent(); 163 164 if ( !parentDn.equals( ouSchemaDn ) ) 165 { 166 throw new LdapInvalidDnException( ResultCodeEnum.NAMING_VIOLATION, I18n.err( I18n.ERR_380, 167 ouSchemaDn.getName(), 168 parentDn.getName() ) ); 169 } 170 171 // check if the new schema is enabled or disabled 172 boolean isEnabled = false; 173 Attribute disabled = entry.get( disabledAT ); 174 175 if ( disabled == null ) 176 { 177 // If the attribute is absent, then the schema is enabled by default 178 isEnabled = true; 179 } 180 else if ( !disabled.contains( "TRUE" ) ) 181 { 182 isEnabled = true; 183 } 184 185 // check to see that all dependencies are resolved and loaded if this 186 // schema is enabled, otherwise check that the dependency schemas exist 187 checkForDependencies( isEnabled, entry ); 188 189 /* 190 * There's a slight problem that may result when adding a metaSchema 191 * object if the addition of the physical entry fails. If the schema 192 * is enabled when added in the condition tested below, that schema 193 * is added to the global registries. We need to add this so subsequent 194 * schema entity additions are loaded into the registries as they are 195 * added to the schema partition. However if the metaSchema object 196 * addition fails then we're left with this schema object looking like 197 * it is enabled in the registries object's schema hash. The effects 198 * of this are unpredictable. 199 * 200 * This whole problem is due to the inability of these handlers to 201 * react to a failed operation. To fix this we would need some way 202 * for these handlers to respond to failed operations and revert their 203 * effects on the registries. 204 * 205 * TODO: might want to add a set of failedOnXXX methods to the adapter 206 * where on failure the schema service calls the schema manager and it 207 * calls the appropriate methods on the respective handler. This way 208 * the schema manager can rollback registry changes when LDAP operations 209 * fail. 210 */ 211 212 if ( isEnabled ) 213 { 214 Schema schema = factory.getSchema( entry ); 215 schemaManager.load( schema ); 216 } 217 } 218 219 220 /** 221 * Called to react to the deletion of a metaSchema object. This method 222 * simply removes the schema from the loaded schema map of the global 223 * registries. 224 * 225 * @param entry the attributes of the metaSchema object 226 * @param cascade If we have to process recursively 227 * @throws LdapException If the delete failed 228 */ 229 @Override 230 public void delete( Entry entry, boolean cascade ) throws LdapException 231 { 232 Attribute cn = entry.get( cnAT ); 233 String schemaName = cn.getString(); 234 235 // Before allowing a schema object to be deleted we must check 236 // to make sure it's not depended upon by another schema 237 Set<String> dependents = schemaManager.listDependentSchemaNames( schemaName ); 238 239 if ( ( dependents != null ) && !dependents.isEmpty() ) 240 { 241 String msg = I18n.err( I18n.ERR_381, dependents ); 242 LOG.warn( msg ); 243 throw new LdapUnwillingToPerformException( 244 ResultCodeEnum.UNWILLING_TO_PERFORM, 245 msg ); 246 } 247 248 // no need to check if schema is enabled or disabled here 249 // if not in the loaded set there will be no negative effect 250 schemaManager.unload( schemaName ); 251 } 252 253 254 /** 255 * Responds to the rdn (commonName) of the metaSchema object being 256 * changed. Changes all the schema entities associated with the 257 * renamed schema so they now map to a new schema name. 258 * 259 * @param entry the entry of the metaSchema object before the rename 260 * @param newRdn the new commonName of the metaSchema object 261 * @param cascade If we have to process recursively 262 * @throws LdapException If the rename failed 263 */ 264 @Override 265 public void rename( Entry entry, Rdn newRdn, boolean cascade ) throws LdapException 266 { 267 String rdnAttribute = newRdn.getNormType(); 268 String rdnAttributeOid = schemaManager.getAttributeTypeRegistry().getOidByName( rdnAttribute ); 269 270 if ( !rdnAttributeOid.equals( cnAT.getOid() ) ) 271 { 272 throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, 273 I18n.err( I18n.ERR_382, rdnAttribute ) ); 274 } 275 276 /* 277 * This operation has to do the following: 278 * 279 * [1] check and make sure there are no dependent schemas on the 280 * one being renamed - if so an exception should result 281 * 282 * [2] make non-schema object registries modify the mapping 283 * for their entities: non-schema object registries contain 284 * objects that are not SchemaObjects and hence do not carry 285 * their schema within the object as a property 286 * 287 * [3] make schema object registries do the same but the way 288 * they do them will be different since these objects will 289 * need to be replaced or will require a setter for the 290 * schema name 291 */ 292 293 // step [1] 294 /* 295 String schemaName = getSchemaName( entry.getDn() ); 296 Set<String> dependents = schemaManager.listDependentSchemaNames( schemaName ); 297 if ( ! dependents.isEmpty() ) 298 { 299 throw new LdapUnwillingToPerformException( 300 "Cannot allow a rename on " + schemaName + " schema while it has depentents.", 301 ResultCodeEnum.UNWILLING_TO_PERFORM ); 302 } 303 304 // check if the new schema is enabled or disabled 305 boolean isEnabled = false; 306 EntryAttribute disabled = entry.get( disabledAT ); 307 308 if ( disabled == null ) 309 { 310 isEnabled = true; 311 } 312 else if ( ! disabled.get().equals( "TRUE" ) ) 313 { 314 isEnabled = true; 315 } 316 317 if ( ! isEnabled ) 318 { 319 return; 320 } 321 322 // do steps 2 and 3 if the schema has been enabled and is loaded 323 324 // step [2] 325 String newSchemaName = ( String ) newRdn.getUpValue(); 326 registries.getComparatorRegistry().renameSchema( schemaName, newSchemaName ); 327 registries.getNormalizerRegistry().renameSchema( schemaName, newSchemaName ); 328 registries.getSyntaxCheckerRegistry().renameSchema( schemaName, newSchemaName ); 329 330 // step [3] 331 renameSchema( registries.getAttributeTypeRegistry(), schemaName, newSchemaName ); 332 renameSchema( registries.getDitContentRuleRegistry(), schemaName, newSchemaName ); 333 renameSchema( registries.getDitStructureRuleRegistry(), schemaName, newSchemaName ); 334 renameSchema( registries.getMatchingRuleRegistry(), schemaName, newSchemaName ); 335 renameSchema( registries.getMatchingRuleUseRegistry(), schemaName, newSchemaName ); 336 renameSchema( registries.getNameFormRegistry(), schemaName, newSchemaName ); 337 renameSchema( registries.getObjectClassRegistry(), schemaName, newSchemaName ); 338 renameSchema( registries.getLdapSyntaxRegistry(), schemaName, newSchemaName ); 339 */ 340 } 341 342 343 /** 344 * Moves are not allowed for metaSchema objects so this always throws an 345 * UNWILLING_TO_PERFORM LdapException. 346 * 347 * @param oriChildName The original child name 348 * @param newParentName The new parent name 349 * @param newRdn the new commonName of the metaSchema object 350 * @param deleteOldRdn If we need to delete the old Rdn 351 * @param entry the entry of the metaSchema object before the rename 352 * @param cascade If we have to process recursively 353 * @throws LdapUnwillingToPerformException If the rename failed 354 */ 355 public void moveAndRename( Dn oriChildName, Dn newParentName, String newRdn, boolean deleteOldRdn, 356 Entry entry, boolean cascade ) throws LdapUnwillingToPerformException 357 { 358 throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, 359 I18n.err( I18n.ERR_383 ) ); 360 } 361 362 363 /** 364 * Moves are not allowed for metaSchema objects so this always throws an 365 * UNWILLING_TO_PERFORM LdapException. 366 */ 367 @Override 368 public void move( Dn oriChildName, Dn newParentName, 369 Entry entry, boolean cascade ) throws LdapUnwillingToPerformException 370 { 371 throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, 372 I18n.err( I18n.ERR_383 ) ); 373 } 374 375 376 // ----------------------------------------------------------------------- 377 // private utility methods 378 // ----------------------------------------------------------------------- 379 380 /** 381 * Modify the Disable flag (the flag can be set to true or false). 382 * 383 * We can ADD, REMOVE or MODIFY this flag. The following matrix expose what will be the consequences 384 * of this operation, depending on the current state 385 * 386 * <pre> 387 * +-------------------+--------------------+--------------------+ 388 * op/state | TRUE | FALSE | ABSENT | 389 * +-------+-------+----------------------------------------+--------------------+ 390 * | ADD | TRUE | do nothing | do nothing | disable the schema | 391 * | +-------+-------------------+--------------------+--------------------+ 392 * | | FALSE | do nothing | do nothing | do nothing | 393 * +-------+-------+-------------------+--------------------+--------------------+ 394 * |REMOVE | N/A | enable the schema | do nothing | do nothing | 395 * +-------+-------+-------------------+--------------------+--------------------+ 396 * |MODIFY | TRUE | do nothing | disable the schema | disable the schema | 397 * | +-------+-------------------+--------------------+--------------------+ 398 * | | FALSE | enable the schema | do nothing | do nothing | 399 * +-------+-------+-------------------+--------------------+--------------------+ 400 * </pre> 401 */ 402 private boolean modifyDisable( ModifyOperationContext modifyContext, ModificationOperation modOp, 403 Attribute disabledInMods, Attribute disabledInEntry ) throws LdapException 404 { 405 Dn name = modifyContext.getDn(); 406 407 switch ( modOp ) 408 { 409 /* 410 * If the user is adding a new m-disabled attribute to an enabled schema, 411 * we check that the value is "TRUE" and disable that schema if so. 412 */ 413 case ADD_ATTRIBUTE: 414 if ( disabledInEntry == null && "TRUE".equalsIgnoreCase( disabledInMods.getString() ) ) 415 { 416 return disableSchema( getSchemaName( name ) ); 417 } 418 419 break; 420 421 /* 422 * If the user is removing the m-disabled attribute we check if the schema is currently 423 * disabled. If so we enable the schema. 424 */ 425 case REMOVE_ATTRIBUTE: 426 if ( ( disabledInEntry != null ) && ( "TRUE".equalsIgnoreCase( disabledInEntry.getString() ) ) ) 427 { 428 return enableSchema( getSchemaName( name ) ); 429 } 430 431 break; 432 433 /* 434 * If the user is replacing the m-disabled attribute we check if the schema is 435 * currently disabled and enable it if the new state has it as enabled. If the 436 * schema is not disabled we disable it if the mods set m-disabled to true. 437 */ 438 case REPLACE_ATTRIBUTE: 439 440 boolean isCurrentlyDisabled = false; 441 442 if ( disabledInEntry != null ) 443 { 444 isCurrentlyDisabled = "TRUE".equalsIgnoreCase( disabledInEntry.getString() ); 445 } 446 447 boolean isNewStateDisabled = false; 448 449 if ( disabledInMods != null ) 450 { 451 Value val = disabledInMods.get(); 452 453 if ( val == null ) 454 { 455 isNewStateDisabled = false; 456 } 457 else 458 { 459 isNewStateDisabled = "TRUE".equalsIgnoreCase( val.getString() ); 460 } 461 } 462 463 if ( isCurrentlyDisabled && !isNewStateDisabled ) 464 { 465 return enableSchema( getSchemaName( name ) ); 466 } 467 468 if ( !isCurrentlyDisabled && isNewStateDisabled ) 469 { 470 return disableSchema( getSchemaName( name ) ); 471 } 472 473 break; 474 475 default: 476 throw new IllegalArgumentException( I18n.err( I18n.ERR_384, modOp ) ); 477 } 478 479 return SCHEMA_UNCHANGED; 480 } 481 482 483 private String getSchemaName( Dn schema ) 484 { 485 return schema.getRdn().getValue(); 486 } 487 488 489 private boolean disableSchema( String schemaName ) throws LdapException 490 { 491 Schema schema = schemaManager.getLoadedSchema( schemaName ); 492 493 if ( schema == null ) 494 { 495 // This is not possible. We can't enable a schema which is not loaded. 496 String msg = I18n.err( I18n.ERR_85, schemaName ); 497 LOG.error( msg ); 498 throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, msg ); 499 } 500 501 return schemaManager.disable( schemaName ); 502 503 } 504 505 506 /** 507 * Enabling a schema consist on switching all of its schema element to enable. 508 * We have to do it on a temporary registries. 509 */ 510 private boolean enableSchema( String schemaName ) throws LdapException 511 { 512 Schema schema = schemaManager.getLoadedSchema( schemaName ); 513 514 if ( schema == null ) 515 { 516 // We have to load the schema before enabling it. 517 schemaManager.loadDisabled( schemaName ); 518 } 519 520 return schemaManager.enable( schemaName ); 521 } 522 523 524 /** 525 * Checks to make sure the dependencies either exist for disabled metaSchemas, 526 * or exist and are loaded (enabled) for enabled metaSchemas. 527 * 528 * @param isEnabled whether or not the new metaSchema is enabled 529 * @param entry the Attributes for the new metaSchema object 530 * @throws NamingException if the dependencies do not resolve or are not 531 * loaded (enabled) 532 */ 533 private void checkForDependencies( boolean isEnabled, Entry entry ) throws LdapException 534 { 535 Attribute dependencies = entry.get( this.dependenciesAT ); 536 537 if ( dependencies == null ) 538 { 539 return; 540 } 541 542 if ( isEnabled ) 543 { 544 // check to make sure all the dependencies are also enabled 545 Map<String, Schema> loaded = schemaManager.getRegistries().getLoadedSchemas(); 546 547 for ( Value value : dependencies ) 548 { 549 String dependency = value.getString(); 550 551 if ( !loaded.containsKey( dependency ) ) 552 { 553 throw new LdapUnwillingToPerformException( 554 ResultCodeEnum.UNWILLING_TO_PERFORM, 555 "Unwilling to perform operation on enabled schema with disabled or missing dependencies: " 556 + dependency ); 557 } 558 } 559 } 560 else 561 { 562 for ( Value value : dependencies ) 563 { 564 String dependency = value.getString(); 565 566 if ( schemaManager.getLoadedSchema( Strings.toLowerCaseAscii( dependency ) ) == null ) 567 { 568 throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, 569 I18n.err( I18n.ERR_385, dependency ) ); 570 } 571 } 572 } 573 } 574}