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