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}