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.i18n.I18n;
030import org.apache.directory.api.ldap.model.exception.LdapException;
031import org.apache.directory.api.ldap.model.exception.LdapProtocolErrorException;
032import org.apache.directory.api.ldap.model.filter.BranchNormalizedVisitor;
033import org.apache.directory.api.ldap.model.filter.ExprNode;
034import org.apache.directory.api.ldap.model.filter.FilterParser;
035import org.apache.directory.api.ldap.model.name.Dn;
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( filter );
181        }
182        catch ( ParseException pe )
183        {
184            throw new LdapProtocolErrorException( I18n.err( I18n.ERR_13508_INVALID_FILTER, filter ), pe );
185        }
186
187        return this;
188    }
189
190
191    /**
192     * {@inheritDoc}
193     */
194    @Override
195    public MessageTypeEnum[] getResponseTypes()
196    {
197        return RESPONSE_TYPES;
198    }
199
200
201    /**
202     * {@inheritDoc}
203     */
204    @Override
205    public SearchScope getScope()
206    {
207        return scope;
208    }
209
210
211    /**
212     * {@inheritDoc}
213     */
214    @Override
215    public SearchRequest setScope( SearchScope scope )
216    {
217        this.scope = scope;
218
219        return this;
220    }
221
222
223    /**
224     * {@inheritDoc}
225     */
226    @Override
227    public long getSizeLimit()
228    {
229        return sizeLimit;
230    }
231
232
233    /**
234     * {@inheritDoc}
235     */
236    @Override
237    public SearchRequest setSizeLimit( long entriesMax )
238    {
239        sizeLimit = entriesMax;
240
241        return this;
242    }
243
244
245    /**
246     * {@inheritDoc}
247     */
248    @Override
249    public int getTimeLimit()
250    {
251        return timeLimit;
252    }
253
254
255    /**
256     * {@inheritDoc}
257     */
258    @Override
259    public SearchRequest setTimeLimit( int secondsMax )
260    {
261        timeLimit = secondsMax;
262
263        return this;
264    }
265
266
267    /**
268     * {@inheritDoc}
269     */
270    @Override
271    public boolean getTypesOnly()
272    {
273        return typesOnly;
274    }
275
276
277    /**
278     * {@inheritDoc}
279     */
280    @Override
281    public SearchRequest setTypesOnly( boolean typesOnly )
282    {
283        this.typesOnly = typesOnly;
284
285        return this;
286    }
287
288
289    /**
290     * {@inheritDoc}
291     */
292    @Override
293    public SearchRequest addAttributes( String... attributesToAdd )
294    {
295        this.attributes.addAll( Arrays.asList( attributesToAdd ) );
296
297        return this;
298    }
299
300
301    /**
302     * {@inheritDoc}
303     */
304    @Override
305    public SearchRequest removeAttribute( String attribute )
306    {
307        attributes.remove( attribute );
308
309        return this;
310    }
311
312
313    /**
314     * {@inheritDoc}
315     */
316    @Override
317    public SearchResultDone getResultResponse()
318    {
319        if ( response == null )
320        {
321            response = new SearchResultDoneImpl( getMessageId() );
322        }
323
324        return response;
325    }
326
327
328    /**
329     * {@inheritDoc}
330     */
331    @Override
332    public SearchRequest setMessageId( int messageId )
333    {
334        super.setMessageId( messageId );
335
336        return this;
337    }
338
339
340    /**
341     * {@inheritDoc}
342     */
343    @Override
344    public SearchRequest addControl( Control control )
345    {
346        return ( SearchRequest ) super.addControl( control );
347    }
348
349
350    /**
351     * {@inheritDoc}
352     */
353    @Override
354    public SearchRequest addAllControls( Control[] controls )
355    {
356        return ( SearchRequest ) super.addAllControls( controls );
357    }
358
359
360    /**
361     * {@inheritDoc}
362     */
363    @Override
364    public SearchRequest removeControl( Control control )
365    {
366        return ( SearchRequest ) super.removeControl( control );
367    }
368
369
370    /**
371     * {@inheritDoc}
372     */
373    @Override
374    public int hashCode()
375    {
376        int hash = 37;
377
378        if ( baseDn != null )
379        {
380            hash = hash * 17 + baseDn.hashCode();
381        }
382
383        hash = hash * 17 + aliasDerefMode.hashCode();
384        hash = hash * 17 + scope.hashCode();
385        hash = hash * 17 + Long.valueOf( sizeLimit ).hashCode();
386        hash = hash * 17 + timeLimit;
387        hash = hash * 17 + ( typesOnly ? 0 : 1 );
388
389        if ( attributes != null )
390        {
391            hash = hash * 17 + attributes.size();
392
393            // Order doesn't matter, thus just add hashCode
394            for ( String attr : attributes )
395            {
396                if ( attr != null )
397                {
398                    hash = hash + attr.hashCode();
399                }
400            }
401        }
402
403        BranchNormalizedVisitor visitor = new BranchNormalizedVisitor();
404        filterNode.accept( visitor );
405        hash = hash * 17 + filterNode.toString().hashCode();
406        hash = hash * 17 + super.hashCode();
407
408        return hash;
409    }
410
411
412    /**
413     * Checks to see if two search requests are equal. The Lockable properties
414     * and the get/set context specific parameters are not consulted to
415     * determine equality. The filter expression tree comparison will normalize
416     * the child order of filter branch nodes then generate a string
417     * representation which is comparable. For the time being this is a very
418     * costly operation.
419     * 
420     * @param obj the object to check for equality to this SearchRequest
421     * @return true if the obj is a SearchRequest and equals this SearchRequest,
422     *         false otherwise
423     */
424    @Override
425    public boolean equals( Object obj )
426    {
427        if ( obj == this )
428        {
429            return true;
430        }
431
432        if ( !super.equals( obj ) )
433        {
434            return false;
435        }
436
437        SearchRequest req = ( SearchRequest ) obj;
438
439        if ( !req.getBase().equals( baseDn ) )
440        {
441            return false;
442        }
443
444        if ( req.getDerefAliases() != aliasDerefMode )
445        {
446            return false;
447        }
448
449        if ( req.getScope() != scope )
450        {
451            return false;
452        }
453
454        if ( req.getSizeLimit() != sizeLimit )
455        {
456            return false;
457        }
458
459        if ( req.getTimeLimit() != timeLimit )
460        {
461            return false;
462        }
463
464        if ( req.getTypesOnly() != typesOnly )
465        {
466            return false;
467        }
468
469        if ( req.getAttributes() == null && attributes != null && !attributes.isEmpty() )
470        {
471            return false;
472        }
473
474        if ( req.getAttributes() != null && attributes == null && !req.getAttributes().isEmpty() )
475        {
476            return false;
477        }
478
479        if ( req.getAttributes() != null && attributes != null )
480        {
481            if ( req.getAttributes().size() != attributes.size() )
482            {
483                return false;
484            }
485
486            for ( String attribute : attributes )
487            {
488                if ( !req.getAttributes().contains( attribute ) )
489                {
490                    return false;
491                }
492            }
493        }
494
495        BranchNormalizedVisitor visitor = new BranchNormalizedVisitor();
496        req.getFilter().accept( visitor );
497        filterNode.accept( visitor );
498
499        String myFilterString = filterNode.toString();
500        String reqFilterString = req.getFilter().toString();
501
502        return myFilterString.equals( reqFilterString );
503    }
504
505
506    /**
507     * Return a string the represent a SearchRequest
508     * {@inheritDoc}
509     */
510    @Override
511    public String toString()
512    {
513        StringBuilder sb = new StringBuilder();
514
515        sb.append( "    SearchRequest\n" );
516        sb.append( "        baseDn : '" ).append( baseDn ).append( "'\n" );
517
518        if ( filterNode != null )
519        {
520            sb.append( "        filter : '" );
521            sb.append( filterNode.toString() );
522            sb.append( "'\n" );
523        }
524
525        sb.append( "        scope : " );
526
527        switch ( scope )
528        {
529            case OBJECT:
530                sb.append( "base object" );
531                break;
532
533            case ONELEVEL:
534                sb.append( "single level" );
535                break;
536
537            case SUBTREE:
538                sb.append( "whole subtree" );
539                break;
540
541            default:
542                throw new IllegalArgumentException( I18n.err( I18n.ERR_13509_UNEXPECTED_SCOPE, scope ) );
543        }
544
545        sb.append( '\n' );
546
547        sb.append( "        typesOnly : " ).append( typesOnly ).append( '\n' );
548
549        sb.append( "        Size Limit : " );
550
551        if ( sizeLimit == 0L )
552        {
553            sb.append( "no limit" );
554        }
555        else
556        {
557            sb.append( sizeLimit );
558        }
559
560        sb.append( '\n' );
561
562        sb.append( "        Time Limit : " );
563
564        if ( timeLimit == 0 )
565        {
566            sb.append( "no limit" );
567        }
568        else
569        {
570            sb.append( timeLimit );
571        }
572
573        sb.append( '\n' );
574
575        sb.append( "        Deref Aliases : " );
576
577        switch ( aliasDerefMode )
578        {
579            case NEVER_DEREF_ALIASES:
580                sb.append( "never Deref Aliases" );
581                break;
582
583            case DEREF_IN_SEARCHING:
584                sb.append( "deref In Searching" );
585                break;
586
587            case DEREF_FINDING_BASE_OBJ:
588                sb.append( "deref Finding Base Obj" );
589                break;
590
591            case DEREF_ALWAYS:
592                sb.append( "deref Always" );
593                break;
594
595            default:
596                throw new IllegalArgumentException( I18n.err( I18n.ERR_13517_UNEXPECTED_ALIAS_DEREF_MODE, aliasDerefMode ) );
597        }
598
599        sb.append( '\n' );
600        sb.append( "        attributes : " );
601
602        boolean isFirst = true;
603
604        if ( attributes != null )
605        {
606            for ( String attribute : attributes )
607            {
608                if ( isFirst )
609                {
610                    isFirst = false;
611                }
612                else
613                {
614                    sb.append( ", " );
615                }
616
617                sb.append( '\'' ).append( attribute ).append( '\'' );
618            }
619        }
620
621        sb.append( '\n' );
622
623        // The controls
624        sb.append( super.toString() );
625
626        return super.toString( sb.toString() );
627    }
628
629
630    /**
631     * {@inheritDoc}
632     */
633    @Override
634    public boolean isFollowReferrals()
635    {
636        return referralHandling == ReferralsPolicyEnum.FOLLOW;
637    }
638
639
640    /**
641     * {@inheritDoc}
642     */
643    @Override
644    public SearchRequest followReferrals()
645    {
646        referralHandling = ReferralsPolicyEnum.FOLLOW;
647
648        return this;
649    }
650
651
652    /**
653     * {@inheritDoc}
654     */
655    @Override
656    public boolean isIgnoreReferrals()
657    {
658        return referralHandling == ReferralsPolicyEnum.IGNORE;
659    }
660
661
662    /**
663     * {@inheritDoc}
664     */
665    @Override
666    public SearchRequest ignoreReferrals()
667    {
668        referralHandling = ReferralsPolicyEnum.IGNORE;
669
670        return this;
671    }
672}