001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 *
019 */
020package org.apache.directory.server.ldap.handlers.request;
021
022
023import static java.lang.Math.min;
024import static org.apache.directory.server.ldap.LdapServer.NO_SIZE_LIMIT;
025import static org.apache.directory.server.ldap.LdapServer.NO_TIME_LIMIT;
026
027import java.util.Collection;
028import java.util.Map;
029import java.util.concurrent.TimeUnit;
030
031import org.apache.commons.lang3.exception.ExceptionUtils;
032import org.apache.directory.api.ldap.extras.controls.syncrepl.syncRequest.SyncRequestValue;
033import org.apache.directory.api.ldap.model.constants.SchemaConstants;
034import org.apache.directory.api.ldap.model.cursor.Cursor;
035import org.apache.directory.api.ldap.model.cursor.CursorClosedException;
036import org.apache.directory.api.ldap.model.entry.Attribute;
037import org.apache.directory.api.ldap.model.entry.Entry;
038import org.apache.directory.api.ldap.model.entry.Value;
039import org.apache.directory.api.ldap.model.exception.LdapException;
040import org.apache.directory.api.ldap.model.exception.LdapOperationException;
041import org.apache.directory.api.ldap.model.exception.LdapURLEncodingException;
042import org.apache.directory.api.ldap.model.exception.OperationAbandonedException;
043import org.apache.directory.api.ldap.model.filter.EqualityNode;
044import org.apache.directory.api.ldap.model.filter.ExprNode;
045import org.apache.directory.api.ldap.model.filter.OrNode;
046import org.apache.directory.api.ldap.model.filter.PresenceNode;
047import org.apache.directory.api.ldap.model.message.Control;
048import org.apache.directory.api.ldap.model.message.LdapResult;
049import org.apache.directory.api.ldap.model.message.MessageTypeEnum;
050import org.apache.directory.api.ldap.model.message.Referral;
051import org.apache.directory.api.ldap.model.message.ReferralImpl;
052import org.apache.directory.api.ldap.model.message.Response;
053import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
054import org.apache.directory.api.ldap.model.message.ResultResponseRequest;
055import org.apache.directory.api.ldap.model.message.SearchRequest;
056import org.apache.directory.api.ldap.model.message.SearchResultDone;
057import org.apache.directory.api.ldap.model.message.SearchResultEntry;
058import org.apache.directory.api.ldap.model.message.SearchResultEntryImpl;
059import org.apache.directory.api.ldap.model.message.SearchResultReference;
060import org.apache.directory.api.ldap.model.message.SearchResultReferenceImpl;
061import org.apache.directory.api.ldap.model.message.SearchScope;
062import org.apache.directory.api.ldap.model.message.controls.ManageDsaIT;
063import org.apache.directory.api.ldap.model.message.controls.PagedResults;
064import org.apache.directory.api.ldap.model.message.controls.PagedResultsImpl;
065import org.apache.directory.api.ldap.model.message.controls.PersistentSearch;
066import org.apache.directory.api.ldap.model.name.Dn;
067import org.apache.directory.api.ldap.model.schema.AttributeType;
068import org.apache.directory.api.ldap.model.url.LdapUrl;
069import org.apache.directory.api.util.Strings;
070import org.apache.directory.server.core.api.DirectoryService;
071import org.apache.directory.server.core.api.ReferralManager;
072import org.apache.directory.server.core.api.entry.ClonedServerEntry;
073import org.apache.directory.server.core.api.event.EventType;
074import org.apache.directory.server.core.api.event.NotificationCriteria;
075import org.apache.directory.server.core.api.filtering.EntryFilteringCursor;
076import org.apache.directory.server.core.api.partition.PartitionNexus;
077import org.apache.directory.server.i18n.I18n;
078import org.apache.directory.server.ldap.LdapSession;
079import org.apache.directory.server.ldap.handlers.LdapRequestHandler;
080import org.apache.directory.server.ldap.handlers.PersistentSearchListener;
081import org.apache.directory.server.ldap.handlers.SearchAbandonListener;
082import org.apache.directory.server.ldap.handlers.SearchTimeLimitingMonitor;
083import org.apache.directory.server.ldap.handlers.controls.PagedSearchContext;
084import org.apache.directory.server.ldap.replication.provider.ReplicationRequestHandler;
085import org.slf4j.Logger;
086import org.slf4j.LoggerFactory;
087
088
089/**
090 * A MessageReceived handler for processing search requests.
091 *
092 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
093 */
094public class SearchRequestHandler extends LdapRequestHandler<SearchRequest>
095{
096    /** The logger */
097    private static final Logger LOG = LoggerFactory.getLogger( SearchRequestHandler.class );
098
099    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        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        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 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}