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