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