View Javadoc

1   /*
2    *   Copyright 2004 The Apache Software Foundation
3    *
4    *   Licensed under the Apache License, Version 2.0 (the "License");
5    *   you may not use this file except in compliance with the License.
6    *   You may obtain a copy of the License at
7    *
8    *       http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *   Unless required by applicable law or agreed to in writing, software
11   *   distributed under the License is distributed on an "AS IS" BASIS,
12   *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *   See the License for the specific language governing permissions and
14   *   limitations under the License.
15   *
16   */
17  package org.apache.ldap.server.schema;
18  
19  
20  import java.util.Collections;
21  import java.util.HashSet;
22  import java.util.Iterator;
23  import java.util.Map;
24  import java.util.Set;
25  
26  import javax.naming.Name;
27  import javax.naming.NamingEnumeration;
28  import javax.naming.NamingException;
29  import javax.naming.directory.*;
30  
31  import org.apache.ldap.common.filter.ExprNode;
32  import org.apache.ldap.common.filter.PresenceNode;
33  import org.apache.ldap.common.filter.SimpleNode;
34  import org.apache.ldap.common.message.LockableAttributeImpl;
35  import org.apache.ldap.common.message.LockableAttributesImpl;
36  import org.apache.ldap.common.message.ResultCodeEnum;
37  import org.apache.ldap.common.name.LdapName;
38  import org.apache.ldap.common.schema.AttributeType;
39  import org.apache.ldap.common.schema.DITContentRule;
40  import org.apache.ldap.common.schema.DITStructureRule;
41  import org.apache.ldap.common.schema.MatchingRule;
42  import org.apache.ldap.common.schema.MatchingRuleUse;
43  import org.apache.ldap.common.schema.NameForm;
44  import org.apache.ldap.common.schema.ObjectClass;
45  import org.apache.ldap.common.schema.SchemaUtils;
46  import org.apache.ldap.common.schema.Syntax;
47  import org.apache.ldap.common.util.SingletonEnumeration;
48  import org.apache.ldap.common.util.DateUtils;
49  import org.apache.ldap.common.util.AttributeUtils;
50  import org.apache.ldap.common.exception.LdapSchemaViolationException;
51  import org.apache.ldap.common.exception.LdapInvalidAttributeIdentifierException;
52  import org.apache.ldap.common.exception.LdapNoSuchAttributeException;
53  import org.apache.ldap.server.DirectoryServiceConfiguration;
54  import org.apache.ldap.server.invocation.Invocation;
55  import org.apache.ldap.server.invocation.InvocationStack;
56  import org.apache.ldap.server.configuration.InterceptorConfiguration;
57  import org.apache.ldap.server.enumeration.SearchResultFilteringEnumeration;
58  import org.apache.ldap.server.enumeration.SearchResultFilter;
59  import org.apache.ldap.server.interceptor.BaseInterceptor;
60  import org.apache.ldap.server.interceptor.NextInterceptor;
61  import org.apache.ldap.server.partition.DirectoryPartitionNexus;
62  import org.slf4j.Logger;
63  import org.slf4j.LoggerFactory;
64  
65  
66  /***
67   * An {@link org.apache.ldap.server.interceptor.Interceptor} that manages and enforces schemas.
68   *
69   * @todo Better interceptor description required.
70   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
71   * @version $Rev: 326075 $, $Date: 2005-10-18 06:13:54 -0400 (Tue, 18 Oct 2005) $
72   */
73  public class SchemaService extends BaseInterceptor
74  {
75      private static final String[] EMPTY_STRING_ARRAY = new String[0];
76      private static final String BINARY_KEY = "java.naming.ldap.attributes.binary";
77  
78      /*** The LoggerFactory used by this Interceptor */
79      private static Logger log = LoggerFactory.getLogger( SchemaService.class );
80  
81      /***
82       * the root nexus to all database partitions
83       */
84      private DirectoryPartitionNexus nexus;
85  
86      /***
87       * a binary attribute tranforming filter: String -> byte[]
88       */
89      private BinaryAttributeFilter binaryAttributeFilter;
90  
91      /***
92       * the global schema object registries
93       */
94      private GlobalRegistries globalRegistries;
95  
96      /***
97       * subschemaSubentry attribute's value from Root DSE
98       */
99      private String subentryDn;
100 
101     /***
102      * The time when the server started up.
103      */
104     private String startUpTimeStamp;
105 
106     /***
107      * Creates a schema service interceptor.
108      */
109     public SchemaService()
110     {
111         startUpTimeStamp = DateUtils.getGeneralizedTime();
112     }
113 
114 
115     public void init( DirectoryServiceConfiguration factoryCfg, InterceptorConfiguration cfg ) throws NamingException
116     {
117         this.nexus = factoryCfg.getPartitionNexus();
118         this.globalRegistries = factoryCfg.getGlobalRegistries();
119         binaryAttributeFilter = new BinaryAttributeFilter();
120 
121         // stuff for dealing with subentries (garbage for now)
122         String subschemaSubentry = ( String ) nexus.getRootDSE().get( "subschemaSubentry" ).get();
123         subentryDn = new LdapName( subschemaSubentry ).toString().toLowerCase();
124     }
125 
126 
127     public void destroy()
128     {
129     }
130 
131 
132     public NamingEnumeration list( NextInterceptor nextInterceptor, Name base ) throws NamingException
133     {
134         NamingEnumeration e = nextInterceptor.list( base );
135         Invocation invocation = InvocationStack.getInstance().peek();
136         return new SearchResultFilteringEnumeration( e, new SearchControls(), invocation, binaryAttributeFilter );
137     }
138 
139 
140     public NamingEnumeration search( NextInterceptor nextInterceptor,
141                                      Name base, Map env, ExprNode filter,
142                                      SearchControls searchCtls ) throws NamingException
143     {
144         // check to make sure the DN searched for is a subentry
145         if ( !subentryDn.equals( base.toString() ) )
146         {
147             return nextInterceptor.search( base, env, filter, searchCtls );
148         }
149 
150         if ( searchCtls.getSearchScope() == SearchControls.OBJECT_SCOPE &&
151                 filter instanceof SimpleNode )
152         {
153             SimpleNode node = ( SimpleNode ) filter;
154 
155             if ( node.getAttribute().equalsIgnoreCase( "objectClass" ) &&
156                     node.getValue().equalsIgnoreCase( "subschema" ) &&
157                     node.getAssertionType() == SimpleNode.EQUALITY
158             )
159             {
160                 // call.setBypass( true );
161                 Attributes attrs = getSubschemaEntry( searchCtls.getReturningAttributes() );
162                 SearchResult result = new SearchResult( base.toString(), null, attrs );
163                 return new SingletonEnumeration( result );
164             }
165         }
166         else if ( searchCtls.getSearchScope() == SearchControls.OBJECT_SCOPE &&
167                 filter instanceof PresenceNode )
168         {
169             PresenceNode node = ( PresenceNode ) filter;
170 
171             if ( node.getAttribute().equalsIgnoreCase( "objectClass" ) )
172             {
173                 // call.setBypass( true );
174                 Attributes attrs = getSubschemaEntry( searchCtls.getReturningAttributes() );
175                 SearchResult result = new SearchResult( base.toString(), null, attrs );
176                 return new SingletonEnumeration( result );
177             }
178         }
179 
180         NamingEnumeration e = nextInterceptor.search( base, env, filter, searchCtls );
181 
182         if ( searchCtls.getReturningAttributes() != null )
183         {
184             return e;
185         }
186 
187         Invocation invocation = InvocationStack.getInstance().peek();
188         return new SearchResultFilteringEnumeration( e, searchCtls, invocation, binaryAttributeFilter );
189     }
190 
191 
192     private Attributes getSubschemaEntry( String[] ids ) throws NamingException
193     {
194         if ( ids == null )
195         {
196             ids = EMPTY_STRING_ARRAY;
197         }
198 
199         Set set = new HashSet();
200         LockableAttributesImpl attrs = new LockableAttributesImpl();
201         LockableAttributeImpl attr;
202 
203         for ( int ii = 0; ii < ids.length; ii++ )
204         {
205             set.add( ids[ii].toLowerCase() );
206         }
207 
208         // Check whether the set contains a plus, and use it below to include all
209         // operational attributes.  Due to RFC 3673, and issue DIREVE-228 in JIRA
210         boolean returnAllOperationalAttributes = set.contains( "+" );
211 
212         if ( returnAllOperationalAttributes || set.contains( "objectclasses" ) )
213         {
214             attr = new LockableAttributeImpl( attrs, "objectClasses" );
215             Iterator list = globalRegistries.getObjectClassRegistry().list();
216             while ( list.hasNext() )
217             {
218                 ObjectClass oc = ( ObjectClass ) list.next();
219                 attr.add( SchemaUtils.render( oc ).toString() );
220             }
221             attrs.put( attr );
222         }
223 
224         if ( returnAllOperationalAttributes || set.contains( "attributetypes" ) )
225         {
226             attr = new LockableAttributeImpl( attrs, "attributeTypes" );
227             Iterator list = globalRegistries.getAttributeTypeRegistry().list();
228             while ( list.hasNext() )
229             {
230                 AttributeType at = ( AttributeType ) list.next();
231                 attr.add( SchemaUtils.render( at ).toString() );
232             }
233             attrs.put( attr );
234         }
235 
236         if ( returnAllOperationalAttributes || set.contains( "matchingrules" ) )
237         {
238             attr = new LockableAttributeImpl( attrs, "matchingRules" );
239             Iterator list = globalRegistries.getMatchingRuleRegistry().list();
240             while ( list.hasNext() )
241             {
242                 MatchingRule mr = ( MatchingRule ) list.next();
243                 attr.add( SchemaUtils.render( mr ).toString() );
244             }
245             attrs.put( attr );
246         }
247 
248         if ( returnAllOperationalAttributes || set.contains( "matchingruleuse" ) )
249         {
250             attr = new LockableAttributeImpl( attrs, "matchingRuleUse" );
251             Iterator list = globalRegistries.getMatchingRuleUseRegistry().list();
252             while ( list.hasNext() )
253             {
254                 MatchingRuleUse mru = ( MatchingRuleUse ) list.next();
255                 attr.add( SchemaUtils.render( mru ).toString() );
256             }
257             attrs.put( attr );
258         }
259 
260         if ( returnAllOperationalAttributes || set.contains( "ldapsyntaxes" ) )
261         {
262             attr = new LockableAttributeImpl( attrs, "ldapSyntaxes" );
263             Iterator list = globalRegistries.getSyntaxRegistry().list();
264             while ( list.hasNext() )
265             {
266                 Syntax syntax = ( Syntax ) list.next();
267                 attr.add( SchemaUtils.render( syntax ).toString() );
268             }
269             attrs.put( attr );
270         }
271 
272         if ( returnAllOperationalAttributes || set.contains( "ditcontentrules" ) )
273         {
274             attr = new LockableAttributeImpl( attrs, "dITContentRules" );
275             Iterator list = globalRegistries.getDitContentRuleRegistry().list();
276             while ( list.hasNext() )
277             {
278                 DITContentRule dcr = ( DITContentRule ) list.next();
279                 attr.add( SchemaUtils.render( dcr ).toString() );
280             }
281             attrs.put( attr );
282         }
283 
284         if ( returnAllOperationalAttributes || set.contains( "ditstructurerules" ) )
285         {
286             attr = new LockableAttributeImpl( attrs, "dITStructureRules" );
287             Iterator list = globalRegistries.getDitStructureRuleRegistry().list();
288             while ( list.hasNext() )
289             {
290                 DITStructureRule dsr = ( DITStructureRule ) list.next();
291                 attr.add( SchemaUtils.render( dsr ).toString() );
292             }
293             attrs.put( attr );
294         }
295 
296         if ( returnAllOperationalAttributes || set.contains( "nameforms" ) )
297         {
298             attr = new LockableAttributeImpl( attrs, "nameForms" );
299             Iterator list = globalRegistries.getNameFormRegistry().list();
300             while ( list.hasNext() )
301             {
302                 NameForm nf = ( NameForm ) list.next();
303                 attr.add( SchemaUtils.render( nf ).toString() );
304             }
305             attrs.put( attr );
306         }
307 
308         // timeestamps are hacks for now until the schema is actually updateable these
309         // use the servers startup time stamp for both modify and create timestamps
310 
311 
312         if ( returnAllOperationalAttributes || set.contains( "createtimestamp" ) )
313         {
314             attr = new LockableAttributeImpl( attrs, "createTimestamp" );
315             attr.add( startUpTimeStamp );
316             attrs.put( attr );
317         }
318 
319         if ( returnAllOperationalAttributes || set.contains( "modifytimestamp" ) )
320         {
321             attr = new LockableAttributeImpl( attrs, "modifyTimestamp" );
322             attr.add( startUpTimeStamp );
323             attrs.put( attr );
324         }
325 
326         if ( returnAllOperationalAttributes || set.contains( "creatorsname" ) )
327         {
328             attr = new LockableAttributeImpl( attrs, "creatorsName" );
329             attr.add( DirectoryPartitionNexus.ADMIN_PRINCIPAL );
330             attrs.put( attr );
331         }
332 
333         if ( returnAllOperationalAttributes || set.contains( "modifiersname" ) )
334         {
335             attr = new LockableAttributeImpl( attrs, "modifiersName" );
336             attr.add( DirectoryPartitionNexus.ADMIN_PRINCIPAL );
337             attrs.put( attr );
338         }
339 
340         int minSetSize = 0;
341         if ( set.contains( "+" ) )
342         {
343             minSetSize++;
344         }
345         if ( set.contains( "*" ) )
346         {
347             minSetSize++;
348         }
349         if ( set.contains( "ref" ) )
350         {
351             minSetSize++;
352         }
353 
354         // add the objectClass attribute
355         if ( set.contains( "*" ) || set.contains( "objectclass" ) || set.size() == minSetSize )
356         {
357             attr = new LockableAttributeImpl( attrs, "objectClass" );
358             attr.add( "top" );
359             attr.add( "subschema" );
360             attrs.put( attr );
361         }
362 
363         // add the cn attribute as required for the RDN
364         if ( set.contains( "*" ) || set.contains( "cn" ) || set.contains( "commonname" ) || set.size() == minSetSize )
365         {
366             attrs.put( "cn", "schema" );
367         }
368 
369         return attrs;
370     }
371 
372 
373     public Attributes lookup( NextInterceptor nextInterceptor, Name name ) throws NamingException
374     {
375         Attributes result = nextInterceptor.lookup( name );
376         Invocation invocation = InvocationStack.getInstance().peek();
377         doFilter( invocation, result );
378         return result;
379     }
380 
381 
382     public Attributes lookup( NextInterceptor nextInterceptor, Name name, String[] attrIds ) throws NamingException
383     {
384         Attributes result = nextInterceptor.lookup( name, attrIds );
385         if ( result == null )
386         {
387             return null;
388         }
389 
390         Invocation invocation = InvocationStack.getInstance().peek();
391         doFilter( invocation, result );
392         return result;
393     }
394 
395 
396     /***
397      * Checks to see if an attribute is required by as determined from an entry's
398      * set of objectClass attribute values.
399      *
400      * @param attrId the attribute to test if required by a set of objectClass values
401      * @param objectClass the objectClass values
402      * @return true if the objectClass values require the attribute, false otherwise
403      * @throws NamingException if the attribute is not recognized
404      */
405     private boolean isRequired( String attrId, Attribute objectClass ) throws NamingException
406     {
407         OidRegistry oidRegistry = globalRegistries.getOidRegistry();
408         ObjectClassRegistry registry = globalRegistries.getObjectClassRegistry();
409 
410         if ( ! oidRegistry.hasOid( attrId ) )
411         {
412             return false;
413         }
414 
415         String attrOid = oidRegistry.getOid( attrId );
416         for ( int ii = 0; ii < objectClass.size(); ii++ )
417         {
418             ObjectClass ocSpec = registry.lookup( ( String ) objectClass.get( ii ) );
419             AttributeType[] mustList = ocSpec.getMustList();
420             for ( int jj = 0; jj < mustList.length; jj++ )
421             {
422                 if ( mustList[jj].getOid().equals( attrOid ) )
423                 {
424                     return true;
425                 }
426             }
427         }
428 
429         return false;
430     }
431 
432 
433     /***
434      * Checks to see if removing a set of attributes from an entry completely removes
435      * that attribute's values.  If change has zero size then all attributes are
436      * presumed to be removed.
437      *
438      * @param change
439      * @param entry
440      * @return
441      * @throws NamingException
442      */
443     private boolean isCompleteRemoval( Attribute change, Attributes entry ) throws NamingException
444     {
445         // if change size is 0 then all values are deleted then we're screwed
446         if ( change.size() == 0 )
447         {
448             return true;
449         }
450 
451         // can't do math to figure our if all values are removed since some
452         // values in the modify request may not be in the entry.  we need to
453         // remove the values from a cloned version of the attribute and see
454         // if nothing is left.
455         Attribute changedEntryAttr = entry.get( change.getID() );
456         for ( int jj = 0; jj < change.size(); jj++ )
457         {
458             changedEntryAttr.remove( change.get( jj ) );
459         }
460 
461         return changedEntryAttr.size() == 0;
462     }
463 
464 
465     Attribute getResultantObjectClasses( int modOp, Attribute changes, Attribute existing ) throws NamingException
466     {
467         if ( changes == null && existing == null )
468         {
469             return new LockableAttributeImpl( "objectClass" );
470         }
471 
472         if ( changes == null )
473         {
474             return existing;
475         }
476 
477         if ( existing == null && modOp == DirContext.ADD_ATTRIBUTE )
478         {
479             return changes;
480         }
481         else if ( existing == null )
482         {
483             return new LockableAttributeImpl( "objectClasses" );
484         }
485 
486         switch( modOp )
487         {
488             case( DirContext.ADD_ATTRIBUTE ):
489                 return AttributeUtils.getUnion( existing, changes );
490             case( DirContext.REPLACE_ATTRIBUTE ):
491                 return ( Attribute ) changes.clone();
492             case( DirContext.REMOVE_ATTRIBUTE ):
493                 return AttributeUtils.getDifference( existing, changes );
494             default:
495                 throw new InternalError( "" );
496         }
497     }
498 
499 
500     public void modify( NextInterceptor next, Name name, int modOp, Attributes mods ) throws NamingException
501     {
502         Attributes entry = nexus.lookup( name );
503         Attribute objectClass = getResultantObjectClasses( modOp, mods.get( "objectClass"), entry.get( "objectClass" ) );
504         ObjectClassRegistry ocRegistry = this.globalRegistries.getObjectClassRegistry();
505         AttributeTypeRegistry atRegistry = this.globalRegistries.getAttributeTypeRegistry();
506 
507         NamingEnumeration changes = mods.getIDs();
508         while ( changes.hasMore() )
509         {
510             String id = ( String ) changes.next();
511             Attribute change = mods.get( id );
512 
513             if ( ! atRegistry.hasAttributeType( change.getID() ) && ! objectClass.contains( "extensibleObject" ) )
514             {
515                 throw new LdapInvalidAttributeIdentifierException( "unrecognized attributeID " + change.getID() );
516             }
517 
518             if ( modOp == DirContext.REMOVE_ATTRIBUTE && entry.get( change.getID() ) == null )
519             {
520                 throw new LdapNoSuchAttributeException();
521             }
522 
523             // for required attributes we need to check if all values are removed
524             // if so then we have a schema violation that must be thrown
525             if ( modOp == DirContext.REMOVE_ATTRIBUTE &&
526                  isRequired( change.getID(), objectClass ) &&
527                  isCompleteRemoval( change, entry ) )
528             {
529                 throw new LdapSchemaViolationException( ResultCodeEnum.OBJECTCLASSVIOLATION );
530             }
531         }
532 
533         if ( modOp == DirContext.REMOVE_ATTRIBUTE )
534         {
535             SchemaChecker.preventRdnChangeOnModifyRemove( name, modOp, mods );
536             SchemaChecker.preventStructuralClassRemovalOnModifyRemove( ocRegistry, name, modOp, mods, objectClass );
537         }
538 
539         if ( modOp == DirContext.REPLACE_ATTRIBUTE )
540         {
541             SchemaChecker.preventRdnChangeOnModifyReplace( name, modOp, mods );
542             SchemaChecker.preventStructuralClassRemovalOnModifyReplace( ocRegistry, name, modOp, mods );
543         }
544 
545         next.modify( name, modOp, mods );
546     }
547 
548 
549     public void modify( NextInterceptor next, Name name, ModificationItem[] mods ) throws NamingException
550     {
551         Attributes entry = nexus.lookup( name );
552 
553         ModificationItem objectClassMod = null;
554         for ( int ii = 0; ii < mods.length; ii++ )
555         {
556             if ( mods[ii].getAttribute().getID().equalsIgnoreCase( "objectclass" ) )
557             {
558                 objectClassMod = mods[ii];
559             }
560         }
561         Attribute objectClass;
562 
563         if ( objectClassMod == null )
564         {
565             objectClass = entry.get( "objectClass" );
566         }
567         else
568         {
569             objectClass = getResultantObjectClasses( objectClassMod.getModificationOp(),
570                     objectClassMod.getAttribute(), entry.get( "objectClass" ) );
571         }
572 
573         ObjectClassRegistry ocRegistry = this.globalRegistries.getObjectClassRegistry();
574         AttributeTypeRegistry atRegistry = this.globalRegistries.getAttributeTypeRegistry();
575 
576         for ( int ii = 0; ii < mods.length; ii++ )
577         {
578             int modOp = mods[ii].getModificationOp();
579             Attribute change = mods[ii].getAttribute();
580 
581             if ( ! atRegistry.hasAttributeType( change.getID() ) && ! objectClass.contains( "extensibleObject" ) )
582             {
583                 throw new LdapInvalidAttributeIdentifierException();
584             }
585 
586             if ( modOp == DirContext.REMOVE_ATTRIBUTE && entry.get( change.getID() ) == null )
587             {
588                 throw new LdapNoSuchAttributeException();
589             }
590 
591             if ( modOp == DirContext.REMOVE_ATTRIBUTE )
592             {
593                 // for required attributes we need to check if all values are removed
594                 // if so then we have a schema violation that must be thrown
595                 if ( isRequired( change.getID(), objectClass ) && isCompleteRemoval( change, entry ) )
596                 {
597                     throw new LdapSchemaViolationException( ResultCodeEnum.OBJECTCLASSVIOLATION );
598                 }
599                 SchemaChecker.preventRdnChangeOnModifyRemove( name, modOp, change );
600                 SchemaChecker.preventStructuralClassRemovalOnModifyRemove( ocRegistry, name, modOp, change, objectClass );
601             }
602 
603             if ( modOp == DirContext.REPLACE_ATTRIBUTE )
604             {
605                 SchemaChecker.preventRdnChangeOnModifyReplace( name, modOp, change );
606                 SchemaChecker.preventStructuralClassRemovalOnModifyReplace( ocRegistry, name, modOp, change );
607             }
608         }
609 
610         next.modify( name, mods );
611     }
612 
613 
614     private void doFilter( Invocation invocation, Attributes entry )
615             throws NamingException
616     {
617         // set of AttributeType objects that are to behave as binaries
618         Set binaries;
619 
620         // construct the set for fast lookups while filtering
621         String binaryIds = ( String ) invocation.getCaller().getEnvironment().get( BINARY_KEY );
622 
623         if ( binaryIds == null )
624         {
625             binaries = Collections.EMPTY_SET;
626         }
627         else
628         {
629             String[] binaryArray = binaryIds.split( " " );
630 
631             binaries = new HashSet( binaryArray.length );
632 
633             for ( int ii = 0; ii < binaryArray.length; ii++ )
634             {
635                 AttributeType type = globalRegistries.getAttributeTypeRegistry().lookup( binaryArray[ii] );
636 
637                 binaries.add( type );
638             }
639         }
640 
641         /*
642         * start converting values of attributes to byte[]s which are not
643         * human readable and those that are in the binaries set
644         */
645         NamingEnumeration list = entry.getIDs();
646 
647         while ( list.hasMore() )
648         {
649             String id = ( String ) list.next();
650 
651             AttributeType type = null;
652 
653             boolean asBinary = false;
654 
655             if ( globalRegistries.getAttributeTypeRegistry().hasAttributeType( id ) )
656             {
657                 type = globalRegistries.getAttributeTypeRegistry().lookup( id );
658             }
659 
660             if ( type != null )
661             {
662                 asBinary = !type.getSyntax().isHumanReadible();
663 
664                 asBinary = asBinary || binaries.contains( type );
665             }
666 
667             if ( asBinary )
668             {
669                 Attribute attribute = entry.get( id );
670 
671                 Attribute binary = new LockableAttributeImpl( id );
672 
673                 for ( int ii = 0; ii < attribute.size(); ii++ )
674                 {
675                     Object value = attribute.get( ii );
676 
677                     if ( value instanceof String )
678                     {
679                         binary.add( ii, ( ( String ) value ).getBytes() );
680                     }
681                     else
682                     {
683                         binary.add( ii, value );
684                     }
685                 }
686 
687                 entry.remove( id );
688 
689                 entry.put( binary );
690             }
691         }
692     }
693 
694 
695     /***
696      * A special filter over entry attributes which replaces Attribute String values with their respective byte[]
697      * representations using schema information and the value held in the JNDI environment property:
698      * <code>java.naming.ldap.attributes.binary</code>.
699      *
700      * @see <a href= "http://java.sun.com/j2se/1.4.2/docs/guide/jndi/jndi-ldap-gl.html#binary">
701      *      java.naming.ldap.attributes.binary</a>
702      */
703     private class BinaryAttributeFilter implements SearchResultFilter
704     {
705         public BinaryAttributeFilter()
706         {
707         }
708 
709 
710         public boolean accept( Invocation invocation, SearchResult result, SearchControls controls ) throws NamingException
711         {
712             doFilter( invocation, result.getAttributes() );
713             return true;
714         }
715     }
716 }