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