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