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}