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    }