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.ldap.handlers;
21  
22  
23  import java.util.concurrent.TimeUnit;
24  
25  import org.apache.directory.server.core.DirectoryService;
26  import org.apache.directory.server.core.entry.ClonedServerEntry;
27  import org.apache.directory.server.core.entry.ServerStringValue;
28  import org.apache.directory.server.core.event.EventType;
29  import org.apache.directory.server.core.event.NotificationCriteria;
30  import org.apache.directory.server.core.filtering.EntryFilteringCursor;
31  import org.apache.directory.server.core.partition.PartitionNexus;
32  import org.apache.directory.server.ldap.LdapSession;
33  import org.apache.directory.shared.ldap.codec.util.LdapURLEncodingException;
34  import org.apache.directory.shared.ldap.constants.SchemaConstants;
35  import org.apache.directory.shared.ldap.entry.EntryAttribute;
36  import org.apache.directory.shared.ldap.entry.Value;
37  import org.apache.directory.shared.ldap.exception.OperationAbandonedException;
38  import org.apache.directory.shared.ldap.filter.EqualityNode;
39  import org.apache.directory.shared.ldap.filter.OrNode;
40  import org.apache.directory.shared.ldap.filter.PresenceNode;
41  import org.apache.directory.shared.ldap.message.LdapResult;
42  import org.apache.directory.shared.ldap.message.ManageDsaITControl;
43  import org.apache.directory.shared.ldap.message.PersistentSearchControl;
44  import org.apache.directory.shared.ldap.message.ReferralImpl;
45  import org.apache.directory.shared.ldap.message.Response;
46  import org.apache.directory.shared.ldap.message.ResultCodeEnum;
47  import org.apache.directory.shared.ldap.filter.SearchScope;
48  import org.apache.directory.shared.ldap.message.SearchRequest;
49  import org.apache.directory.shared.ldap.message.SearchResponseDone;
50  import org.apache.directory.shared.ldap.message.SearchResponseEntry;
51  import org.apache.directory.shared.ldap.message.SearchResponseEntryImpl;
52  import org.apache.directory.shared.ldap.message.SearchResponseReference;
53  import org.apache.directory.shared.ldap.message.SearchResponseReferenceImpl;
54  import org.apache.directory.shared.ldap.name.LdapDN;
55  import org.apache.directory.shared.ldap.schema.AttributeType;
56  import org.apache.directory.shared.ldap.util.LdapURL;
57  import org.slf4j.Logger;
58  import org.slf4j.LoggerFactory;
59  
60  import static org.apache.directory.server.ldap.LdapService.NO_SIZE_LIMIT;
61  import static org.apache.directory.server.ldap.LdapService.NO_TIME_LIMIT;
62  
63  import javax.naming.NamingException;
64  
65  
66  /**
67   * A handler for processing search requests.
68   *
69   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
70   * @version $Rev: 664302 $
71   */
72  public class SearchHandler extends ReferralAwareRequestHandler<SearchRequest>
73  {
74      private static final Logger LOG = LoggerFactory.getLogger( SearchHandler.class );
75  
76      /** Speedup for logs */
77      private static final boolean IS_DEBUG = LOG.isDebugEnabled();
78  
79      /** cached to save redundant lookups into registries */ 
80      private AttributeType objectClassAttributeType;
81      
82      
83      /**
84       * Constructs a new filter EqualityNode asserting that a candidate 
85       * objectClass is a referral.
86       *
87       * @param session the {@link LdapSession} to construct the node for
88       * @return the {@link EqualityNode} (objectClass=referral) non-normalized
89       * @throws Exception in the highly unlikely event of schema related failures
90       */
91      private EqualityNode<String> newIsReferralEqualityNode( LdapSession session ) throws Exception
92      {
93          if ( objectClassAttributeType == null )
94          {
95              objectClassAttributeType = session.getCoreSession().getDirectoryService().getRegistries()
96                  .getAttributeTypeRegistry().lookup( SchemaConstants.OBJECT_CLASS_AT );
97          }
98          
99          EqualityNode<String> ocIsReferral = new EqualityNode<String>( SchemaConstants.OBJECT_CLASS_AT,
100             new ServerStringValue( objectClassAttributeType, SchemaConstants.REFERRAL_OC ) );
101         
102         return ocIsReferral;
103     }
104     
105     
106     /**
107      * Handles search requests containing the persistent search control but 
108      * delegates to doSimpleSearch() if the changesOnly parameter of the 
109      * control is set to false.
110      *
111      * @param session the LdapSession for which this search is conducted 
112      * @param req the search request containing the persistent search control
113      * @param psearchControl the persistent search control extracted
114      * @throws Exception if failures are encountered while searching
115      */
116     private void handlePersistentSearch( LdapSession session, SearchRequest req, 
117         PersistentSearchControl psearchControl ) throws Exception 
118     {
119         /*
120          * We want the search to complete first before we start listening to 
121          * events when the control does NOT specify changes ONLY mode.
122          */
123         if ( ! psearchControl.isChangesOnly() )
124         {
125             SearchResponseDone done = doSimpleSearch( session, req );
126             
127             // ok if normal search beforehand failed somehow quickly abandon psearch
128             if ( done.getLdapResult().getResultCode() != ResultCodeEnum.SUCCESS )
129             {
130                 session.getIoSession().write( done );
131                 return;
132             }
133         }
134 
135         if ( req.isAbandoned() )
136         {
137             return;
138         }
139         
140         // now we process entries forever as they change
141         PersistentSearchListener handler = new PersistentSearchListener( session, req );
142         
143         // compose notification criteria and add the listener to the event 
144         // service using that notification criteria to determine which events 
145         // are to be delivered to the persistent search issuing client
146         NotificationCriteria criteria = new NotificationCriteria();
147         criteria.setAliasDerefMode( req.getDerefAliases() );
148         criteria.setBase( req.getBase() );
149         criteria.setFilter( req.getFilter() );
150         criteria.setScope( req.getScope() );
151         criteria.setEventMask( EventType.getEventTypes( psearchControl.getChangeTypes() ) );
152         getLdapServer().getDirectoryService().getEventService().addListener( handler, criteria );
153         req.addAbandonListener( new SearchAbandonListener( ldapService, handler ) );
154         return;
155     }
156     
157     
158     /**
159      * Handles search requests on the RootDSE. 
160      * 
161      * @param session the LdapSession for which this search is conducted 
162      * @param req the search request on the RootDSE
163      * @throws Exception if failures are encountered while searching
164      */
165     private void handleRootDseSearch( LdapSession session, SearchRequest req ) throws Exception
166     {
167         EntryFilteringCursor cursor = null;
168         
169         try
170         {
171             cursor = session.getCoreSession().search( req );
172             
173             // Position the cursor at the beginning
174             cursor.beforeFirst();
175             boolean hasRootDSE = false;
176             
177             while ( cursor.next() )
178             {
179             	if ( hasRootDSE )
180             	{
181             		// This is an error ! We should never find more than one rootDSE !
182             	    LOG.error( "Got back more than one entry for search on RootDSE which means " +
183             	    		"Cursor is not functioning properly!" );
184             	}
185             	else
186             	{
187             		hasRootDSE = true;
188 	                ClonedServerEntry entry = cursor.get();
189 	                session.getIoSession().write( generateResponse( session, req, entry ) );
190             	}
191             }
192     
193             // write the SearchResultDone message
194             session.getIoSession().write( req.getResultResponse() );
195         }
196         finally
197         {
198         	// Close the cursor now.
199             if ( cursor != null )
200             {
201                 try
202                 {
203                     cursor.close();
204                 }
205                 catch ( NamingException e )
206                 {
207                     LOG.error( "failed on list.close()", e );
208                 }
209             }
210         }
211     }
212     
213     
214     /**
215      * Based on the server maximum time limits configured for search and the 
216      * requested time limits this method determines if at all to replace the 
217      * default ClosureMonitor of the result set Cursor with one that closes
218      * the Cursor when either server mandated or request mandated time limits 
219      * are reached.
220      *
221      * @param req the {@link SearchRequest} issued
222      * @param session the {@link LdapSession} on which search was requested
223      * @param cursor the {@link EntryFilteringCursor} over the search results
224      */
225     private void setTimeLimitsOnCursor( SearchRequest req, LdapSession session, final EntryFilteringCursor cursor )
226     {
227         // Don't bother setting time limits for administrators
228         if ( session.getCoreSession().isAnAdministrator() && req.getTimeLimit() == NO_TIME_LIMIT )
229         {
230             return;
231         }
232         
233         /*
234          * Non administrator based searches are limited by time if the server 
235          * has been configured with unlimited time and the request specifies 
236          * unlimited search time
237          */
238         if ( ldapService.getMaxTimeLimit() == NO_TIME_LIMIT && req.getTimeLimit() == NO_TIME_LIMIT )
239         {
240             return;
241         }
242         
243         /*
244          * If the non-administrator user specifies unlimited time but the server 
245          * is configured to limit the search time then we limit by the max time 
246          * allowed by the configuration 
247          */
248         if ( req.getTimeLimit() == 0 )
249         {
250             cursor.setClosureMonitor( new SearchTimeLimitingMonitor( ldapService.getMaxTimeLimit(), TimeUnit.SECONDS ) );
251             return;
252         }
253         
254         /*
255          * If the non-administrative user specifies a time limit equal to or 
256          * less than the maximum limit configured in the server then we 
257          * constrain search by the amount specified in the request
258          */
259         if ( ldapService.getMaxTimeLimit() >= req.getTimeLimit() )
260         {
261             cursor.setClosureMonitor( new SearchTimeLimitingMonitor( req.getTimeLimit(), TimeUnit.SECONDS ) );
262             return;
263         }
264 
265         /*
266          * Here the non-administrative user's requested time limit is greater 
267          * than what the server's configured maximum limit allows so we limit
268          * the search to the configured limit
269          */
270         cursor.setClosureMonitor( new SearchTimeLimitingMonitor( ldapService.getMaxTimeLimit(), TimeUnit.SECONDS ) );
271     }
272     
273     
274     private int getSearchSizeLimits( SearchRequest req, LdapSession session )
275     {
276         LOG.debug( "req size limit = {}, configured size limit = {}", req.getSizeLimit(), 
277             ldapService.getMaxSizeLimit() );
278         
279         // Don't bother setting size limits for administrators that don't ask for it
280         if ( session.getCoreSession().isAnAdministrator() && req.getSizeLimit() == NO_SIZE_LIMIT )
281         {
282             return NO_SIZE_LIMIT;
283         }
284         
285         // Don't bother setting size limits for administrators that don't ask for it
286         if ( session.getCoreSession().isAnAdministrator() )
287         {
288             return req.getSizeLimit();
289         }
290         
291         /*
292          * Non administrator based searches are limited by size if the server 
293          * has been configured with unlimited size and the request specifies 
294          * unlimited search size
295          */
296         if ( ldapService.getMaxSizeLimit() == NO_SIZE_LIMIT && req.getSizeLimit() == NO_SIZE_LIMIT )
297         {
298             return NO_SIZE_LIMIT;
299         }
300         
301         /*
302          * If the non-administrator user specifies unlimited size but the server 
303          * is configured to limit the search size then we limit by the max size
304          * allowed by the configuration 
305          */
306         if ( req.getSizeLimit() == 0 )
307         {
308             return ldapService.getMaxSizeLimit();
309         }
310         
311         if ( ldapService.getMaxSizeLimit() == NO_SIZE_LIMIT )
312         {
313             return req.getSizeLimit();
314         }
315         
316         /*
317          * If the non-administrative user specifies a size limit equal to or 
318          * less than the maximum limit configured in the server then we 
319          * constrain search by the amount specified in the request
320          */
321         if ( ldapService.getMaxSizeLimit() >= req.getSizeLimit() )
322         {
323             return req.getSizeLimit();
324         }
325         
326         /*
327          * Here the non-administrative user's requested size limit is greater 
328          * than what the server's configured maximum limit allows so we limit
329          * the search to the configured limit
330          */
331         return ldapService.getMaxSizeLimit();
332     }
333     
334     
335     /**
336      * Conducts a simple search across the result set returning each entry 
337      * back except for the search response done.  This is calculated but not
338      * returned so the persistent search mechanism can leverage this method
339      * along with standard search.
340      *
341      * @param session the LDAP session object for this request
342      * @param req the search request 
343      * @return the result done 
344      * @throws Exception if there are failures while processing the request
345      */
346     private SearchResponseDone doSimpleSearch( LdapSession session, SearchRequest req ) 
347         throws Exception
348     {
349         /*
350          * Iterate through all search results building and sending back responses
351          * for each search result returned.
352          */
353         EntryFilteringCursor cursor = null;
354         
355         try
356         {
357             LdapResult ldapResult = req.getResultResponse().getLdapResult();
358             cursor = session.getCoreSession().search( req );
359             req.addAbandonListener( new SearchAbandonListener( ldapService, cursor ) );
360             setTimeLimitsOnCursor( req, session, cursor );
361             final int sizeLimit = getSearchSizeLimits( req, session );
362             LOG.debug( "using {} for size limit", sizeLimit );
363             
364             // Position the cursor at the beginning
365             cursor.beforeFirst();
366 
367             if ( sizeLimit == NO_SIZE_LIMIT )
368             {
369                 while ( cursor.next() )
370                 {
371                     if ( session.getIoSession().isClosing() )
372                     {
373                         break;
374                     }
375                     ClonedServerEntry entry = cursor.get();
376                     session.getIoSession().write( generateResponse( session, req, entry ) );
377                 }
378             }
379             else
380             {
381                 int count = 0;
382                 while ( cursor.next() )
383                 {
384                     if ( session.getIoSession().isClosing() )
385                     {
386                         break;
387                     }
388                     if ( count < sizeLimit )
389                     {
390                         ClonedServerEntry entry = cursor.get();
391                         session.getIoSession().write( generateResponse( session, req, entry ) );
392                         count++;
393                     }
394                     else
395                     {
396                         // DO NOT WRITE THE RESPONSE - JUST RETURN IT
397                         ldapResult.setResultCode( ResultCodeEnum.SIZE_LIMIT_EXCEEDED );
398                         return ( SearchResponseDone ) req.getResultResponse();
399                     }  
400                 }
401             }
402     
403             // DO NOT WRITE THE RESPONSE - JUST RETURN IT
404             ldapResult.setResultCode( ResultCodeEnum.SUCCESS );
405             return ( SearchResponseDone ) req.getResultResponse();
406         }
407         finally
408         {
409             if ( cursor != null )
410             {
411                 try
412                 {
413                     cursor.close();
414                 }
415                 catch ( NamingException e )
416                 {
417                     LOG.error( "failed on list.close()", e );
418                 }
419             }
420         }
421     }
422     
423 
424     /**
425      * Generates a response for an entry retrieved from the server core based 
426      * on the nature of the request with respect to referral handling.  This 
427      * method will either generate a SearchResponseEntry or a 
428      * SearchResponseReference depending on if the entry is a referral or if 
429      * the ManageDSAITControl has been enabled.
430      *
431      * @param req the search request
432      * @param entry the entry to be handled
433      * @return the response for the entry
434      * @throws Exception if there are problems in generating the response
435      */
436     private Response generateResponse( LdapSession session, SearchRequest req, ClonedServerEntry entry ) throws Exception
437     {
438         EntryAttribute ref = entry.getOriginalEntry().get( SchemaConstants.REF_AT );
439         boolean hasManageDsaItControl = req.getControls().containsKey( ManageDsaITControl.CONTROL_OID );
440 
441         if ( ref != null && ! hasManageDsaItControl )
442         {
443             SearchResponseReference respRef;
444             respRef = new SearchResponseReferenceImpl( req.getMessageId() );
445             respRef.setReferral( new ReferralImpl() );
446             
447             for ( Value<?> val : ref )
448             {
449                 String url = ( String ) val.get();
450                 
451                 if ( ! url.startsWith( "ldap" ) )
452                 {
453                     respRef.getReferral().addLdapUrl( url );
454                 }
455                 
456                 LdapURL ldapUrl = new LdapURL();
457                 ldapUrl.setForceScopeRendering( true );
458                 try
459                 {
460                     ldapUrl.parse( url.toCharArray() );
461                 }
462                 catch ( LdapURLEncodingException e )
463                 {
464                     LOG.error( "Bad URL ({}) for ref in {}.  Reference will be ignored.", url, entry );
465                 }
466 
467                 switch( req.getScope() )
468                 {
469                     case SUBTREE:
470                         ldapUrl.setScope( SearchScope.SUBTREE.getJndiScope() );
471                         break;
472                     case ONELEVEL: // one level here is object level on remote server
473                         ldapUrl.setScope( SearchScope.OBJECT.getJndiScope() );
474                         break;
475                     default:
476                         throw new IllegalStateException( "Unexpected base scope." );
477                 }
478                 
479                 respRef.getReferral().addLdapUrl( ldapUrl.toString() );
480             }
481             
482             return respRef;
483         }
484         else 
485         {
486             SearchResponseEntry respEntry;
487             respEntry = new SearchResponseEntryImpl( req.getMessageId() );
488             respEntry.setEntry( entry );
489             respEntry.setObjectName( entry.getDn() );
490             
491             return respEntry;
492         }
493     }
494     
495     
496     /**
497      * Alters the filter expression based on the presence of the 
498      * ManageDsaIT control.  If the control is not present, the search
499      * filter will be altered to become a disjunction with two terms.
500      * The first term is the original filter.  The second term is a
501      * (objectClass=referral) assertion.  When OR'd together these will
502      * make sure we get all referrals so we can process continuations 
503      * properly without having the filter remove them from the result 
504      * set.
505      * 
506      * NOTE: original filter is first since most entries are not referrals 
507      * so it has a higher probability on average of accepting and shorting 
508      * evaluation before having to waste cycles trying to evaluate if the 
509      * entry is a referral.
510      *
511      * @param session the session to use to construct the filter (schema access)
512      * @param req the request to get the original filter from
513      * @throws Exception if there are schema access problems
514      */
515     public void modifyFilter( LdapSession session, SearchRequest req ) throws Exception
516     {
517         if ( req.hasControl( ManageDsaITControl.CONTROL_OID ) )
518         {
519             return;
520         }
521         
522         /*
523          * Do not add the OR'd (objectClass=referral) expression if the user 
524          * searches for the subSchemaSubEntry as the SchemaIntercepter can't 
525          * handle an OR'd filter.
526          */
527         if ( isSubSchemaSubEntrySearch( session, req ) )
528         {
529             return;
530         }
531         
532         /*
533          * Most of the time the search filter is just (objectClass=*) and if 
534          * this is the case then there's no reason at all to OR this with an
535          * (objectClass=referral).  If we detect this case then we leave it 
536          * as is to represent the OR condition:
537          * 
538          *  (| (objectClass=referral)(objectClass=*)) == (objectClass=*)
539          */
540         if ( req.getFilter() instanceof PresenceNode )
541         {
542             PresenceNode presenceNode = ( PresenceNode ) req.getFilter();
543             
544             AttributeType at = session.getCoreSession().getDirectoryService()
545                 .getRegistries().getAttributeTypeRegistry().lookup( presenceNode.getAttribute() );
546             if ( at.getOid().equals( SchemaConstants.OBJECT_CLASS_AT_OID ) )
547             {
548                 return;
549             }
550         }
551 
552         // using varags to add two expressions to an OR node 
553         req.setFilter( new OrNode( req.getFilter(), newIsReferralEqualityNode( session ) ) );
554     }
555     
556     
557     /**
558      * Main message handing method for search requests.  This will be called 
559      * even if the ManageDsaIT control is present because the super class does
560      * not know that the search operation has more to do after finding the 
561      * base.  The call to this means that finding the base can ignore 
562      * referrals.
563      * 
564      * @param session the associated session
565      * @param req the received SearchRequest
566      */
567     public void handleIgnoringReferrals( LdapSession session, LdapDN reqTargetDn, 
568         ClonedServerEntry entry, SearchRequest req )
569     {
570         if ( IS_DEBUG )
571         {
572             LOG.debug( "Message received:  {}", req.toString() );
573         }
574 
575         // add the search request to the registry of outstanding requests for this session
576         session.registerOutstandingRequest( req );
577 
578         try
579         {
580             // modify the filter to affect continuation support
581             modifyFilter( session, req );
582             
583             // ===============================================================
584             // Handle search in rootDSE differently.
585             // ===============================================================
586             if ( isRootDSESearch( req ) )
587             {
588                 handleRootDseSearch( session, req );
589                 return;
590             }
591 
592             // ===============================================================
593             // Handle psearch differently
594             // ===============================================================
595 
596             PersistentSearchControl psearchControl = ( PersistentSearchControl ) 
597                 req.getControls().get( PersistentSearchControl.CONTROL_OID );
598             
599             if ( psearchControl != null )
600             {
601                 handlePersistentSearch( session, req, psearchControl );
602                 
603                 // do not unregister the outstanding request unlike below
604                 return;
605             }
606 
607             // ===============================================================
608             // Handle regular search requests from here down
609             // ===============================================================
610 
611             SearchResponseDone done = doSimpleSearch( session, req );
612             session.getIoSession().write( done );
613             session.unregisterOutstandingRequest( req );
614         }
615         catch ( Exception e )
616         {
617             /*
618              * From RFC 2251 Section 4.11:
619              *
620              * In the event that a server receives an Abandon Request on a Search
621              * operation in the midst of transmitting responses to the Search, that
622              * server MUST cease transmitting entry responses to the abandoned
623              * request immediately, and MUST NOT send the SearchResultDone. Of
624              * course, the server MUST ensure that only properly encoded LDAPMessage
625              * PDUs are transmitted.
626              *
627              * SO DON'T SEND BACK ANYTHING!!!!!
628              */
629             if ( e instanceof OperationAbandonedException )
630             {
631                 return;
632             }
633 
634             handleException( session, req, e );
635         }
636     }
637 
638 
639     /**
640      * Determines if a search request is on the RootDSE of the server.
641      * 
642      * It is a RootDSE search if :
643      * - the base DN is empty
644      * - and the scope is BASE OBJECT
645      * - and the filter is (ObjectClass = *)
646      * 
647      * (RFC 4511, 5.1, par. 1 & 2)
648      *
649      * @param req the request issued
650      * @return true if the search is on the RootDSE false otherwise
651      */
652     private static boolean isRootDSESearch( SearchRequest req )
653     {
654         boolean isBaseIsRoot = req.getBase().isEmpty();
655         boolean isBaseScope = req.getScope() == SearchScope.OBJECT;
656         boolean isRootDSEFilter = false;
657         
658         if ( req.getFilter() instanceof PresenceNode )
659         {
660             String attribute = ( ( PresenceNode ) req.getFilter() ).getAttribute();
661             isRootDSEFilter = attribute.equalsIgnoreCase( SchemaConstants.OBJECT_CLASS_AT ) ||
662                                 attribute.equals( SchemaConstants.OBJECT_CLASS_AT_OID );
663         }
664         
665         return isBaseIsRoot && isBaseScope && isRootDSEFilter;
666     }
667     
668     
669     /**
670      * <p>
671      * Determines if a search request is a subSchemaSubEntry search.
672      * </p>
673      * <p>
674      * It is a schema search if:
675      * - the base DN is the DN of the subSchemaSubEntry of the root DSE
676      * - and the scope is BASE OBJECT
677      * - and the filter is (objectClass=subschema)
678      * (RFC 4512, 4.4,)
679      * </p>
680      * <p>
681      * However in this method we only check the first condition to avoid
682      * performance issues.
683      * </p>
684      * 
685      * @param session the LDAP session
686      * @param req the request issued
687      * 
688      * @return true if the search is on the subSchemaSubEntry, false otherwise
689      * 
690      * @throws Exception the exception
691      */
692     private static boolean isSubSchemaSubEntrySearch( LdapSession session, SearchRequest req ) throws Exception
693     {
694         LdapDN base = req.getBase();
695         String baseNormForm = ( base.isNormalized() ? base.getNormName() : base.toNormName() );
696 
697         DirectoryService ds = session.getCoreSession().getDirectoryService();
698         PartitionNexus nexus = ds.getPartitionNexus();
699         Value<?> subschemaSubentry = nexus.getRootDSE( null ).get( SchemaConstants.SUBSCHEMA_SUBENTRY_AT ).get();
700         LdapDN subschemaSubentryDn = new LdapDN( ( String ) ( subschemaSubentry.get() ) );
701         subschemaSubentryDn.normalize( ds.getRegistries().getAttributeTypeRegistry().getNormalizerMapping() );
702         String subschemaSubentryDnNorm = subschemaSubentryDn.getNormName();
703         
704         return subschemaSubentryDnNorm.equals( baseNormForm );
705     }
706 }