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 */ 020 package org.apache.directory.server.core.schema; 021 022 023 import java.util.ArrayList; 024 import java.util.List; 025 026 import javax.naming.NamingException; 027 028 import org.apache.directory.server.constants.ApacheSchemaConstants; 029 import org.apache.directory.server.constants.ServerDNConstants; 030 import org.apache.directory.server.core.entry.ClonedServerEntry; 031 import org.apache.directory.server.core.filtering.EntryFilteringCursor; 032 import org.apache.directory.server.core.interceptor.context.AddOperationContext; 033 import org.apache.directory.server.core.interceptor.context.BindOperationContext; 034 import org.apache.directory.server.core.interceptor.context.DeleteOperationContext; 035 import org.apache.directory.server.core.interceptor.context.EntryOperationContext; 036 import org.apache.directory.server.core.interceptor.context.ListOperationContext; 037 import org.apache.directory.server.core.interceptor.context.LookupOperationContext; 038 import org.apache.directory.server.core.interceptor.context.ModifyOperationContext; 039 import org.apache.directory.server.core.interceptor.context.MoveAndRenameOperationContext; 040 import org.apache.directory.server.core.interceptor.context.MoveOperationContext; 041 import org.apache.directory.server.core.interceptor.context.OperationContext; 042 import org.apache.directory.server.core.interceptor.context.RenameOperationContext; 043 import org.apache.directory.server.core.interceptor.context.SearchOperationContext; 044 import org.apache.directory.server.core.interceptor.context.UnbindOperationContext; 045 import org.apache.directory.server.core.partition.AbstractPartition; 046 import org.apache.directory.server.core.partition.ByPassConstants; 047 import org.apache.directory.server.core.partition.NullPartition; 048 import org.apache.directory.server.core.partition.Partition; 049 import org.apache.directory.server.core.schema.registries.synchronizers.RegistrySynchronizerAdaptor; 050 import org.apache.directory.server.i18n.I18n; 051 import org.apache.directory.shared.ldap.codec.controls.CascadeControl; 052 import org.apache.directory.shared.ldap.constants.SchemaConstants; 053 import org.apache.directory.shared.ldap.entry.DefaultServerAttribute; 054 import org.apache.directory.shared.ldap.entry.Modification; 055 import org.apache.directory.shared.ldap.entry.ModificationOperation; 056 import org.apache.directory.shared.ldap.entry.ServerEntry; 057 import org.apache.directory.shared.ldap.entry.ServerModification; 058 import org.apache.directory.shared.ldap.name.DN; 059 import org.apache.directory.shared.ldap.schema.SchemaManager; 060 import org.apache.directory.shared.ldap.schema.SchemaUtils; 061 import org.apache.directory.shared.ldap.util.DateUtils; 062 import org.slf4j.Logger; 063 import org.slf4j.LoggerFactory; 064 065 066 /** 067 * A special partition designed to contain the portion of the DIT where schema 068 * information for the server is stored. 069 * 070 * In an effort to make sure that all Partition implementations are equal 071 * citizens to ApacheDS we want to be able to swap in and out any kind of 072 * Partition to store schema. This also has the added advantage of making 073 * sure the core, and hence the server is not dependent on any specific 074 * partition, which reduces coupling in the server's modules. 075 * 076 * The SchemaPartition achieves this by not really being a backing store 077 * itself for the schema entries. It instead delegates to another Partition 078 * via containment. It delegates all calls to this contained Partition. While 079 * doing so it also manages certain things: 080 * 081 * <ol> 082 * <li>Checks that schema changes are valid.</li> 083 * <li>Updates the schema Registries on valid schema changes making sure 084 * the schema on disk is in sync with the schema in memory. 085 * </li> 086 * <li>Will eventually manage transaction based changes to schema where 087 * between some sequence of operations the schema may be inconsistent. 088 * </li> 089 * <li>Delegates read/write operations to contained Partition.</li> 090 * <li> 091 * Responsible for initializing schema for the entire server. ApacheDS 092 * cannot start up other partitions until this Partition is started 093 * without having access to the Registries. This Partition supplies the 094 * Registries on initialization for the server. That's one of it's core 095 * responsibilities. 096 * </li> 097 * 098 * So by containing another Partition, we abstract the storage mechanism away 099 * from the management responsibilities while decoupling the server from a 100 * specific partition implementation and removing complexity in the Schema 101 * interceptor service which before managed synchronization. 102 * </ol> 103 * 104 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 105 * @version $Rev$, $Date$ 106 */ 107 public final class SchemaPartition extends AbstractPartition 108 { 109 /** the logger */ 110 private static final Logger LOG = LoggerFactory.getLogger( SchemaPartition.class ); 111 112 /** the fixed id: 'schema' */ 113 private static final String ID = "schema"; 114 115 /** the wrapped Partition */ 116 private Partition wrapped = new NullPartition(); 117 118 /** schema manager */ 119 private SchemaManager schemaManager; 120 121 /** registry synchronizer adaptor */ 122 private RegistrySynchronizerAdaptor synchronizer; 123 124 /** A static DN for the ou=schemaModifications entry */ 125 private static DN schemaModificationDN; 126 127 128 /** 129 * Sets the wrapped {@link Partition} which must be supplied or 130 * {@link Partition#initialize()} will fail with a NullPointerException. 131 * 132 * @param wrapped the Partition being wrapped 133 */ 134 public void setWrappedPartition( Partition wrapped ) 135 { 136 if ( this.isInitialized() ) 137 { 138 throw new IllegalStateException( I18n.err( I18n.ERR_429 ) ); 139 } 140 141 this.wrapped = wrapped; 142 } 143 144 145 /** 146 * Gets the {@link Partition} being wrapped. 147 * 148 * @return the wrapped Partition 149 */ 150 public Partition getWrappedPartition() 151 { 152 return wrapped; 153 } 154 155 156 /** 157 * Get's the ID which is fixed: 'schema'. 158 */ 159 public final String getId() 160 { 161 return ID; 162 } 163 164 165 /** 166 * Has no affect: the id is fixed at {@link SchemaPartition#ID}: 'schema'. 167 * A warning is logged. 168 */ 169 public final void setId( String id ) 170 { 171 LOG.warn( "This partition's ID is fixed: {}", ID ); 172 } 173 174 175 /** 176 * Always returns {@link ServerDNConstants#OU_SCHEMA_DN_NORMALIZED}: '2.5.4.11=schema'. 177 */ 178 public final DN getSuffixDn() 179 { 180 return wrapped.getSuffixDn(); 181 } 182 183 184 /** 185 * Always returns {@link ServerDNConstants#OU_SCHEMA_DN}: 'ou=schema'. 186 */ 187 public final String getSuffix() 188 { 189 return SchemaConstants.OU_SCHEMA; 190 } 191 192 193 /** 194 * Has no affect: just logs a warning. 195 */ 196 public final void setSuffix( String suffix ) 197 { 198 LOG.warn( "This partition's suffix is fixed: {}", SchemaConstants.OU_SCHEMA ); 199 } 200 201 202 // ----------------------------------------------------------------------- 203 // Partition Interface Method Overrides 204 // ----------------------------------------------------------------------- 205 206 @Override 207 public void sync() throws Exception 208 { 209 wrapped.sync(); 210 } 211 212 213 @Override 214 protected void doInit() throws Exception 215 { 216 // ----------------------------------------------------------------------- 217 // Load apachemeta schema from within the ldap-schema Jar with all the 218 // schema it depends on. This is a minimal mandatory set of schemas. 219 // ----------------------------------------------------------------------- 220 //SerializableComparator.setSchemaManager( schemaManager ); 221 222 wrapped.setId( ID ); 223 wrapped.setSuffix( SchemaConstants.OU_SCHEMA ); 224 wrapped.getSuffixDn().normalize( schemaManager.getNormalizerMapping() ); 225 wrapped.setSchemaManager( schemaManager ); 226 227 try 228 { 229 wrapped.initialize(); 230 231 PartitionSchemaLoader partitionLoader = new PartitionSchemaLoader( wrapped, schemaManager ); 232 synchronizer = new RegistrySynchronizerAdaptor( schemaManager ); 233 234 if ( wrapped instanceof NullPartition ) 235 { 236 LOG.warn( "BYPASSING CRITICAL SCHEMA PROCESSING CODE DURING HEAVY DEV. " 237 + "PLEASE REMOVE THIS CONDITION BY USING A VALID SCHEMA PARTITION!!!" ); 238 return; 239 } 240 } 241 catch ( Exception e ) 242 { 243 LOG.error( I18n.err( I18n.ERR_90 ), e ); 244 throw new RuntimeException( e ); 245 } 246 247 schemaModificationDN = new DN( ServerDNConstants.SCHEMA_MODIFICATIONS_DN ); 248 schemaModificationDN.normalize( schemaManager.getNormalizerMapping() ); 249 } 250 251 252 @Override 253 protected void doDestroy() 254 { 255 try 256 { 257 wrapped.destroy(); 258 } 259 catch ( Exception e ) 260 { 261 LOG.error( I18n.err( I18n.ERR_91 ), e ); 262 throw new RuntimeException( e ); 263 } 264 } 265 266 267 // ----------------------------------------------------------------------- 268 // Partition Interface Methods 269 // ----------------------------------------------------------------------- 270 271 /** 272 * {@inheritDoc} 273 */ 274 public void add( AddOperationContext opContext ) throws Exception 275 { 276 // At this point, the added SchemaObject does not exist in the partition 277 // We have to check if it's enabled and then inject it into the registries 278 // but only if it does not break the server. 279 synchronizer.add( opContext ); 280 281 // Now, write the newly added SchemaObject into the schemaPartition 282 try 283 { 284 wrapped.add( opContext ); 285 } 286 catch ( Exception e ) 287 { 288 // If something went wrong, we have to unregister the schemaObject 289 // from the registries 290 // TODO : deregister the newly added element. 291 throw e; 292 } 293 294 updateSchemaModificationAttributes( opContext ); 295 } 296 297 298 /* (non-Javadoc) 299 * @see org.apache.directory.server.core.partition.Partition#bind(org.apache.directory.server.core.interceptor.context.BindOperationContext) 300 */ 301 public void bind( BindOperationContext opContext ) throws Exception 302 { 303 wrapped.bind( opContext ); 304 } 305 306 307 /* (non-Javadoc) 308 * @see org.apache.directory.server.core.partition.Partition#delete(org.apache.directory.server.core.interceptor.context.DeleteOperationContext) 309 */ 310 public void delete( DeleteOperationContext opContext ) throws Exception 311 { 312 boolean cascade = opContext.hasRequestControl( CascadeControl.CONTROL_OID ); 313 314 // The SchemaObject always exist when we reach this method. 315 synchronizer.delete( opContext, cascade ); 316 317 try 318 { 319 wrapped.delete( opContext ); 320 } 321 catch ( Exception e ) 322 { 323 // TODO : If something went wrong, what should we do here ? 324 throw e; 325 } 326 327 updateSchemaModificationAttributes( opContext ); 328 } 329 330 331 /* (non-Javadoc) 332 * @see org.apache.directory.server.core.partition.Partition#list(org.apache.directory.server.core.interceptor.context.ListOperationContext) 333 */ 334 public EntryFilteringCursor list( ListOperationContext opContext ) throws Exception 335 { 336 return wrapped.list( opContext ); 337 } 338 339 340 /** 341 * {@inheritDoc} 342 */ 343 public boolean hasEntry( EntryOperationContext entryContext ) throws Exception 344 { 345 return wrapped.hasEntry( entryContext ); 346 } 347 348 349 /** 350 * {@inheritDoc} 351 */ 352 public void modify( ModifyOperationContext opContext ) throws Exception 353 { 354 ServerEntry entry = opContext.getEntry(); 355 356 if ( entry == null ) 357 { 358 LookupOperationContext lookupCtx = new LookupOperationContext( opContext.getSession(), opContext.getDn() ); 359 entry = wrapped.lookup( lookupCtx ); 360 } 361 362 ServerEntry targetEntry = ( ServerEntry ) SchemaUtils.getTargetEntry( opContext.getModItems(), entry ); 363 364 boolean cascade = opContext.hasRequestControl( CascadeControl.CONTROL_OID ); 365 366 boolean hasModification = synchronizer.modify( opContext, targetEntry, cascade ); 367 368 if ( hasModification ) 369 { 370 wrapped.modify( opContext ); 371 } 372 373 if ( !opContext.getDn().equals( schemaModificationDN ) ) 374 { 375 updateSchemaModificationAttributes( opContext ); 376 } 377 } 378 379 380 /* (non-Javadoc) 381 * @see org.apache.directory.server.core.partition.Partition#move(org.apache.directory.server.core.interceptor.context.MoveOperationContext) 382 */ 383 public void move( MoveOperationContext opContext ) throws Exception 384 { 385 boolean cascade = opContext.hasRequestControl( CascadeControl.CONTROL_OID ); 386 ClonedServerEntry entry = opContext.lookup( opContext.getDn(), ByPassConstants.LOOKUP_BYPASS ); 387 synchronizer.move( opContext, entry, cascade ); 388 wrapped.move( opContext ); 389 updateSchemaModificationAttributes( opContext ); 390 } 391 392 393 /* (non-Javadoc) 394 * @see org.apache.directory.server.core.partition.Partition#moveAndRename(org.apache.directory.server.core.interceptor.context.MoveAndRenameOperationContext) 395 */ 396 public void moveAndRename( MoveAndRenameOperationContext opContext ) throws Exception 397 { 398 boolean cascade = opContext.hasRequestControl( CascadeControl.CONTROL_OID ); 399 ClonedServerEntry entry = opContext.lookup( opContext.getDn(), ByPassConstants.LOOKUP_BYPASS ); 400 synchronizer.moveAndRename( opContext, entry, cascade ); 401 wrapped.moveAndRename( opContext ); 402 updateSchemaModificationAttributes( opContext ); 403 } 404 405 406 /** 407 * {@inheritDoc} 408 */ 409 public void rename( RenameOperationContext opContext ) throws Exception 410 { 411 boolean cascade = opContext.hasRequestControl( CascadeControl.CONTROL_OID ); 412 413 // First update the registries 414 synchronizer.rename( opContext, cascade ); 415 416 // Update the schema partition 417 wrapped.rename( opContext ); 418 419 // Update the SSSE operational attributes 420 updateSchemaModificationAttributes( opContext ); 421 } 422 423 424 /* (non-Javadoc) 425 * @see org.apache.directory.server.core.partition.Partition#search(org.apache.directory.server.core.interceptor.context.SearchOperationContext) 426 */ 427 public EntryFilteringCursor search( SearchOperationContext opContext ) throws Exception 428 { 429 return wrapped.search( opContext ); 430 } 431 432 433 /* (non-Javadoc) 434 * @see org.apache.directory.server.core.partition.Partition#unbind(org.apache.directory.server.core.interceptor.context.UnbindOperationContext) 435 */ 436 public void unbind( UnbindOperationContext opContext ) throws Exception 437 { 438 wrapped.unbind( opContext ); 439 } 440 441 442 /* (non-Javadoc) 443 * @see org.apache.directory.server.core.partition.Partition#lookup(org.apache.directory.server.core.interceptor.context.LookupOperationContext) 444 */ 445 public ClonedServerEntry lookup( LookupOperationContext lookupContext ) throws Exception 446 { 447 return wrapped.lookup( lookupContext ); 448 } 449 450 451 /** 452 * Updates the schemaModifiersName and schemaModifyTimestamp attributes of 453 * the schemaModificationAttributes entry for the global schema at 454 * ou=schema,cn=schemaModifications. This entry is hardcoded at that 455 * position for now. 456 * 457 * The current time is used to set the timestamp and the DN of current user 458 * is set for the modifiersName. 459 * 460 * @throws NamingException if the update fails 461 */ 462 private void updateSchemaModificationAttributes( OperationContext opContext ) throws Exception 463 { 464 String modifiersName = opContext.getSession().getEffectivePrincipal().getName(); 465 String modifyTimestamp = DateUtils.getGeneralizedTime(); 466 467 List<Modification> mods = new ArrayList<Modification>( 2 ); 468 469 mods.add( new ServerModification( ModificationOperation.REPLACE_ATTRIBUTE, new DefaultServerAttribute( 470 ApacheSchemaConstants.SCHEMA_MODIFY_TIMESTAMP_AT, schemaManager 471 .lookupAttributeTypeRegistry( ApacheSchemaConstants.SCHEMA_MODIFY_TIMESTAMP_AT ), modifyTimestamp ) ) ); 472 473 mods.add( new ServerModification( ModificationOperation.REPLACE_ATTRIBUTE, new DefaultServerAttribute( 474 ApacheSchemaConstants.SCHEMA_MODIFIERS_NAME_AT, schemaManager 475 .lookupAttributeTypeRegistry( ApacheSchemaConstants.SCHEMA_MODIFIERS_NAME_AT ), modifiersName ) ) ); 476 477 opContext.modify( schemaModificationDN, mods, ByPassConstants.SCHEMA_MODIFICATION_ATTRIBUTES_UPDATE_BYPASS ); 478 } 479 480 481 /** 482 * @param schemaManager the SchemaManager to set 483 */ 484 public void setSchemaManager( SchemaManager schemaManager ) 485 { 486 this.schemaManager = schemaManager; 487 } 488 489 490 /** 491 * @return The schemaManager 492 */ 493 public SchemaManager getSchemaManager() 494 { 495 return schemaManager; 496 } 497 498 499 /** 500 * @see Object#toString() 501 */ 502 public String toString() 503 { 504 return "Partition : " + ID; 505 } 506 }