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 }