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