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 *    https://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.api.dsmlv2.request;
021
022
023import java.util.ArrayList;
024import java.util.List;
025
026import org.apache.directory.api.asn1.DecoderException;
027import org.apache.directory.api.dsmlv2.ParserUtils;
028import org.apache.directory.api.i18n.I18n;
029import org.apache.directory.api.ldap.codec.api.LdapApiService;
030import org.apache.directory.api.ldap.codec.api.LdapCodecConstants;
031import org.apache.directory.api.ldap.model.entry.Value;
032import org.apache.directory.api.ldap.model.exception.LdapException;
033import org.apache.directory.api.ldap.model.exception.LdapSchemaException;
034import org.apache.directory.api.ldap.model.filter.AndNode;
035import org.apache.directory.api.ldap.model.filter.ApproximateNode;
036import org.apache.directory.api.ldap.model.filter.BranchNode;
037import org.apache.directory.api.ldap.model.filter.EqualityNode;
038import org.apache.directory.api.ldap.model.filter.ExprNode;
039import org.apache.directory.api.ldap.model.filter.ExtensibleNode;
040import org.apache.directory.api.ldap.model.filter.GreaterEqNode;
041import org.apache.directory.api.ldap.model.filter.LeafNode;
042import org.apache.directory.api.ldap.model.filter.LessEqNode;
043import org.apache.directory.api.ldap.model.filter.NotNode;
044import org.apache.directory.api.ldap.model.filter.OrNode;
045import org.apache.directory.api.ldap.model.filter.PresenceNode;
046import org.apache.directory.api.ldap.model.filter.SimpleNode;
047import org.apache.directory.api.ldap.model.filter.SubstringNode;
048import org.apache.directory.api.ldap.model.message.AliasDerefMode;
049import org.apache.directory.api.ldap.model.message.Control;
050import org.apache.directory.api.ldap.model.message.MessageTypeEnum;
051import org.apache.directory.api.ldap.model.message.SearchRequest;
052import org.apache.directory.api.ldap.model.message.SearchRequestImpl;
053import org.apache.directory.api.ldap.model.message.SearchResultDone;
054import org.apache.directory.api.ldap.model.message.SearchScope;
055import org.apache.directory.api.ldap.model.name.Dn;
056import org.dom4j.Element;
057import org.dom4j.Namespace;
058import org.dom4j.QName;
059
060
061/**
062 * DSML Decorator for SearchRequest
063 *
064 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
065 */
066public class SearchRequestDsml
067    extends AbstractResultResponseRequestDsml<SearchRequest, SearchResultDone>
068    implements SearchRequest
069{
070    /** Some string constants */
071    private static final String DEREF_ALIASES = "derefAliases";
072    private static final String NAME = "name";
073    private static final String VALUE = "value";
074    
075    /** A temporary storage for a terminal Filter */
076    private Filter terminalFilter;
077
078    /** The current filter. This is used while decoding a PDU */
079    private Filter currentFilter;
080
081    /** The global filter. This is used while decoding a PDU */
082    private Filter topFilter;
083
084
085    /**
086     * Creates a new getDecoratedMessage() of SearchRequestDsml.
087     * 
088     * @param codec The LDAP Service to use
089     */
090    public SearchRequestDsml( LdapApiService codec )
091    {
092        super( codec, new SearchRequestImpl() );
093    }
094
095
096    /**
097     * Creates a new getDecoratedMessage() of SearchRequestDsml.
098     *
099     * @param codec The LDAP Service to use
100     * @param ldapMessage the message to decorate
101     */
102    public SearchRequestDsml( LdapApiService codec, SearchRequest ldapMessage )
103    {
104        super( codec, ldapMessage );
105    }
106
107
108    /**
109     * Gets the search filter associated with this search request.
110     *
111     * @return the expression node for the root of the filter expression tree.
112     */
113    public Filter getCodecFilter()
114    {
115        return topFilter;
116    }
117
118
119    /**
120     * Gets the search filter associated with this search request.
121     *
122     * @return the expression node for the root of the filter expression tree.
123     * @throws LdapSchemaException If the filter is invalid
124     */
125    public ExprNode getFilterNode() throws LdapSchemaException
126    {
127        return transform( topFilter );
128    }
129
130
131    /**
132     * Get the terminal filter
133     *
134     * @return Returns the terminal filter.
135     */
136    public Filter getTerminalFilter()
137    {
138        return terminalFilter;
139    }
140
141
142    /**
143     * Set the terminal filter
144     *
145     * @param terminalFilter the teminalFilter.
146     */
147    public void setTerminalFilter( Filter terminalFilter )
148    {
149        this.terminalFilter = terminalFilter;
150    }
151
152
153    /**
154     * set the currentFilter to its parent
155     */
156    public void endCurrentConnectorFilter()
157    {
158        currentFilter = currentFilter.getParent();
159    }
160
161
162    /**
163     * Add a current filter. We have two cases :
164     * <ul>
165     *   <li>there is no previous current filter : the filter
166     *     is the top level filter</li>
167     *   <li>there is a previous current filter : the filter is added
168     *     to the currentFilter set, and the current filter is changed</li>
169     * </ul>
170     * In any case, the previous current filter will always be a
171     * ConnectorFilter when this method is called.
172     *
173     * @param localFilter The filter to set.
174     * @throws DecoderException If the added filter is invalid
175     */
176    public void addCurrentFilter( Filter localFilter ) throws DecoderException
177    {
178        if ( currentFilter != null )
179        {
180            // Ok, we have a parent. The new Filter will be added to
181            // this parent, and will become the currentFilter if it's a connector.
182            ( ( ConnectorFilter ) currentFilter ).addFilter( localFilter );
183            localFilter.setParent( currentFilter );
184
185            if ( localFilter instanceof ConnectorFilter )
186            {
187                currentFilter = localFilter;
188            }
189        }
190        else
191        {
192            // No parent. This Filter will become the root.
193            currentFilter = localFilter;
194            currentFilter.setParent( null );
195            topFilter = localFilter;
196        }
197    }
198
199
200    /**
201     * Transform the Filter part of a SearchRequest to an ExprNode
202     *
203     * @param filter The filter to be transformed
204     * @return An ExprNode
205     * @throws LdapSchemaException If the filter contains a wrong schema element
206     */
207    @SuppressWarnings({ "rawtypes" })
208    private ExprNode transform( Filter filter ) throws LdapSchemaException
209    {
210        if ( filter != null )
211        {
212            // Transform OR, AND or NOT leaves
213            if ( filter instanceof ConnectorFilter )
214            {
215                BranchNode branch;
216
217                if ( filter instanceof AndFilter )
218                {
219                    branch = new AndNode();
220                }
221                else if ( filter instanceof OrFilter )
222                {
223                    branch = new OrNode();
224                }
225                else
226                {
227                    branch = new NotNode();
228                }
229
230                List<Filter> filtersSet = ( ( ConnectorFilter ) filter ).getFilterSet();
231
232                // Loop on all AND/OR children
233                if ( filtersSet != null )
234                {
235                    for ( Filter node : filtersSet )
236                    {
237                        branch.addNode( transform( node ) );
238                    }
239                }
240
241                return branch;
242            }
243            else
244            {
245                // Transform PRESENT or ATTRIBUTE_VALUE_ASSERTION
246                LeafNode branch = null;
247
248                if ( filter instanceof PresentFilter )
249                {
250                    branch = new PresenceNode( ( ( PresentFilter ) filter ).getAttributeDescription() );
251                }
252                else if ( filter instanceof AttributeValueAssertionFilter )
253                {
254                    AttributeValueAssertionFilter avaFilter = ( AttributeValueAssertionFilter ) filter;
255
256                    AttributeValueAssertion ava = avaFilter.getAssertion();
257
258                    // Transform =, >=, <=, ~= filters
259                    int filterType = avaFilter.getFilterType();
260                    byte[] value = null;
261                    
262                    if ( ava.getAssertionValue() != null )
263                    {
264                        value = ava.getAssertionValue().getBytes();
265                    }
266                    
267                    switch ( filterType )
268                    {
269                        case LdapCodecConstants.EQUALITY_MATCH_FILTER:
270                            branch = new EqualityNode( ava.getAttributeDesc(), value );
271                            break;
272
273                        case LdapCodecConstants.GREATER_OR_EQUAL_FILTER:
274                            branch = new GreaterEqNode( ava.getAttributeDesc(), value );
275                            break;
276
277                        case LdapCodecConstants.LESS_OR_EQUAL_FILTER:
278                            branch = new LessEqNode( ava.getAttributeDesc(), value );
279                            break;
280
281                        case LdapCodecConstants.APPROX_MATCH_FILTER:
282                            branch = new ApproximateNode( ava.getAttributeDesc(), value );
283                            break;
284
285                        default:
286                            throw new IllegalStateException( I18n.err( I18n.ERR_03042_UNEXPECTED_FILTER_TYPE, filterType ) );
287                    }
288
289                }
290                else if ( filter instanceof SubstringFilter )
291                {
292                    // Transform Substring filters
293                    SubstringFilter substrFilter = ( SubstringFilter ) filter;
294                    String initialString = null;
295                    String finalString = null;
296                    List<String> anyString = null;
297
298                    if ( substrFilter.getInitialSubstrings() != null )
299                    {
300                        initialString = substrFilter.getInitialSubstrings();
301                    }
302
303                    if ( substrFilter.getFinalSubstrings() != null )
304                    {
305                        finalString = substrFilter.getFinalSubstrings();
306                    }
307
308                    if ( substrFilter.getAnySubstrings() != null )
309                    {
310                        anyString = new ArrayList<>();
311
312                        for ( String any : substrFilter.getAnySubstrings() )
313                        {
314                            anyString.add( any );
315                        }
316                    }
317
318                    branch = new SubstringNode( anyString, substrFilter.getType(), initialString, finalString );
319                }
320                else if ( filter instanceof ExtensibleMatchFilter )
321                {
322                    // Transform Extensible Match Filter
323                    ExtensibleMatchFilter extFilter = ( ExtensibleMatchFilter ) filter;
324                    String matchingRule = null;
325
326                    Value value = extFilter.getMatchValue();
327
328                    if ( extFilter.getMatchingRule() != null )
329                    {
330                        matchingRule = extFilter.getMatchingRule();
331                    }
332
333                    branch = new ExtensibleNode( extFilter.getType(), value, matchingRule, extFilter.isDnAttributes() );
334                }
335
336                return branch;
337            }
338        }
339        else
340        {
341            // We have found nothing to transform. Return null then.
342            return null;
343        }
344    }
345
346
347    /**
348     * {@inheritDoc}
349     */
350    @Override
351    public MessageTypeEnum getType()
352    {
353        return getDecorated().getType();
354    }
355
356
357    /**
358     * {@inheritDoc}
359     */
360    @Override
361    public Element toDsml( Element root )
362    {
363        Element element = super.toDsml( root );
364
365        SearchRequest request = getDecorated();
366
367        // Dn
368        if ( request.getBase() != null )
369        {
370            element.addAttribute( "dn", request.getBase().getName() );
371        }
372
373        // Scope
374        SearchScope scope = request.getScope();
375        if ( scope != null )
376        {
377            if ( scope == SearchScope.OBJECT )
378            {
379                element.addAttribute( "scope", "baseObject" );
380            }
381            else if ( scope == SearchScope.ONELEVEL )
382            {
383                element.addAttribute( "scope", "singleLevel" );
384            }
385            else if ( scope == SearchScope.SUBTREE )
386            {
387                element.addAttribute( "scope", "wholeSubtree" );
388            }
389        }
390
391        // DerefAliases
392        AliasDerefMode derefAliases = request.getDerefAliases();
393
394        switch ( derefAliases )
395        {
396            case NEVER_DEREF_ALIASES:
397                element.addAttribute( DEREF_ALIASES, "neverDerefAliases" );
398                break;
399
400            case DEREF_ALWAYS:
401                element.addAttribute( DEREF_ALIASES, "derefAlways" );
402                break;
403
404            case DEREF_FINDING_BASE_OBJ:
405                element.addAttribute( DEREF_ALIASES, "derefFindingBaseObj" );
406                break;
407
408            case DEREF_IN_SEARCHING:
409                element.addAttribute( DEREF_ALIASES, "derefInSearching" );
410                break;
411
412            default:
413                throw new IllegalStateException( I18n.err( I18n.ERR_03043_UNEXPECTED_DEREF_ALIAS, derefAliases ) );
414        }
415
416        // SizeLimit
417        if ( request.getSizeLimit() != 0L )
418        {
419            element.addAttribute( "sizeLimit", Long.toString( request.getSizeLimit() ) );
420        }
421
422        // TimeLimit
423        if ( request.getTimeLimit() != 0 )
424        {
425            element.addAttribute( "timeLimit", Integer.toString( request.getTimeLimit() ) );
426        }
427
428        // TypesOnly
429        if ( request.getTypesOnly() )
430        {
431            element.addAttribute( "typesOnly", "true" );
432        }
433
434        // Filter
435        Element filterElement = element.addElement( "filter" );
436        toDsml( filterElement, request.getFilter() );
437
438        // Attributes
439        List<String> attributes = request.getAttributes();
440
441        if ( !attributes.isEmpty() )
442        {
443            Element attributesElement = element.addElement( "attributes" );
444
445            for ( String entryAttribute : attributes )
446            {
447                attributesElement.addElement( "attribute" ).addAttribute( NAME, entryAttribute );
448            }
449        }
450
451        return element;
452    }
453
454
455    /**
456     * Recursively converts the filter of the Search Request into a DSML representation and adds 
457     * it to the XML Element corresponding to the Search Request
458     *
459     * @param element
460     *      the parent Element
461     * @param filter
462     *      the filter to convert
463     */
464    private void toDsml( Element element, ExprNode filter )
465    {
466        // AND FILTER
467        if ( filter instanceof AndNode )
468        {
469            Element newElement = element.addElement( "and" );
470
471            List<ExprNode> filterList = ( ( AndNode ) filter ).getChildren();
472
473            for ( int i = 0; i < filterList.size(); i++ )
474            {
475                toDsml( newElement, filterList.get( i ) );
476            }
477        }
478
479        // OR FILTER
480        else if ( filter instanceof OrNode )
481        {
482            Element newElement = element.addElement( "or" );
483
484            List<ExprNode> filterList = ( ( OrNode ) filter ).getChildren();
485
486            for ( int i = 0; i < filterList.size(); i++ )
487            {
488                toDsml( newElement, filterList.get( i ) );
489            }
490        }
491
492        // NOT FILTER
493        else if ( filter instanceof NotNode )
494        {
495            Element newElement = element.addElement( "not" );
496
497            toDsml( newElement, ( ( NotNode ) filter ).getFirstChild() );
498        }
499
500        // SUBSTRING FILTER
501        else if ( filter instanceof SubstringNode )
502        {
503            Element newElement = element.addElement( "substrings" );
504
505            SubstringNode substringFilter = ( SubstringNode ) filter;
506
507            newElement.addAttribute( NAME, substringFilter.getAttribute() );
508
509            String initial = substringFilter.getInitial();
510
511            if ( ( initial != null ) && ( !"".equals( initial ) ) )
512            {
513                newElement.addElement( "initial" ).setText( initial );
514            }
515
516            List<String> anyList = substringFilter.getAny();
517
518            for ( int i = 0; i < anyList.size(); i++ )
519            {
520                newElement.addElement( "any" ).setText( anyList.get( i ) );
521            }
522
523            String finalString = substringFilter.getFinal();
524
525            if ( ( finalString != null ) && ( !"".equals( finalString ) ) )
526            {
527                newElement.addElement( "final" ).setText( finalString );
528            }
529        }
530
531        // APPROXMATCH, EQUALITYMATCH, GREATEROREQUALS & LESSOREQUAL FILTERS
532        else if ( filter instanceof SimpleNode )
533        {
534            Element newElement;
535
536            if ( filter instanceof ApproximateNode )
537            {
538                newElement = element.addElement( "approxMatch" );
539            }
540            else if ( filter instanceof EqualityNode )
541            {
542                newElement = element.addElement( "equalityMatch" );
543            }
544            else if ( filter instanceof GreaterEqNode )
545            {
546                newElement = element.addElement( "greaterOrEqual" );
547            }
548            else
549            // it is a LessEqNode )
550            {
551                newElement = element.addElement( "lessOrEqual" );
552            }
553
554            String attributeName = ( ( SimpleNode<?> ) filter ).getAttribute();
555            newElement.addAttribute( NAME, attributeName );
556
557            Value value = ( ( SimpleNode<?> ) filter ).getValue();
558            
559            if ( value != null )
560            {
561                if ( ParserUtils.needsBase64Encoding( value ) )
562                {
563                    Namespace xsdNamespace = new Namespace( "xsd", ParserUtils.XML_SCHEMA_URI );
564                    Namespace xsiNamespace = new Namespace( "xsi", ParserUtils.XML_SCHEMA_INSTANCE_URI );
565                    element.getDocument().getRootElement().add( xsdNamespace );
566                    element.getDocument().getRootElement().add( xsiNamespace );
567
568                    Element valueElement = newElement.addElement( VALUE ).addText(
569                        ParserUtils.base64Encode( value ) );
570                    valueElement
571                        .addAttribute( new QName( "type", xsiNamespace ), "xsd:" + ParserUtils.BASE64BINARY );
572                }
573                else
574                {
575                    newElement.addElement( VALUE ).setText( value.getString() );
576                }
577            }
578        }
579
580        // PRESENT FILTER
581        else if ( filter instanceof PresenceNode )
582        {
583            Element newElement = element.addElement( "present" );
584
585            newElement.addAttribute( NAME, ( ( PresenceNode ) filter ).getAttribute() );
586        }
587
588        // EXTENSIBLEMATCH
589        else if ( filter instanceof ExtensibleNode )
590        {
591            Element newElement = element.addElement( "extensibleMatch" );
592
593            Value value = ( ( ExtensibleNode ) filter ).getValue();
594            
595            if ( value != null )
596            {
597                if ( ParserUtils.needsBase64Encoding( value ) )
598                {
599                    Namespace xsdNamespace = new Namespace( "xsd", ParserUtils.XML_SCHEMA_URI );
600                    Namespace xsiNamespace = new Namespace( "xsi", ParserUtils.XML_SCHEMA_INSTANCE_URI );
601                    element.getDocument().getRootElement().add( xsdNamespace );
602                    element.getDocument().getRootElement().add( xsiNamespace );
603
604                    Element valueElement = newElement.addElement( VALUE ).addText(
605                        ParserUtils.base64Encode( value.getString() ) );
606                    valueElement.addAttribute( new QName( "type", xsiNamespace ), "xsd:" + ParserUtils.BASE64BINARY );
607                }
608                else
609                {
610                    newElement.addElement( VALUE ).setText( value.getString() );
611                }
612            }
613
614            if ( ( ( ExtensibleNode ) filter ).hasDnAttributes() )
615            {
616                newElement.addAttribute( "dnAttributes", "true" );
617            }
618
619            String matchingRule = ( ( ExtensibleNode ) filter ).getMatchingRuleId();
620            if ( ( matchingRule != null ) && ( "".equals( matchingRule ) ) )
621            {
622                newElement.addAttribute( "matchingRule", matchingRule );
623            }
624        }
625    }
626
627
628    /**
629     * {@inheritDoc}
630     */
631    @Override
632    public MessageTypeEnum[] getResponseTypes()
633    {
634        return getDecorated().getResponseTypes();
635    }
636
637
638    /**
639     * {@inheritDoc}
640     */
641    @Override
642    public Dn getBase()
643    {
644        return getDecorated().getBase();
645    }
646
647
648    /**
649     * {@inheritDoc}
650     */
651    @Override
652    public SearchRequest setBase( Dn baseDn )
653    {
654        getDecorated().setBase( baseDn );
655
656        return this;
657    }
658
659
660    /**
661     * {@inheritDoc}
662     */
663    @Override
664    public SearchScope getScope()
665    {
666        return getDecorated().getScope();
667    }
668
669
670    /**
671     * {@inheritDoc}
672     */
673    @Override
674    public SearchRequest setScope( SearchScope scope )
675    {
676        getDecorated().setScope( scope );
677
678        return this;
679    }
680
681
682    /**
683     * {@inheritDoc}
684     */
685    @Override
686    public AliasDerefMode getDerefAliases()
687    {
688        return getDecorated().getDerefAliases();
689    }
690
691
692    /**
693     * {@inheritDoc}
694     */
695    @Override
696    public SearchRequest setDerefAliases( AliasDerefMode aliasDerefAliases )
697    {
698        getDecorated().setDerefAliases( aliasDerefAliases );
699
700        return this;
701    }
702
703
704    /**
705     * {@inheritDoc}
706     */
707    @Override
708    public long getSizeLimit()
709    {
710        return getDecorated().getSizeLimit();
711    }
712
713
714    /**
715     * {@inheritDoc}
716     */
717    @Override
718    public SearchRequest setSizeLimit( long entriesMax )
719    {
720        getDecorated().setSizeLimit( entriesMax );
721
722        return this;
723    }
724
725
726    /**
727     * {@inheritDoc}
728     */
729    @Override
730    public int getTimeLimit()
731    {
732        return getDecorated().getTimeLimit();
733    }
734
735
736    /**
737     * {@inheritDoc}
738     */
739    @Override
740    public SearchRequest setTimeLimit( int secondsMax )
741    {
742        getDecorated().setTimeLimit( secondsMax );
743
744        return this;
745    }
746
747
748    /**
749     * {@inheritDoc}
750     */
751    @Override
752    public boolean getTypesOnly()
753    {
754        return getDecorated().getTypesOnly();
755    }
756
757
758    /**
759     * {@inheritDoc}
760     */
761    @Override
762    public SearchRequest setTypesOnly( boolean typesOnly )
763    {
764        getDecorated().setTypesOnly( typesOnly );
765
766        return this;
767    }
768
769
770    /**
771     * {@inheritDoc}
772     */
773    @Override
774    public ExprNode getFilter()
775    {
776        return getDecorated().getFilter();
777    }
778
779
780    /**
781     * {@inheritDoc}
782     */
783    @Override
784    public SearchRequest setFilter( ExprNode filter )
785    {
786        getDecorated().setFilter( filter );
787
788        return this;
789    }
790
791
792    /**
793     * {@inheritDoc}
794     */
795    @Override
796    public SearchRequest setFilter( String filter ) throws LdapException
797    {
798        getDecorated().setFilter( filter );
799
800        return this;
801    }
802
803
804    /**
805     * {@inheritDoc}
806     */
807    @Override
808    public List<String> getAttributes()
809    {
810        return getDecorated().getAttributes();
811    }
812
813
814    /**
815     * {@inheritDoc}
816     */
817    @Override
818    public SearchRequest addAttributes( String... attributes )
819    {
820        getDecorated().addAttributes( attributes );
821
822        return this;
823    }
824
825
826    /**
827     * {@inheritDoc}
828     */
829    @Override
830    public SearchRequest removeAttribute( String attribute )
831    {
832        getDecorated().removeAttribute( attribute );
833
834        return this;
835    }
836
837
838    /**
839     * {@inheritDoc}
840     */
841    @Override
842    public SearchRequest setMessageId( int messageId )
843    {
844        return ( SearchRequest ) super.setMessageId( messageId );
845    }
846
847
848    /**
849     * {@inheritDoc}
850     */
851    @Override
852    public SearchRequest addControl( Control control )
853    {
854        return ( SearchRequest ) super.addControl( control );
855    }
856
857
858    /**
859     * {@inheritDoc}
860     */
861    @Override
862    public SearchRequest addAllControls( Control[] controls )
863    {
864        return ( SearchRequest ) super.addAllControls( controls );
865    }
866
867
868    /**
869     * {@inheritDoc}
870     */
871    @Override
872    public SearchRequest removeControl( Control control )
873    {
874        return ( SearchRequest ) super.removeControl( control );
875    }
876
877
878    /**
879     * {@inheritDoc}
880     */
881    @Override
882    public boolean isFollowReferrals()
883    {
884        return getDecorated().isFollowReferrals();
885    }
886
887
888    /**
889     * {@inheritDoc}
890     */
891    @Override
892    public SearchRequest followReferrals()
893    {
894        return getDecorated().followReferrals();
895    }
896
897
898    /**
899     * {@inheritDoc}
900     */
901    @Override
902    public boolean isIgnoreReferrals()
903    {
904        return getDecorated().isIgnoreReferrals();
905    }
906
907
908    /**
909     * {@inheritDoc}
910     */
911    @Override
912    public SearchRequest ignoreReferrals()
913    {
914        return getDecorated().ignoreReferrals();
915    }
916}