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