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}