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.server.ldap.handlers.controls;
021
022
023import java.util.HashSet;
024import java.util.Set;
025import java.util.concurrent.atomic.AtomicInteger;
026
027import org.apache.directory.api.asn1.ber.tlv.BerValue;
028import org.apache.directory.api.ldap.model.constants.SchemaConstants;
029import org.apache.directory.api.ldap.model.cursor.Cursor;
030import org.apache.directory.api.ldap.model.entry.Entry;
031import org.apache.directory.api.ldap.model.exception.LdapException;
032import org.apache.directory.api.ldap.model.message.SearchRequest;
033import org.apache.directory.api.ldap.model.name.Dn;
034import org.apache.directory.api.ldap.model.schema.AttributeType;
035import org.apache.directory.api.ldap.model.schema.SchemaManager;
036import org.apache.directory.api.util.Strings;
037import org.apache.directory.server.ldap.LdapSession;
038
039
040/**
041 * The structure which stores the informations relative to the pagedSearch control.
042 * They are associated to a cookie, stored into the session and associated to an
043 * instance of this class.
044 *
045 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
046 */
047public class PagedSearchContext
048{
049    /** The previous search request */
050    private SearchRequest previousSearchRequest;
051
052    /** The current position in the cursor */
053    private int currentPosition;
054
055    /** The cookie key */
056    private byte[] cookie;
057
058    /** The integer value for the cookie */
059    private AtomicInteger cookieValue;
060
061    /** The associated cursor for the current search request */
062    private Cursor<Entry> cursor;
063
064
065    /**
066     * Creates a new instance of this class, storing the SearchRequest into it.
067     * 
068     * @param searchRequest The SearchRequest
069     */
070    public PagedSearchContext( SearchRequest searchRequest )
071    {
072        previousSearchRequest = searchRequest;
073        currentPosition = 0;
074
075        // We compute a key for this cookie. It combines the search request
076        // and some time seed, in order to avoid possible collisions, as
077        // a user may send more than one PagedSearch on the same session.
078        cookieValue = new AtomicInteger( searchRequest.getMessageId() << 16 );
079
080        cookie = BerValue.getBytes( cookieValue.get() );
081    }
082
083
084    /**
085     * Compute a new key for this cookie, based on the current searchRequest
086     * hashCode and the current position. This value will be stored into the
087     * session, and will permit the retrieval of this instance.
088     *
089     * @return The new cookie's key
090     */
091    public byte[] getCookie()
092    {
093        return cookie;
094    }
095
096
097    public int getCookieValue()
098    {
099        return cookieValue.get();
100    }
101
102
103    /**
104     * Compute a new cookie, if the previous one already exists. This
105     * is unlikely, as we are based on some time seed, but just in case,
106     * this method will generate a new one.
107     * @return The new cookie
108     */
109    public byte[] getNewCookie()
110    {
111        cookie = BerValue.getBytes( cookieValue.incrementAndGet() );
112
113        return cookie;
114    }
115
116
117    /**
118     * Build a set of OIDs from the list of attributes we have in the search request
119     */
120    private Set<String> buildAttributeSet( SearchRequest request,
121        SchemaManager schemaManager )
122    {
123        Set<String> requestSet = new HashSet<>();
124
125        // Build the set of attributeType from the attributes
126        for ( String attribute : request.getAttributes() )
127        {
128            try
129            {
130                AttributeType at = schemaManager.lookupAttributeTypeRegistry( attribute );
131                requestSet.add( at.getOid() );
132            }
133            catch ( LdapException le )
134            {
135                // Deal with special attributes : '*', '+' and '1.1'
136                if ( attribute.equals( SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES )
137                    || attribute.equals( SchemaConstants.ALL_USER_ATTRIBUTES )
138                    || attribute.equals( SchemaConstants.NO_ATTRIBUTE ) )
139                {
140                    requestSet.add( attribute );
141                }
142
143                // Otherwise, don't add the attribute to the set
144            }
145        }
146
147        return requestSet;
148    }
149
150
151    /**
152     * Compare the previous search request and the new one, and return
153     * true if they are equal. We compare every field but the MessageID.
154     *
155     * @param request The new SearchRequest
156     * @param session The LdapSession in use
157     * @return true if both request are equal.
158     */
159    public boolean hasSameRequest( SearchRequest request, LdapSession session )
160    {
161        // Compares the scope
162        if ( request.getScope() != previousSearchRequest.getScope() )
163        {
164            return false;
165        }
166
167        // Compares the sizeLimit
168        if ( request.getSizeLimit() != previousSearchRequest.getSizeLimit() )
169        {
170            return false;
171        }
172
173        // Compares the timeLimit
174        if ( request.getTimeLimit() != previousSearchRequest.getTimeLimit() )
175        {
176            return false;
177        }
178
179        // Compares the TypesOnly
180        if ( request.getTypesOnly() != previousSearchRequest.getTypesOnly() )
181        {
182            return false;
183        }
184
185        // Compares the deref aliases mode
186        if ( request.getDerefAliases() != previousSearchRequest.getDerefAliases() )
187        {
188            return false;
189        }
190
191        SchemaManager schemaManager =
192            session.getLdapServer().getDirectoryService().getSchemaManager();
193
194        // Compares the attributes
195        if ( request.getAttributes() == null )
196        {
197            if ( previousSearchRequest.getAttributes() != null )
198            {
199                return false;
200            }
201        }
202        else
203        {
204            if ( previousSearchRequest.getAttributes() == null )
205            {
206                return false;
207            }
208            else
209            {
210                // We have to normalize the attributes in order to compare them
211                if ( request.getAttributes().size() != previousSearchRequest.getAttributes().size() )
212                {
213                    return false;
214                }
215
216                // Build the set of attributeType from both requests
217                Set<String> requestSet = buildAttributeSet( request, schemaManager );
218                Set<String> previousRequestSet = buildAttributeSet( previousSearchRequest, schemaManager );
219
220                // Check that both sets have the same size again after having converted
221                // the attributes to OID
222                if ( requestSet.size() != previousRequestSet.size() )
223                {
224                    return false;
225                }
226
227                for ( String attribute : requestSet )
228                {
229                    previousRequestSet.remove( attribute );
230                }
231
232                // The other set must be empty
233                if ( !previousRequestSet.isEmpty() )
234                {
235                    return false;
236                }
237            }
238        }
239
240        // Compare the baseDN
241        try
242        {
243            if ( !request.getBase().isSchemaAware() )
244            {
245                request.setBase( new Dn( schemaManager, request.getBase() ) );
246            }
247
248            if ( !previousSearchRequest.getBase().isSchemaAware() )
249            {
250                previousSearchRequest.setBase( new Dn( schemaManager, previousSearchRequest.getBase() ) );
251            }
252
253            if ( !request.getBase().equals( previousSearchRequest.getBase() ) )
254            {
255                return false;
256            }
257        }
258        catch ( LdapException le )
259        {
260            return false;
261        }
262
263        // Compare the filters
264        // Here, we assume the user hasn't changed the filter's order or content,
265        // as the filter is not normalized. This is a real problem, as the normalization
266        // phase is done in the interceptor chain, which is a bad decision wrt what we
267        // do here.
268        return true; //request.getFilter().equals( previousSearchRequest.getFilter() );
269    }
270
271
272    /**
273     * @return The current position in the cursor. This value is updated
274     * after each successful search request.
275     */
276    public int getCurrentPosition()
277    {
278        return currentPosition;
279    }
280
281
282    /**
283     * Set the new current position, incrementing it with the
284     * number of returned entries.
285     *
286     * @param returnedEntries The number of returned entries
287     */
288    public void incrementCurrentPosition( int returnedEntries )
289    {
290        this.currentPosition += returnedEntries;
291    }
292
293
294    /**
295     * @return The previous search request
296     */
297    public SearchRequest getPreviousSearchRequest()
298    {
299        return previousSearchRequest;
300    }
301
302
303    /**
304     * @return The associated cursor
305     */
306    public Cursor<Entry> getCursor()
307    {
308        return cursor;
309    }
310
311
312    /**
313     * Set the new cursor for this search request
314     * @param cursor The associated cursor
315     */
316    public void setCursor( Cursor<Entry> cursor )
317    {
318        this.cursor = cursor;
319    }
320
321
322    /**
323     * @see Object#toString()
324     */
325    public String toString()
326    {
327        StringBuilder sb = new StringBuilder();
328
329        sb.append( "PagedSearch context : <" );
330        sb.append( Strings.dumpBytes( cookie ) );
331        sb.append( ", " );
332        sb.append( currentPosition );
333        sb.append( ">" );
334
335        return sb.toString();
336    }
337}