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.controls.pagedSearch;
021    
022    
023    import java.nio.ByteBuffer;
024    
025    import org.apache.directory.shared.asn1.Asn1Object;
026    import org.apache.directory.shared.asn1.ber.tlv.TLV;
027    import org.apache.directory.shared.asn1.ber.tlv.UniversalTag;
028    import org.apache.directory.shared.asn1.ber.tlv.Value;
029    import org.apache.directory.shared.asn1.codec.EncoderException;
030    import org.apache.directory.shared.i18n.I18n;
031    import org.apache.directory.shared.ldap.codec.controls.AbstractControl;
032    import org.apache.directory.shared.ldap.util.StringTools;
033    
034    
035    /**
036     * A request/response control used to implement a simple paging of search
037     * results. This is an implementation of RFC 2696 :
038     * <a href="http://www.faqs.org/rfcs/rfc2696.html">LDAP Control Extension for Simple Paged Results Manipulation</a>
039     * <br/>
040     * <pre>
041     *    This control is included in the searchRequest and searchResultDone
042     *    messages as part of the controls field of the LDAPMessage, as defined
043     *    in Section 4.1.12 of [LDAPv3]. The structure of this control is as
044     *    follows:
045     *
046     * pagedResultsControl ::= SEQUENCE {
047     *         controlType     1.2.840.113556.1.4.319,
048     *         criticality     BOOLEAN DEFAULT FALSE,
049     *         controlValue    searchControlValue
050     * }
051     * 
052     * The searchControlValue is an OCTET STRING wrapping the BER-encoded
053     * version of the following SEQUENCE:
054     * 
055     * realSearchControlValue ::= SEQUENCE {
056     *         size            INTEGER (0..maxInt),
057     *                                 -- requested page size from client
058     *                                 -- result set size estimate from server
059     *         cookie          OCTET STRING
060     * }
061     * 
062     * </pre>
063     * 
064     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
065     * @version $Rev:  $
066     */
067    public class PagedResultsControl extends AbstractControl
068    {
069        /** The Paged Search Control OID */
070        public static final String CONTROL_OID = "1.2.840.113556.1.4.319";
071    
072        /** The number of entries to return, or returned */
073        private int size;
074        
075        /** The exchanged cookie */
076        private byte[] cookie;
077    
078        /** The entry change global length */
079        private int pscSeqLength;
080    
081        /**
082         * @see Asn1Object#Asn1Object
083         */
084        public PagedResultsControl()
085        {
086            super( CONTROL_OID );
087            
088            decoder = new PagedResultsControlDecoder();
089        }
090    
091        
092        /**
093         * Compute the PagedSearchControl length, which is the sum
094         * of the control length and the value length.
095         * 
096         * <pre>
097         * PagedSearchControl value length :
098         * 
099         * 0x30 L1 
100         *   | 
101         *   +--> 0x02 0x0(1-4) [0..2^63-1] (size) 
102         *   +--> 0x04 L2 (cookie)
103         * </pre> 
104         */
105        public int computeLength()
106        {
107            int sizeLength = 1 + 1 + Value.getNbBytes( size );
108    
109            int cookieLength = 0;
110            
111            if ( cookie != null )
112            {
113                cookieLength = 1 + TLV.getNbBytes( cookie.length ) + cookie.length;
114            }
115            else
116            {
117                cookieLength = 1 + 1;
118            }
119    
120            pscSeqLength = sizeLength + cookieLength;
121            valueLength = 1 + TLV.getNbBytes( pscSeqLength ) + pscSeqLength;
122    
123            // Call the super class to compute the global control length
124            return super.computeLength( valueLength );
125        }
126    
127    
128        /**
129         * Encodes the paged search control.
130         * 
131         * @param buffer The encoded sink
132         * @return A ByteBuffer that contains the encoded PDU
133         * @throws EncoderException If anything goes wrong.
134         */
135        public ByteBuffer encode( ByteBuffer buffer ) throws EncoderException
136        {
137            if ( buffer == null )
138            {
139                throw new EncoderException( I18n.err( I18n.ERR_04023 ) );
140            }
141    
142            // Encode the Control envelop
143            super.encode( buffer );
144            
145            // Encode the OCTET_STRING tag
146            buffer.put( UniversalTag.OCTET_STRING_TAG );
147            buffer.put( TLV.getBytes( valueLength ) );
148            
149            // Now encode the PagedSearch specific part
150            buffer.put( UniversalTag.SEQUENCE_TAG );
151            buffer.put( TLV.getBytes( pscSeqLength ) );
152    
153            Value.encode( buffer, size );
154            Value.encode( buffer, cookie );
155            
156            return buffer;
157        }
158        
159        
160        /**
161         * {@inheritDoc}
162         */
163        public byte[] getValue()
164        {
165            if ( value == null )
166            {
167                try
168                { 
169                    computeLength();
170                    ByteBuffer buffer = ByteBuffer.allocate( valueLength );
171                    
172                    // Now encode the PagedSearch specific part
173                    buffer.put( UniversalTag.SEQUENCE_TAG );
174                    buffer.put( TLV.getBytes( pscSeqLength ) );
175    
176                    Value.encode( buffer, size );
177                    Value.encode( buffer, cookie );
178                    
179                    value = buffer.array();
180                }
181                catch ( Exception e )
182                {
183                    return null;
184                }
185            }
186            
187            return value;
188        }
189    
190    
191        /**
192         * @return The requested or returned number of entries
193         */
194        public int getSize()
195        {
196            return size;
197        }
198    
199    
200        /**
201         * Set the number of entry requested or returned
202         *
203         * @param size The number of entries 
204         */
205        public void setSize( int size )
206        {
207            this.size = size;
208        }
209    
210    
211        /**
212         * @return The stored cookie
213         */
214        public byte[] getCookie()
215        {
216            return cookie;
217        }
218    
219    
220        /**
221         * Set the cookie
222         *
223         * @param cookie The cookie to store in this control
224         */
225        public void setCookie( byte[] cookie )
226        {
227            this.cookie = cookie;
228        }
229    
230        
231        /**
232         * @return The integer value for the current cookie
233         */
234        public int getCookieValue()
235        {
236            int value = ((cookie[0]&0x00FF)<<24) + ((cookie[1]&0x00FF)<<16) + ((cookie[2]&0x00FF)<<8) + (cookie[3]&0x00FF);
237            
238            return value;
239        }
240        
241        
242        /**
243         * Return a String representing this PagedSearchControl.
244         */
245        public String toString()
246        {
247            StringBuffer sb = new StringBuffer();
248    
249            sb.append( "    Paged Search Control\n" );
250            sb.append( "        oid : " ).append( getOid() ).append( '\n' );
251            sb.append( "        critical : " ).append( isCritical() ).append( '\n' );
252            sb.append( "        size   : '" ).append( size ).append( "'\n" );
253            sb.append( "        cookie   : '" ).append( StringTools.dumpBytes( cookie ) ).append( "'\n" );
254            
255            return sb.toString();
256        }
257    }