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     */
020    package org.apache.directory.shared.ldap.codec.search;
021    
022    
023    import java.io.UnsupportedEncodingException;
024    import java.nio.BufferOverflowException;
025    import java.nio.ByteBuffer;
026    import java.util.ArrayList;
027    import java.util.List;
028    
029    import org.apache.directory.shared.asn1.Asn1Object;
030    import org.apache.directory.shared.asn1.ber.IAsn1Container;
031    import org.apache.directory.shared.asn1.ber.tlv.TLV;
032    import org.apache.directory.shared.asn1.ber.tlv.UniversalTag;
033    import org.apache.directory.shared.asn1.ber.tlv.Value;
034    import org.apache.directory.shared.asn1.codec.DecoderException;
035    import org.apache.directory.shared.asn1.codec.EncoderException;
036    import org.apache.directory.shared.ldap.codec.LdapConstants;
037    import org.apache.directory.shared.ldap.codec.LdapMessageCodec;
038    import org.apache.directory.shared.ldap.codec.LdapMessageContainer;
039    import org.apache.directory.shared.ldap.entry.EntryAttribute;
040    import org.apache.directory.shared.ldap.entry.client.DefaultClientAttribute;
041    import org.apache.directory.shared.ldap.filter.SearchScope;
042    import org.apache.directory.shared.ldap.name.LdapDN;
043    
044    
045    /**
046     * A SearchRequest ldapObject. It's a sub-class of Asn1Object, and it implements
047     * the ldapObject class to be seen as a member of the LdapMessage CHOICE.
048     * 
049     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
050     * @version $Rev: 764131 $, $Date: 2009-04-11 03:03:00 +0200 (Sam, 11 avr 2009) $, 
051     */
052    public class SearchRequestCodec extends LdapMessageCodec
053    {
054        // ~ Instance fields
055        // ----------------------------------------------------------------------------
056    
057        /** The base DN */
058        private LdapDN baseObject;
059    
060        /** The scope. It could be baseObject, singleLevel or wholeSubtree. */
061        private SearchScope scope;
062    
063        /**
064         * The deref alias could be neverDerefAliases, derefInSearching,
065         * derefFindingBaseObj or derefAlways.
066         */
067        private int derefAliases;
068    
069        /** The size limit (number of objects returned) */
070        private int sizeLimit;
071    
072        /**
073         * The time limit (max time to process the response before returning the
074         * result)
075         */
076        private int timeLimit;
077    
078        /**
079         * An indicator as to whether search results will contain both attribute
080         * types and values, or just attribute types. Setting this field to TRUE
081         * causes only attribute types (no values) to be returned. Setting this
082         * field to FALSE causes both attribute types and values to be returned.
083         */
084        private boolean typesOnly;
085    
086        /** The filter tree */
087        private Filter filter;
088        
089        /** The list of attributes to get */
090        private List<EntryAttribute> attributes = new ArrayList<EntryAttribute>();
091    
092        /** The current filter. This is used while decoding a PDU */
093        private Filter currentFilter;
094    
095        /** A temporary storage for a terminal Filter */
096        private Filter terminalFilter;
097        
098        /** The searchRequest length */
099        private int searchRequestLength;
100    
101        /** The attributeDescriptionList length */
102        private int attributeDescriptionListLength;
103    
104    
105        // ~ Constructors
106        // -------------------------------------------------------------------------------
107    
108        /**
109         * Creates a new SearchRequest object.
110         */
111        public SearchRequestCodec()
112        {
113            super();
114        }
115    
116    
117        // ~ Methods
118        // ------------------------------------------------------------------------------------
119    
120        /**
121         * Get the message type
122         * 
123         * @return Returns the type.
124         */
125        public int getMessageType()
126        {
127            return LdapConstants.SEARCH_REQUEST;
128        }
129    
130    
131        /**
132         * Get the list of attributes
133         * 
134         * @return Returns the attributes.
135         */
136        public List<EntryAttribute> getAttributes()
137        {
138            return attributes;
139        }
140    
141    
142        /**
143         * Add an attribute to the attributes list.
144         * 
145         * @param attribute The attribute to add to the list
146         */
147        public void addAttribute( String attribute )
148        {
149            attributes.add( new DefaultClientAttribute( attribute ) );
150        }
151    
152    
153        /**
154         * Get the base object
155         * 
156         * @return Returns the baseObject.
157         */
158        public LdapDN getBaseObject()
159        {
160            return baseObject;
161        }
162    
163    
164        /**
165         * Set the base object
166         * 
167         * @param baseObject The baseObject to set.
168         */
169        public void setBaseObject( LdapDN baseObject )
170        {
171            this.baseObject = baseObject;
172        }
173    
174    
175        /**
176         * Get the derefAliases flag
177         * 
178         * @return Returns the derefAliases.
179         */
180        public int getDerefAliases()
181        {
182            return derefAliases;
183        }
184    
185    
186        /**
187         * Set the derefAliases flag
188         * 
189         * @param derefAliases The derefAliases to set.
190         */
191        public void setDerefAliases( int derefAliases )
192        {
193            this.derefAliases = derefAliases;
194        }
195    
196    
197        /**
198         * Get the filter
199         * 
200         * @return Returns the filter.
201         */
202        public Filter getFilter()
203        {
204            return filter;
205        }
206    
207    
208        /**
209         * Set the filter
210         * 
211         * @param filter The filter to set.
212         */
213        public void setFilter( Filter filter )
214        {
215            this.filter = filter;
216        }
217    
218    
219        /**
220         * Get the search scope
221         * 
222         * @return Returns the scope.
223         */
224        public SearchScope getScope()
225        {
226            return scope;
227        }
228    
229    
230        /**
231         * Set the search scope
232         * 
233         * @param scope The scope to set.
234         */
235        public void setScope( SearchScope scope )
236        {
237            this.scope = scope;
238        }
239    
240    
241        /**
242         * Get the size limit
243         * 
244         * @return Returns the sizeLimit.
245         */
246        public int getSizeLimit()
247        {
248            return sizeLimit;
249        }
250    
251    
252        /**
253         * Set the size limit
254         * 
255         * @param sizeLimit The sizeLimit to set.
256         */
257        public void setSizeLimit( int sizeLimit )
258        {
259            this.sizeLimit = sizeLimit;
260        }
261    
262    
263        /**
264         * Get the time limit
265         * 
266         * @return Returns the timeLimit.
267         */
268        public int getTimeLimit()
269        {
270            return timeLimit;
271        }
272    
273    
274        /**
275         * Set the time limit
276         * 
277         * @param timeLimit The timeLimit to set.
278         */
279        public void setTimeLimit( int timeLimit )
280        {
281            this.timeLimit = timeLimit;
282        }
283    
284    
285        /**
286         * Get the typesOnly flag
287         * 
288         * @return Returns the typesOnly.
289         */
290        public boolean isTypesOnly()
291        {
292            return typesOnly;
293        }
294    
295    
296        /**
297         * Set the typesOnly flag
298         * 
299         * @param typesOnly The typesOnly to set.
300         */
301        public void setTypesOnly( boolean typesOnly )
302        {
303            this.typesOnly = typesOnly;
304        }
305    
306    
307        /**
308         * Get the current dilter
309         * 
310         * @return Returns the currentFilter.
311         */
312        public Filter getCurrentFilter()
313        {
314            return currentFilter;
315        }
316    
317        /**
318         * Get the comparison dilter
319         * 
320         * @return Returns the comparisonFilter.
321         */
322        public Filter getTerminalFilter()
323        {
324            return terminalFilter;
325        }
326    
327        /**
328         * Set the terminal filter
329         * 
330         * @param terminalFilter the teminalFilter.
331         */
332        public void setTerminalFilter( Filter terminalFilter )
333        {
334            this.terminalFilter = terminalFilter;
335        }
336    
337    
338        /**
339         * Add a current filter. We have two cases :
340         * - there is no previous current filter : the filter
341         * is the top level filter
342         * - there is a previous current filter : the filter is added 
343         * to the currentFilter set, and the current filter is changed
344         * 
345         * In any case, the previous current filter will always be a
346         * ConnectorFilter when this method is called.
347         * 
348         * @param localFilter The filter to set.
349         */
350        public void addCurrentFilter( Filter localFilter ) throws DecoderException
351        {
352            if ( currentFilter != null )
353            {
354                // Ok, we have a parent. The new Filter will be added to
355                // this parent, and will become the currentFilter if it's a connector.
356                ( ( ConnectorFilter ) currentFilter ).addFilter( localFilter );
357                localFilter.setParent( currentFilter );
358                
359                if ( localFilter instanceof ConnectorFilter )
360                {
361                    currentFilter = localFilter;
362                }
363            }
364            else
365            {
366                // No parent. This Filter will become the root.
367                currentFilter = localFilter;
368                currentFilter.setParent( this );
369                this.filter = localFilter;
370            }
371        }
372    
373        /**
374         * Set the current dilter
375         * 
376         * @param filter The filter to set.
377         */
378        public void setCurrentFilter( Filter filter )
379        {
380            currentFilter = filter;
381        }
382    
383    
384        /**
385         * This method is used to clear the filter's stack for terminated elements. An element
386         * is considered as terminated either if :
387         *  - it's a final element (ie an element which cannot contains a Filter)
388         *  - its current length equals its expected length.
389         * 
390         * @param container The container being decoded
391         */
392        public void unstackFilters( IAsn1Container container )
393        {
394            LdapMessageContainer ldapMessageContainer = ( LdapMessageContainer ) container;
395    
396            TLV tlv = ldapMessageContainer.getCurrentTLV();
397            TLV localParent = tlv.getParent();
398            Filter localFilter = terminalFilter;
399    
400            // The parent has been completed, so fold it
401            while ( ( localParent != null ) && ( localParent.getExpectedLength() == 0 ) )
402            {
403                if ( localParent.getId() != localFilter.getParent().getTlvId() )
404                {
405                    localParent = localParent.getParent();
406                    
407                }
408                else
409                {
410                    Asn1Object filterParent = localFilter.getParent();
411                    
412                    // We have a special case with PresentFilter, which has not been 
413                    // pushed on the stack, so we need to get its parent's parent
414                    if ( localFilter instanceof PresentFilter )
415                    {
416                        filterParent = filterParent.getParent();
417                    }
418                    else if ( filterParent instanceof Filter )
419                    {
420                        filterParent = filterParent.getParent();
421                    }
422                    
423                    if ( filterParent instanceof Filter )
424                    {
425                        // The parent is a filter ; it will become the new currentFilter
426                        // and we will loop again. 
427                        currentFilter = (Filter)filterParent;
428                        localFilter = currentFilter;
429                        localParent = localParent.getParent();
430                    }
431                    else
432                    {
433                        // We can stop the recursion, we have reached the searchResult Object
434                        break;
435                    }
436                }
437            }
438        }
439    
440        /**
441         * Compute the SearchRequest length
442         * 
443         * SearchRequest :
444         * 
445         * 0x63 L1
446         *  |
447         *  +--> 0x04 L2 baseObject
448         *  +--> 0x0A 0x01 scope
449         *  +--> 0x0A 0x01 derefAliases
450         *  +--> 0x02 0x0(1..4) sizeLimit
451         *  +--> 0x02 0x0(1..4) timeLimit
452         *  +--> 0x01 0x01 typesOnly
453         *  +--> filter.computeLength()
454         *  +--> 0x30 L3 (Attribute description list)
455         *        |
456         *        +--> 0x04 L4-1 Attribute description 
457         *        +--> 0x04 L4-2 Attribute description 
458         *        +--> ... 
459         *        +--> 0x04 L4-i Attribute description 
460         *        +--> ... 
461         *        +--> 0x04 L4-n Attribute description 
462         */
463        public int computeLength()
464        {
465            searchRequestLength = 0;
466    
467            // The baseObject
468            searchRequestLength += 1 + TLV.getNbBytes( LdapDN.getNbBytes( baseObject ) )
469                + LdapDN.getNbBytes( baseObject );
470    
471            // The scope
472            searchRequestLength += 1 + 1 + 1;
473    
474            // The derefAliases
475            searchRequestLength += 1 + 1 + 1;
476    
477            // The sizeLimit
478            searchRequestLength += 1 + 1 + Value.getNbBytes( sizeLimit );
479    
480            // The timeLimit
481            searchRequestLength += 1 + 1 + Value.getNbBytes( timeLimit );
482    
483            // The typesOnly
484            searchRequestLength += 1 + 1 + 1;
485    
486            // The filter
487            searchRequestLength += filter.computeLength();
488    
489            // The attributes description list
490            attributeDescriptionListLength = 0;
491    
492            if ( ( attributes != null ) && ( attributes.size() != 0 ) )
493            {
494                // Compute the attributes length
495                for ( EntryAttribute attribute:attributes )
496                {
497                    // add the attribute length to the attributes length
498                    try
499                    {
500                        int idLength = attribute.getId().getBytes( "UTF-8" ).length;
501                        attributeDescriptionListLength += 1 + TLV.getNbBytes( idLength ) + idLength;
502                    }
503                    catch ( UnsupportedEncodingException uee )
504                    {
505                        // Should not be possible. The encoding of the Attribute ID
506                        // will check that this ID is valid, and if not, it will
507                        // throw an exception.
508                        // The allocated length will be set to a null length value
509                        // in order to avoid an exception thrown while encoding the
510                        // Attribute ID.
511                        attributeDescriptionListLength += 1 + 1;
512                    }
513                }
514            }
515    
516            searchRequestLength += 1 + TLV.getNbBytes( attributeDescriptionListLength ) + attributeDescriptionListLength;
517    
518            // Return the result.
519            return 1 + TLV.getNbBytes( searchRequestLength ) + searchRequestLength;
520        }
521    
522    
523        /**
524         * Encode the SearchRequest message to a PDU.
525         * 
526         * SearchRequest :
527         * 
528         * 0x63 LL
529         *   0x04 LL baseObject
530         *   0x0A 01 scope
531         *   0x0A 01 derefAliases
532         *   0x02 0N sizeLimit
533         *   0x02 0N timeLimit
534         *   0x01 0x01 typesOnly
535         *   filter.encode()
536         *   0x30 LL attributeDescriptionList
537         *     0x04 LL attributeDescription
538         *     ... 
539         *     0x04 LL attributeDescription
540         * 
541         * @param buffer The buffer where to put the PDU
542         * @return The PDU.
543         */
544        public ByteBuffer encode( ByteBuffer buffer ) throws EncoderException
545        {
546            if ( buffer == null )
547            {
548                throw new EncoderException( "Cannot put a PDU in a null buffer !" );
549            }
550    
551            try
552            {
553                // The SearchRequest Tag
554                buffer.put( LdapConstants.SEARCH_REQUEST_TAG );
555                buffer.put( TLV.getBytes( searchRequestLength ) );
556    
557                // The baseObject
558                Value.encode( buffer, LdapDN.getBytes( baseObject ) );
559    
560                // The scope
561                Value.encodeEnumerated( buffer, scope.getJndiScope() );
562    
563                // The derefAliases
564                Value.encodeEnumerated( buffer, derefAliases );
565    
566                // The sizeLimit
567                Value.encode( buffer, sizeLimit );
568    
569                // The timeLimit
570                Value.encode( buffer, timeLimit );
571    
572                // The typesOnly
573                Value.encode( buffer, typesOnly );
574    
575                // The filter
576                filter.encode( buffer );
577    
578                // The attributeDescriptionList
579                buffer.put( UniversalTag.SEQUENCE_TAG );
580                buffer.put( TLV.getBytes( attributeDescriptionListLength ) );
581    
582                if ( ( attributes != null ) && ( attributes.size() != 0 ) )
583                {
584                    // encode each attribute
585                    for ( EntryAttribute attribute:attributes )
586                    {
587                        Value.encode( buffer, attribute.getId() );
588                    }
589                }
590            }
591            catch ( BufferOverflowException boe )
592            {
593                throw new EncoderException( "The PDU buffer size is too small !" );
594            }
595    
596            return buffer;
597        }
598    
599    
600        /**
601         * @return A string that represent the Filter
602         */
603        private String buildFilter()
604        {
605            if ( filter == null )
606            {
607                return "";
608            }
609    
610            StringBuffer sb = new StringBuffer();
611    
612            sb.append( "(" ).append( filter ).append( ")" );
613    
614            return sb.toString();
615        }
616    
617    
618        /**
619         * @return A string that represent the atributes list
620         */
621        private String buildAttributes()
622        {
623            StringBuffer sb = new StringBuffer();
624    
625            if ( attributes != null )
626            {
627                boolean isFirst = true;
628        
629                if ( ( attributes != null ) && ( attributes.size() != 0 ) )
630                {
631                    // encode each attribute
632                    for ( EntryAttribute attribute:attributes )
633                    {
634                        if ( isFirst )
635                        {
636                            isFirst = false;
637                        }
638                        else
639                        {
640                            sb.append( ", " );
641                        }
642            
643                        sb.append( attribute != null ? attribute.getId() : "<no ID>" );
644                    }
645                }
646            }
647    
648            return sb.toString();
649        }
650    
651    
652        /**
653         * Return a string the represent a SearchRequest
654         */
655        public String toString()
656        {
657            StringBuffer sb = new StringBuffer();
658    
659            sb.append( "    Search Request\n" );
660            sb.append( "        Base Object : '" ).append( baseObject ).append( "'\n" );
661            sb.append( "        Scope : " );
662    
663            switch ( scope )
664            {
665                case OBJECT:
666                    sb.append( "base object" );
667                    break;
668    
669                case ONELEVEL:
670                    sb.append( "single level" );
671                    break;
672    
673                case SUBTREE:
674                    sb.append( "whole subtree" );
675                    break;
676            }
677    
678            sb.append( "\n" );
679    
680            sb.append( "        Deref Aliases : " );
681    
682            switch ( derefAliases )
683            {
684                case LdapConstants.NEVER_DEREF_ALIASES:
685                    sb.append( "never Deref Aliases" );
686                    break;
687    
688                case LdapConstants.DEREF_IN_SEARCHING:
689                    sb.append( "deref In Searching" );
690                    break;
691    
692                case LdapConstants.DEREF_FINDING_BASE_OBJ:
693                    sb.append( "deref Finding Base Obj" );
694                    break;
695    
696                case LdapConstants.DEREF_ALWAYS:
697                    sb.append( "deref Always" );
698                    break;
699            }
700    
701            sb.append( "\n" );
702    
703            sb.append( "        Size Limit : " );
704    
705            if ( sizeLimit == 0 )
706            {
707                sb.append( "no limit" );
708            }
709            else
710            {
711                sb.append( sizeLimit );
712            }
713    
714            sb.append( "\n" );
715    
716            sb.append( "        Time Limit : " );
717    
718            if ( timeLimit == 0 )
719            {
720                sb.append( "no limit" );
721            }
722            else
723            {
724                sb.append( timeLimit );
725            }
726    
727            sb.append( "\n" );
728    
729            sb.append( "        Types Only : " ).append( typesOnly ).append( "\n" );
730            sb.append( "        Filter : '" ).append( buildFilter() ).append( "'\n" );
731    
732            if ( ( attributes != null ) && ( attributes.size() != 0 ) )
733            {
734                sb.append( "        Attributes : " ).append( buildAttributes() ).append( "\n" );
735            }
736            return sb.toString();
737        }
738    }