1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 import javax.naming.ldap.LdapContext;
31
32 import org.apache.ldap.common.filter.ExprNode;
33 import org.apache.ldap.common.filter.PresenceNode;
34 import org.apache.ldap.common.filter.SimpleNode;
35 import org.apache.ldap.common.message.LockableAttributeImpl;
36 import org.apache.ldap.common.message.LockableAttributesImpl;
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.server.configuration.InterceptorConfiguration;
50 import org.apache.ldap.server.enumeration.SearchResultFilteringEnumeration;
51 import org.apache.ldap.server.enumeration.SearchResultFilter;
52 import org.apache.ldap.server.interceptor.BaseInterceptor;
53 import org.apache.ldap.server.interceptor.NextInterceptor;
54 import org.apache.ldap.server.jndi.ContextFactoryConfiguration;
55 import org.apache.ldap.server.jndi.ServerLdapContext;
56 import org.apache.ldap.server.partition.ContextPartitionNexus;
57 import org.slf4j.Logger;
58 import org.slf4j.LoggerFactory;
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: 264732 $, $Date: 2005-08-30 04:04:51 -0400 (Tue, 30 Aug 2005) $
67 */
68 public class SchemaService extends BaseInterceptor
69 {
70 private static final String[] EMPTY_STRING_ARRAY = new String[0];
71 private static final String BINARY_KEY = "java.naming.ldap.attributes.binary";
72
73 /*** The LoggerFactory used by this Interceptor */
74 private static Logger log = LoggerFactory.getLogger( SchemaService.class );
75
76 /***
77 * the root nexus to all database partitions
78 */
79 private ContextPartitionNexus nexus;
80
81 /***
82 * a binary attribute tranforming filter: String -> byte[]
83 */
84 private BinaryAttributeFilter binaryAttributeFilter;
85
86 /***
87 * the global schema object registries
88 */
89 private GlobalRegistries globalRegistries;
90
91 private AttributeTypeRegistry attributeRegistry;
92
93 /***
94 * subschemaSubentry attribute's value from Root DSE
95 */
96 private String subentryDn;
97 private String startUpTimeStamp;
98
99 /***
100 * Creates a schema service interceptor.
101 */
102 public SchemaService()
103 {
104 startUpTimeStamp = DateUtils.getGeneralizedTime();
105 }
106
107
108 public void init( ContextFactoryConfiguration factoryCfg, InterceptorConfiguration cfg ) throws NamingException
109 {
110 this.nexus = factoryCfg.getPartitionNexus();
111 this.globalRegistries = factoryCfg.getGlobalRegistries();
112 attributeRegistry = globalRegistries.getAttributeTypeRegistry();
113 binaryAttributeFilter = new BinaryAttributeFilter();
114
115
116 String subschemaSubentry = ( String ) nexus.getRootDSE().get( "subschemaSubentry" ).get();
117 subentryDn = new LdapName( subschemaSubentry ).toString().toLowerCase();
118 }
119
120
121 public void destroy()
122 {
123 }
124
125
126 public NamingEnumeration list( NextInterceptor nextInterceptor, Name base ) throws NamingException
127 {
128 NamingEnumeration e = nextInterceptor.list( base );
129 LdapContext ctx = getContext();
130 return new SearchResultFilteringEnumeration( e, new SearchControls(), ctx, binaryAttributeFilter );
131 }
132
133
134 public NamingEnumeration search( NextInterceptor nextInterceptor,
135 Name base, Map env, ExprNode filter,
136 SearchControls searchCtls ) throws NamingException
137 {
138
139 if ( !subentryDn.equals( base.toString() ) )
140 {
141 return nextInterceptor.search( base, env, filter, searchCtls );
142 }
143
144 if ( searchCtls.getSearchScope() == SearchControls.OBJECT_SCOPE &&
145 filter instanceof SimpleNode )
146 {
147 SimpleNode node = ( SimpleNode ) filter;
148
149 if ( node.getAttribute().equalsIgnoreCase( "objectClass" ) &&
150 node.getValue().equalsIgnoreCase( "subschema" ) &&
151 node.getAssertionType() == SimpleNode.EQUALITY
152 )
153 {
154
155 Attributes attrs = getSubschemaEntry( searchCtls.getReturningAttributes() );
156 SearchResult result = new SearchResult( base.toString(), null, attrs );
157 return new SingletonEnumeration( result );
158 }
159 }
160 else if ( searchCtls.getSearchScope() == SearchControls.OBJECT_SCOPE &&
161 filter instanceof PresenceNode )
162 {
163 PresenceNode node = ( PresenceNode ) filter;
164
165 if ( node.getAttribute().equalsIgnoreCase( "objectClass" ) )
166 {
167
168 Attributes attrs = getSubschemaEntry( searchCtls.getReturningAttributes() );
169 SearchResult result = new SearchResult( base.toString(), null, attrs );
170 return new SingletonEnumeration( result );
171 }
172 }
173
174 NamingEnumeration e = nextInterceptor.search( base, env, filter, searchCtls );
175
176 if ( searchCtls.getReturningAttributes() != null )
177 {
178 return e;
179 }
180
181 LdapContext ctx = getContext();
182 return new SearchResultFilteringEnumeration( e, searchCtls, ctx, binaryAttributeFilter );
183 }
184
185
186 private Attributes getSubschemaEntry( String[] ids ) throws NamingException
187 {
188 if ( ids == null )
189 {
190 ids = EMPTY_STRING_ARRAY;
191 }
192
193 Set set = new HashSet();
194 LockableAttributesImpl attrs = new LockableAttributesImpl();
195 LockableAttributeImpl attr = null;
196
197 for ( int ii = 0; ii < ids.length; ii++ )
198 {
199 set.add( ids[ii].toLowerCase() );
200 }
201
202
203
204 boolean returnAllOperationalAttributes = set.contains( "+" );
205
206 if ( returnAllOperationalAttributes || set.contains( "objectclasses" ) )
207 {
208 attr = new LockableAttributeImpl( attrs, "objectClasses" );
209 Iterator list = globalRegistries.getObjectClassRegistry().list();
210 while ( list.hasNext() )
211 {
212 ObjectClass oc = ( ObjectClass ) list.next();
213 attr.add( SchemaUtils.render( oc ).toString() );
214 }
215 attrs.put( attr );
216 }
217
218 if ( returnAllOperationalAttributes || set.contains( "attributetypes" ) )
219 {
220 attr = new LockableAttributeImpl( attrs, "attributeTypes" );
221 Iterator list = globalRegistries.getAttributeTypeRegistry().list();
222 while ( list.hasNext() )
223 {
224 AttributeType at = ( AttributeType ) list.next();
225 attr.add( SchemaUtils.render( at ).toString() );
226 }
227 attrs.put( attr );
228 }
229
230 if ( returnAllOperationalAttributes || set.contains( "matchingrules" ) )
231 {
232 attr = new LockableAttributeImpl( attrs, "matchingRules" );
233 Iterator list = globalRegistries.getMatchingRuleRegistry().list();
234 while ( list.hasNext() )
235 {
236 MatchingRule mr = ( MatchingRule ) list.next();
237 attr.add( SchemaUtils.render( mr ).toString() );
238 }
239 attrs.put( attr );
240 }
241
242 if ( returnAllOperationalAttributes || set.contains( "matchingruleuse" ) )
243 {
244 attr = new LockableAttributeImpl( attrs, "matchingRuleUse" );
245 Iterator list = globalRegistries.getMatchingRuleUseRegistry().list();
246 while ( list.hasNext() )
247 {
248 MatchingRuleUse mru = ( MatchingRuleUse ) list.next();
249 attr.add( SchemaUtils.render( mru ).toString() );
250 }
251 attrs.put( attr );
252 }
253
254 if ( returnAllOperationalAttributes || set.contains( "ldapsyntaxes" ) )
255 {
256 attr = new LockableAttributeImpl( attrs, "ldapSyntaxes" );
257 Iterator list = globalRegistries.getSyntaxRegistry().list();
258 while ( list.hasNext() )
259 {
260 Syntax syntax = ( Syntax ) list.next();
261 attr.add( SchemaUtils.render( syntax ).toString() );
262 }
263 attrs.put( attr );
264 }
265
266 if ( returnAllOperationalAttributes || set.contains( "ditcontentrules" ) )
267 {
268 attr = new LockableAttributeImpl( attrs, "dITContentRules" );
269 Iterator list = globalRegistries.getDitContentRuleRegistry().list();
270 while ( list.hasNext() )
271 {
272 DITContentRule dcr = ( DITContentRule ) list.next();
273 attr.add( SchemaUtils.render( dcr ).toString() );
274 }
275 attrs.put( attr );
276 }
277
278 if ( returnAllOperationalAttributes || set.contains( "ditstructurerules" ) )
279 {
280 attr = new LockableAttributeImpl( attrs, "dITStructureRules" );
281 Iterator list = globalRegistries.getDitStructureRuleRegistry().list();
282 while ( list.hasNext() )
283 {
284 DITStructureRule dsr = ( DITStructureRule ) list.next();
285 attr.add( SchemaUtils.render( dsr ).toString() );
286 }
287 attrs.put( attr );
288 }
289
290 if ( returnAllOperationalAttributes || set.contains( "nameforms" ) )
291 {
292 attr = new LockableAttributeImpl( attrs, "nameForms" );
293 Iterator list = globalRegistries.getNameFormRegistry().list();
294 while ( list.hasNext() )
295 {
296 NameForm nf = ( NameForm ) list.next();
297 attr.add( SchemaUtils.render( nf ).toString() );
298 }
299 attrs.put( attr );
300 }
301
302
303
304
305
306 if ( returnAllOperationalAttributes || set.contains( "createtimestamp" ) )
307 {
308 attr = new LockableAttributeImpl( attrs, "createTimestamp" );
309 attr.add( startUpTimeStamp );
310 attrs.put( attr );
311 }
312
313 if ( returnAllOperationalAttributes || set.contains( "modifytimestamp" ) )
314 {
315 attr = new LockableAttributeImpl( attrs, "modifyTimestamp" );
316 attr.add( startUpTimeStamp );
317 attrs.put( attr );
318 }
319
320 if ( returnAllOperationalAttributes || set.contains( "creatorsname" ) )
321 {
322 attr = new LockableAttributeImpl( attrs, "creatorsName" );
323 attr.add( ContextPartitionNexus.ADMIN_PRINCIPAL );
324 attrs.put( attr );
325 }
326
327 if ( returnAllOperationalAttributes || set.contains( "modifiersname" ) )
328 {
329 attr = new LockableAttributeImpl( attrs, "modifiersName" );
330 attr.add( ContextPartitionNexus.ADMIN_PRINCIPAL );
331 attrs.put( attr );
332 }
333
334 int minSetSize = 0;
335 if ( set.contains( "+" ) )
336 {
337 minSetSize++;
338 }
339 if ( set.contains( "*" ) )
340 {
341 minSetSize++;
342 }
343 if ( set.contains( "ref" ) )
344 {
345 minSetSize++;
346 }
347
348
349 if ( set.contains( "*" ) || set.contains( "objectclass" ) || set.size() == minSetSize )
350 {
351 attr = new LockableAttributeImpl( attrs, "objectClass" );
352 attr.add( "top" );
353 attr.add( "subschema" );
354 attrs.put( attr );
355 }
356
357
358 if ( set.contains( "*" ) || set.contains( "cn" ) || set.contains( "commonname" ) || set.size() == minSetSize )
359 {
360 attrs.put( "cn", "schema" );
361 }
362
363 return attrs;
364 }
365
366
367 public Attributes lookup( NextInterceptor nextInterceptor, Name name ) throws NamingException
368 {
369 Attributes result = nextInterceptor.lookup( name );
370
371 ServerLdapContext ctx = ( ServerLdapContext ) getContext();
372 doFilter( ctx, result );
373 return result;
374 }
375
376
377 public Attributes lookup( NextInterceptor nextInterceptor, Name name, String[] attrIds ) throws NamingException
378 {
379 Attributes result = nextInterceptor.lookup( name, attrIds );
380 if ( result == null )
381 {
382 return null;
383 }
384
385 ServerLdapContext ctx = ( ServerLdapContext ) getContext();
386 doFilter( ctx, result );
387 return result;
388 }
389
390
391 public void modify( NextInterceptor next, Name name, int modOp, Attributes mods ) throws NamingException
392 {
393 ObjectClassRegistry ocRegistry = this.globalRegistries.getObjectClassRegistry();
394
395 if ( modOp == DirContext.REMOVE_ATTRIBUTE )
396 {
397 SchemaChecker.preventRdnChangeOnModifyRemove( name, modOp, mods );
398 Attribute ocAttr = this.nexus.lookup( name ).get( "objectClass" );
399 SchemaChecker.preventStructuralClassRemovalOnModifyRemove( ocRegistry, name, modOp, mods, ocAttr );
400 }
401
402 if ( modOp == DirContext.REPLACE_ATTRIBUTE )
403 {
404 SchemaChecker.preventRdnChangeOnModifyReplace( name, modOp, mods );
405 SchemaChecker.preventStructuralClassRemovalOnModifyReplace( ocRegistry, name, modOp, mods );
406 }
407
408 next.modify( name, modOp, mods );
409 }
410
411
412 public void modify( NextInterceptor next, Name name, ModificationItem[] mods ) throws NamingException
413 {
414 ObjectClassRegistry ocRegistry = this.globalRegistries.getObjectClassRegistry();
415
416 for ( int ii = 0; ii < mods.length; ii++ )
417 {
418 int modOp = mods[ii].getModificationOp();
419 Attribute change = mods[ii].getAttribute();
420
421 if ( modOp == DirContext.REMOVE_ATTRIBUTE )
422 {
423 SchemaChecker.preventRdnChangeOnModifyRemove( name, modOp, change );
424 Attribute ocAttr = this.nexus.lookup( name ).get( "objectClass" );
425 SchemaChecker.preventStructuralClassRemovalOnModifyRemove( ocRegistry, name, modOp, change, ocAttr );
426 }
427
428 if ( modOp == DirContext.REPLACE_ATTRIBUTE )
429 {
430 SchemaChecker.preventRdnChangeOnModifyReplace( name, modOp, change );
431 SchemaChecker.preventStructuralClassRemovalOnModifyReplace( ocRegistry, name, modOp, change );
432 }
433 }
434
435 next.modify( name, mods );
436 }
437
438
439 private void doFilter( LdapContext ctx, Attributes entry )
440 throws NamingException
441 {
442
443 Set binaries;
444
445
446 String binaryIds = ( String ) ctx.getEnvironment().get( BINARY_KEY );
447
448 if ( binaryIds == null )
449 {
450 binaries = Collections.EMPTY_SET;
451 }
452 else
453 {
454 String[] binaryArray = binaryIds.split( " " );
455
456 binaries = new HashSet( binaryArray.length );
457
458 for ( int ii = 0; ii < binaryArray.length; ii++ )
459 {
460 AttributeType type = attributeRegistry.lookup( binaryArray[ii] );
461
462 binaries.add( type );
463 }
464 }
465
466
467
468
469
470 NamingEnumeration list = entry.getIDs();
471
472 while ( list.hasMore() )
473 {
474 String id = ( String ) list.next();
475
476 AttributeType type = null;
477
478 boolean asBinary = false;
479
480 if ( attributeRegistry.hasAttributeType( id ) )
481 {
482 type = attributeRegistry.lookup( id );
483 }
484
485 if ( type != null )
486 {
487 asBinary = !type.getSyntax().isHumanReadible();
488
489 asBinary = asBinary || binaries.contains( type );
490 }
491
492 if ( asBinary )
493 {
494 Attribute attribute = entry.get( id );
495
496 Attribute binary = new LockableAttributeImpl( id );
497
498 for ( int ii = 0; ii < attribute.size(); ii++ )
499 {
500 Object value = attribute.get( ii );
501
502 if ( value instanceof String )
503 {
504 binary.add( ii, ( ( String ) value ).getBytes() );
505 }
506 else
507 {
508 binary.add( ii, value );
509 }
510 }
511
512 entry.remove( id );
513
514 entry.put( binary );
515 }
516 }
517 }
518
519
520 /***
521 * A special filter over entry attributes which replaces Attribute String values with their respective byte[]
522 * representations using schema information and the value held in the JNDI environment property:
523 * <code>java.naming.ldap.attributes.binary</code>.
524 *
525 * @see <a href= "http://java.sun.com/j2se/1.4.2/docs/guide/jndi/jndi-ldap-gl.html#binary">
526 * java.naming.ldap.attributes.binary</a>
527 */
528 private class BinaryAttributeFilter implements SearchResultFilter
529 {
530 public BinaryAttributeFilter()
531 {
532 }
533
534
535 public boolean accept( LdapContext ctx, SearchResult result, SearchControls controls ) throws NamingException
536 {
537 doFilter( ctx, result.getAttributes() );
538 return true;
539 }
540 }
541 }