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