View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  package org.apache.directory.server.core.schema;
21  
22  
23  import java.util.ArrayList;
24  import java.util.HashMap;
25  import java.util.HashSet;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Set;
30  import java.util.concurrent.ConcurrentHashMap;
31  
32  import org.apache.commons.codec.Charsets;
33  import org.apache.directory.api.ldap.model.constants.MetaSchemaConstants;
34  import org.apache.directory.api.ldap.model.constants.SchemaConstants;
35  import org.apache.directory.api.ldap.model.cursor.EmptyCursor;
36  import org.apache.directory.api.ldap.model.cursor.SingletonCursor;
37  import org.apache.directory.api.ldap.model.entry.Attribute;
38  import org.apache.directory.api.ldap.model.entry.DefaultAttribute;
39  import org.apache.directory.api.ldap.model.entry.DefaultModification;
40  import org.apache.directory.api.ldap.model.entry.Entry;
41  import org.apache.directory.api.ldap.model.entry.Modification;
42  import org.apache.directory.api.ldap.model.entry.Value;
43  import org.apache.directory.api.ldap.model.exception.LdapAttributeInUseException;
44  import org.apache.directory.api.ldap.model.exception.LdapException;
45  import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeTypeException;
46  import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException;
47  import org.apache.directory.api.ldap.model.exception.LdapNoPermissionException;
48  import org.apache.directory.api.ldap.model.exception.LdapNoSuchAttributeException;
49  import org.apache.directory.api.ldap.model.exception.LdapSchemaViolationException;
50  import org.apache.directory.api.ldap.model.filter.ApproximateNode;
51  import org.apache.directory.api.ldap.model.filter.BranchNode;
52  import org.apache.directory.api.ldap.model.filter.EqualityNode;
53  import org.apache.directory.api.ldap.model.filter.ExprNode;
54  import org.apache.directory.api.ldap.model.filter.ExtensibleNode;
55  import org.apache.directory.api.ldap.model.filter.GreaterEqNode;
56  import org.apache.directory.api.ldap.model.filter.LessEqNode;
57  import org.apache.directory.api.ldap.model.filter.ObjectClassNode;
58  import org.apache.directory.api.ldap.model.filter.SimpleNode;
59  import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
60  import org.apache.directory.api.ldap.model.message.SearchScope;
61  import org.apache.directory.api.ldap.model.message.controls.Cascade;
62  import org.apache.directory.api.ldap.model.name.Ava;
63  import org.apache.directory.api.ldap.model.name.Dn;
64  import org.apache.directory.api.ldap.model.name.Rdn;
65  import org.apache.directory.api.ldap.model.schema.AttributeType;
66  import org.apache.directory.api.ldap.model.schema.ObjectClass;
67  import org.apache.directory.api.ldap.model.schema.ObjectClassTypeEnum;
68  import org.apache.directory.api.ldap.model.schema.SyntaxChecker;
69  import org.apache.directory.api.ldap.model.schema.UsageEnum;
70  import org.apache.directory.api.ldap.model.schema.registries.Schema;
71  import org.apache.directory.api.ldap.model.schema.syntaxCheckers.OctetStringSyntaxChecker;
72  import org.apache.directory.api.util.Strings;
73  import org.apache.directory.server.core.api.DirectoryService;
74  import org.apache.directory.server.core.api.InterceptorEnum;
75  import org.apache.directory.server.core.api.entry.ClonedServerEntry;
76  import org.apache.directory.server.core.api.entry.ServerEntryUtils;
77  import org.apache.directory.server.core.api.filtering.EntryFilteringCursorImpl;
78  import org.apache.directory.server.core.api.filtering.EntryFilter;
79  import org.apache.directory.server.core.api.filtering.EntryFilteringCursor;
80  import org.apache.directory.server.core.api.interceptor.BaseInterceptor;
81  import org.apache.directory.server.core.api.interceptor.context.AddOperationContext;
82  import org.apache.directory.server.core.api.interceptor.context.CompareOperationContext;
83  import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext;
84  import org.apache.directory.server.core.api.interceptor.context.ModDnAva;
85  import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext;
86  import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext;
87  import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext;
88  import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext;
89  import org.apache.directory.server.core.api.partition.PartitionNexus;
90  import org.apache.directory.server.core.shared.SchemaService;
91  import org.apache.directory.server.i18n.I18n;
92  import org.slf4j.Logger;
93  import org.slf4j.LoggerFactory;
94  
95  
96  /**
97   * An {@link org.apache.directory.server.core.api.interceptor.Interceptor} that manages and enforces schemas.
98   *
99   * TODO Better interceptor description required.
100  *
101  * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
102  */
103 public 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 }