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 }