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.request;
21  
22  
23  import static java.lang.Math.min;
24  import static org.apache.directory.server.ldap.LdapServer.NO_SIZE_LIMIT;
25  import static org.apache.directory.server.ldap.LdapServer.NO_TIME_LIMIT;
26  
27  import java.util.Collection;
28  import java.util.Map;
29  import java.util.concurrent.TimeUnit;
30  
31  import org.apache.commons.lang3.exception.ExceptionUtils;
32  import org.apache.directory.api.ldap.extras.controls.syncrepl.syncRequest.SyncRequestValue;
33  import org.apache.directory.api.ldap.model.constants.SchemaConstants;
34  import org.apache.directory.api.ldap.model.cursor.Cursor;
35  import org.apache.directory.api.ldap.model.cursor.CursorClosedException;
36  import org.apache.directory.api.ldap.model.entry.Attribute;
37  import org.apache.directory.api.ldap.model.entry.Entry;
38  import org.apache.directory.api.ldap.model.entry.Value;
39  import org.apache.directory.api.ldap.model.exception.LdapException;
40  import org.apache.directory.api.ldap.model.exception.LdapOperationException;
41  import org.apache.directory.api.ldap.model.exception.LdapURLEncodingException;
42  import org.apache.directory.api.ldap.model.exception.OperationAbandonedException;
43  import org.apache.directory.api.ldap.model.filter.EqualityNode;
44  import org.apache.directory.api.ldap.model.filter.ExprNode;
45  import org.apache.directory.api.ldap.model.filter.OrNode;
46  import org.apache.directory.api.ldap.model.filter.PresenceNode;
47  import org.apache.directory.api.ldap.model.message.Control;
48  import org.apache.directory.api.ldap.model.message.LdapResult;
49  import org.apache.directory.api.ldap.model.message.MessageTypeEnum;
50  import org.apache.directory.api.ldap.model.message.Referral;
51  import org.apache.directory.api.ldap.model.message.ReferralImpl;
52  import org.apache.directory.api.ldap.model.message.Response;
53  import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
54  import org.apache.directory.api.ldap.model.message.ResultResponseRequest;
55  import org.apache.directory.api.ldap.model.message.SearchRequest;
56  import org.apache.directory.api.ldap.model.message.SearchResultDone;
57  import org.apache.directory.api.ldap.model.message.SearchResultEntry;
58  import org.apache.directory.api.ldap.model.message.SearchResultEntryImpl;
59  import org.apache.directory.api.ldap.model.message.SearchResultReference;
60  import org.apache.directory.api.ldap.model.message.SearchResultReferenceImpl;
61  import org.apache.directory.api.ldap.model.message.SearchScope;
62  import org.apache.directory.api.ldap.model.message.controls.ManageDsaIT;
63  import org.apache.directory.api.ldap.model.message.controls.PagedResults;
64  import org.apache.directory.api.ldap.model.message.controls.PagedResultsImpl;
65  import org.apache.directory.api.ldap.model.message.controls.PersistentSearch;
66  import org.apache.directory.api.ldap.model.name.Dn;
67  import org.apache.directory.api.ldap.model.schema.AttributeType;
68  import org.apache.directory.api.ldap.model.url.LdapUrl;
69  import org.apache.directory.api.util.Strings;
70  import org.apache.directory.server.core.api.DirectoryService;
71  import org.apache.directory.server.core.api.ReferralManager;
72  import org.apache.directory.server.core.api.entry.ClonedServerEntry;
73  import org.apache.directory.server.core.api.event.EventType;
74  import org.apache.directory.server.core.api.event.NotificationCriteria;
75  import org.apache.directory.server.core.api.filtering.EntryFilteringCursor;
76  import org.apache.directory.server.core.api.partition.PartitionNexus;
77  import org.apache.directory.server.i18n.I18n;
78  import org.apache.directory.server.ldap.LdapSession;
79  import org.apache.directory.server.ldap.handlers.LdapRequestHandler;
80  import org.apache.directory.server.ldap.handlers.PersistentSearchListener;
81  import org.apache.directory.server.ldap.handlers.SearchAbandonListener;
82  import org.apache.directory.server.ldap.handlers.SearchTimeLimitingMonitor;
83  import org.apache.directory.server.ldap.handlers.controls.PagedSearchContext;
84  import org.apache.directory.server.ldap.replication.provider.ReplicationRequestHandler;
85  import org.slf4j.Logger;
86  import org.slf4j.LoggerFactory;
87  
88  
89  /**
90   * A MessageReceived handler for processing search requests.
91   *
92   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
93   */
94  public class SearchRequestHandler extends LdapRequestHandler<SearchRequest>
95  {
96      /** The logger */
97      private static final Logger LOG = LoggerFactory.getLogger( SearchRequestHandler.class );
98  
99      private static final Logger SEARCH_TIME_LOG = LoggerFactory.getLogger( "org.apache.directory.server.ldap.handlers.request.SEARCH_TIME_LOG" );
100 
101     /** Speedup for logs */
102     private static final boolean IS_DEBUG = LOG.isDebugEnabled();
103 
104     /** The replication handler */
105     protected ReplicationRequestHandler replicationReqHandler;
106 
107 
108     /**
109      * Constructs a new filter EqualityNode asserting that a candidate
110      * objectClass is a referral.
111      *
112      * @param session the {@link LdapSession} to construct the node for
113      * @return the {@link org.apache.directory.api.ldap.model.filter.EqualityNode} (objectClass=referral) non-normalized
114      * @throws Exception in the highly unlikely event of schema related failures
115      */
116     private EqualityNode<String> newIsReferralEqualityNode( LdapSession session ) throws Exception
117     {
118         AttributeType objectClassAT = session.getCoreSession().getDirectoryService().getAtProvider().getObjectClass();
119 
120         return new EqualityNode<>( objectClassAT, new Value( objectClassAT, SchemaConstants.REFERRAL_OC ) );
121     }
122 
123 
124     /**
125      * Handles search requests containing the persistent search decorator but
126      * delegates to doSimpleSearch() if the changesOnly parameter of the
127      * decorator is set to false.
128      *
129      * @param session the LdapSession for which this search is conducted
130      * @param req the search request containing the persistent search decorator
131      * @param psearchDecorator the persistent search decorator extracted
132      * @throws Exception if failures are encountered while searching
133      */
134     private void handlePersistentSearch( LdapSession session, SearchRequest req, PersistentSearch psearch ) throws Exception
135     {
136         /*
137          * We want the search to complete first before we start listening to
138          * events when the decorator does NOT specify changes ONLY mode.
139          */
140         if ( !psearch.isChangesOnly() )
141         {
142             SearchResultDone done = doSimpleSearch( session, req );
143 
144             // ok if normal search beforehand failed somehow quickly abandon psearch
145             if ( done.getLdapResult().getResultCode() != ResultCodeEnum.SUCCESS )
146             {
147                 session.getIoSession().write( done );
148 
149                 return;
150             }
151         }
152 
153         if ( req.isAbandoned() )
154         {
155             return;
156         }
157 
158         // now we process entries forever as they change
159         PersistentSearchListenerrchListener.html#PersistentSearchListener">PersistentSearchListener persistentSearchListener = new PersistentSearchListener( session, req );
160 
161         // compose notification criteria and add the listener to the event
162         // service using that notification criteria to determine which events
163         // are to be delivered to the persistent search issuing client
164         NotificationCriteriai/event/NotificationCriteria.html#NotificationCriteria">NotificationCriteria criteria = new NotificationCriteria( session.getCoreSession().getDirectoryService().getSchemaManager() );
165         criteria.setAliasDerefMode( req.getDerefAliases() );
166         criteria.setBase( req.getBase() );
167         criteria.setFilter( req.getFilter() );
168         criteria.setScope( req.getScope() );
169         criteria.setEventMask( EventType.getEventTypes( psearch.getChangeTypes() ) );
170         getLdapServer().getDirectoryService().getEventService().addListener( persistentSearchListener, criteria );
171         req.addAbandonListener( new SearchAbandonListener( ldapServer, persistentSearchListener ) );
172     }
173 
174 
175     /**
176      * {@inheritDoc}
177      */
178     @Override
179     public final void handle( LdapSession session, SearchRequest req ) throws Exception
180     {
181         if ( IS_DEBUG )
182         {
183             LOG.debug( "Handling single reply request: {}", req );
184         }
185 
186         // check first for the syncrepl search request decorator
187         if ( req.getControls().containsKey( SyncRequestValue.OID ) )
188         {
189             handleReplication( session, req );
190         }
191         // if we have the ManageDSAIt decorator, go directly
192         // to the handling without pre-processing the request
193         else if ( req.getControls().containsKey( ManageDsaIT.OID ) )
194         {
195             // If the ManageDsaIT decorator is present, we will
196             // consider that the user wants to get entry which
197             // are referrals as plain entry. We have to return
198             // SearchResponseEntry elements instead of
199             // SearchResponseReference elements.
200             LOG.debug( "ManageDsaITControl detected." );
201             handleIgnoringReferrals( session, req );
202         }
203         else
204         {
205             // No ManageDsaIT decorator. If the found entries is a referral,
206             // we will return SearchResponseReference elements.
207             LOG.debug( "ManageDsaITControl NOT detected." );
208 
209             if ( req.getType() == MessageTypeEnum.SEARCH_REQUEST )
210             {
211                 handleWithReferrals( session, req );
212             }
213             else
214             {
215                 throw new IllegalStateException( I18n.err( I18n.ERR_685, req ) );
216             }
217         }
218     }
219 
220 
221     /**
222      * Handle the replication request.
223      */
224     private void handleReplication( LdapSession session, SearchRequest searchRequest ) throws LdapException
225     {
226         SearchResultDone done = ( SearchResultDone ) searchRequest.getResultResponse();
227 
228         if ( replicationReqHandler != null )
229         {
230             replicationReqHandler.handleSyncRequest( session, searchRequest );
231         }
232         else
233         {
234             // Replication is not allowed on this server. generate a error message
235             LOG.warn( "This server does not allow replication" );
236             LdapResult result = done.getLdapResult();
237 
238             result.setDiagnosticMessage( "Replication is not allowed on this server" );
239             result.setResultCode( ResultCodeEnum.OTHER );
240             session.getIoSession().write( done );
241         }
242     }
243 
244 
245     /**
246      * Handles a simple lookup, or a RootDSE lookup.
247      *
248      * @param session the LdapSession for which this search is conducted
249      * @param req the search request on the RootDSE
250      * @throws Exception if failures are encountered while searching
251      */
252     private void handleLookup( LdapSession session, SearchRequest req ) throws Exception
253     {
254         Map<String, Control> controlMap = req.getControls();
255         Control[] controls = null;
256 
257         if ( controlMap != null )
258         {
259             Collection<Control> controlValues = controlMap.values();
260 
261             controls = new Control[controlValues.size()];
262             int pos = 0;
263 
264             for ( Control control : controlMap.values() )
265             {
266                 controls[pos++] = control;
267             }
268         }
269 
270         Entry entry = session.getCoreSession().lookup(
271             req.getBase(),
272             controls,
273             req.getAttributes().toArray( new String[]
274                 {} ) );
275 
276         session.getIoSession().write( generateResponse( session, req, entry ) );
277 
278         // write the SearchResultDone message
279         session.getIoSession().write( req.getResultResponse() );
280     }
281 
282 
283     /**
284      * Based on the server maximum time limits configured for search and the
285      * requested time limits this method determines if at all to replace the
286      * default ClosureMonitor of the result set Cursor with one that closes
287      * the Cursor when either server mandated or request mandated time limits
288      * are reached.
289      *
290      * @param req the {@link SearchRequest} issued
291      * @param session the {@link LdapSession} on which search was requested
292      * @param cursor the {@link EntryFilteringCursor} over the search results
293      */
294     private void setTimeLimitsOnCursor( SearchRequest req, LdapSession session,
295         final Cursor<Entry> cursor )
296     {
297         // Don't bother setting time limits for administrators
298         if ( session.getCoreSession().isAnAdministrator() && req.getTimeLimit() == NO_TIME_LIMIT )
299         {
300             return;
301         }
302 
303         /*
304          * Non administrator based searches are limited by time if the server
305          * has been configured with unlimited time and the request specifies
306          * unlimited search time
307          */
308         if ( ldapServer.getMaxTimeLimit() == NO_TIME_LIMIT && req.getTimeLimit() == NO_TIME_LIMIT )
309         {
310             return;
311         }
312 
313         /*
314          * If the non-administrator user specifies unlimited time but the server
315          * is configured to limit the search time then we limit by the max time
316          * allowed by the configuration
317          */
318         if ( req.getTimeLimit() == 0 )
319         {
320             cursor.setClosureMonitor( new SearchTimeLimitingMonitor( ldapServer.getMaxTimeLimit(), TimeUnit.SECONDS ) );
321             return;
322         }
323 
324         /*
325          * If the non-administrative user specifies a time limit equal to or
326          * less than the maximum limit configured in the server then we
327          * constrain search by the amount specified in the request
328          */
329         if ( ldapServer.getMaxTimeLimit() >= req.getTimeLimit() )
330         {
331             cursor.setClosureMonitor( new SearchTimeLimitingMonitor( req.getTimeLimit(), TimeUnit.SECONDS ) );
332             return;
333         }
334 
335         /*
336          * Here the non-administrative user's requested time limit is greater
337          * than what the server's configured maximum limit allows so we limit
338          * the search to the configured limit
339          */
340         cursor.setClosureMonitor( new SearchTimeLimitingMonitor( ldapServer.getMaxTimeLimit(), TimeUnit.SECONDS ) );
341     }
342 
343 
344     /**
345      * Return the server size limit
346      */
347     private long getServerSizeLimit( LdapSession session, SearchRequest request )
348     {
349         if ( session.getCoreSession().isAnAdministrator() )
350         {
351             if ( request.getSizeLimit() == NO_SIZE_LIMIT )
352             {
353                 return Long.MAX_VALUE;
354             }
355             else
356             {
357                 return request.getSizeLimit();
358             }
359         }
360         else
361         {
362             if ( ldapServer.getMaxSizeLimit() == NO_SIZE_LIMIT )
363             {
364                 return Long.MAX_VALUE;
365             }
366             else
367             {
368                 return ldapServer.getMaxSizeLimit();
369             }
370         }
371     }
372 
373 
374     private void writeResults( LdapSession session, SearchRequest req, LdapResult ldapResult,
375         Cursor<Entry> cursor, long sizeLimit ) throws Exception
376     {
377         long count = 0;
378 
379         while ( ( count < sizeLimit ) && cursor.next() )
380         {
381             // Handle closed session
382             if ( session.getIoSession().isClosing() )
383             {
384                 // The client has closed the connection
385                 if ( IS_DEBUG )
386                 {
387                     LOG.debug( "Request terminated for message {}, the client has closed the session",
388                         req.getMessageId() );
389                 }
390 
391                 break;
392             }
393 
394             if ( req.isAbandoned() )
395             {
396                 cursor.close( new OperationAbandonedException() );
397 
398                 // The cursor has been closed by an abandon request.
399                 if ( IS_DEBUG )
400                 {
401                     LOG.debug( "Request terminated by an AbandonRequest for message {}", req.getMessageId() );
402                 }
403 
404                 break;
405             }
406 
407             Entry entry = cursor.get();
408             session.getIoSession().write( generateResponse( session, req, entry ) );
409 
410             if ( IS_DEBUG )
411             {
412                 LOG.debug( "Sending {}", entry.getDn() );
413             }
414 
415             count++;
416         }
417 
418         // check if the result code is not already set
419         // the result code might be set when sort control is present
420         if ( ldapResult.getResultCode() == null )
421         {
422             // DO NOT WRITE THE RESPONSE - JUST RETURN IT
423             ldapResult.setResultCode( ResultCodeEnum.SUCCESS );
424         }
425 
426         if ( ( count >= sizeLimit ) && ( cursor.next() ) )
427         {
428             // We have reached the limit
429             // Move backward on the cursor to restore the previous position, as we moved forward
430             // to check if there is one more entry available
431             cursor.previous();
432             // Special case if the user has requested more elements than the request size limit
433             ldapResult.setResultCode( ResultCodeEnum.SIZE_LIMIT_EXCEEDED );
434         }
435     }
436 
437 
438     private void readPagedResults( LdapSession session, SearchRequest req, LdapResult ldapResult,
439         Cursor<Entry> cursor, long sizeLimit, int pagedLimit, PagedSearchContext pagedContext,
440         PagedResults pagedResultsControl ) throws Exception
441     {
442         req.addAbandonListener( new SearchAbandonListener( ldapServer, cursor ) );
443         setTimeLimitsOnCursor( req, session, cursor );
444 
445         if ( IS_DEBUG )
446         {
447             LOG.debug( "using <{},{}> for size limit", sizeLimit, pagedLimit );
448         }
449 
450         int cookieValue = 0;
451 
452         int count = pagedContext.getCurrentPosition();
453         int pageCount = 0;
454 
455         while ( ( count < sizeLimit ) && ( pageCount < pagedLimit ) && cursor.next() )
456         {
457             if ( session.getIoSession().isClosing() )
458             {
459                 break;
460             }
461 
462             Entry entry = cursor.get();
463             session.getIoSession().write( generateResponse( session, req, entry ) );
464             count++;
465             pageCount++;
466         }
467 
468         // DO NOT WRITE THE RESPONSE - JUST RETURN IT
469         ldapResult.setResultCode( ResultCodeEnum.SUCCESS );
470 
471         boolean hasMoreEntry = cursor.next();
472 
473         // We have some entry, move back to the first one, as we just moved forward
474         // to get the first entry
475         if ( hasMoreEntry )
476         {
477             cursor.previous();
478         }
479 
480         if ( !hasMoreEntry )
481         {
482             // That means we don't have anymore entry
483             // If we are here, it means we have returned all the entries
484             // We have to remove the cookie from the session
485             cookieValue = pagedContext.getCookieValue();
486             PagedSearchContext psCookie = session.removePagedSearchContext( cookieValue );
487 
488             // Close the cursor if there is one
489             if ( psCookie != null )
490             {
491                 cursor = psCookie.getCursor();
492 
493                 if ( cursor != null )
494                 {
495                     cursor.close();
496                 }
497             }
498 
499             pagedResultsControl = new PagedResultsImpl();
500             pagedResultsControl.setCritical( true );
501             pagedResultsControl.setSize( 0 );
502             req.getResultResponse().addControl( pagedResultsControl );
503         }
504         else
505         {
506             // We have reached one limit
507 
508             if ( count < sizeLimit )
509             {
510                 // We stop here. We have to add a ResponseControl
511                 // DO NOT WRITE THE RESPONSE - JUST RETURN IT
512                 ldapResult.setResultCode( ResultCodeEnum.SUCCESS );
513                 req.getResultResponse().addControl( pagedResultsControl );
514 
515                 // Stores the cursor current position
516                 pagedContext.incrementCurrentPosition( pageCount );
517             }
518             else
519             {
520                 // Return an exception, close the cursor, and clean the session
521                 ldapResult.setResultCode( ResultCodeEnum.SIZE_LIMIT_EXCEEDED );
522 
523                 cursor.close();
524 
525                 session.removePagedSearchContext( cookieValue );
526             }
527         }
528     }
529 
530 
531     /**
532      * Manage the abandoned Paged Search (when paged size = 0). We have to
533      * remove the cookie and its associated cursor from the session.
534      */
535     private SearchResultDone abandonPagedSearch( LdapSession session, SearchRequest req ) throws Exception
536     {
537         PagedResults pagedSearchControl = ( PagedResults ) req.getControls().get( PagedResults.OID );
538         byte[] cookie = pagedSearchControl.getCookie();
539 
540         if ( !Strings.isEmpty( cookie ) )
541         {
542             // If the cookie is not null, we have to destroy the associated
543             // cursor stored into the session (if any)
544             int cookieValue = pagedSearchControl.getCookieValue();
545             PagedSearchContext psCookie = session.removePagedSearchContext( cookieValue );
546             pagedSearchControl.setCookie( psCookie.getCookie() );
547             pagedSearchControl.setSize( 0 );
548             pagedSearchControl.setCritical( true );
549 
550             // Close the cursor
551             Cursor<Entry> cursor = psCookie.getCursor();
552 
553             if ( cursor != null )
554             {
555                 cursor.close();
556             }
557         }
558         else
559         {
560             pagedSearchControl.setSize( 0 );
561             pagedSearchControl.setCritical( true );
562         }
563 
564         // and return
565         // DO NOT WRITE THE RESPONSE - JUST RETURN IT
566         LdapResult ldapResult = req.getResultResponse().getLdapResult();
567         ldapResult.setResultCode( ResultCodeEnum.SUCCESS );
568         req.getResultResponse().addControl( pagedSearchControl );
569 
570         return ( SearchResultDone ) req.getResultResponse();
571     }
572 
573 
574     /**
575      * Remove a cookie instance from the session, if it exists.
576      */
577     private PagedSearchContextory/server/ldap/handlers/controls/PagedSearchContext.html#PagedSearchContext">PagedSearchContext removeContext( LdapSession session, PagedSearchContext cookieInstance )
578     {
579         if ( cookieInstance == null )
580         {
581             return null;
582         }
583 
584         int cookieValue = cookieInstance.getCookieValue();
585 
586         return session.removePagedSearchContext( cookieValue );
587     }
588 
589 
590     /**
591      * Handle a Paged Search request.
592      */
593     private SearchResultDone doPagedSearch( LdapSession session, SearchRequest req, PagedResults control )
594         throws Exception
595     {
596         PagedResults pagedSearchControl = control;
597         PagedResults pagedResultsControl = null;
598 
599         // Get the size limits
600         // Don't bother setting size limits for administrators that don't ask for it
601         long serverLimit = getServerSizeLimit( session, req );
602 
603         long requestLimit = req.getSizeLimit() == 0L ? Long.MAX_VALUE : req.getSizeLimit();
604         long sizeLimit = min( serverLimit, requestLimit );
605 
606         int pagedLimit = pagedSearchControl.getSize();
607         Cursor<Entry> cursor = null;
608         PagedSearchContext pagedContext = null;
609 
610         // We have the following cases :
611         // 1) The SIZE is 0 and the cookie is the same than the previous one : this
612         // is a abandon request for this paged search.
613         // 2) The cookie is empty : this is a new request. If the requested
614         // size is above the serverLimit and the request limit, this is a normal
615         // search
616         // 3) The cookie is not empty and the request is the same, we return
617         // the next SIZE elements
618         // 4) The cookie is not empty, but the request is not the same : this is
619         // a new request (we have to discard the cookie and do a new search from
620         // the beginning)
621         // 5) The SIZE is above the size-limit : the request is treated as if it
622         // was a simple search
623 
624         // Case 1
625         if ( pagedLimit == 0L )
626         {
627             // An abandoned paged search
628             return abandonPagedSearch( session, req );
629         }
630 
631         // Now, depending on the cookie, we will deal with case 2, 3, 4 and 5
632         byte[] cookie = pagedSearchControl.getCookie();
633         LdapResult ldapResult = req.getResultResponse().getLdapResult();
634 
635         if ( Strings.isEmpty( cookie ) )
636         {
637             // No cursor : do a search.
638             cursor = session.getCoreSession().search( req );
639 
640             // Position the cursor at the beginning
641             cursor.beforeFirst();
642 
643             // This is a new search. We have a special case when the paged size
644             // is above the server size limit : in this case, we default to a
645             // standard search
646             if ( pagedLimit > sizeLimit )
647             {
648                 // Normal search : create the cursor, and set pagedControl to false
649                 try
650                 {
651                     // And write the entries
652                     writeResults( session, req, ldapResult, cursor, sizeLimit );
653                 }
654                 finally
655                 {
656                     try
657                     {
658                         cursor.close();
659                     }
660                     catch ( Exception e )
661                     {
662                         LOG.error( I18n.err( I18n.ERR_168 ), e );
663                     }
664                 }
665 
666                 // If we had a cookie in the session, remove it
667                 removeContext( session, pagedContext );
668 
669                 return ( SearchResultDone ) req.getResultResponse();
670             }
671             else
672             {
673                 // Case 2 : create the context
674                 pagedContext = new PagedSearchContext( req );
675 
676                 session.addPagedSearchContext( pagedContext );
677                 cookie = pagedContext.getCookie();
678                 pagedResultsControl = new PagedResultsImpl();
679                 pagedResultsControl.setCookie( cookie );
680                 pagedResultsControl.setSize( 0 );
681                 pagedResultsControl.setCritical( true );
682 
683                 // And stores the cursor into the session
684                 pagedContext.setCursor( cursor );
685             }
686         }
687         else
688         {
689             // We have a cookie
690             // Either case 3, 4 or 5
691             int cookieValue = pagedSearchControl.getCookieValue();
692             pagedContext = session.getPagedSearchContext( cookieValue );
693 
694             if ( pagedContext == null )
695             {
696                 // We didn't found the cookie into the session : it must be invalid
697                 // send an error.
698                 ldapResult.setDiagnosticMessage( "Invalid cookie for this PagedSearch request." );
699                 ldapResult.setResultCode( ResultCodeEnum.UNWILLING_TO_PERFORM );
700 
701                 return ( SearchResultDone ) req.getResultResponse();
702             }
703 
704             if ( pagedContext.hasSameRequest( req, session ) )
705             {
706                 // Case 3 : continue the search
707                 cursor = pagedContext.getCursor();
708 
709                 // get the cookie
710                 cookie = pagedContext.getCookie();
711                 pagedResultsControl = new PagedResultsImpl();
712                 pagedResultsControl.setCookie( cookie );
713                 pagedResultsControl.setSize( 0 );
714                 pagedResultsControl.setCritical( true );
715 
716             }
717             else
718             {
719                 // case 2 : create a new cursor
720                 // We have to close the cursor
721                 cursor = pagedContext.getCursor();
722 
723                 if ( cursor != null )
724                 {
725                     cursor.close();
726                 }
727 
728                 // Now create a new context and stores it into the session
729                 pagedContext = new PagedSearchContext( req );
730 
731                 session.addPagedSearchContext( pagedContext );
732 
733                 cookie = pagedContext.getCookie();
734                 pagedResultsControl = new PagedResultsImpl();
735                 pagedResultsControl.setCookie( cookie );
736                 pagedResultsControl.setSize( 0 );
737                 pagedResultsControl.setCritical( true );
738             }
739         }
740 
741         // Now, do the real search
742         /*
743          * Iterate through all search results building and sending back responses
744          * for each search result returned.
745          */
746         try
747         {
748             readPagedResults( session, req, ldapResult, cursor, sizeLimit, pagedLimit, pagedContext,
749                 pagedResultsControl );
750         }
751         catch ( Exception e )
752         {
753             if ( cursor != null )
754             {
755                 try
756                 {
757                     cursor.close();
758                 }
759                 catch ( Exception ne )
760                 {
761                     LOG.error( I18n.err( I18n.ERR_168 ), ne );
762                 }
763             }
764         }
765 
766         return ( SearchResultDone ) req.getResultResponse();
767     }
768 
769 
770     /**
771      * Conducts a simple search across the result set returning each entry
772      * back except for the search response done.  This is calculated but not
773      * returned so the persistent search mechanism can leverage this method
774      * along with standard search.<br>
775      * <br>
776      * @param session the LDAP session object for this request
777      * @param req the search request
778      * @return the result done
779      * @throws Exception if there are failures while processing the request
780      */
781     private SearchResultDone doSimpleSearch( LdapSession session, SearchRequest req ) throws Exception
782     {
783         LdapResult ldapResult = req.getResultResponse().getLdapResult();
784 
785         // Check if we are using the Paged Search Control
786         Object control = req.getControls().get( PagedResults.OID );
787 
788         if ( control != null )
789         {
790             // Let's deal with the pagedControl
791             return doPagedSearch( session, req, ( PagedResults ) control );
792         }
793 
794         // A normal search
795         // Check that we have a cursor or not.
796         // No cursor : do a search.
797         Cursor<Entry> cursor = session.getCoreSession().search( req );
798 
799         // register the request in the session
800         session.registerSearchRequest( req, cursor );
801 
802         // Position the cursor at the beginning
803         cursor.beforeFirst();
804 
805         /*
806          * Iterate through all search results building and sending back responses
807          * for each search result returned.
808          */
809         try
810         {
811             // Get the size limits
812             // Don't bother setting size limits for administrators that don't ask for it
813             long serverLimit = getServerSizeLimit( session, req );
814 
815             long requestLimit = req.getSizeLimit() == 0L ? Long.MAX_VALUE : req.getSizeLimit();
816 
817             req.addAbandonListener( new SearchAbandonListener( ldapServer, cursor ) );
818             setTimeLimitsOnCursor( req, session, cursor );
819 
820             if ( IS_DEBUG )
821             {
822                 LOG.debug( "using <{},{}> for size limit", requestLimit, serverLimit );
823             }
824 
825             long sizeLimit = min( requestLimit, serverLimit );
826 
827             writeResults( session, req, ldapResult, cursor, sizeLimit );
828         }
829         finally
830         {
831             if ( !cursor.isClosed() )
832             {
833                 try
834                 {
835                     cursor.close();
836                 }
837                 catch ( Exception e )
838                 {
839                     LOG.error( I18n.err( I18n.ERR_168 ), e );
840                 }
841             }
842         }
843 
844         return ( SearchResultDone ) req.getResultResponse();
845     }
846 
847 
848     /**
849      * Generates a response for an entry retrieved from the server core based
850      * on the nature of the request with respect to referral handling.  This
851      * method will either generate a SearchResponseEntry or a
852      * SearchResponseReference depending on if the entry is a referral or if
853      * the ManageDSAITControl has been enabled.
854      *
855      * @param req the search request
856      * @param entry the entry to be handled
857      * @return the response for the entry
858      * @throws Exception if there are problems in generating the response
859      */
860     private Response generateResponse( LdapSession session, SearchRequest req, Entry entry ) throws Exception
861     {
862         Attribute ref = entry.get( SchemaConstants.REF_AT );
863         boolean hasManageDsaItControl = req.getControls().containsKey( ManageDsaIT.OID );
864 
865         if ( ( ref != null ) && !hasManageDsaItControl )
866         {
867             // The entry is a referral.
868             SearchResultReference respRef;
869             respRef = new SearchResultReferenceImpl( req.getMessageId() );
870             respRef.setReferral( new ReferralImpl() );
871 
872             for ( Value val : ref )
873             {
874                 String url = val.getString();
875 
876                 if ( !url.startsWith( "ldap" ) )
877                 {
878                     respRef.getReferral().addLdapUrl( url );
879                 }
880 
881                 LdapUrl ldapUrl = null;
882 
883                 try
884                 {
885                     ldapUrl = new LdapUrl( url );
886                     ldapUrl.setForceScopeRendering( true );
887 
888                     switch ( req.getScope() )
889                     {
890                         case SUBTREE:
891                             ldapUrl.setScope( SearchScope.SUBTREE.getScope() );
892                             break;
893 
894                         case ONELEVEL: // one level here is object level on remote server
895                             ldapUrl.setScope( SearchScope.OBJECT.getScope() );
896                             break;
897 
898                         default:
899                             ldapUrl.setScope( SearchScope.OBJECT.getScope() );
900                     }
901                 }
902                 catch ( LdapURLEncodingException e )
903                 {
904                     LOG.error( I18n.err( I18n.ERR_165, url, entry ) );
905                     ldapUrl = new LdapUrl();
906                 }
907 
908                 respRef.getReferral().addLdapUrl( ldapUrl.toString() );
909             }
910 
911             return respRef;
912         }
913         else
914         {
915             // The entry is not a referral, or the ManageDsaIt decorator is set
916             SearchResultEntry respEntry;
917             respEntry = new SearchResultEntryImpl( req.getMessageId() );
918             respEntry.setEntry( entry );
919             respEntry.setObjectName( entry.getDn() );
920 
921             // Filter the userPassword if the server mandate to do so
922             if ( session.getCoreSession().getDirectoryService().isPasswordHidden() )
923             {
924                 // Remove the userPassord attribute from the entry.
925                 respEntry.getEntry().removeAttributes( SchemaConstants.USER_PASSWORD_AT );
926             }
927 
928             return respEntry;
929         }
930     }
931 
932 
933     /**
934      * Alters the filter expression based on the presence of the
935      * ManageDsaIT decorator.  If the decorator is not present, the search
936      * filter will be altered to become a disjunction with two terms.
937      * The first term is the original filter.  The second term is a
938      * (objectClass=referral) assertion.  When OR'd together these will
939      * make sure we get all referrals so we can process continuations
940      * properly without having the filter remove them from the result
941      * set.
942      *
943      * NOTE: original filter is first since most entries are not referrals
944      * so it has a higher probability on average of accepting and shorting
945      * evaluation before having to waste cycles trying to evaluate if the
946      * entry is a referral.
947      *
948      * @param session the session to use to construct the filter (schema access)
949      * @param req the request to get the original filter from
950      * @throws Exception if there are schema access problems
951      */
952     private void modifyFilter( LdapSession session, SearchRequest req ) throws Exception
953     {
954         if ( req.hasControl( ManageDsaIT.OID ) )
955         {
956             return;
957         }
958 
959         /*
960          * Most of the time the search filter is just (objectClass=*) and if
961          * this is the case then there's no reason at all to OR this with an
962          * (objectClass=referral).  If we detect this case then we leave it
963          * as is to represent the OR condition:
964          *
965          *  (| (objectClass=referral)(objectClass=*)) == (objectClass=*)
966          */
967         if ( req.getFilter() instanceof PresenceNode )
968         {
969             PresenceNode presenceNode = ( PresenceNode ) req.getFilter();
970 
971             if ( presenceNode.isSchemaAware() )
972             {
973                 AttributeType attributeType = presenceNode.getAttributeType();
974 
975                 AttributeType objectClassAT = session.getCoreSession().getDirectoryService().getAtProvider().getObjectClass();
976                 if ( attributeType.equals( objectClassAT ) )
977                 {
978                     return;
979                 }
980             }
981             else
982             {
983                 String attribute = presenceNode.getAttribute();
984 
985                 if ( attribute.equalsIgnoreCase( SchemaConstants.OBJECT_CLASS_AT )
986                     || attribute.equalsIgnoreCase( SchemaConstants.OBJECT_CLASS_AT_OID ) )
987                 {
988                     return;
989                 }
990             }
991         }
992 
993         /*
994          * Do not add the OR'd (objectClass=referral) expression if the user
995          * searches for the subSchemaSubEntry as the SchemaIntercepter can't
996          * handle an OR'd filter.
997          */
998         if ( isSubSchemaSubEntrySearch( session, req ) )
999         {
1000             return;
1001         }
1002 
1003         // using varags to add two expressions to an OR node
1004         req.setFilter( new OrNode( req.getFilter(), newIsReferralEqualityNode( session ) ) );
1005     }
1006 
1007 
1008     /**
1009      * Handles the RootDSE and lookups searches
1010      */
1011     private boolean handleLookupAndRootDse( LdapSession session, SearchRequest req ) throws Exception
1012     {
1013         boolean isBaseScope = req.getScope() == SearchScope.OBJECT;
1014         boolean isObjectClassFilter = false;
1015 
1016         if ( req.getFilter() instanceof PresenceNode )
1017         {
1018             ExprNode filter = req.getFilter();
1019 
1020             if ( filter.isSchemaAware() )
1021             {
1022                 AttributeType attributeType = ( ( PresenceNode ) req.getFilter() ).getAttributeType();
1023                 isObjectClassFilter = attributeType.equals( session.getCoreSession().getDirectoryService()
1024                     .getAtProvider().getObjectClass() );
1025             }
1026             else
1027             {
1028                 String attribute = ( ( PresenceNode ) req.getFilter() ).getAttribute();
1029                 isObjectClassFilter = attribute.equalsIgnoreCase( SchemaConstants.OBJECT_CLASS_AT )
1030                     || attribute.equals( SchemaConstants.OBJECT_CLASS_AT_OID );
1031             }
1032         }
1033 
1034         /*
1035                 if ( isBaseScope && isObjectClassFilter )
1036                 {
1037                     // This is a lookup
1038                     handleLookup( session, req );
1039 
1040                     return true;
1041                 }
1042                 else
1043                 {
1044                     // a standard search
1045                     return false;
1046                 }
1047         */
1048         boolean isBaseIsRoot = req.getBase().isEmpty();
1049 
1050         if ( isBaseScope && isObjectClassFilter )
1051         {
1052             if ( isBaseIsRoot )
1053             {
1054                 // This is a rootDse lookup
1055                 handleLookup( session, req );
1056 
1057                 return true;
1058             }
1059             else
1060             {
1061                 // This is a lookup
1062                 //handleLookup( session, req );
1063 
1064                 return false;
1065             }
1066         }
1067         else
1068         {
1069             // a standard search
1070             return false;
1071         }
1072     }
1073 
1074 
1075     /**
1076      * Main message handing method for search requests.  This will be called
1077      * even if the ManageDsaIT decorator is present because the super class does
1078      * not know that the search operation has more to do after finding the
1079      * base.  The call to this means that finding the base can ignore
1080      * referrals.
1081      *
1082      * @param session the associated session
1083      * @param req the received SearchRequest
1084      */
1085     private void handleIgnoringReferrals( LdapSession session, SearchRequest req )
1086     {
1087         if ( IS_DEBUG )
1088         {
1089             LOG.debug( "Message received:  {}", req );
1090         }
1091 
1092         // A flag set if we have a persistent search
1093         boolean isPersistentSearch = false;
1094 
1095         // A flag set when we've got an exception while processing a
1096         // persistent search
1097         boolean persistentSearchException = false;
1098 
1099         // add the search request to the registry of outstanding requests for this session
1100         session.registerOutstandingRequest( req );
1101 
1102         try
1103         {
1104             // ===============================================================
1105             // Handle search in rootDSE and simple lookups differently.
1106             // ===============================================================
1107             if ( handleLookupAndRootDse( session, req ) )
1108             {
1109                 return;
1110             }
1111 
1112             // modify the filter to affect continuation support
1113             modifyFilter( session, req );
1114 
1115             // ===============================================================
1116             // Handle psearch differently
1117             // ===============================================================
1118 
1119             PersistentSearch psearch = ( PersistentSearch ) req.getControls().get( PersistentSearch.OID );
1120 
1121             if ( psearch != null )
1122             {
1123                 // Set the flag to avoid the request being removed
1124                 // from the session
1125                 isPersistentSearch = true;
1126 
1127                 handlePersistentSearch( session, req, psearch );
1128 
1129                 return;
1130             }
1131 
1132             // ===============================================================
1133             // Handle regular search requests from here down
1134             // ===============================================================
1135 
1136             boolean isLogSearchTime = SEARCH_TIME_LOG.isDebugEnabled();
1137 
1138             long t0 = 0;
1139             String filter = null;
1140 
1141             if ( isLogSearchTime )
1142             {
1143                 t0 = System.nanoTime();
1144                 filter = req.getFilter().toString();
1145             }
1146 
1147             SearchResultDone done = doSimpleSearch( session, req );
1148             session.getIoSession().write( done );
1149 
1150             if ( isLogSearchTime )
1151             {
1152                 long t1 = System.nanoTime();
1153                 SEARCH_TIME_LOG.debug( "Search with filter {} took {}ms. Filter with assigned counts is {}", filter,
1154                     ( ( t1 - t0 ) / 1000000 ), req.getFilter() );
1155             }
1156         }
1157         catch ( Exception e )
1158         {
1159             /*
1160              * From RFC 2251 Section 4.11:
1161              *
1162              * In the event that a server receives an Abandon Request on a Search
1163              * operation in the midst of transmitting responses to the Search, that
1164              * server MUST cease transmitting entry responses to the abandoned
1165              * request immediately, and MUST NOT send the SearchResultDone. Of
1166              * course, the server MUST ensure that only properly encoded LDAPMessage
1167              * PDUs are transmitted.
1168              *
1169              * SO DON'T SEND BACK ANYTHING!!!!!
1170              */
1171             if ( e instanceof OperationAbandonedException )
1172             {
1173                 return;
1174             }
1175 
1176             // If it was a persistent search and if we had an exception,
1177             // we set the flag to remove the request from the session
1178             if ( isPersistentSearch )
1179             {
1180                 persistentSearchException = true;
1181             }
1182 
1183             handleException( session, req, e );
1184         }
1185         finally
1186         {
1187 
1188             // remove the request from the session, except if
1189             // we didn't got an exception for a Persistent search
1190             if ( !isPersistentSearch || persistentSearchException )
1191             {
1192                 session.unregisterOutstandingRequest( req );
1193             }
1194         }
1195     }
1196 
1197 
1198     /**
1199      * Handles processing with referrals without ManageDsaIT decorator.
1200      */
1201     private void handleWithReferrals( LdapSession session, SearchRequest req ) throws LdapException
1202     {
1203         LdapResult result = req.getResultResponse().getLdapResult();
1204         Entry entry = null;
1205         boolean isReferral = false;
1206         boolean isparentReferral = false;
1207         DirectoryService directoryService = session.getCoreSession().getDirectoryService();
1208         ReferralManager referralManager = directoryService.getReferralManager();
1209         Dn reqTargetDn = req.getBase();
1210 
1211         if ( !reqTargetDn.isSchemaAware() )
1212         {
1213             reqTargetDn = new Dn( directoryService.getSchemaManager(), reqTargetDn );
1214             req.setBase( reqTargetDn );
1215         }
1216 
1217         // Check if the entry itself is a referral
1218         referralManager.lockRead();
1219 
1220         try
1221         {
1222             isReferral = referralManager.isReferral( reqTargetDn );
1223 
1224             if ( !isReferral )
1225             {
1226                 // Check if the entry has a parent which is a referral
1227                 isparentReferral = referralManager.hasParentReferral( reqTargetDn );
1228             }
1229         }
1230         finally
1231         {
1232             // Unlock the ReferralManager
1233             referralManager.unlock();
1234         }
1235 
1236         if ( !isReferral && !isparentReferral )
1237         {
1238             // This is not a referral and it does not have a parent which
1239             // is a referral : standard case, just deal with the request
1240             if ( IS_DEBUG )
1241             {
1242                 LOG.debug( "Entry {} is NOT a referral.", reqTargetDn );
1243             }
1244 
1245             handleIgnoringReferrals( session, req );
1246         }
1247         else
1248         {
1249             // -------------------------------------------------------------------
1250             // Lookup Entry
1251             // -------------------------------------------------------------------
1252 
1253             // try to lookup the entry but ignore exceptions when it does not
1254             // exist since entry may not exist but may have an ancestor that is a
1255             // referral - would rather attempt a lookup that fails then do check
1256             // for existence than have to do another lookup to get entry info
1257             try
1258             {
1259                 entry = session.getCoreSession().lookup( reqTargetDn );
1260 
1261                 if ( IS_DEBUG )
1262                 {
1263                     LOG.debug( "Entry for {} was found: ", reqTargetDn, entry );
1264                 }
1265             }
1266             catch ( LdapException e )
1267             {
1268                 /* ignore */
1269                 LOG.debug( "Entry for {} not found.", reqTargetDn );
1270             }
1271             catch ( Exception e )
1272             {
1273                 /* serious and needs handling */
1274                 handleException( session, req, e );
1275 
1276                 return;
1277             }
1278 
1279             // -------------------------------------------------------------------
1280             // Handle Existing Entry
1281             // -------------------------------------------------------------------
1282 
1283             if ( entry != null )
1284             {
1285                 try
1286                 {
1287                     if ( IS_DEBUG )
1288                     {
1289                         LOG.debug( "Entry is a referral: {}", entry );
1290                     }
1291 
1292                     handleReferralEntryForSearch( session, req, entry );
1293                 }
1294                 catch ( Exception e )
1295                 {
1296                     handleException( session, req, e );
1297                 }
1298             }
1299 
1300             // -------------------------------------------------------------------
1301             // Handle Non-existing Entry
1302             // -------------------------------------------------------------------
1303 
1304             // if the entry is null we still have to check for a referral ancestor
1305             // also the referrals need to be adjusted based on the ancestor's ref
1306             // values to yield the correct path to the entry in the target DSAs
1307 
1308             else
1309             {
1310                 // The entry is null : it has a parent referral.
1311                 Entry referralAncestor = null;
1312 
1313                 try
1314                 {
1315                     referralAncestor = getFarthestReferralAncestor( session, reqTargetDn );
1316                 }
1317                 catch ( Exception e )
1318                 {
1319                     handleException( session, req, e );
1320 
1321                     return;
1322                 }
1323 
1324                 if ( referralAncestor == null )
1325                 {
1326                     result.setDiagnosticMessage( "Entry not found." );
1327                     result.setResultCode( ResultCodeEnum.NO_SUCH_OBJECT );
1328                     session.getIoSession().write( req.getResultResponse() );
1329 
1330                     return;
1331                 }
1332 
1333                 // if we get here then we have a valid referral ancestor
1334                 try
1335                 {
1336                     Referral referral = getReferralOnAncestorForSearch( session, req, referralAncestor );
1337 
1338                     result.setResultCode( ResultCodeEnum.REFERRAL );
1339                     result.setReferral( referral );
1340                     session.getIoSession().write( req.getResultResponse() );
1341                 }
1342                 catch ( Exception e )
1343                 {
1344                     handleException( session, req, e );
1345                 }
1346             }
1347         }
1348     }
1349 
1350 
1351     /**
1352      * Handles processing a referral response on a target entry which is a
1353      * referral.  It will for any request that returns an LdapResult in it's
1354      * response.
1355      *
1356      * @param session the session to use for processing
1357      * @param req the request
1358      * @param entry the entry associated with the request
1359      */
1360     private void handleReferralEntryForSearch( LdapSession session, SearchRequest req, Entry entry )
1361         throws Exception
1362     {
1363         LdapResult result = req.getResultResponse().getLdapResult();
1364         ReferralImpl referral = new ReferralImpl();
1365         result.setReferral( referral );
1366         result.setResultCode( ResultCodeEnum.REFERRAL );
1367         result.setDiagnosticMessage( "Encountered referral attempting to handle request." );
1368         result.setMatchedDn( req.getBase() );
1369 
1370         Attribute refAttr = ( ( ClonedServerEntry ) entry ).getOriginalEntry().get( SchemaConstants.REF_AT );
1371 
1372         for ( Value refval : refAttr )
1373         {
1374             String refstr = refval.getString();
1375 
1376             // need to add non-ldap URLs as-is
1377             if ( !refstr.startsWith( "ldap" ) )
1378             {
1379                 referral.addLdapUrl( refstr );
1380                 continue;
1381             }
1382 
1383             // parse the ref value and normalize the Dn
1384             LdapUrl ldapUrl = null;
1385 
1386             try
1387             {
1388                 ldapUrl = new LdapUrl( refstr );
1389             }
1390             catch ( LdapURLEncodingException e )
1391             {
1392                 LOG.error( I18n.err( I18n.ERR_165, refstr, entry ) );
1393                 continue;
1394             }
1395 
1396             ldapUrl.setForceScopeRendering( true );
1397             ldapUrl.setAttributes( req.getAttributes() );
1398             ldapUrl.setScope( req.getScope().getScope() );
1399             referral.addLdapUrl( ldapUrl.toString() );
1400         }
1401 
1402         session.getIoSession().write( req.getResultResponse() );
1403     }
1404 
1405 
1406     /**
1407      * <p>
1408      * Determines if a search request is a subSchemaSubEntry search.
1409      * </p>
1410      * <p>
1411      * It is a schema search if:
1412      * - the base Dn is the Dn of the subSchemaSubEntry of the root DSE
1413      * - and the scope is BASE OBJECT
1414      * - and the filter is (objectClass=subschema)
1415      * (RFC 4512, 4.4,)
1416      * </p>
1417      * <p>
1418      * However in this method we only check the first condition to avoid
1419      * performance issues.
1420      * </p>
1421      *
1422      * @param session the LDAP session
1423      * @param req the request issued
1424      *
1425      * @return true if the search is on the subSchemaSubEntry, false otherwise
1426      *
1427      * @throws Exception the exception
1428      */
1429     private boolean isSubSchemaSubEntrySearch( LdapSession session, SearchRequest req ) throws Exception
1430     {
1431         Dn base = req.getBase();
1432 
1433         DirectoryService ds = session.getCoreSession().getDirectoryService();
1434         PartitionNexus nexus = ds.getPartitionNexus();
1435 
1436         Value subschemaSubentry = nexus.getRootDseValue( ds.getAtProvider().getSubschemaSubentry() );
1437         Dn subschemaSubentryDn = ds.getDnFactory().create( subschemaSubentry.getString() );
1438 
1439         return subschemaSubentryDn.equals( base );
1440     }
1441 
1442 
1443     /**
1444      * Handles processing with referrals without ManageDsaIT decorator and with
1445      * an ancestor that is a referral.  The original entry was not found and
1446      * the walk of the ancestry returned a referral.
1447      *
1448      * @param session The LdapSession in use
1449      * @param req The SearchRequest
1450      * @param referralAncestor the farthest referral ancestor of the missing
1451      * entry
1452      * @return The found referral
1453      * @throws LdapException If we weren't able to retrieve the referral
1454      */
1455     public Referral getReferralOnAncestorForSearch( LdapSession session, SearchRequest req,
1456         Entry referralAncestor ) throws LdapException
1457     {
1458         if ( IS_DEBUG )
1459         {
1460             LOG.debug( "Inside getReferralOnAncestor()" );
1461         }
1462 
1463         Attribute refAttr = ( ( ClonedServerEntry ) referralAncestor ).getOriginalEntry().get( SchemaConstants.REF_AT );
1464         Referral referral = new ReferralImpl();
1465 
1466         for ( Value value : refAttr )
1467         {
1468             String ref = value.getString();
1469 
1470             if ( IS_DEBUG )
1471             {
1472                 LOG.debug( "Calculating LdapURL for referrence value {}", ref );
1473             }
1474 
1475             // need to add non-ldap URLs as-is
1476             if ( !ref.startsWith( "ldap" ) )
1477             {
1478                 referral.addLdapUrl( ref );
1479                 continue;
1480             }
1481 
1482             // Parse the ref value
1483             LdapUrl ldapUrl = null;
1484 
1485             try
1486             {
1487                 ldapUrl = new LdapUrl( ref );
1488             }
1489             catch ( LdapURLEncodingException e )
1490             {
1491                 LOG.error( I18n.err( I18n.ERR_165, ref, referralAncestor ) );
1492                 ldapUrl = new LdapUrl();
1493             }
1494 
1495             // Normalize the Dn to check for same dn
1496             Dn urlDn = new Dn( session.getCoreSession().getDirectoryService()
1497                 .getSchemaManager(), ldapUrl.getDn().getName() );
1498 
1499             if ( urlDn.equals( req.getBase() ) )
1500             {
1501                 ldapUrl.setForceScopeRendering( true );
1502                 ldapUrl.setAttributes( req.getAttributes() );
1503                 ldapUrl.setScope( req.getScope().getScope() );
1504                 referral.addLdapUrl( ldapUrl.toString() );
1505                 continue;
1506             }
1507 
1508             /*
1509              * If we get here then the Dn of the referral was not the same as the
1510              * Dn of the ref LDAP URL.  We must calculate the remaining (difference)
1511              * name past the farthest referral Dn which the target name extends.
1512              */
1513             Dn suffix = req.getBase().getDescendantOf( referralAncestor.getDn() );
1514             Dn refDn = urlDn.add( suffix );
1515 
1516             ldapUrl.setDn( refDn );
1517             ldapUrl.setForceScopeRendering( true );
1518             ldapUrl.setAttributes( req.getAttributes() );
1519             ldapUrl.setScope( req.getScope().getScope() );
1520             referral.addLdapUrl( ldapUrl.toString() );
1521         }
1522 
1523         return referral;
1524     }
1525 
1526 
1527     /**
1528      * Handles processing with referrals without ManageDsaIT decorator and with
1529      * an ancestor that is a referral.  The original entry was not found and
1530      * the walk of the ancestry returned a referral.
1531      *
1532      * @param session The LdapSession in use
1533      * @param reqTargetDn the request target Dn
1534      * @param req The SearchRequest
1535      * @param referralAncestor the farthest referral ancestor of the missing
1536      * entry
1537      * @return The found referral
1538      * @throws LdapException If we weren't able to retrieve the ancestor
1539      */
1540     public Referral getReferralOnAncestor( LdapSession session, Dn reqTargetDn, SearchRequest req,
1541         Entry referralAncestor ) throws LdapException
1542     {
1543         if ( IS_DEBUG )
1544         {
1545             LOG.debug( "Inside getReferralOnAncestor()" );
1546         }
1547 
1548         Attribute refAttr = ( ( ClonedServerEntry ) referralAncestor ).getOriginalEntry().get( SchemaConstants.REF_AT );
1549         Referral referral = new ReferralImpl();
1550 
1551         for ( Value value : refAttr )
1552         {
1553             String ref = value.getString();
1554 
1555             if ( IS_DEBUG )
1556             {
1557                 LOG.debug( "Calculating LdapURL for referrence value {}", ref );
1558             }
1559 
1560             // need to add non-ldap URLs as-is
1561             if ( !ref.startsWith( "ldap" ) )
1562             {
1563                 referral.addLdapUrl( ref );
1564                 continue;
1565             }
1566 
1567             // parse the ref value and normalize the Dn
1568             LdapUrl ldapUrl = null;
1569 
1570             try
1571             {
1572                 ldapUrl = new LdapUrl( ref );
1573             }
1574             catch ( LdapURLEncodingException e )
1575             {
1576                 LOG.error( I18n.err( I18n.ERR_165, ref, referralAncestor ) );
1577                 ldapUrl = new LdapUrl();
1578             }
1579 
1580             Dn urlDn = new Dn( session.getCoreSession().getDirectoryService()
1581                 .getSchemaManager(), ldapUrl.getDn().getName() );
1582 
1583             if ( urlDn.equals( referralAncestor.getDn() ) )
1584             {
1585                 // according to the protocol there is no need for the dn since it is the same as this request
1586                 StringBuilder buf = new StringBuilder();
1587                 buf.append( ldapUrl.getScheme() );
1588                 buf.append( ldapUrl.getHost() );
1589 
1590                 if ( ldapUrl.getPort() > 0 )
1591                 {
1592                     buf.append( ":" );
1593                     buf.append( ldapUrl.getPort() );
1594                 }
1595 
1596                 referral.addLdapUrl( buf.toString() );
1597                 continue;
1598             }
1599 
1600             /*
1601              * If we get here then the Dn of the referral was not the same as the
1602              * Dn of the ref LDAP URL.  We must calculate the remaining (difference)
1603              * name past the farthest referral Dn which the target name extends.
1604              */
1605             Dn suffix = req.getBase().getDescendantOf( referralAncestor.getDn() );
1606             urlDn = urlDn.add( suffix );
1607 
1608             StringBuilder buf = new StringBuilder();
1609             buf.append( ldapUrl.getScheme() );
1610             buf.append( ldapUrl.getHost() );
1611 
1612             if ( ldapUrl.getPort() > 0 )
1613             {
1614                 buf.append( ":" );
1615                 buf.append( ldapUrl.getPort() );
1616             }
1617 
1618             buf.append( "/" );
1619             buf.append( LdapUrl.urlEncode( urlDn.getName(), false ) );
1620             referral.addLdapUrl( buf.toString() );
1621         }
1622 
1623         return referral;
1624     }
1625 
1626 
1627     /**
1628      * Handles processing with referrals without ManageDsaIT decorator.
1629      */
1630     public void handleException( LdapSession session, ResultResponseRequest req, Exception e )
1631     {
1632         SearchResultDone done = ( SearchResultDone ) req.getResultResponse();
1633         LdapResult result = done.getLdapResult();
1634         Exception cause = null;
1635 
1636         /*
1637          * Set the result code or guess the best option.
1638          */
1639         ResultCodeEnum code;
1640 
1641         if ( e instanceof CursorClosedException )
1642         {
1643             cause = ( Exception ) ( ( CursorClosedException ) e ).getCause();
1644 
1645             if ( cause == null )
1646             {
1647                 cause = e;
1648             }
1649         }
1650         else
1651         {
1652             cause = e;
1653         }
1654 
1655         if ( cause instanceof LdapOperationException )
1656         {
1657             code = ( ( LdapOperationException ) cause ).getResultCode();
1658         }
1659         else
1660         {
1661             code = ResultCodeEnum.getBestEstimate( cause, req.getType() );
1662         }
1663 
1664         result.setResultCode( code );
1665 
1666         /*
1667          * Setup the error message to put into the request and put entire
1668          * exception into the message if we are in debug mode.  Note we
1669          * embed the result code name into the message.
1670          */
1671         String msg = code.toString() + ": failed for " + req + ": " + cause.getLocalizedMessage();
1672 
1673         if ( IS_DEBUG )
1674         {
1675             LOG.debug( msg, cause );
1676             msg += ":\n" + ExceptionUtils.getStackTrace( cause );
1677         }
1678 
1679         result.setDiagnosticMessage( msg );
1680 
1681         if ( cause instanceof LdapOperationException )
1682         {
1683             LdapOperationException ne = ( LdapOperationException ) cause;
1684 
1685             // Add the matchedDN if necessary
1686             boolean setMatchedDn = code == ResultCodeEnum.NO_SUCH_OBJECT || code == ResultCodeEnum.ALIAS_PROBLEM
1687                 || code == ResultCodeEnum.INVALID_DN_SYNTAX || code == ResultCodeEnum.ALIAS_DEREFERENCING_PROBLEM;
1688 
1689             if ( ( ne.getResolvedDn() != null ) && setMatchedDn )
1690             {
1691                 result.setMatchedDn( ne.getResolvedDn() );
1692             }
1693         }
1694 
1695         session.getIoSession().write( done );
1696     }
1697 
1698 
1699     /**
1700      * Searches up the ancestry of a Dn searching for the farthest referral
1701      * ancestor.  This is required to properly handle referrals.  Note that
1702      * this function is quite costly since it attempts to lookup all the
1703      * ancestors up the hierarchy just to see if they represent referrals.
1704      * Techniques can be employed later to improve this performance hit by
1705      * having an intelligent referral cache.
1706      *
1707      * @param session The LdapSession in use
1708      * @param target the base Dn
1709      * @return the farthest referral ancestor or null
1710      */
1711     // This will suppress PMD.EmptyCatchBlock warnings in this method
1712     public static final Entry getFarthestReferralAncestor( LdapSession session, Dn target )
1713     {
1714         Entry entry;
1715         Entry farthestReferralAncestor = null;
1716         Dn dn = target;
1717 
1718         dn = dn.getParent();
1719 
1720         while ( !dn.isEmpty() )
1721         {
1722             if ( IS_DEBUG )
1723             {
1724                 LOG.debug( "Walking ancestors of {} to find referrals.", dn );
1725             }
1726 
1727             try
1728             {
1729                 entry = session.getCoreSession().lookup( dn );
1730 
1731                 boolean isReferral = ( ( ClonedServerEntry ) entry ).getOriginalEntry().contains(
1732                     SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.REFERRAL_OC );
1733 
1734                 if ( isReferral )
1735                 {
1736                     farthestReferralAncestor = entry;
1737                 }
1738 
1739                 dn = dn.getParent();
1740             }
1741             catch ( LdapException e )
1742             {
1743                 if ( IS_DEBUG )
1744                 {
1745                     LOG.debug( "Entry for {} not found.", dn );
1746                 }
1747 
1748                 // update the Dn as we strip last component
1749                 dn = dn.getParent();
1750             }
1751         }
1752 
1753         return farthestReferralAncestor;
1754     }
1755 
1756 
1757     /**
1758      * Install the replication handler when it's allowed by this server
1759      * @param replicationReqHandler The replication handler provider
1760      */
1761     public void setReplicationReqHandler( ReplicationRequestHandler replicationReqHandler )
1762     {
1763         this.replicationReqHandler = replicationReqHandler;
1764     }
1765 }