001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2009-2010 Sun Microsystems, Inc. 025 * Portions Copyright 2011-2014 ForgeRock AS 026 */ 027 028package org.forgerock.opendj.ldap; 029 030import java.util.Collection; 031 032import org.forgerock.opendj.ldap.controls.SubtreeDeleteRequestControl; 033import org.forgerock.opendj.ldap.requests.AddRequest; 034import org.forgerock.opendj.ldap.requests.BindRequest; 035import org.forgerock.opendj.ldap.requests.CompareRequest; 036import org.forgerock.opendj.ldap.requests.DeleteRequest; 037import org.forgerock.opendj.ldap.requests.ExtendedRequest; 038import org.forgerock.opendj.ldap.requests.ModifyDNRequest; 039import org.forgerock.opendj.ldap.requests.ModifyRequest; 040import org.forgerock.opendj.ldap.requests.Requests; 041import org.forgerock.opendj.ldap.requests.SearchRequest; 042import org.forgerock.opendj.ldap.responses.BindResult; 043import org.forgerock.opendj.ldap.responses.CompareResult; 044import org.forgerock.opendj.ldap.responses.ExtendedResult; 045import org.forgerock.opendj.ldap.responses.GenericExtendedResult; 046import org.forgerock.opendj.ldap.responses.Result; 047import org.forgerock.opendj.ldap.responses.SearchResultEntry; 048import org.forgerock.opendj.ldap.responses.SearchResultReference; 049import org.forgerock.opendj.ldif.ChangeRecord; 050import org.forgerock.opendj.ldif.ChangeRecordVisitor; 051import org.forgerock.opendj.ldif.ConnectionEntryReader; 052import org.forgerock.util.Reject; 053import org.forgerock.util.Function; 054 055import static org.forgerock.opendj.ldap.LdapException.*; 056import static org.forgerock.opendj.ldap.requests.Requests.*; 057import static org.forgerock.opendj.ldap.spi.LdapPromises.*; 058 059import static com.forgerock.opendj.ldap.CoreMessages.*; 060 061/** 062 * This class provides a skeletal implementation of the {@code Connection} 063 * interface, to minimize the effort required to implement this interface. 064 */ 065public abstract class AbstractConnection implements Connection { 066 067 private static final class SingleEntryHandler implements SearchResultHandler { 068 private volatile SearchResultEntry firstEntry; 069 private volatile SearchResultReference firstReference; 070 private volatile int entryCount; 071 072 @Override 073 public boolean handleEntry(final SearchResultEntry entry) { 074 if (firstEntry == null) { 075 firstEntry = entry; 076 } 077 entryCount++; 078 return true; 079 } 080 081 @Override 082 public boolean handleReference(final SearchResultReference reference) { 083 if (firstReference == null) { 084 firstReference = reference; 085 } 086 return true; 087 } 088 089 /** 090 * Filter the provided error in order to transform size limit exceeded 091 * error to a client side error, or leave it as is for any other error. 092 * 093 * @param error 094 * to filter 095 * @return provided error in most case, or 096 * <code>ResultCode.CLIENT_SIDE_UNEXPECTED_RESULTS_RETURNED</code> 097 * error if provided error is 098 * <code>ResultCode.SIZE_LIMIT_EXCEEDED</code> 099 */ 100 private LdapException filterError(final LdapException error) { 101 if (error.getResult().getResultCode().equals(ResultCode.SIZE_LIMIT_EXCEEDED)) { 102 return newLdapException(ResultCode.CLIENT_SIDE_UNEXPECTED_RESULTS_RETURNED, 103 ERR_UNEXPECTED_SEARCH_RESULT_ENTRIES_NO_COUNT.get().toString()); 104 } else { 105 return error; 106 } 107 } 108 109 /** 110 * Check for any error related to number of search result at client-side 111 * level: no result, too many result, search result reference. This 112 * method should be called only after search operation is finished. 113 * 114 * @return The single search result entry. 115 * @throws LdapException 116 * If an error is detected. 117 */ 118 private SearchResultEntry getSingleEntry() throws LdapException { 119 if (entryCount == 0) { 120 // Did not find any entries. 121 throw newLdapException(ResultCode.CLIENT_SIDE_NO_RESULTS_RETURNED, 122 ERR_NO_SEARCH_RESULT_ENTRIES.get().toString()); 123 } else if (entryCount > 1) { 124 // Got more entries than expected. 125 throw newLdapException(ResultCode.CLIENT_SIDE_UNEXPECTED_RESULTS_RETURNED, 126 ERR_UNEXPECTED_SEARCH_RESULT_ENTRIES.get(entryCount).toString()); 127 } else if (firstReference != null) { 128 // Got an unexpected search result reference. 129 throw newLdapException(ResultCode.CLIENT_SIDE_UNEXPECTED_RESULTS_RETURNED, 130 ERR_UNEXPECTED_SEARCH_RESULT_REFERENCES.get(firstReference.getURIs().iterator().next()) 131 .toString()); 132 } else { 133 return firstEntry; 134 } 135 } 136 } 137 138 /** Visitor used for processing synchronous change requests. */ 139 private static final ChangeRecordVisitor<Object, Connection> SYNC_VISITOR = 140 new ChangeRecordVisitor<Object, Connection>() { 141 142 @Override 143 public Object visitChangeRecord(final Connection p, final AddRequest change) { 144 try { 145 return p.add(change); 146 } catch (final LdapException e) { 147 return e; 148 } 149 } 150 151 @Override 152 public Object visitChangeRecord(final Connection p, final DeleteRequest change) { 153 try { 154 return p.delete(change); 155 } catch (final LdapException e) { 156 return e; 157 } 158 } 159 160 @Override 161 public Object visitChangeRecord(final Connection p, final ModifyDNRequest change) { 162 try { 163 return p.modifyDN(change); 164 } catch (final LdapException e) { 165 return e; 166 } 167 } 168 169 @Override 170 public Object visitChangeRecord(final Connection p, final ModifyRequest change) { 171 try { 172 return p.modify(change); 173 } catch (final LdapException e) { 174 return e; 175 } 176 } 177 }; 178 179 /** 180 * Creates a new abstract connection. 181 */ 182 protected AbstractConnection() { 183 // No implementation required. 184 } 185 186 @Override 187 public Result add(final Entry entry) throws LdapException { 188 return add(Requests.newAddRequest(entry)); 189 } 190 191 @Override 192 public Result add(final String... ldifLines) throws LdapException { 193 return add(Requests.newAddRequest(ldifLines)); 194 } 195 196 @Override 197 public LdapPromise<Result> addAsync(final AddRequest request) { 198 return addAsync(request, null); 199 } 200 201 @Override 202 public Result applyChange(final ChangeRecord request) throws LdapException { 203 final Object result = request.accept(SYNC_VISITOR, this); 204 if (result instanceof Result) { 205 return (Result) result; 206 } else { 207 throw (LdapException) result; 208 } 209 } 210 211 @Override 212 public LdapPromise<Result> applyChangeAsync(ChangeRecord request) { 213 return applyChangeAsync(request, null); 214 } 215 216 @Override 217 public LdapPromise<Result> applyChangeAsync(final ChangeRecord request, 218 final IntermediateResponseHandler intermediateResponseHandler) { 219 final ChangeRecordVisitor<LdapPromise<Result>, Connection> visitor = 220 new ChangeRecordVisitor<LdapPromise<Result>, Connection>() { 221 222 @Override 223 public LdapPromise<Result> visitChangeRecord(final Connection p, final AddRequest change) { 224 return p.addAsync(change, intermediateResponseHandler); 225 } 226 227 @Override 228 public LdapPromise<Result> visitChangeRecord(final Connection p, final DeleteRequest change) { 229 return p.deleteAsync(change, intermediateResponseHandler); 230 } 231 232 @Override 233 public LdapPromise<Result> visitChangeRecord(final Connection p, final ModifyDNRequest change) { 234 return p.modifyDNAsync(change, intermediateResponseHandler); 235 } 236 237 @Override 238 public LdapPromise<Result> visitChangeRecord(final Connection p, final ModifyRequest change) { 239 return p.modifyAsync(change, intermediateResponseHandler); 240 } 241 }; 242 return request.accept(visitor, this); 243 } 244 245 @Override 246 public BindResult bind(final String name, final char[] password) throws LdapException { 247 return bind(Requests.newSimpleBindRequest(name, password)); 248 } 249 250 @Override 251 public LdapPromise<BindResult> bindAsync(final BindRequest request) { 252 return bindAsync(request, null); 253 } 254 255 @Override 256 public void close() { 257 close(Requests.newUnbindRequest(), null); 258 } 259 260 @Override 261 public CompareResult compare(final String name, final String attributeDescription, final String assertionValue) 262 throws LdapException { 263 return compare(Requests.newCompareRequest(name, attributeDescription, assertionValue)); 264 } 265 266 @Override 267 public LdapPromise<CompareResult> compareAsync(final CompareRequest request) { 268 return compareAsync(request, null); 269 } 270 271 @Override 272 public Result delete(final String name) throws LdapException { 273 return delete(Requests.newDeleteRequest(name)); 274 } 275 276 @Override 277 public LdapPromise<Result> deleteAsync(final DeleteRequest request) { 278 return deleteAsync(request, null); 279 } 280 281 @Override 282 public Result deleteSubtree(final String name) throws LdapException { 283 return delete(Requests.newDeleteRequest(name).addControl(SubtreeDeleteRequestControl.newControl(true))); 284 } 285 286 @Override 287 public <R extends ExtendedResult> R extendedRequest(final ExtendedRequest<R> request) throws LdapException { 288 return extendedRequest(request, null); 289 } 290 291 @Override 292 public GenericExtendedResult extendedRequest(final String requestName, final ByteString requestValue) 293 throws LdapException { 294 return extendedRequest(Requests.newGenericExtendedRequest(requestName, requestValue)); 295 } 296 297 @Override 298 public <R extends ExtendedResult> LdapPromise<R> extendedRequestAsync(final ExtendedRequest<R> request) { 299 return extendedRequestAsync(request, null); 300 } 301 302 @Override 303 public Result modify(final String... ldifLines) throws LdapException { 304 return modify(Requests.newModifyRequest(ldifLines)); 305 } 306 307 @Override 308 public LdapPromise<Result> modifyAsync(final ModifyRequest request) { 309 return modifyAsync(request, null); 310 } 311 312 @Override 313 public Result modifyDN(final String name, final String newRDN) throws LdapException { 314 return modifyDN(Requests.newModifyDNRequest(name, newRDN)); 315 } 316 317 @Override 318 public LdapPromise<Result> modifyDNAsync(final ModifyDNRequest request) { 319 return modifyDNAsync(request, null); 320 } 321 322 @Override 323 public SearchResultEntry readEntry(final DN baseObject, final String... attributeDescriptions) 324 throws LdapException { 325 final SearchRequest request = 326 Requests.newSingleEntrySearchRequest(baseObject, SearchScope.BASE_OBJECT, Filter.objectClassPresent(), 327 attributeDescriptions); 328 return searchSingleEntry(request); 329 } 330 331 @Override 332 public SearchResultEntry readEntry(final String baseObject, final String... attributeDescriptions) 333 throws LdapException { 334 return readEntry(DN.valueOf(baseObject), attributeDescriptions); 335 } 336 337 @Override 338 public LdapPromise<SearchResultEntry> readEntryAsync(final DN name, 339 final Collection<String> attributeDescriptions) { 340 final SearchRequest request = Requests.newSingleEntrySearchRequest(name, SearchScope.BASE_OBJECT, 341 Filter.objectClassPresent()); 342 if (attributeDescriptions != null) { 343 request.getAttributes().addAll(attributeDescriptions); 344 } 345 return searchSingleEntryAsync(request); 346 } 347 348 @Override 349 public ConnectionEntryReader search(final SearchRequest request) { 350 return new ConnectionEntryReader(this, request); 351 } 352 353 @Override 354 public Result search(final SearchRequest request, final Collection<? super SearchResultEntry> entries) 355 throws LdapException { 356 return search(request, entries, null); 357 } 358 359 @Override 360 public Result search(final SearchRequest request, final Collection<? super SearchResultEntry> entries, 361 final Collection<? super SearchResultReference> references) throws LdapException { 362 Reject.ifNull(request, entries); 363 // FIXME: does this need to be thread safe? 364 final SearchResultHandler handler = new SearchResultHandler() { 365 @Override 366 public boolean handleEntry(final SearchResultEntry entry) { 367 entries.add(entry); 368 return true; 369 } 370 371 @Override 372 public boolean handleReference(final SearchResultReference reference) { 373 if (references != null) { 374 references.add(reference); 375 } 376 return true; 377 } 378 }; 379 380 return search(request, handler); 381 } 382 383 @Override 384 public ConnectionEntryReader search(final String baseObject, final SearchScope scope, final String filter, 385 final String... attributeDescriptions) { 386 return search(newSearchRequest(baseObject, scope, filter, attributeDescriptions)); 387 } 388 389 @Override 390 public LdapPromise<Result> searchAsync(final SearchRequest request, final SearchResultHandler resultHandler) { 391 return searchAsync(request, null, resultHandler); 392 } 393 394 @Override 395 public SearchResultEntry searchSingleEntry(final SearchRequest request) throws LdapException { 396 final SingleEntryHandler handler = new SingleEntryHandler(); 397 try { 398 search(enforceSingleEntrySearchRequest(request), handler); 399 return handler.getSingleEntry(); 400 } catch (final LdapException e) { 401 throw handler.filterError(e); 402 } 403 } 404 405 @Override 406 public SearchResultEntry searchSingleEntry(final String baseObject, final SearchScope scope, final String filter, 407 final String... attributeDescriptions) throws LdapException { 408 final SearchRequest request = 409 Requests.newSingleEntrySearchRequest(baseObject, scope, filter, attributeDescriptions); 410 return searchSingleEntry(request); 411 } 412 413 @Override 414 public LdapPromise<SearchResultEntry> searchSingleEntryAsync(final SearchRequest request) { 415 final SingleEntryHandler handler = new SingleEntryHandler(); 416 return asPromise(searchAsync(enforceSingleEntrySearchRequest(request), handler).then( 417 new Function<Result, SearchResultEntry, LdapException>() { 418 @Override 419 public SearchResultEntry apply(final Result value) throws LdapException { 420 return handler.getSingleEntry(); 421 } 422 }, new Function<LdapException, SearchResultEntry, LdapException>() { 423 @Override 424 public SearchResultEntry apply(final LdapException error) throws LdapException { 425 throw handler.filterError(error); 426 } 427 })); 428 } 429 430 /** 431 * Ensure that a single entry search request is returned, based on provided request. 432 * 433 * @param request 434 * to be checked 435 * @return a single entry search request, equal to or based on the provided request 436 */ 437 private SearchRequest enforceSingleEntrySearchRequest(final SearchRequest request) { 438 if (request.isSingleEntrySearch()) { 439 return request; 440 } else { 441 return Requests.copyOfSearchRequest(request).setSizeLimit(1); 442 } 443 } 444 445 /** 446 * {@inheritDoc} 447 * <p> 448 * Sub-classes should provide an implementation which returns an appropriate 449 * description of the connection which may be used for debugging purposes. 450 * </p> 451 */ 452 @Override 453 public abstract String toString(); 454 455}