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.io.UnsupportedEncodingException;
024    import java.util.ArrayList;
025    import java.util.HashMap;
026    import java.util.HashSet;
027    import java.util.Iterator;
028    import java.util.List;
029    import java.util.Map;
030    import java.util.Set;
031    import java.util.concurrent.ConcurrentHashMap;
032    
033    import javax.naming.directory.SearchControls;
034    
035    import org.apache.directory.server.constants.ServerDNConstants;
036    import org.apache.directory.server.core.DirectoryService;
037    import org.apache.directory.server.core.entry.ClonedServerEntry;
038    import org.apache.directory.server.core.filtering.BaseEntryFilteringCursor;
039    import org.apache.directory.server.core.filtering.EntryFilter;
040    import org.apache.directory.server.core.filtering.EntryFilteringCursor;
041    import org.apache.directory.server.core.interceptor.BaseInterceptor;
042    import org.apache.directory.server.core.interceptor.NextInterceptor;
043    import org.apache.directory.server.core.interceptor.context.AddOperationContext;
044    import org.apache.directory.server.core.interceptor.context.ListOperationContext;
045    import org.apache.directory.server.core.interceptor.context.LookupOperationContext;
046    import org.apache.directory.server.core.interceptor.context.ModifyOperationContext;
047    import org.apache.directory.server.core.interceptor.context.RenameOperationContext;
048    import org.apache.directory.server.core.interceptor.context.SearchOperationContext;
049    import org.apache.directory.server.core.interceptor.context.SearchingOperationContext;
050    import org.apache.directory.server.core.partition.PartitionNexus;
051    import org.apache.directory.server.i18n.I18n;
052    import org.apache.directory.shared.ldap.codec.controls.CascadeControl;
053    import org.apache.directory.shared.ldap.constants.MetaSchemaConstants;
054    import org.apache.directory.shared.ldap.constants.SchemaConstants;
055    import org.apache.directory.shared.ldap.cursor.EmptyCursor;
056    import org.apache.directory.shared.ldap.cursor.SingletonCursor;
057    import org.apache.directory.shared.ldap.entry.BinaryValue;
058    import org.apache.directory.shared.ldap.entry.StringValue;
059    import org.apache.directory.shared.ldap.entry.DefaultServerAttribute;
060    import org.apache.directory.shared.ldap.entry.Entry;
061    import org.apache.directory.shared.ldap.entry.EntryAttribute;
062    import org.apache.directory.shared.ldap.entry.Modification;
063    import org.apache.directory.shared.ldap.entry.ModificationOperation;
064    import org.apache.directory.shared.ldap.entry.ServerEntry;
065    import org.apache.directory.shared.ldap.entry.ServerModification;
066    import org.apache.directory.shared.ldap.entry.Value;
067    import org.apache.directory.shared.ldap.exception.LdapAttributeInUseException;
068    import org.apache.directory.shared.ldap.exception.LdapException;
069    import org.apache.directory.shared.ldap.exception.LdapInvalidAttributeTypeException;
070    import org.apache.directory.shared.ldap.exception.LdapInvalidAttributeValueException;
071    import org.apache.directory.shared.ldap.exception.LdapNoPermissionException;
072    import org.apache.directory.shared.ldap.exception.LdapNoSuchAttributeException;
073    import org.apache.directory.shared.ldap.exception.LdapSchemaViolationException;
074    import org.apache.directory.shared.ldap.filter.ApproximateNode;
075    import org.apache.directory.shared.ldap.filter.AssertionNode;
076    import org.apache.directory.shared.ldap.filter.BranchNode;
077    import org.apache.directory.shared.ldap.filter.EqualityNode;
078    import org.apache.directory.shared.ldap.filter.ExprNode;
079    import org.apache.directory.shared.ldap.filter.ExtensibleNode;
080    import org.apache.directory.shared.ldap.filter.GreaterEqNode;
081    import org.apache.directory.shared.ldap.filter.LessEqNode;
082    import org.apache.directory.shared.ldap.filter.PresenceNode;
083    import org.apache.directory.shared.ldap.filter.ScopeNode;
084    import org.apache.directory.shared.ldap.filter.SimpleNode;
085    import org.apache.directory.shared.ldap.filter.SubstringNode;
086    import org.apache.directory.shared.ldap.message.ResultCodeEnum;
087    import org.apache.directory.shared.ldap.name.AVA;
088    import org.apache.directory.shared.ldap.name.DN;
089    import org.apache.directory.shared.ldap.name.RDN;
090    import org.apache.directory.shared.ldap.schema.AttributeType;
091    import org.apache.directory.shared.ldap.schema.AttributeTypeOptions;
092    import org.apache.directory.shared.ldap.schema.ObjectClass;
093    import org.apache.directory.shared.ldap.schema.ObjectClassTypeEnum;
094    import org.apache.directory.shared.ldap.schema.SchemaManager;
095    import org.apache.directory.shared.ldap.schema.SyntaxChecker;
096    import org.apache.directory.shared.ldap.schema.UsageEnum;
097    import org.apache.directory.shared.ldap.schema.registries.OidRegistry;
098    import org.apache.directory.shared.ldap.schema.registries.Schema;
099    import org.apache.directory.shared.ldap.schema.registries.SchemaLoader;
100    import org.apache.directory.shared.ldap.schema.syntaxCheckers.OctetStringSyntaxChecker;
101    import org.slf4j.Logger;
102    import org.slf4j.LoggerFactory;
103    
104    
105    /**
106     * An {@link org.apache.directory.server.core.interceptor.Interceptor} that manages and enforces schemas.
107     *
108     * @todo Better interceptor description required.
109    
110     * @org.apache.xbean.XBean
111     *
112     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
113     * @version $Rev: 928945 $, $Date: 2010-03-30 01:59:49 +0200 (Tue, 30 Mar 2010) $
114     */
115    public class SchemaInterceptor extends BaseInterceptor
116    {
117        /** The LoggerFactory used by this Interceptor */
118        private static Logger LOG = LoggerFactory.getLogger( SchemaInterceptor.class );
119    
120        private static final String[] SCHEMA_SUBENTRY_RETURN_ATTRIBUTES = new String[]
121            { 
122                SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES, 
123                SchemaConstants.ALL_USER_ATTRIBUTES 
124            };
125    
126        /** Speedup for logs */
127        private static final boolean IS_DEBUG = LOG.isDebugEnabled();
128    
129        /**
130         * the root nexus to all database partitions
131         */
132        private PartitionNexus nexus;
133    
134        /**
135         * a binary attribute tranforming filter: String -> byte[]
136         */
137        private BinaryAttributeFilter binaryAttributeFilter;
138    
139        private TopFilter topFilter;
140    
141        private List<EntryFilter> filters = new ArrayList<EntryFilter>();
142    
143        /** the global schema object SchemaManager */
144        private SchemaManager schemaManager;
145    
146        /** A global reference to the ObjectClass attributeType */
147        private AttributeType OBJECT_CLASS;
148    
149        /** A normalized form for the SubschemaSubentry DN */
150        private String subschemaSubentryDnNorm;
151    
152        /** The SubschemaSubentry DN */
153        private DN subschemaSubentryDn;
154    
155        /**
156         * the normalized name for the schema modification attributes
157         */
158        private DN schemaModificationAttributesDN;
159    
160        /** The schema manager */
161        private SchemaSubentryManager schemaSubEntryManager;
162    
163        private SchemaService schemaService;
164    
165        /** the base DN (normalized) of the schema partition */
166        private DN schemaBaseDN;
167    
168        /** A map used to store all the objectClasses superiors */
169        private Map<String, List<ObjectClass>> superiors;
170    
171        /** A map used to store all the objectClasses may attributes */
172        private Map<String, List<AttributeType>> allMay;
173    
174        /** A map used to store all the objectClasses must */
175        private Map<String, List<AttributeType>> allMust;
176    
177        /** A map used to store all the objectClasses allowed attributes (may + must) */
178        private Map<String, List<AttributeType>> allowed;
179    
180        private static AttributeType MODIFIERS_NAME_ATTRIBUTE_TYPE;
181        private static AttributeType MODIFY_TIMESTAMP_ATTRIBUTE_TYPE;
182    
183        /**
184         * Initialize the Schema Service
185         *
186         * @param directoryService the directory service core
187         * @throws Exception if there are problems during initialization
188         */
189        public void init( DirectoryService directoryService ) throws Exception
190        {
191            if ( IS_DEBUG )
192            {
193                LOG.debug( "Initializing SchemaInterceptor..." );
194            }
195    
196            nexus = directoryService.getPartitionNexus();
197            schemaManager = directoryService.getSchemaManager();
198            OBJECT_CLASS = schemaManager.lookupAttributeTypeRegistry( SchemaConstants.OBJECT_CLASS_AT );
199            binaryAttributeFilter = new BinaryAttributeFilter();
200            topFilter = new TopFilter();
201            filters.add( binaryAttributeFilter );
202            filters.add( topFilter );
203    
204            schemaBaseDN = new DN( SchemaConstants.OU_SCHEMA );
205            schemaBaseDN.normalize( schemaManager.getNormalizerMapping() );
206            schemaService = directoryService.getSchemaService();
207    
208            // stuff for dealing with subentries (garbage for now)
209            Value<?> subschemaSubentry = nexus.getRootDSE( null ).get( SchemaConstants.SUBSCHEMA_SUBENTRY_AT ).get();
210            subschemaSubentryDn = new DN( subschemaSubentry.getString() );
211            subschemaSubentryDn.normalize( schemaManager.getNormalizerMapping() );
212            subschemaSubentryDnNorm = subschemaSubentryDn.getNormName();
213    
214            schemaModificationAttributesDN = new DN( ServerDNConstants.SCHEMA_MODIFICATIONS_DN );
215            schemaModificationAttributesDN.normalize( schemaManager.getNormalizerMapping() );
216    
217            computeSuperiors();
218            
219            // Initialize the schema manager
220            SchemaLoader loader = schemaService.getSchemaPartition().getSchemaManager().getLoader();
221            schemaSubEntryManager = new SchemaSubentryManager( schemaManager, loader );
222    
223            MODIFIERS_NAME_ATTRIBUTE_TYPE = schemaManager.lookupAttributeTypeRegistry( SchemaConstants.MODIFIERS_NAME_AT );
224            MODIFY_TIMESTAMP_ATTRIBUTE_TYPE = schemaManager.lookupAttributeTypeRegistry( SchemaConstants.MODIFY_TIMESTAMP_AT );
225            
226            if ( IS_DEBUG )
227            {
228                LOG.debug( "SchemaInterceptor Initialized !" );
229            }
230        }
231    
232    
233        /**
234         * Compute the MUST attributes for an objectClass. This method gather all the
235         * MUST from all the objectClass and its superors.
236         *
237         * @param atSeen ???
238         * @param objectClass the object class to gather MUST attributes for
239         * @throws Exception if there are problems resolving schema entitites
240         */
241        private void computeMustAttributes( ObjectClass objectClass, Set<String> atSeen ) throws Exception
242        {
243            List<ObjectClass> parents = superiors.get( objectClass.getOid() );
244    
245            List<AttributeType> mustList = new ArrayList<AttributeType>();
246            List<AttributeType> allowedList = new ArrayList<AttributeType>();
247            Set<String> mustSeen = new HashSet<String>();
248    
249            allMust.put( objectClass.getOid(), mustList );
250            allowed.put( objectClass.getOid(), allowedList );
251    
252            for ( ObjectClass parent : parents )
253            {
254                List<AttributeType> mustParent = parent.getMustAttributeTypes();
255    
256                if ( ( mustParent != null ) && ( mustParent.size() != 0 ) )
257                {
258                    for ( AttributeType attributeType : mustParent )
259                    {
260                        String oid = attributeType.getOid();
261    
262                        if ( !mustSeen.contains( oid ) )
263                        {
264                            mustSeen.add( oid );
265                            mustList.add( attributeType );
266                            allowedList.add( attributeType );
267                            atSeen.add( attributeType.getOid() );
268                        }
269                    }
270                }
271            }
272        }
273    
274    
275        /**
276         * Compute the MAY attributes for an objectClass. This method gather all the
277         * MAY from all the objectClass and its superors.
278         *
279         * The allowed attributes is also computed, it's the union of MUST and MAY
280         *
281         * @param atSeen ???
282         * @param objectClass the object class to get all the MAY attributes for
283         * @throws Exception with problems accessing registries
284         */
285        private void computeMayAttributes( ObjectClass objectClass, Set<String> atSeen ) throws Exception
286        {
287            List<ObjectClass> parents = superiors.get( objectClass.getOid() );
288    
289            List<AttributeType> mayList = new ArrayList<AttributeType>();
290            Set<String> maySeen = new HashSet<String>();
291            List<AttributeType> allowedList = allowed.get( objectClass.getOid() );
292    
293            allMay.put( objectClass.getOid(), mayList );
294    
295            for ( ObjectClass parent : parents )
296            {
297                List<AttributeType> mustParent = parent.getMustAttributeTypes();
298    
299                if ( ( mustParent != null ) && ( mustParent.size() != 0 ) )
300                {
301                    for ( AttributeType attributeType : mustParent )
302                    {
303                        String oid = attributeType.getOid();
304    
305                        if ( !maySeen.contains( oid ) )
306                        {
307                            maySeen.add( oid );
308                            mayList.add( attributeType );
309    
310                            if ( !atSeen.contains( oid ) )
311                            {
312                                allowedList.add( attributeType );
313                            }
314                        }
315                    }
316                }
317            }
318        }
319    
320    
321        /**
322         * Recursively compute all the superiors of an object class. For instance, considering
323         * 'inetOrgPerson', it's direct superior is 'organizationalPerson', which direct superior
324         * is 'Person', which direct superior is 'top'.
325         *
326         * As a result, we will gather all of these three ObjectClasses in 'inetOrgPerson' ObjectClasse
327         * superiors.
328         */
329        private void computeOCSuperiors( ObjectClass objectClass, List<ObjectClass> superiors, Set<String> ocSeen )
330            throws Exception
331        {
332            List<ObjectClass> parents = objectClass.getSuperiors();
333    
334            // Loop on all the objectClass superiors
335            if ( ( parents != null ) && ( parents.size() != 0 ) )
336            {
337                for ( ObjectClass parent : parents )
338                {
339                    // Top is not added
340                    if ( SchemaConstants.TOP_OC.equals( parent.getName() ) )
341                    {
342                        continue;
343                    }
344    
345                    // For each one, recurse
346                    computeOCSuperiors( parent, superiors, ocSeen );
347    
348                    String oid = parent.getOid();
349    
350                    if ( !ocSeen.contains( oid ) )
351                    {
352                        superiors.add( parent );
353                        ocSeen.add( oid );
354                    }
355                }
356            }
357        }
358    
359    
360        /**
361         * Compute the superiors and MUST/MAY attributes for a specific
362         * ObjectClass
363         */
364        private void computeSuperior( ObjectClass objectClass ) throws Exception
365        {
366            List<ObjectClass> ocSuperiors = new ArrayList<ObjectClass>();
367    
368            superiors.put( objectClass.getOid(), ocSuperiors );
369    
370            computeOCSuperiors( objectClass, ocSuperiors, new HashSet<String>() );
371    
372            Set<String> atSeen = new HashSet<String>();
373            computeMustAttributes( objectClass, atSeen );
374            computeMayAttributes( objectClass, atSeen );
375    
376            superiors.put( objectClass.getName(), ocSuperiors );
377        }
378        
379    
380        /**
381         * Compute all ObjectClasses superiors, MAY and MUST attributes.
382         * @throws Exception
383         */
384        private void computeSuperiors() throws Exception
385        {
386            Iterator<ObjectClass> objectClasses = schemaManager.getObjectClassRegistry().iterator();
387            superiors = new ConcurrentHashMap<String, List<ObjectClass>>();
388            allMust = new ConcurrentHashMap<String, List<AttributeType>>();
389            allMay = new ConcurrentHashMap<String, List<AttributeType>>();
390            allowed = new ConcurrentHashMap<String, List<AttributeType>>();
391    
392            while ( objectClasses.hasNext() )
393            {
394                ObjectClass objectClass = objectClasses.next();
395                computeSuperior( objectClass );
396            }
397        }
398    
399    
400        public EntryFilteringCursor list( NextInterceptor nextInterceptor, ListOperationContext opContext )
401            throws Exception
402        {
403            EntryFilteringCursor cursor = nextInterceptor.list( opContext );
404            cursor.addEntryFilter( binaryAttributeFilter );
405            return cursor;
406        }
407    
408    
409        /**
410         * Remove all unknown attributes from the searchControls, to avoid an exception.
411         *
412         * RFC 2251 states that :
413         * " Attributes MUST be named at most once in the list, and are returned "
414         * " at most once in an entry. "
415         * " If there are attribute descriptions in "
416         * " the list which are not recognized, they are ignored by the server."
417         *
418         * @param searchCtls The SearchControls we will filter
419         */
420        private void filterAttributesToReturn( SearchControls searchCtls )
421        {
422            String[] attributes = searchCtls.getReturningAttributes();
423    
424            if ( ( attributes == null ) || ( attributes.length == 0 ) )
425            {
426                // We have no attributes, that means "*" (all users attributes)
427                searchCtls.setReturningAttributes( SchemaConstants.ALL_USER_ATTRIBUTES_ARRAY );
428                return;
429            }
430    
431            Map<String, String> filteredAttrs = new HashMap<String, String>();
432            boolean hasNoAttribute = false;
433            boolean hasAttributes = false;
434    
435            for ( String attribute : attributes )
436            {
437                // Skip special attributes
438                if ( ( SchemaConstants.ALL_USER_ATTRIBUTES.equals( attribute ) )
439                    || ( SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES.equals( attribute ) )
440                    || ( SchemaConstants.NO_ATTRIBUTE.equals( attribute ) ) )
441                {
442                    if ( !filteredAttrs.containsKey( attribute ) )
443                    {
444                        filteredAttrs.put( attribute, attribute );
445                    }
446    
447                    if ( SchemaConstants.NO_ATTRIBUTE.equals( attribute ) )
448                    {
449                        hasNoAttribute = true;
450                    }
451                    else
452                    {
453                        hasAttributes = true;
454                    }
455    
456                    continue;
457                }
458    
459                try
460                {
461                    // Check that the attribute is declared
462                    if ( schemaManager.getAttributeTypeRegistry().contains( attribute ) )
463                    {
464                        String oid = schemaManager.getAttributeTypeRegistry().getOidByName( attribute );
465    
466                        // The attribute must be an AttributeType
467                        if ( schemaManager.getAttributeTypeRegistry().contains( oid ) )
468                        {
469                            if ( !filteredAttrs.containsKey( oid ) )
470                            {
471                                // Ok, we can add the attribute to the list of filtered attributes
472                                filteredAttrs.put( oid, attribute );
473                            }
474                        }
475                    }
476    
477                    hasAttributes = true;
478                }
479                catch ( Exception ne )
480                {
481                    /* Do nothing, the attribute does not exist */
482                }
483            }
484    
485            // Treat a special case : if we have an attribute and "1.1", then discard "1.1"
486            if ( hasAttributes && hasNoAttribute )
487            {
488                filteredAttrs.remove( SchemaConstants.NO_ATTRIBUTE );
489            }
490    
491            // If we still have the same attribute number, then we can just get out the method
492            if ( filteredAttrs.size() == attributes.length )
493            {
494                return;
495            }
496    
497            // Deal with the special case where the attribute list is now empty
498            if ( filteredAttrs.size() == 0 )
499            {
500                // We just have to pass the special 1.1 attribute,
501                // as we don't want to return any attribute
502                searchCtls.setReturningAttributes( SchemaConstants.NO_ATTRIBUTE_ARRAY );
503                return;
504            }
505    
506            // Some attributes have been removed. let's modify the searchControl
507            String[] newAttributesList = new String[filteredAttrs.size()];
508    
509            int pos = 0;
510    
511            for ( String key : filteredAttrs.keySet() )
512            {
513                newAttributesList[pos++] = filteredAttrs.get( key );
514            }
515    
516            searchCtls.setReturningAttributes( newAttributesList );
517        }
518    
519    
520        private Value<?> convert( String id, Object value ) throws Exception
521        {
522            AttributeType at = schemaManager.lookupAttributeTypeRegistry( id );
523    
524            if ( at.getSyntax().isHumanReadable() )
525            {
526                if ( value instanceof byte[] )
527                {
528                    try
529                    {
530                        return new StringValue( new String( ( byte[] ) value, "UTF-8" ) );
531                    }
532                    catch ( UnsupportedEncodingException uee )
533                    {
534                        String message = I18n.err( I18n.ERR_47 );
535                        LOG.error( message );
536                        throw new LdapException( message );
537                    }
538                }
539            }
540            else
541            {
542                if ( value instanceof String )
543                {
544                    try
545                    {
546                        return new BinaryValue( ( ( String ) value ).getBytes( "UTF-8" ) );
547                    }
548                    catch ( UnsupportedEncodingException uee )
549                    {
550                        String message = I18n.err( I18n.ERR_48 );
551                        LOG.error( message );
552                        throw new LdapException( message );
553                    }
554                }
555            }
556    
557            return null;
558        }
559    
560    
561        /**
562         * Check that the filter values are compatible with the AttributeType. Typically,
563         * a HumanReadible filter should have a String value. The substring filter should
564         * not be used with binary attributes.
565         */
566        private void checkFilter( ExprNode filter ) throws Exception
567        {
568            if ( filter == null )
569            {
570                String message = I18n.err( I18n.ERR_49 );
571                LOG.error( message );
572                throw new LdapException( message );
573            }
574    
575            if ( filter.isLeaf() )
576            {
577                if ( filter instanceof EqualityNode )
578                {
579                    EqualityNode node = ( ( EqualityNode ) filter );
580                    Object value = node.getValue();
581    
582                    Value<?> newValue = convert( node.getAttribute(), value );
583    
584                    if ( newValue != null )
585                    {
586                        node.setValue( newValue );
587                    }
588                }
589                else if ( filter instanceof SubstringNode )
590                {
591                    SubstringNode node = ( ( SubstringNode ) filter );
592    
593                    if ( !schemaManager.lookupAttributeTypeRegistry( node.getAttribute() ).getSyntax().isHumanReadable() )
594                    {
595                        String message = I18n.err( I18n.ERR_50 );
596                        LOG.error( message );
597                        throw new LdapException( message );
598                    }
599                }
600                else if ( filter instanceof PresenceNode )
601                {
602                    // Nothing to do
603                }
604                else if ( filter instanceof GreaterEqNode )
605                {
606                    GreaterEqNode node = ( ( GreaterEqNode ) filter );
607                    Object value = node.getValue();
608    
609                    Value<?> newValue = convert( node.getAttribute(), value );
610    
611                    if ( newValue != null )
612                    {
613                        node.setValue( newValue );
614                    }
615    
616                }
617                else if ( filter instanceof LessEqNode )
618                {
619                    LessEqNode node = ( ( LessEqNode ) filter );
620                    Object value = node.getValue();
621    
622                    Value<?> newValue = convert( node.getAttribute(), value );
623    
624                    if ( newValue != null )
625                    {
626                        node.setValue( newValue );
627                    }
628                }
629                else if ( filter instanceof ExtensibleNode )
630                {
631                    ExtensibleNode node = ( ( ExtensibleNode ) filter );
632    
633                    if ( !schemaManager.lookupAttributeTypeRegistry( node.getAttribute() ).getSyntax().isHumanReadable() )
634                    {
635                        String message = I18n.err( I18n.ERR_51 );
636                        LOG.error( message );
637                        throw new LdapException( message );
638                    }
639                }
640                else if ( filter instanceof ApproximateNode )
641                {
642                    ApproximateNode node = ( ( ApproximateNode ) filter );
643                    Object value = node.getValue();
644    
645                    Value<?> newValue = convert( node.getAttribute(), value );
646    
647                    if ( newValue != null )
648                    {
649                        node.setValue( newValue );
650                    }
651                }
652                else if ( filter instanceof AssertionNode )
653                {
654                    // Nothing to do
655                    return;
656                }
657                else if ( filter instanceof ScopeNode )
658                {
659                    // Nothing to do
660                    return;
661                }
662            }
663            else
664            {
665                // Recursively iterate through all the children.
666                for ( ExprNode child : ( ( BranchNode ) filter ).getChildren() )
667                {
668                    checkFilter( child );
669                }
670            }
671        }
672    
673    
674        public EntryFilteringCursor search( NextInterceptor nextInterceptor, SearchOperationContext opContext )
675            throws Exception
676        {
677            DN base = opContext.getDn();
678            SearchControls searchCtls = opContext.getSearchControls();
679            ExprNode filter = opContext.getFilter();
680    
681            // We have to eliminate bad attributes from the request, accordingly
682            // to RFC 2251, chap. 4.5.1. Basically, all unknown attributes are removed
683            // from the list
684            if ( searchCtls.getReturningAttributes() != null )
685            {
686                filterAttributesToReturn( searchCtls );
687            }
688    
689            // We also have to check the H/R flag for the filter attributes
690            checkFilter( filter );
691    
692            String baseNormForm = ( base.isNormalized() ? base.getNormName() : base.getNormName() );
693    
694            // Deal with the normal case : searching for a normal value (not subSchemaSubEntry)
695            if ( !subschemaSubentryDnNorm.equals( baseNormForm ) )
696            {
697                EntryFilteringCursor cursor = nextInterceptor.search( opContext );
698    
699                if ( searchCtls.getReturningAttributes() != null )
700                {
701                    cursor.addEntryFilter( topFilter );
702                    return cursor;
703                }
704    
705                for ( EntryFilter ef : filters )
706                {
707                    cursor.addEntryFilter( ef );
708                }
709    
710                return cursor;
711            }
712    
713            // The user was searching into the subSchemaSubEntry
714            // This kind of search _must_ be limited to OBJECT scope (the subSchemaSubEntry
715            // does not have any sub level)
716            if ( searchCtls.getSearchScope() == SearchControls.OBJECT_SCOPE )
717            {
718                // The filter can be an equality or a presence, but nothing else
719                if ( filter instanceof SimpleNode )
720                {
721                    // We should get the value for the filter.
722                    // only 'top' and 'subSchema' are valid values
723                    SimpleNode node = ( SimpleNode ) filter;
724                    String objectClass;
725    
726                    objectClass = node.getValue().getString();
727    
728                    String objectClassOid = null;
729    
730                    if ( schemaManager.getObjectClassRegistry().contains( objectClass ) )
731                    {
732                        objectClassOid = schemaManager.getObjectClassRegistry().lookup( objectClass ).getOid();
733                    }
734                    else
735                    {
736                        return new BaseEntryFilteringCursor( new EmptyCursor<ServerEntry>(), opContext );
737                    }
738    
739                    String nodeOid = schemaManager.getAttributeTypeRegistry().getOidByName( node.getAttribute() );
740    
741                    // see if node attribute is objectClass
742                    if ( nodeOid.equals( SchemaConstants.OBJECT_CLASS_AT_OID )
743                        && ( objectClassOid.equals( SchemaConstants.TOP_OC_OID ) || objectClassOid
744                            .equals( SchemaConstants.SUBSCHEMA_OC_OID ) ) && ( node instanceof EqualityNode ) )
745                    {
746                        // call.setBypass( true );
747                        ServerEntry serverEntry = schemaService.getSubschemaEntry( searchCtls.getReturningAttributes() );
748                        serverEntry.setDn( base );
749                        return new BaseEntryFilteringCursor( new SingletonCursor<ServerEntry>( serverEntry ), opContext );
750                    }
751                    else
752                    {
753                        return new BaseEntryFilteringCursor( new EmptyCursor<ServerEntry>(), opContext );
754                    }
755                }
756                else if ( filter instanceof PresenceNode )
757                {
758                    PresenceNode node = ( PresenceNode ) filter;
759    
760                    // see if node attribute is objectClass
761                    if ( node.getAttribute().equals( SchemaConstants.OBJECT_CLASS_AT_OID ) )
762                    {
763                        // call.setBypass( true );
764                        ServerEntry serverEntry = schemaService.getSubschemaEntry( searchCtls.getReturningAttributes() );
765                        serverEntry.setDn( base );
766                        EntryFilteringCursor cursor = new BaseEntryFilteringCursor( new SingletonCursor<ServerEntry>(
767                            serverEntry ), opContext );
768                        return cursor;
769                    }
770                }
771            }
772    
773            // In any case not handled previously, just return an empty result
774            return new BaseEntryFilteringCursor( new EmptyCursor<ServerEntry>(), opContext );
775        }
776    
777    
778        /**
779         * Search for an entry, using its DN. Binary attributes and ObjectClass attribute are removed.
780         */
781        public ClonedServerEntry lookup( NextInterceptor nextInterceptor, LookupOperationContext opContext )
782            throws Exception
783        {
784            ClonedServerEntry result = nextInterceptor.lookup( opContext );
785    
786            if ( result == null )
787            {
788                return null;
789            }
790    
791            filterBinaryAttributes( result );
792            filterObjectClass( result );
793    
794            return result;
795        }
796    
797    
798        private void getSuperiors( ObjectClass oc, Set<String> ocSeen, List<ObjectClass> result ) throws Exception
799        {
800            for ( ObjectClass parent : oc.getSuperiors() )
801            {
802                // Skip 'top'
803                if ( SchemaConstants.TOP_OC.equals( parent.getName() ) )
804                {
805                    continue;
806                }
807    
808                if ( !ocSeen.contains( parent.getOid() ) )
809                {
810                    ocSeen.add( parent.getOid() );
811                    result.add( parent );
812                }
813    
814                // Recurse on the parent
815                getSuperiors( parent, ocSeen, result );
816            }
817        }
818    
819    
820        /**
821         * Checks to see if an attribute is required by as determined from an entry's
822         * set of objectClass attribute values.
823         *
824         * @param attrId the attribute to test if required by a set of objectClass values
825         * @param objectClass the objectClass values
826         * @return true if the objectClass values require the attribute, false otherwise
827         * @throws Exception if the attribute is not recognized
828         */
829        private boolean isRequired( String attrId, EntryAttribute objectClasses ) throws Exception
830        {
831            OidRegistry oidRegistry = schemaManager.getGlobalOidRegistry();
832    
833            if ( !oidRegistry.contains( attrId ) )
834            {
835                return false;
836            }
837    
838            String attrOid = schemaManager.getAttributeTypeRegistry().getOidByName( attrId );
839    
840            for ( Value<?> objectClass : objectClasses )
841            {
842                ObjectClass ocSpec = schemaManager.getObjectClassRegistry().lookup( objectClass.getString() );
843    
844                for ( AttributeType must : ocSpec.getMustAttributeTypes() )
845                {
846                    if ( must.getOid().equals( attrOid ) )
847                    {
848                        return true;
849                    }
850                }
851            }
852    
853            return false;
854        }
855    
856    
857        /**
858         * Checks to see if removing a set of attributes from an entry completely removes
859         * that attribute's values.  If change has zero size then all attributes are
860         * presumed to be removed.
861         *
862         * @param change
863         * @param entry
864         * @return
865         * @throws Exception
866         */
867        private boolean isCompleteRemoval( EntryAttribute change, ServerEntry entry ) throws Exception
868        {
869            // if change size is 0 then all values are deleted then we're in trouble
870            if ( change.size() == 0 )
871            {
872                return true;
873            }
874    
875            // can't do math to figure our if all values are removed since some
876            // values in the modify request may not be in the entry.  we need to
877            // remove the values from a cloned version of the attribute and see
878            // if nothing is left.
879            EntryAttribute changedEntryAttr = entry.get( change.getUpId() ).clone();
880    
881            for ( Value<?> value : change )
882            {
883                changedEntryAttr.remove( value );
884            }
885    
886            return changedEntryAttr.size() == 0;
887        }
888    
889    
890        /**
891         * 
892         * @param modOp
893         * @param changes
894         * @param existing
895         * @return
896         * @throws Exception
897         */
898        private EntryAttribute getResultantObjectClasses( ModificationOperation modOp, EntryAttribute changes,
899            EntryAttribute existing ) throws Exception
900        {
901            if ( ( changes == null ) && ( existing == null ) )
902            {
903                return new DefaultServerAttribute( SchemaConstants.OBJECT_CLASS_AT, OBJECT_CLASS );
904            }
905    
906            if ( changes == null )
907            {
908                return existing;
909            }
910    
911            if ( ( existing == null ) && ( modOp == ModificationOperation.ADD_ATTRIBUTE ) )
912            {
913                return changes;
914            }
915            else if ( existing == null )
916            {
917                return new DefaultServerAttribute( SchemaConstants.OBJECT_CLASS_AT, OBJECT_CLASS );
918            }
919    
920            switch ( modOp )
921            {
922                case ADD_ATTRIBUTE:
923                    for ( Value<?> value : changes )
924                    {
925                        existing.add( value );
926                    }
927    
928                    return existing;
929    
930                case REPLACE_ATTRIBUTE:
931                    return changes.clone();
932    
933                case REMOVE_ATTRIBUTE:
934                    for ( Value<?> value : changes )
935                    {
936                        existing.remove( value );
937                    }
938    
939                    return existing;
940    
941                default:
942                    throw new InternalError( "" );
943            }
944        }
945    
946    
947        private boolean getObjectClasses( EntryAttribute objectClasses, List<ObjectClass> result ) throws Exception
948        {
949            Set<String> ocSeen = new HashSet<String>();
950    
951            // We must select all the ObjectClasses, except 'top',
952            // but including all the inherited ObjectClasses
953            boolean hasExtensibleObject = false;
954    
955            for ( Value<?> objectClass : objectClasses )
956            {
957                String objectClassName = objectClass.getString();
958    
959                if ( SchemaConstants.TOP_OC.equals( objectClassName ) )
960                {
961                    continue;
962                }
963    
964                if ( SchemaConstants.EXTENSIBLE_OBJECT_OC.equalsIgnoreCase( objectClassName ) )
965                {
966                    hasExtensibleObject = true;
967                }
968    
969                ObjectClass oc = schemaManager.getObjectClassRegistry().lookup( objectClassName );
970    
971                // Add all unseen objectClasses to the list, except 'top'
972                if ( !ocSeen.contains( oc.getOid() ) )
973                {
974                    ocSeen.add( oc.getOid() );
975                    result.add( oc );
976                }
977    
978                // Find all current OC parents
979                getSuperiors( oc, ocSeen, result );
980            }
981    
982            return hasExtensibleObject;
983        }
984    
985    
986        private Set<String> getAllMust( EntryAttribute objectClasses ) throws Exception
987        {
988            Set<String> must = new HashSet<String>();
989    
990            // Loop on all objectclasses
991            for ( Value<?> value : objectClasses )
992            {
993                String ocName = value.getString();
994                ObjectClass oc = schemaManager.getObjectClassRegistry().lookup( ocName );
995    
996                List<AttributeType> types = oc.getMustAttributeTypes();
997    
998                // For each objectClass, loop on all MUST attributeTypes, if any
999                if ( ( types != null ) && ( types.size() > 0 ) )
1000                {
1001                    for ( AttributeType type : types )
1002                    {
1003                        must.add( type.getOid() );
1004                    }
1005                }
1006            }
1007    
1008            return must;
1009        }
1010    
1011    
1012        private Set<String> getAllAllowed( EntryAttribute objectClasses, Set<String> must ) throws Exception
1013        {
1014            Set<String> allowed = new HashSet<String>( must );
1015    
1016            // Add the 'ObjectClass' attribute ID
1017            allowed.add( schemaManager.getAttributeTypeRegistry().getOidByName( SchemaConstants.OBJECT_CLASS_AT ) );
1018    
1019            // Loop on all objectclasses
1020            for ( Value<?> objectClass : objectClasses )
1021            {
1022                String ocName = objectClass.getString();
1023                ObjectClass oc = schemaManager.getObjectClassRegistry().lookup( ocName );
1024    
1025                List<AttributeType> types = oc.getMayAttributeTypes();
1026    
1027                // For each objectClass, loop on all MAY attributeTypes, if any
1028                if ( ( types != null ) && ( types.size() > 0 ) )
1029                {
1030                    for ( AttributeType type : types )
1031                    {
1032                        String oid = type.getOid();
1033    
1034                        allowed.add( oid );
1035                    }
1036                }
1037            }
1038    
1039            return allowed;
1040        }
1041    
1042    
1043        /**
1044         * Given the objectClasses for an entry, this method adds missing ancestors 
1045         * in the hierarchy except for top which it removes.  This is used for this
1046         * solution to DIREVE-276.  More information about this solution can be found
1047         * <a href="http://docs.safehaus.org:8080/x/kBE">here</a>.
1048         * 
1049         * @param objectClassAttr the objectClass attribute to modify
1050         * @throws Exception if there are problems 
1051         */
1052        private void alterObjectClasses( EntryAttribute objectClassAttr ) throws Exception
1053        {
1054            Set<String> objectClasses = new HashSet<String>();
1055            Set<String> objectClassesUP = new HashSet<String>();
1056    
1057            // Init the objectClass list with 'top'
1058            objectClasses.add( SchemaConstants.TOP_OC );
1059            objectClassesUP.add( SchemaConstants.TOP_OC );
1060    
1061            // Construct the new list of ObjectClasses
1062            for ( Value<?> ocValue : objectClassAttr )
1063            {
1064                String ocName = ocValue.getString();
1065    
1066                if ( !ocName.equalsIgnoreCase( SchemaConstants.TOP_OC ) )
1067                {
1068                    String ocLowerName = ocName.toLowerCase();
1069    
1070                    ObjectClass objectClass = schemaManager.getObjectClassRegistry().lookup( ocLowerName );
1071    
1072                    if ( !objectClasses.contains( ocLowerName ) )
1073                    {
1074                        objectClasses.add( ocLowerName );
1075                        objectClassesUP.add( ocName );
1076                    }
1077    
1078                    List<ObjectClass> ocSuperiors = superiors.get( objectClass.getOid() );
1079    
1080                    if ( ocSuperiors != null )
1081                    {
1082                        for ( ObjectClass oc : ocSuperiors )
1083                        {
1084                            if ( !objectClasses.contains( oc.getName().toLowerCase() ) )
1085                            {
1086                                objectClasses.add( oc.getName() );
1087                                objectClassesUP.add( oc.getName() );
1088                            }
1089                        }
1090                    }
1091                }
1092            }
1093    
1094            // Now, reset the ObjectClass attribute and put the new list into it
1095            objectClassAttr.clear();
1096    
1097            for ( String attribute : objectClassesUP )
1098            {
1099                objectClassAttr.add( attribute );
1100            }
1101        }
1102    
1103    
1104        public void rename( NextInterceptor next, RenameOperationContext opContext ) throws Exception
1105        {
1106            DN oldDn = opContext.getDn();
1107            RDN newRdn = opContext.getNewRdn();
1108            boolean deleteOldRn = opContext.getDelOldDn();
1109            ServerEntry entry =  (ServerEntry)opContext.getEntry().getClonedEntry();
1110    
1111            /*
1112             *  Note: This is only a consistency checks, to the ensure that all
1113             *  mandatory attributes are available after deleting the old RDN.
1114             *  The real modification is done in the XdbmStore class.
1115             *  - TODO: this check is missing in the moveAndRename() method
1116             */
1117            if ( deleteOldRn )
1118            {
1119                ServerEntry tmpEntry = ( ServerEntry ) entry.clone();
1120                RDN oldRDN = oldDn.getRdn();
1121    
1122                // Delete the old RDN means we remove some attributes and values.
1123                // We must make sure that after this operation all must attributes
1124                // are still present in the entry.
1125                for ( AVA atav : oldRDN )
1126                {
1127                    AttributeType type = schemaManager.lookupAttributeTypeRegistry( atav.getUpType() );
1128                    tmpEntry.remove( type, atav.getUpValue() );
1129                }
1130                
1131                for ( AVA atav : newRdn )
1132                {
1133                    AttributeType type = schemaManager.lookupAttributeTypeRegistry( atav.getUpType() );
1134    
1135                    if ( !tmpEntry.contains( type, atav.getNormValue() ) )
1136                    {
1137                        tmpEntry.add( new DefaultServerAttribute( type, atav.getUpValue() ) );
1138                    }
1139                }
1140    
1141                // Substitute the RDN and check if the new entry is correct
1142                tmpEntry.setDn( opContext.getNewDn() );
1143    
1144                check( opContext.getNewDn(), tmpEntry );
1145    
1146                // Check that no operational attributes are removed
1147                for ( AVA atav : oldRDN )
1148                {
1149                    AttributeType attributeType = schemaManager.lookupAttributeTypeRegistry( atav.getUpType() );
1150    
1151                    if ( !attributeType.isUserModifiable() )
1152                    {
1153                        throw new LdapNoPermissionException( "Cannot modify the attribute '" + atav.getUpType() + "'" );
1154                    }
1155                }
1156            }
1157    
1158            next.rename( opContext );
1159        }
1160    
1161    
1162        /**
1163         * Create a new attribute using the given values
1164         */
1165        private EntryAttribute createNewAttribute( EntryAttribute attribute )
1166        {
1167            AttributeType attributeType = attribute.getAttributeType();
1168            
1169            // Create the new Attribute
1170            EntryAttribute newAttribute = new DefaultServerAttribute( attribute.getUpId(), attributeType );
1171    
1172            for ( Value<?> value : attribute )
1173            {
1174                newAttribute.add( value );
1175            }
1176    
1177            return newAttribute;
1178        }
1179        
1180        
1181        /**
1182         * Modify an entry, applying the given modifications, and check if it's OK
1183         */
1184        private void checkModifyEntry( DN dn, ServerEntry currentEntry, List<Modification> mods ) throws Exception
1185        {
1186            // The first step is to check that the modifications are valid :
1187            // - the ATs are present in the schema
1188            // - The value is syntaxically correct
1189            //
1190            // While doing that, we will apply the modification to a copy of the current entry
1191            ServerEntry tempEntry = (ServerEntry)currentEntry.clone();
1192            
1193            // Now, apply each mod one by one
1194            for ( Modification mod:mods )
1195            {
1196                EntryAttribute attribute = mod.getAttribute();
1197                AttributeType attributeType = attribute.getAttributeType();
1198                
1199                // We don't allow modification of operational attributes
1200                if ( !attributeType.isUserModifiable() )
1201                {
1202                    if ( !attributeType.equals( MODIFIERS_NAME_ATTRIBUTE_TYPE ) &&
1203                         !attributeType.equals( MODIFY_TIMESTAMP_ATTRIBUTE_TYPE ) )
1204                    {
1205                        String msg = I18n.err( I18n.ERR_52, attributeType );
1206                        LOG.error( msg );
1207                        throw new LdapNoPermissionException( msg );
1208                    }
1209                }
1210                
1211                switch ( mod.getOperation() )
1212                {
1213                    case ADD_ATTRIBUTE :
1214                        // Check the syntax here
1215                        if ( !attribute.isValid() )
1216                        {
1217                            // The value syntax is incorrect : this is an error
1218                            String msg = I18n.err( I18n.ERR_53, attributeType );
1219                            LOG.error( msg );
1220                            throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, 
1221                                msg );
1222                        }
1223    
1224                        EntryAttribute currentAttribute = tempEntry.get( attributeType );
1225                        
1226                        // First check if the added Attribute is already present in the entry
1227                        // If not, we have to create the entry
1228                        if ( currentAttribute != null )
1229                        {
1230                            for ( Value<?> value : attribute )
1231                            {
1232                                // At this point, we know that the attribute's syntax is correct
1233                                // We just have to check that the current attribute does not 
1234                                // contains the value already
1235                                if ( currentAttribute.contains( value ))
1236                                {
1237                                    // This is an error. 
1238                                    String msg = I18n.err( I18n.ERR_54, value );
1239                                    LOG.error( msg );
1240                                    throw new LdapAttributeInUseException( msg );
1241                                }
1242                                
1243                                currentAttribute.add( value );
1244                            }
1245                        }
1246                        else
1247                        {
1248                            // We don't check if the attribute is not in the MUST or MAY at this
1249                            // point, as one of the following modification can change the 
1250                            // ObjectClasses.
1251                            EntryAttribute newAttribute = createNewAttribute( attribute );
1252    
1253                            tempEntry.put( newAttribute );
1254                        }
1255                        
1256                        break;
1257                        
1258                    case REMOVE_ATTRIBUTE :
1259                        // First check that the removed attribute exists
1260                        if ( !tempEntry.containsAttribute( attributeType ) )
1261                        {
1262                            String msg = I18n.err( I18n.ERR_55, attributeType );
1263                            LOG.error( msg );
1264                            throw new LdapNoSuchAttributeException( msg );
1265                        }
1266    
1267                        // We may have to remove the attribute or only some values
1268                        if ( attribute.size() == 0 )
1269                        {
1270                            // No value : we have to remove the entire attribute
1271                            tempEntry.removeAttributes( attributeType );
1272                        }
1273                        else
1274                        {
1275                            currentAttribute = tempEntry.get( attributeType );
1276                            
1277                            // Now remove all the values
1278                            for ( Value<?> value:attribute )
1279                            {
1280                                // We can only remove existing values.
1281                                if ( currentAttribute.contains( value ) )
1282                                {
1283                                    currentAttribute.remove( value );
1284                                }
1285                                else
1286                                {
1287                                    String msg = I18n.err( I18n.ERR_56, attributeType );
1288                                    LOG.error( msg );
1289                                    throw new LdapNoSuchAttributeException( msg );
1290                                }
1291                            }
1292    
1293                            
1294                            // If the current attribute is empty, we have to remove
1295                            // it from the entry
1296                            if ( currentAttribute.size() == 0 )
1297                            {
1298                                tempEntry.removeAttributes( attributeType );
1299                            }
1300                        }
1301    
1302                        break;
1303                    
1304                    case REPLACE_ATTRIBUTE :
1305                        // The replaced attribute might not exist, it will then be a Add
1306                        // If there is no value, then the attribute will be removed
1307                        if ( !tempEntry.containsAttribute( attributeType ) )
1308                        {
1309                            if ( attribute.size() == 0 )
1310                            {
1311                                // Ignore the modification, as the attributeType does not
1312                                // exists in the entry
1313                                break;
1314                            }
1315                            else
1316                            {
1317                                // Create the new Attribute
1318                                EntryAttribute newAttribute = createNewAttribute( attribute );
1319    
1320                                tempEntry.put( newAttribute );
1321                            }
1322                        }
1323                        else
1324                        {
1325                            if ( attribute.size() == 0 )
1326                            {
1327                                // Remove the attribute from the entry
1328                                tempEntry.removeAttributes( attributeType );
1329                            }
1330                            else
1331                            {
1332                                // Replace the existing values with the new values
1333                                // This is done by removing the Attribute
1334                                tempEntry.removeAttributes( attributeType );
1335    
1336                                // Create the new Attribute
1337                                EntryAttribute newAttribute = createNewAttribute( attribute );
1338    
1339                                tempEntry.put( newAttribute );
1340                            }
1341                        }
1342                        
1343                        break;
1344                }
1345            }
1346            
1347            // Ok, we have created the modified entry. We now have to check that it's a valid 
1348            // entry wrt the schema.
1349            // We have to check that :
1350            // - the rdn values are present in the entry
1351            // - the objectClasses inheritence is correct
1352            // - all the MUST are present
1353            // - all the attribute are in MUST and MAY, except fo the extensibleObeject OC
1354            // is present
1355            // - We haven't removed a part of the RDN
1356            check( dn, tempEntry );
1357        }
1358    
1359        
1360        /**
1361         * {@inheritDoc}
1362         */
1363        public void modify( NextInterceptor next, ModifyOperationContext opContext ) throws Exception
1364        {
1365            // A modification on a simple entry will be done in three steps :
1366            // - get the original entry (it should already been in the context)
1367            // - apply the modification on it
1368            // - check that the entry is still correct
1369            // - add the operational attributes (modifiersName/modifyTimeStamp)
1370            // - store the modified entry on the backend.
1371            //
1372            // A modification done on the schema is a bit different, as there is two more
1373            // steps
1374            // - We have to update the registries
1375            // - We have to modify the ou=schemaModifications entry
1376            //
1377            
1378            // First, check that the entry is either a subschemaSubentry or a schema element.
1379            // This is the case if it's a child of cn=schema or ou=schema
1380            DN dn = opContext.getDn();
1381            
1382            // Gets the stored entry on which the modification must be applied
1383            if ( dn.equals( subschemaSubentryDn ) )
1384            {
1385                LOG.debug( "Modification attempt on schema subentry {}: \n{}", dn, opContext );
1386                
1387                // We can get rid of the modifiersName and modifyTimestamp, they are useless.
1388                List<Modification> mods = opContext.getModItems();
1389                List<Modification> cleanMods = new ArrayList<Modification>(); 
1390                
1391                for ( Modification mod:mods )
1392                {
1393                    AttributeType at = ( (ServerModification)mod).getAttribute().getAttributeType();
1394                    
1395                    if ( !MODIFIERS_NAME_ATTRIBUTE_TYPE.equals( at ) && !MODIFY_TIMESTAMP_ATTRIBUTE_TYPE.equals( at ) ) 
1396                    {
1397                        cleanMods.add( mod );
1398                    }
1399                }
1400                
1401                opContext.setModItems( cleanMods );
1402                
1403                // Now that the entry has been modified, update the SSSE
1404                schemaSubEntryManager.modifySchemaSubentry(  opContext, opContext.hasRequestControl( CascadeControl.CONTROL_OID ) );
1405                
1406                return;
1407            }
1408    
1409            ServerEntry entry = opContext.getEntry();
1410            List<Modification> modifications = opContext.getModItems();
1411            checkModifyEntry( dn, entry, modifications );
1412            
1413            next.modify( opContext );
1414        }
1415        
1416    
1417        /**
1418         * Filter the attributes by removing the ones which are not allowed
1419         */
1420        private void filterAttributeTypes( SearchingOperationContext operation, ClonedServerEntry result )
1421        {
1422            if ( operation.getReturningAttributes() == null )
1423            {
1424                return;
1425            }
1426    
1427            for ( AttributeTypeOptions attrOptions : operation.getReturningAttributes() )
1428            {
1429                EntryAttribute attribute = result.get( attrOptions.getAttributeType() );
1430    
1431                if ( attrOptions.hasOption() )
1432                {
1433                    for ( String option : attrOptions.getOptions() )
1434                    {
1435                        if ( "binary".equalsIgnoreCase( option ) )
1436                        {
1437                            continue;
1438                        }
1439                        else
1440                        {
1441                            try
1442                            {
1443                                if ( result.contains( attribute ) )
1444                                {
1445                                    result.remove( attribute );
1446                                }
1447                            }
1448                            catch ( LdapException ne )
1449                            {
1450                                // Do nothings
1451                            }
1452                            break;
1453                        }
1454                    }
1455    
1456                }
1457            }
1458        }
1459    
1460    
1461        private void filterObjectClass( ServerEntry entry ) throws Exception
1462        {
1463            List<ObjectClass> objectClasses = new ArrayList<ObjectClass>();
1464            EntryAttribute oc = entry.get( SchemaConstants.OBJECT_CLASS_AT );
1465    
1466            if ( oc != null )
1467            {
1468                getObjectClasses( oc, objectClasses );
1469    
1470                entry.removeAttributes( SchemaConstants.OBJECT_CLASS_AT );
1471    
1472                EntryAttribute newOc = new DefaultServerAttribute( oc.getAttributeType() );
1473    
1474                for ( ObjectClass currentOC : objectClasses )
1475                {
1476                    newOc.add( currentOC.getName() );
1477                }
1478    
1479                newOc.add( SchemaConstants.TOP_OC );
1480                entry.put( newOc );
1481            }
1482        }
1483    
1484    
1485        private void filterBinaryAttributes( ServerEntry entry ) throws Exception
1486        {
1487            /*
1488             * start converting values of attributes to byte[]s which are not
1489             * human readable and those that are in the binaries set
1490             */
1491            for ( EntryAttribute attribute : entry )
1492            {
1493                if ( !attribute.getAttributeType().getSyntax().isHumanReadable() )
1494                {
1495                    List<Value<?>> binaries = new ArrayList<Value<?>>();
1496    
1497                    for ( Value<?> value : attribute )
1498                    {
1499                        binaries.add( new BinaryValue( attribute.getAttributeType(),
1500                            value.getBytes() ) );
1501                    }
1502    
1503                    attribute.clear();
1504                    attribute.put( binaries );
1505                }
1506            }
1507        }
1508    
1509        /**
1510         * A special filter over entry attributes which replaces Attribute String values with their respective byte[]
1511         * representations using schema information and the value held in the JNDI environment property:
1512         * <code>java.naming.ldap.attributes.binary</code>.
1513         *
1514         * @see <a href= "http://java.sun.com/j2se/1.4.2/docs/guide/jndi/jndi-ldap-gl.html#binary">
1515         *      java.naming.ldap.attributes.binary</a>
1516         */
1517        private class BinaryAttributeFilter implements EntryFilter
1518        {
1519            public boolean accept( SearchingOperationContext operation, ClonedServerEntry result ) throws Exception
1520            {
1521                filterBinaryAttributes( result );
1522                return true;
1523            }
1524        }
1525    
1526        /**
1527         * Filters objectClass attribute to inject top when not present.
1528         */
1529        private class TopFilter implements EntryFilter
1530        {
1531            public boolean accept( SearchingOperationContext operation, ClonedServerEntry result ) throws Exception
1532            {
1533                filterObjectClass( result );
1534                filterAttributeTypes( operation, result );
1535                return true;
1536            }
1537        }
1538    
1539    
1540        /**
1541         * Check that all the attributes exist in the schema for this entry.
1542         * 
1543         * We also check the syntaxes
1544         */
1545        private void check( DN dn, ServerEntry entry ) throws Exception
1546        {
1547            // ---------------------------------------------------------------
1548            // First, make sure all attributes are valid schema defined attributes
1549            // ---------------------------------------------------------------
1550    
1551            for ( AttributeType attributeType : entry.getAttributeTypes() )
1552            {
1553                if ( !schemaManager.getAttributeTypeRegistry().contains( attributeType.getName() ) )
1554                {
1555                    throw new LdapInvalidAttributeTypeException( I18n.err( I18n.ERR_275, 
1556                        attributeType.getName() ) );
1557                }
1558            }
1559    
1560            // We will check some elements :
1561            // 1) the entry must have all the MUST attributes of all its ObjectClass
1562            // 2) The SingleValued attributes must be SingleValued
1563            // 3) No attributes should be used if they are not part of MUST and MAY
1564            // 3-1) Except if the extensibleObject ObjectClass is used
1565            // 3-2) or if the AttributeType is COLLECTIVE
1566            // 4) We also check that for H-R attributes, we have a valid String in the values
1567            EntryAttribute objectClassAttr = entry.get( SchemaConstants.OBJECT_CLASS_AT );
1568    
1569            // Protect the server against a null objectClassAttr
1570            // It can be the case if the user forgot to add it to the entry ...
1571            // In this case, we create an new one, empty
1572            if ( objectClassAttr == null )
1573            {
1574                objectClassAttr = new DefaultServerAttribute( SchemaConstants.OBJECT_CLASS_AT, OBJECT_CLASS );
1575            }
1576    
1577            List<ObjectClass> ocs = new ArrayList<ObjectClass>();
1578    
1579            alterObjectClasses( objectClassAttr );
1580    
1581            // Now we can process the MUST and MAY attributes
1582            Set<String> must = getAllMust( objectClassAttr );
1583            Set<String> allowed = getAllAllowed( objectClassAttr, must );
1584    
1585            boolean hasExtensibleObject = getObjectClasses( objectClassAttr, ocs );
1586    
1587            // As we now have all the ObjectClasses updated, we have
1588            // to check that we don't have conflicting ObjectClasses
1589            assertObjectClasses( dn, ocs );
1590    
1591            assertRequiredAttributesPresent( dn, entry, must );
1592            assertNumberOfAttributeValuesValid( entry );
1593    
1594            if ( !hasExtensibleObject )
1595            {
1596                assertAllAttributesAllowed( dn, entry, allowed );
1597            }
1598    
1599            // Check the attributes values and transform them to String if necessary
1600            assertHumanReadable( entry );
1601    
1602            // Now check the syntaxes
1603            assertSyntaxes( entry );
1604            
1605            assertRdn ( dn, entry );
1606        }
1607    
1608    
1609        private void checkOcSuperior( ServerEntry entry ) throws Exception
1610        {
1611            // handle the m-supObjectClass meta attribute
1612            EntryAttribute supOC = entry.get( MetaSchemaConstants.M_SUP_OBJECT_CLASS_AT );
1613            
1614            if ( supOC != null )
1615            {
1616                ObjectClassTypeEnum ocType = ObjectClassTypeEnum.STRUCTURAL;
1617                
1618                if ( entry.get( MetaSchemaConstants.M_TYPE_OBJECT_CLASS_AT ) != null )
1619                {
1620                    String type = entry.get( MetaSchemaConstants.M_TYPE_OBJECT_CLASS_AT ).getString();
1621                    ocType = ObjectClassTypeEnum.getClassType( type );
1622                }
1623                
1624                // First check that the inheritence scheme is correct.
1625                // 1) If the ocType is ABSTRACT, it should not have any other SUP not ABSTRACT
1626                for ( Value<?> sup:supOC )
1627                {
1628                    try
1629                    {
1630                        String supName = sup.getString();
1631                        
1632                        ObjectClass superior = schemaManager.getObjectClassRegistry().lookup( supName );
1633    
1634                        switch ( ocType )
1635                        {
1636                            case ABSTRACT :
1637                                if ( !superior.isAbstract() )
1638                                {
1639                                    String message = I18n.err( I18n.ERR_57 );
1640                                    LOG.error( message );
1641                                    throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION, message );
1642                                }
1643                                
1644                                break;
1645        
1646                            case AUXILIARY :
1647                                if ( !superior.isAbstract() && ! superior.isAuxiliary() )
1648                                {
1649                                    String message = I18n.err( I18n.ERR_58 );
1650                                    LOG.error( message );
1651                                    throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION, message );
1652                                }
1653                                
1654                                break;
1655    
1656                            case STRUCTURAL :
1657                                break;
1658                        }
1659                    }
1660                    catch ( LdapException ne )
1661                    {
1662                        // The superior OC does not exist : this is an error
1663                        String message = I18n.err( I18n.ERR_59 );
1664                        LOG.error( message );
1665                        throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION, message );
1666                    }
1667                }
1668            }
1669        }
1670    
1671    
1672        /**
1673         * Check that all the attributes exist in the schema for this entry.
1674         */
1675        public void add( NextInterceptor next, AddOperationContext addContext ) throws Exception
1676        {
1677            DN name = addContext.getDn();
1678            ServerEntry entry = addContext.getEntry();
1679    
1680            check( name, entry );
1681    
1682            // Special checks for the MetaSchema branch
1683            if ( name.isChildOf( schemaBaseDN ) )
1684            {
1685                // get the schema name
1686                String schemaName = getSchemaName( name );
1687    
1688                if ( entry.contains( SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.META_SCHEMA_OC ) )
1689                {
1690                    next.add( addContext );
1691    
1692                    if ( schemaManager.isSchemaLoaded( schemaName ) )
1693                    {
1694                        // Update the OC superiors for each added ObjectClass
1695                        computeSuperiors();
1696                    }
1697                }
1698                else if ( entry.contains( SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.META_OBJECT_CLASS_OC ) )
1699                {
1700                    // This is an ObjectClass addition
1701                    checkOcSuperior( addContext.getEntry() );
1702    
1703                    next.add( addContext );
1704    
1705                    // Update the structures now that the schema element has been added
1706                    Schema schema = schemaManager.getLoadedSchema( schemaName );
1707                    
1708                    if ( ( schema != null ) && schema.isEnabled() )
1709                    {
1710                        String ocName = entry.get( MetaSchemaConstants.M_NAME_AT ).getString();
1711                        ObjectClass addedOC = schemaManager.getObjectClassRegistry().lookup( ocName );
1712                        computeSuperior( addedOC );
1713                    }
1714                }
1715                else if ( entry.contains( SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.META_ATTRIBUTE_TYPE_OC ) )
1716                {
1717                    
1718                    // This is an AttributeType addition
1719                    next.add( addContext );
1720                }
1721                else
1722                {
1723                    next.add( addContext );
1724                }
1725                
1726            }
1727            else
1728            {
1729                next.add( addContext );
1730            }
1731        }
1732        
1733        
1734        private String getSchemaName( DN dn ) throws LdapException
1735        {
1736            if ( dn.size() < 2 )
1737            {
1738                throw new LdapException( I18n.err( I18n.ERR_276 ) );
1739            }
1740            
1741            RDN rdn = dn.getRdn( 1 );
1742            return ( String ) rdn.getNormValue();
1743        }
1744    
1745    
1746        /**
1747         * Checks to see if an attribute is required by as determined from an entry's
1748         * set of objectClass attribute values.
1749         *
1750         * @return true if the objectClass values require the attribute, false otherwise
1751         * @throws Exception if the attribute is not recognized
1752         */
1753        private void assertAllAttributesAllowed( DN dn, ServerEntry entry, Set<String> allowed ) throws Exception
1754        {
1755            // Never check the attributes if the extensibleObject objectClass is
1756            // declared for this entry
1757            EntryAttribute objectClass = entry.get( SchemaConstants.OBJECT_CLASS_AT );
1758    
1759            if ( objectClass.contains( SchemaConstants.EXTENSIBLE_OBJECT_OC ) )
1760            {
1761                return;
1762            }
1763    
1764            for ( EntryAttribute attribute : entry )
1765            {
1766                String attrOid = attribute.getAttributeType().getOid();
1767    
1768                AttributeType attributeType = attribute.getAttributeType();
1769    
1770                if ( !attributeType.isCollective() && ( attributeType.getUsage() == UsageEnum.USER_APPLICATIONS ) )
1771                {
1772                    if ( !allowed.contains( attrOid ) )
1773                    {
1774                        throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION, I18n.err( I18n.ERR_277, attribute.getUpId(),
1775                                dn.getName() ) );
1776                    }
1777                }
1778            }
1779        }
1780    
1781    
1782        /**
1783         * Checks to see number of values of an attribute conforms to the schema
1784         */
1785        private void assertNumberOfAttributeValuesValid( Entry entry ) throws LdapInvalidAttributeValueException
1786        {
1787            for ( EntryAttribute attribute : entry )
1788            {
1789                assertNumberOfAttributeValuesValid( attribute );
1790            }
1791        }
1792    
1793    
1794        /**
1795         * Checks to see numbers of values of attributes conforms to the schema
1796         */
1797        private void assertNumberOfAttributeValuesValid( EntryAttribute attribute ) throws LdapInvalidAttributeValueException
1798        {
1799            if ( attribute.size() > 1 && attribute.getAttributeType().isSingleValued() )
1800            {
1801                throw new LdapInvalidAttributeValueException( ResultCodeEnum.CONSTRAINT_VIOLATION,
1802                    I18n.err( I18n.ERR_278, attribute.getUpId() ) );
1803            }
1804        }
1805    
1806    
1807        /**
1808         * Checks to see the presence of all required attributes within an entry.
1809         */
1810        private void assertRequiredAttributesPresent( DN dn, Entry entry, Set<String> must ) throws Exception
1811        {
1812            for ( EntryAttribute attribute : entry )
1813            {
1814                must.remove( attribute.getAttributeType().getOid() );
1815            }
1816    
1817            if ( must.size() != 0 )
1818            {
1819                throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION,
1820                    I18n.err( I18n.ERR_279, must, dn.getName() ) );
1821            }
1822        }
1823    
1824    
1825        /**
1826         * Checck that OC does not conflict :
1827         * - we can't have more than one STRUCTURAL OC unless they are in the same
1828         * inheritance tree
1829         * - we must have at least one STRUCTURAL OC
1830         */
1831        private void assertObjectClasses( DN dn, List<ObjectClass> ocs ) throws Exception
1832        {
1833            Set<ObjectClass> structuralObjectClasses = new HashSet<ObjectClass>();
1834    
1835            /*
1836             * Since the number of ocs present in an entry is small it's not 
1837             * so expensive to take two passes while determining correctness
1838             * since it will result in clear simple code instead of a deep nasty
1839             * for loop with nested loops.  Plus after the first pass we can
1840             * quickly know if there are no structural object classes at all.
1841             */
1842    
1843            // --------------------------------------------------------------------
1844            // Extract all structural objectClasses within the entry
1845            // --------------------------------------------------------------------
1846            for ( ObjectClass oc : ocs )
1847            {
1848                if ( oc.isStructural() )
1849                {
1850                    structuralObjectClasses.add( oc );
1851                }
1852            }
1853    
1854            // --------------------------------------------------------------------
1855            // Throw an error if no STRUCTURAL objectClass are found.
1856            // --------------------------------------------------------------------
1857    
1858            if ( structuralObjectClasses.isEmpty() )
1859            {
1860                String message = I18n.err( I18n.ERR_60, dn );
1861                LOG.error( message );
1862                throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION, message );
1863            }
1864    
1865            // --------------------------------------------------------------------
1866            // Put all structural object classes into new remaining container and
1867            // start removing any which are superiors of others in the set.  What
1868            // is left in the remaining set will be unrelated structural 
1869            /// objectClasses.  If there is more than one then we have a problem.
1870            // --------------------------------------------------------------------
1871    
1872            Set<ObjectClass> remaining = new HashSet<ObjectClass>( structuralObjectClasses.size() );
1873            remaining.addAll( structuralObjectClasses );
1874            
1875            for ( ObjectClass oc : structuralObjectClasses )
1876            {
1877                if ( oc.getSuperiors() != null )
1878                {
1879                    for ( ObjectClass superClass : oc.getSuperiors() )
1880                    {
1881                        if ( superClass.isStructural() )
1882                        {
1883                            remaining.remove( superClass );
1884                        }
1885                    }
1886                }
1887            }
1888    
1889            // Like the highlander there can only be one :).
1890            if ( remaining.size() > 1 )
1891            {
1892                String message = I18n.err( I18n.ERR_61, dn, remaining );
1893                LOG.error( message );
1894                throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION, message );
1895            }
1896        }
1897    
1898    
1899        /**
1900         * Check the entry attributes syntax, using the syntaxCheckers
1901         */
1902        private void assertSyntaxes( Entry entry ) throws Exception
1903        {
1904            // First, loop on all attributes
1905            for ( EntryAttribute attribute : entry )
1906            {
1907                AttributeType attributeType = attribute.getAttributeType();
1908                SyntaxChecker syntaxChecker = attributeType.getSyntax().getSyntaxChecker();
1909    
1910                if ( syntaxChecker instanceof OctetStringSyntaxChecker )
1911                {
1912                    // This is a speedup : no need to check the syntax of any value
1913                    // if all the syntaxes are accepted...
1914                    continue;
1915                }
1916    
1917                // Then loop on all values
1918                for ( Value<?> value : attribute )
1919                {
1920                    if ( value.isValid() )
1921                    {
1922                        // No need to validate something which is already ok
1923                        continue;
1924                    }
1925                    
1926                    try
1927                    {
1928                        syntaxChecker.assertSyntax( value.get() );
1929                    }
1930                    catch ( Exception ne )
1931                    {
1932                        String message = I18n.err( I18n.ERR_280, value.getString(), attribute.getUpId() );
1933                        LOG.info( message );
1934    
1935                        throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, message );
1936                    }
1937                }
1938            }
1939        }
1940        
1941        
1942        private void assertRdn( DN dn, ServerEntry entry ) throws Exception
1943        {
1944            for ( AVA atav : dn.getRdn() )
1945            {
1946                EntryAttribute attribute = entry.get( atav.getNormType() );
1947                
1948                if ( ( attribute == null ) || ( !attribute.contains( atav.getNormValue() ) ) )
1949                {
1950                    String message = I18n.err( I18n.ERR_62, dn, atav.getUpType() );
1951                    LOG.error( message );
1952                    throw new LdapSchemaViolationException( ResultCodeEnum.NOT_ALLOWED_ON_RDN, message );
1953                }
1954            }
1955        }
1956    
1957    
1958        /**
1959         * Check a String attribute to see if there is some byte[] value in it.
1960         * 
1961         * If this is the case, try to change it to a String value.
1962         */
1963        private boolean checkHumanReadable( EntryAttribute attribute ) throws Exception
1964        {
1965            boolean isModified = false;
1966    
1967            // Loop on each values
1968            for ( Value<?> value : attribute )
1969            {
1970                if ( value instanceof StringValue )
1971                {
1972                    continue;
1973                }
1974                else if ( value instanceof BinaryValue )
1975                {
1976                    // we have a byte[] value. It should be a String UTF-8 encoded
1977                    // Let's transform it
1978                    try
1979                    {
1980                        String valStr = new String( value.getBytes(), "UTF-8" );
1981                        attribute.remove( value );
1982                        attribute.add( valStr );
1983                        isModified = true;
1984                    }
1985                    catch ( UnsupportedEncodingException uee )
1986                    {
1987                        throw new LdapException( I18n.err( I18n.ERR_281 ) );
1988                    }
1989                }
1990                else
1991                {
1992                    throw new LdapException( I18n.err( I18n.ERR_282 ) );
1993                }
1994            }
1995    
1996            return isModified;
1997        }
1998    
1999    
2000        /**
2001         * Check a binary attribute to see if there is some String value in it.
2002         * 
2003         * If this is the case, try to change it to a binary value.
2004         */
2005        private boolean checkNotHumanReadable( EntryAttribute attribute ) throws Exception
2006        {
2007            boolean isModified = false;
2008    
2009            // Loop on each values
2010            for ( Value<?> value : attribute )
2011            {
2012                if ( value instanceof BinaryValue )
2013                {
2014                    continue;
2015                }
2016                else if ( value instanceof StringValue )
2017                {
2018                    // We have a String value. It should be a byte[]
2019                    // Let's transform it
2020                    try
2021                    {
2022                        byte[] valBytes = value.getString().getBytes( "UTF-8" );
2023    
2024                        attribute.remove( value );
2025                        attribute.add( valBytes );
2026                        isModified = true;
2027                    }
2028                    catch ( UnsupportedEncodingException uee )
2029                    {
2030                        String message = I18n.err( I18n.ERR_63 );
2031                        LOG.error( message );
2032                        throw new LdapException( message );
2033                    }
2034                }
2035                else
2036                {
2037                    String message = I18n.err( I18n.ERR_64 );
2038                    LOG.error( message );
2039                    throw new LdapException( message );
2040                }
2041            }
2042    
2043            return isModified;
2044        }
2045    
2046    
2047        /**
2048         * Check that all the attribute's values which are Human Readable can be transformed
2049         * to valid String if they are stored as byte[], and that non Human Readable attributes
2050         * stored as String can be transformed to byte[]
2051         */
2052        private void assertHumanReadable( ServerEntry entry ) throws Exception
2053        {
2054            boolean isModified = false;
2055    
2056            ServerEntry clonedEntry = null;
2057    
2058            // Loops on all attributes
2059            for ( EntryAttribute attribute : entry )
2060            {
2061                AttributeType attributeType = attribute.getAttributeType();
2062    
2063                // If the attributeType is H-R, check all of its values
2064                if ( attributeType.getSyntax().isHumanReadable() )
2065                {
2066                    isModified = checkHumanReadable( attribute );
2067                }
2068                else
2069                {
2070                    isModified = checkNotHumanReadable( attribute );
2071                }
2072    
2073                // If we have a returned attribute, then we need to store it
2074                // into a new entry
2075                if ( isModified )
2076                {
2077                    if ( clonedEntry == null )
2078                    {
2079                        clonedEntry = ( ServerEntry ) entry.clone();
2080                    }
2081    
2082                    // Switch the attributes
2083                    clonedEntry.put( attribute );
2084    
2085                    isModified = false;
2086                }
2087            }
2088    
2089            if ( clonedEntry != null )
2090            {
2091                entry = clonedEntry;
2092            }
2093        }
2094    }