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 org.apache.ldap.common.filter.ExprNode;
21  import org.apache.ldap.common.filter.PresenceNode;
22  import org.apache.ldap.common.filter.SimpleNode;
23  import org.apache.ldap.common.message.LockableAttributeImpl;
24  import org.apache.ldap.common.message.LockableAttributesImpl;
25  import org.apache.ldap.common.name.LdapName;
26  import org.apache.ldap.common.schema.*;
27  import org.apache.ldap.common.util.SingletonEnumeration;
28  import org.apache.ldap.server.RootNexus;
29  import org.apache.ldap.server.interceptor.BaseInterceptor;
30  import org.apache.ldap.server.interceptor.InterceptorContext;
31  import org.apache.ldap.server.interceptor.NextInterceptor;
32  import org.apache.ldap.server.db.ResultFilteringEnumeration;
33  import org.apache.ldap.server.db.SearchResultFilter;
34  import org.apache.ldap.server.invocation.List;
35  import org.apache.ldap.server.invocation.Lookup;
36  import org.apache.ldap.server.invocation.LookupWithAttrIds;
37  import org.apache.ldap.server.invocation.Search;
38  import org.apache.ldap.server.jndi.ServerLdapContext;
39  import org.apache.ldap.server.schema.AttributeTypeRegistry;
40  import org.apache.ldap.server.schema.GlobalRegistries;
41  
42  import javax.naming.NamingEnumeration;
43  import javax.naming.NamingException;
44  import javax.naming.directory.Attribute;
45  import javax.naming.directory.Attributes;
46  import javax.naming.directory.SearchControls;
47  import javax.naming.directory.SearchResult;
48  import javax.naming.ldap.LdapContext;
49  import java.util.Collections;
50  import java.util.HashSet;
51  import java.util.Iterator;
52  import java.util.Set;
53  
54  
55  /***
56   * An {@link org.apache.ldap.server.interceptor.Interceptor} that manages and enforces schemas.
57   *
58   * @todo Better interceptor description required.
59   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
60   * @version $Rev: 159262 $, $Date: 2005-03-28 12:18:38 -0500 (Mon, 28 Mar 2005) $
61   */
62  public class SchemaService extends BaseInterceptor
63  {
64      private static final String BINARY_KEY = "java.naming.ldap.attributes.binary";
65  
66      /***
67       * the root nexus to all database partitions
68       */
69      private RootNexus nexus;
70  
71      /***
72       * a binary attribute tranforming filter: String -> byte[]
73       */
74      private BinaryAttributeFilter binaryAttributeFilter;
75  
76      /***
77       * the global schema object registries
78       */
79      private GlobalRegistries globalRegistries;
80  
81      private AttributeTypeRegistry attributeRegistry;
82  
83      /***
84       * subschemaSubentry attribute's value from Root DSE
85       */
86      private String subentryDn;
87  
88  
89      /***
90       * Creates a schema service interceptor.
91       */
92      public SchemaService()
93      {
94      }
95  
96  
97      public void init( InterceptorContext ctx ) throws NamingException
98      {
99          this.nexus = ctx.getRootNexus();
100         this.globalRegistries = ctx.getGlobalRegistries();
101         attributeRegistry = globalRegistries.getAttributeTypeRegistry();
102         binaryAttributeFilter = new BinaryAttributeFilter();
103 
104         // stuff for dealing with subentries (garbage for now)
105         String subschemaSubentry = ( String ) nexus.getRootDSE().get( "subschemaSubentry" ).get();
106         subentryDn = new LdapName( subschemaSubentry ).toString().toLowerCase();
107     }
108 
109 
110     public void destroy()
111     {
112     }
113 
114 
115     protected void process( NextInterceptor nextInterceptor, List call ) throws NamingException
116     {
117         nextInterceptor.process( call );
118 
119         NamingEnumeration e;
120         ResultFilteringEnumeration retval;
121         LdapContext ctx = ( LdapContext ) call.getContextStack().peek();
122         e = ( NamingEnumeration ) call.getReturnValue();
123         retval = new ResultFilteringEnumeration( e, new SearchControls(), ctx, binaryAttributeFilter );
124         call.setReturnValue( retval );
125     }
126 
127 
128     protected void process( NextInterceptor nextInterceptor, Search call ) throws NamingException
129     {
130         // check to make sure the DN searched for is a subentry
131         if ( !subentryDn.equals( call.getBaseName().toString() ) )
132         {
133             nextInterceptor.process( call );
134             return;
135         }
136 
137         boolean bypass = false;
138         SearchControls searchControls = call.getControls();
139         ExprNode filter = call.getFilter();
140         if ( searchControls.getSearchScope() == SearchControls.OBJECT_SCOPE &&
141                 filter instanceof SimpleNode )
142         {
143             SimpleNode node = ( SimpleNode ) filter;
144 
145             if ( node.getAttribute().equalsIgnoreCase( "objectClass" ) &&
146                     node.getValue().equalsIgnoreCase( "subschema" ) &&
147                     node.getAssertionType() == SimpleNode.EQUALITY
148             )
149             {
150                 // call.setBypass( true );
151                 Attributes attrs = getSubschemaEntry( searchControls.getReturningAttributes() );
152                 SearchResult result = new SearchResult( call.getBaseName().toString(), null, attrs );
153                 SingletonEnumeration e = new SingletonEnumeration( result );
154                 call.setReturnValue( e );
155                 bypass = true;
156             }
157         }
158         else if ( searchControls.getSearchScope() == SearchControls.OBJECT_SCOPE &&
159                 filter instanceof PresenceNode )
160         {
161             PresenceNode node = ( PresenceNode ) filter;
162 
163             if ( node.getAttribute().equalsIgnoreCase( "objectClass" ) )
164             {
165                 // call.setBypass( true );
166                 Attributes attrs = getSubschemaEntry( searchControls.getReturningAttributes() );
167                 SearchResult result = new SearchResult( call.getBaseName().toString(), null, attrs );
168                 SingletonEnumeration e = new SingletonEnumeration( result );
169                 call.setReturnValue( e );
170                 bypass = true;
171             }
172         }
173 
174         if ( !bypass )
175         {
176             nextInterceptor.process( call );
177         }
178 
179         if ( searchControls.getReturningAttributes() != null )
180         {
181             return;
182         }
183 
184         NamingEnumeration e;
185         ResultFilteringEnumeration retval;
186         LdapContext ctx = ( LdapContext ) call.getContextStack().peek();
187         e = ( NamingEnumeration ) call.getReturnValue();
188         retval = new ResultFilteringEnumeration( e, searchControls, ctx, binaryAttributeFilter );
189         call.setReturnValue( retval );
190     }
191 
192 
193     private Attributes getSubschemaEntry( String[] ids ) throws NamingException
194     {
195         if ( ids == null )
196         {
197             return new LockableAttributesImpl();
198         }
199 
200         HashSet set = new HashSet( ids.length );
201         LockableAttributesImpl attrs = new LockableAttributesImpl();
202         LockableAttributeImpl attr = null;
203 
204         for ( int ii = 0; ii < ids.length; ii++ )
205         {
206             set.add( ids[ii].toLowerCase() );
207         }
208 
209 
210         if ( set.contains( "objectclasses" ) )
211         {
212             attr = new LockableAttributeImpl( attrs, "objectClasses" );
213             Iterator list = globalRegistries.getObjectClassRegistry().list();
214             while ( list.hasNext() )
215             {
216                 ObjectClass oc = ( ObjectClass ) list.next();
217                 attr.add( SchemaUtils.render( oc ).toString() );
218             }
219             attrs.put( attr );
220         }
221 
222         if ( set.contains( "attributetypes" ) )
223         {
224             attr = new LockableAttributeImpl( attrs, "attributeTypes" );
225             Iterator list = globalRegistries.getAttributeTypeRegistry().list();
226             while ( list.hasNext() )
227             {
228                 AttributeType at = ( AttributeType ) list.next();
229                 attr.add( SchemaUtils.render( at ).toString() );
230             }
231             attrs.put( attr );
232         }
233 
234         if ( set.contains( "matchingrules" ) )
235         {
236             attr = new LockableAttributeImpl( attrs, "matchingRules" );
237             Iterator list = globalRegistries.getMatchingRuleRegistry().list();
238             while ( list.hasNext() )
239             {
240                 MatchingRule mr = ( MatchingRule ) list.next();
241                 attr.add( SchemaUtils.render( mr ).toString() );
242             }
243             attrs.put( attr );
244         }
245 
246         if ( set.contains( "matchingruleuse" ) )
247         {
248             attr = new LockableAttributeImpl( attrs, "matchingRuleUse" );
249             Iterator list = globalRegistries.getMatchingRuleUseRegistry().list();
250             while ( list.hasNext() )
251             {
252                 MatchingRuleUse mru = ( MatchingRuleUse ) list.next();
253                 attr.add( SchemaUtils.render( mru ).toString() );
254             }
255             attrs.put( attr );
256         }
257 
258         if ( set.contains( "ldapsyntaxes" ) )
259         {
260             attr = new LockableAttributeImpl( attrs, "ldapSyntaxes" );
261             Iterator list = globalRegistries.getSyntaxRegistry().list();
262             while ( list.hasNext() )
263             {
264                 Syntax syntax = ( Syntax ) list.next();
265                 attr.add( SchemaUtils.render( syntax ).toString() );
266             }
267             attrs.put( attr );
268         }
269 
270         if ( set.contains( "ditcontentrules" ) )
271         {
272             attr = new LockableAttributeImpl( attrs, "dITContentRules" );
273             Iterator list = globalRegistries.getDitContentRuleRegistry().list();
274             while ( list.hasNext() )
275             {
276                 DITContentRule dcr = ( DITContentRule ) list.next();
277                 attr.add( SchemaUtils.render( dcr ).toString() );
278             }
279             attrs.put( attr );
280         }
281 
282         if ( set.contains( "ditstructurerules" ) )
283         {
284             attr = new LockableAttributeImpl( attrs, "dITStructureRules" );
285             Iterator list = globalRegistries.getDitStructureRuleRegistry().list();
286             while ( list.hasNext() )
287             {
288                 DITStructureRule dsr = ( DITStructureRule ) list.next();
289                 attr.add( SchemaUtils.render( dsr ).toString() );
290             }
291             attrs.put( attr );
292         }
293 
294         if ( set.contains( "nameforms" ) )
295         {
296             attr = new LockableAttributeImpl( attrs, "nameForms" );
297             Iterator list = globalRegistries.getNameFormRegistry().list();
298             while ( list.hasNext() )
299             {
300                 NameForm nf = ( NameForm ) list.next();
301                 attr.add( SchemaUtils.render( nf ).toString() );
302             }
303             attrs.put( attr );
304         }
305 
306         // add the objectClass attribute
307         attr = new LockableAttributeImpl( attrs, "objectClass" );
308         attr.add( "top" );
309         attr.add( "subschema" );
310         attrs.put( attr );
311 
312         // add the cn attribute as required for the RDN
313         attrs.put( "cn", "schema" );
314 
315         return attrs;
316     }
317 
318 
319     protected void process( NextInterceptor nextInterceptor, Lookup call ) throws NamingException
320     {
321         nextInterceptor.process( call );
322 
323         ServerLdapContext ctx = ( ServerLdapContext ) call.getContextStack().peek();
324         Attributes attributes = ( Attributes ) call.getReturnValue();
325         Attributes retval = ( Attributes ) attributes.clone();
326         doFilter( ctx, retval );
327         call.setReturnValue( retval );
328     }
329 
330 
331     protected void process( NextInterceptor nextInterceptor, LookupWithAttrIds call ) throws NamingException
332     {
333         nextInterceptor.process( call );
334 
335         ServerLdapContext ctx = ( ServerLdapContext ) call.getContextStack().peek();
336         Attributes attributes = ( Attributes ) call.getReturnValue();
337         if ( attributes == null )
338         {
339             return;
340         }
341 
342         Attributes retval = ( Attributes ) attributes.clone();
343         doFilter( ctx, retval );
344         call.setReturnValue( retval );
345     }
346 
347 
348     private void doFilter( LdapContext ctx, Attributes entry )
349             throws NamingException
350     {
351         // set of AttributeType objects that are to behave as binaries
352         Set binaries;
353         
354         // construct the set for fast lookups while filtering
355         String binaryIds = ( String ) ctx.getEnvironment().get( BINARY_KEY );
356 
357         if ( binaryIds == null )
358         {
359             binaries = Collections.EMPTY_SET;
360         }
361         else
362         {
363             String[] binaryArray = binaryIds.split( " " );
364 
365             binaries = new HashSet( binaryArray.length );
366 
367             for ( int ii = 0; ii < binaryArray.length; ii++ )
368             {
369                 AttributeType type = attributeRegistry.lookup( binaryArray[ii] );
370 
371                 binaries.add( type );
372             }
373         }
374         
375         /*
376          * start converting values of attributes to byte[]s which are not
377          * human readable and those that are in the binaries set
378          */
379         NamingEnumeration list = entry.getIDs();
380 
381         while ( list.hasMore() )
382         {
383             String id = ( String ) list.next();
384 
385             AttributeType type = null;
386 
387             boolean asBinary = false;
388 
389             if ( attributeRegistry.hasAttributeType( id ) )
390             {
391                 type = attributeRegistry.lookup( id );
392             }
393 
394             if ( type != null )
395             {
396                 asBinary = !type.getSyntax().isHumanReadible();
397 
398                 asBinary = asBinary || binaries.contains( type );
399             }
400 
401             if ( asBinary )
402             {
403                 Attribute attribute = entry.get( id );
404 
405                 Attribute binary = new LockableAttributeImpl( id );
406 
407                 for ( int ii = 0; ii < attribute.size(); ii++ )
408                 {
409                     Object value = attribute.get( ii );
410 
411                     if ( value instanceof String )
412                     {
413                         binary.add( ii, ( ( String ) value ).getBytes() );
414                     }
415                     else
416                     {
417                         binary.add( ii, value );
418                     }
419                 }
420 
421                 entry.remove( id );
422 
423                 entry.put( binary );
424             }
425         }
426     }
427 
428 
429     /***
430      * A special filter over entry attributes which replaces Attribute String values with their respective byte[]
431      * representations using schema information and the value held in the JNDI environment property:
432      * <code>java.naming.ldap.attributes.binary</code>.
433      *
434      * @see <a href= "http://java.sun.com/j2se/1.4.2/docs/guide/jndi/jndi-ldap-gl.html#binary">
435      *      java.naming.ldap.attributes.binary</a>
436      */
437     private class BinaryAttributeFilter implements SearchResultFilter
438     {
439         public BinaryAttributeFilter()
440         {
441         }
442 
443 
444         public boolean accept( LdapContext ctx, SearchResult result, SearchControls controls ) throws NamingException
445         {
446             doFilter( ctx, result.getAttributes() );
447             return true;
448         }
449     }
450 }