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.api.ldap.model.message;
021
022
023import java.text.ParseException;
024import java.util.ArrayList;
025import java.util.Arrays;
026import java.util.Collections;
027import java.util.List;
028
029import org.apache.directory.api.ldap.model.exception.LdapException;
030import org.apache.directory.api.ldap.model.exception.LdapProtocolErrorException;
031import org.apache.directory.api.ldap.model.filter.BranchNormalizedVisitor;
032import org.apache.directory.api.ldap.model.filter.ExprNode;
033import org.apache.directory.api.ldap.model.filter.FilterParser;
034import org.apache.directory.api.ldap.model.name.Dn;
035import org.apache.directory.api.util.Strings;
036
037
038/**
039 * SearchRequest implementation.
040 * 
041 * @author <a href="mailto:dev@directory.apache.org"> Apache Directory Project</a>
042 */
043public class SearchRequestImpl extends AbstractAbandonableRequest implements SearchRequest
044{
045    static final long serialVersionUID = -5655881944020886218L;
046
047    /** Search base distinguished name */
048    private Dn baseDn;
049
050    /** Search filter expression tree's root node */
051    private ExprNode filterNode;
052
053    /** Search scope enumeration value */
054    private SearchScope scope;
055
056    /** Types only return flag */
057    private boolean typesOnly;
058
059    /** Max size in entries to return */
060    private long sizeLimit;
061
062    /** Max seconds to wait for search to complete */
063    private int timeLimit;
064
065    /** Alias dereferencing mode enumeration value (default to DEREF_ALWAYS) */
066    private AliasDerefMode aliasDerefMode = AliasDerefMode.DEREF_ALWAYS;
067
068    /** Attributes to return */
069    private List<String> attributes = new ArrayList<>();
070
071    /** The final result containing SearchResponseDone response */
072    private SearchResultDone response;
073
074    /** A flag set to tell the search what to do wth referrals */
075    private ReferralsPolicyEnum referralHandling = ReferralsPolicyEnum.THROW;
076
077
078    // -----------------------------------------------------------------------
079    // Constructors
080    // -----------------------------------------------------------------------
081    /**
082     * Creates a SearcRequest implementing object used to search the
083     * DIT.
084     */
085    public SearchRequestImpl()
086    {
087        super( -1, MessageTypeEnum.SEARCH_REQUEST );
088    }
089
090
091    // ------------------------------------------------------------------------
092    // SearchRequest Interface Method Implementations
093    // ------------------------------------------------------------------------
094
095    /**
096     * {@inheritDoc}
097     */
098    @Override
099    public List<String> getAttributes()
100    {
101        return Collections.unmodifiableList( attributes );
102    }
103
104
105    /**
106     * Gets the search base as a distinguished name.
107     * 
108     * @return the search base
109     */
110    @Override
111    public Dn getBase()
112    {
113        return baseDn;
114    }
115
116
117    /**
118     * {@inheritDoc}
119     */
120    @Override
121    public SearchRequest setBase( Dn base )
122    {
123        baseDn = base;
124
125        return this;
126    }
127
128
129    /**
130     * {@inheritDoc}
131     */
132    @Override
133    public AliasDerefMode getDerefAliases()
134    {
135        return aliasDerefMode;
136    }
137
138
139    /**
140     * {@inheritDoc}
141     */
142    @Override
143    public SearchRequest setDerefAliases( AliasDerefMode aliasDerefAliases )
144    {
145        this.aliasDerefMode = aliasDerefAliases;
146
147        return this;
148    }
149
150
151    /**
152     * {@inheritDoc}
153     */
154    @Override
155    public ExprNode getFilter()
156    {
157        return filterNode;
158    }
159
160
161    /**
162     * {@inheritDoc}
163     */
164    @Override
165    public SearchRequest setFilter( ExprNode filter )
166    {
167        this.filterNode = filter;
168        return this;
169    }
170
171
172    /**
173     * {@inheritDoc}
174     */
175    @Override
176    public SearchRequest setFilter( String filter ) throws LdapException
177    {
178        try
179        {
180            filterNode = FilterParser.parse( Strings.getBytesUtf8( filter ) );
181        }
182        catch ( ParseException pe )
183        {
184            String msg = "The filter " + filter + " is invalid.";
185            throw new LdapProtocolErrorException( msg, pe );
186        }
187
188        return this;
189    }
190
191
192    /**
193     * {@inheritDoc}
194     */
195    @Override
196    public MessageTypeEnum[] getResponseTypes()
197    {
198        return RESPONSE_TYPES.clone();
199    }
200
201
202    /**
203     * {@inheritDoc}
204     */
205    @Override
206    public SearchScope getScope()
207    {
208        return scope;
209    }
210
211
212    /**
213     * {@inheritDoc}
214     */
215    @Override
216    public SearchRequest setScope( SearchScope scope )
217    {
218        this.scope = scope;
219
220        return this;
221    }
222
223
224    /**
225     * {@inheritDoc}
226     */
227    @Override
228    public long getSizeLimit()
229    {
230        return sizeLimit;
231    }
232
233
234    /**
235     * {@inheritDoc}
236     */
237    @Override
238    public SearchRequest setSizeLimit( long entriesMax )
239    {
240        sizeLimit = entriesMax;
241
242        return this;
243    }
244
245
246    /**
247     * {@inheritDoc}
248     */
249    @Override
250    public int getTimeLimit()
251    {
252        return timeLimit;
253    }
254
255
256    /**
257     * {@inheritDoc}
258     */
259    @Override
260    public SearchRequest setTimeLimit( int secondsMax )
261    {
262        timeLimit = secondsMax;
263
264        return this;
265    }
266
267
268    /**
269     * {@inheritDoc}
270     */
271    @Override
272    public boolean getTypesOnly()
273    {
274        return typesOnly;
275    }
276
277
278    /**
279     * {@inheritDoc}
280     */
281    @Override
282    public SearchRequest setTypesOnly( boolean typesOnly )
283    {
284        this.typesOnly = typesOnly;
285
286        return this;
287    }
288
289
290    /**
291     * {@inheritDoc}
292     */
293    @Override
294    public SearchRequest addAttributes( String... attributesToAdd )
295    {
296        this.attributes.addAll( Arrays.asList( attributesToAdd ) );
297
298        return this;
299    }
300
301
302    /**
303     * {@inheritDoc}
304     */
305    @Override
306    public SearchRequest removeAttribute( String attribute )
307    {
308        attributes.remove( attribute );
309
310        return this;
311    }
312
313
314    /**
315     * {@inheritDoc}
316     */
317    @Override
318    public SearchResultDone getResultResponse()
319    {
320        if ( response == null )
321        {
322            response = new SearchResultDoneImpl( getMessageId() );
323        }
324
325        return response;
326    }
327
328
329    /**
330     * {@inheritDoc}
331     */
332    @Override
333    public SearchRequest setMessageId( int messageId )
334    {
335        super.setMessageId( messageId );
336
337        return this;
338    }
339
340
341    /**
342     * {@inheritDoc}
343     */
344    @Override
345    public SearchRequest addControl( Control control )
346    {
347        return ( SearchRequest ) super.addControl( control );
348    }
349
350
351    /**
352     * {@inheritDoc}
353     */
354    @Override
355    public SearchRequest addAllControls( Control[] controls )
356    {
357        return ( SearchRequest ) super.addAllControls( controls );
358    }
359
360
361    /**
362     * {@inheritDoc}
363     */
364    @Override
365    public SearchRequest removeControl( Control control )
366    {
367        return ( SearchRequest ) super.removeControl( control );
368    }
369
370
371    /**
372     * {@inheritDoc}
373     */
374    @Override
375    public int hashCode()
376    {
377        int hash = 37;
378
379        if ( baseDn != null )
380        {
381            hash = hash * 17 + baseDn.hashCode();
382        }
383
384        hash = hash * 17 + aliasDerefMode.hashCode();
385        hash = hash * 17 + scope.hashCode();
386        hash = hash * 17 + Long.valueOf( sizeLimit ).hashCode();
387        hash = hash * 17 + timeLimit;
388        hash = hash * 17 + ( typesOnly ? 0 : 1 );
389
390        if ( attributes != null )
391        {
392            hash = hash * 17 + attributes.size();
393
394            // Order doesn't matter, thus just add hashCode
395            for ( String attr : attributes )
396            {
397                if ( attr != null )
398                {
399                    hash = hash + attr.hashCode();
400                }
401            }
402        }
403
404        BranchNormalizedVisitor visitor = new BranchNormalizedVisitor();
405        filterNode.accept( visitor );
406        hash = hash * 17 + filterNode.toString().hashCode();
407        hash = hash * 17 + super.hashCode();
408
409        return hash;
410    }
411
412
413    /**
414     * Checks to see if two search requests are equal. The Lockable properties
415     * and the get/set context specific parameters are not consulted to
416     * determine equality. The filter expression tree comparison will normalize
417     * the child order of filter branch nodes then generate a string
418     * representation which is comparable. For the time being this is a very
419     * costly operation.
420     * 
421     * @param obj the object to check for equality to this SearchRequest
422     * @return true if the obj is a SearchRequest and equals this SearchRequest,
423     *         false otherwise
424     */
425    @Override
426    public boolean equals( Object obj )
427    {
428        if ( obj == this )
429        {
430            return true;
431        }
432
433        if ( !super.equals( obj ) )
434        {
435            return false;
436        }
437
438        SearchRequest req = ( SearchRequest ) obj;
439
440        if ( !req.getBase().equals( baseDn ) )
441        {
442            return false;
443        }
444
445        if ( req.getDerefAliases() != aliasDerefMode )
446        {
447            return false;
448        }
449
450        if ( req.getScope() != scope )
451        {
452            return false;
453        }
454
455        if ( req.getSizeLimit() != sizeLimit )
456        {
457            return false;
458        }
459
460        if ( req.getTimeLimit() != timeLimit )
461        {
462            return false;
463        }
464
465        if ( req.getTypesOnly() != typesOnly )
466        {
467            return false;
468        }
469
470        if ( req.getAttributes() == null && attributes != null && !attributes.isEmpty() )
471        {
472            return false;
473        }
474
475        if ( req.getAttributes() != null && attributes == null && !req.getAttributes().isEmpty() )
476        {
477            return false;
478        }
479
480        if ( req.getAttributes() != null && attributes != null )
481        {
482            if ( req.getAttributes().size() != attributes.size() )
483            {
484                return false;
485            }
486
487            for ( String attribute : attributes )
488            {
489                if ( !req.getAttributes().contains( attribute ) )
490                {
491                    return false;
492                }
493            }
494        }
495
496        BranchNormalizedVisitor visitor = new BranchNormalizedVisitor();
497        req.getFilter().accept( visitor );
498        filterNode.accept( visitor );
499
500        String myFilterString = filterNode.toString();
501        String reqFilterString = req.getFilter().toString();
502
503        return myFilterString.equals( reqFilterString );
504    }
505
506
507    /**
508     * Return a string the represent a SearchRequest
509     * {@inheritDoc}
510     */
511    @Override
512    public String toString()
513    {
514        StringBuilder sb = new StringBuilder();
515
516        sb.append( "    SearchRequest\n" );
517        sb.append( "        baseDn : '" ).append( baseDn ).append( "'\n" );
518
519        if ( filterNode != null )
520        {
521            sb.append( "        filter : '" );
522            sb.append( filterNode.toString() );
523            sb.append( "'\n" );
524        }
525
526        sb.append( "        scope : " );
527
528        switch ( scope )
529        {
530            case OBJECT:
531                sb.append( "base object" );
532                break;
533
534            case ONELEVEL:
535                sb.append( "single level" );
536                break;
537
538            case SUBTREE:
539                sb.append( "whole subtree" );
540                break;
541
542            default:
543                throw new IllegalArgumentException( "Unexpected scope " + scope );
544        }
545
546        sb.append( '\n' );
547
548        sb.append( "        typesOnly : " ).append( typesOnly ).append( '\n' );
549
550        sb.append( "        Size Limit : " );
551
552        if ( sizeLimit == 0L )
553        {
554            sb.append( "no limit" );
555        }
556        else
557        {
558            sb.append( sizeLimit );
559        }
560
561        sb.append( '\n' );
562
563        sb.append( "        Time Limit : " );
564
565        if ( timeLimit == 0 )
566        {
567            sb.append( "no limit" );
568        }
569        else
570        {
571            sb.append( timeLimit );
572        }
573
574        sb.append( '\n' );
575
576        sb.append( "        Deref Aliases : " );
577
578        switch ( aliasDerefMode )
579        {
580            case NEVER_DEREF_ALIASES:
581                sb.append( "never Deref Aliases" );
582                break;
583
584            case DEREF_IN_SEARCHING:
585                sb.append( "deref In Searching" );
586                break;
587
588            case DEREF_FINDING_BASE_OBJ:
589                sb.append( "deref Finding Base Obj" );
590                break;
591
592            case DEREF_ALWAYS:
593                sb.append( "deref Always" );
594                break;
595
596            default:
597                throw new IllegalArgumentException( "Unexpected aliasDerefMode " + aliasDerefMode );
598        }
599
600        sb.append( '\n' );
601        sb.append( "        attributes : " );
602
603        boolean isFirst = true;
604
605        if ( attributes != null )
606        {
607            for ( String attribute : attributes )
608            {
609                if ( isFirst )
610                {
611                    isFirst = false;
612                }
613                else
614                {
615                    sb.append( ", " );
616                }
617
618                sb.append( '\'' ).append( attribute ).append( '\'' );
619            }
620        }
621
622        sb.append( '\n' );
623
624        // The controls
625        sb.append( super.toString() );
626
627        return super.toString( sb.toString() );
628    }
629
630
631    /**
632     * {@inheritDoc}
633     */
634    @Override
635    public boolean isFollowReferrals()
636    {
637        return referralHandling == ReferralsPolicyEnum.FOLLOW;
638    }
639
640
641    /**
642     * {@inheritDoc}
643     */
644    @Override
645    public SearchRequest followReferrals()
646    {
647        referralHandling = ReferralsPolicyEnum.FOLLOW;
648
649        return this;
650    }
651
652
653    /**
654     * {@inheritDoc}
655     */
656    @Override
657    public boolean isIgnoreReferrals()
658    {
659        return referralHandling == ReferralsPolicyEnum.IGNORE;
660    }
661
662
663    /**
664     * {@inheritDoc}
665     */
666    @Override
667    public SearchRequest ignoreReferrals()
668    {
669        referralHandling = ReferralsPolicyEnum.IGNORE;
670
671        return this;
672    }
673}