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 */
020
021package org.apache.directory.ldap.client.api;
022
023
024import java.io.IOException;
025import java.util.concurrent.TimeUnit;
026
027import org.apache.directory.api.i18n.I18n;
028import org.apache.directory.api.ldap.model.constants.Loggers;
029import org.apache.directory.api.ldap.model.cursor.AbstractCursor;
030import org.apache.directory.api.ldap.model.cursor.CursorException;
031import org.apache.directory.api.ldap.model.cursor.InvalidCursorPositionException;
032import org.apache.directory.api.ldap.model.cursor.SearchCursor;
033import org.apache.directory.api.ldap.model.entry.Entry;
034import org.apache.directory.api.ldap.model.exception.LdapException;
035import org.apache.directory.api.ldap.model.exception.LdapReferralException;
036import org.apache.directory.api.ldap.model.message.IntermediateResponse;
037import org.apache.directory.api.ldap.model.message.Referral;
038import org.apache.directory.api.ldap.model.message.Response;
039import org.apache.directory.api.ldap.model.message.SearchResultDone;
040import org.apache.directory.api.ldap.model.message.SearchResultEntry;
041import org.apache.directory.api.ldap.model.message.SearchResultReference;
042import org.apache.directory.ldap.client.api.exception.LdapConnectionTimeOutException;
043import org.apache.directory.ldap.client.api.future.SearchFuture;
044import org.slf4j.Logger;
045import org.slf4j.LoggerFactory;
046
047
048/**
049 * An implementation of Cursor based on the underlying SearchFuture instance.
050 * 
051 * Note: This is a forward only cursor hence the only valid operations are next(), get() and close() 
052 * 
053 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
054 */
055public class SearchCursorImpl extends AbstractCursor<Response> implements SearchCursor
056{
057    /** A dedicated log for cursors */
058    private static final Logger LOG_CURSOR = LoggerFactory.getLogger( Loggers.CURSOR_LOG.getName() );
059
060    /** Speedup for logs */
061    private static final boolean IS_DEBUG = LOG_CURSOR.isDebugEnabled();
062
063    /** the search future */
064    private SearchFuture future;
065
066    /** wait time while polling for a SearchResponse */
067    private long timeout;
068
069    /** time units of timeout value */
070    private TimeUnit timeUnit;
071
072    /** a reference to hold the retrieved SearchResponse object from SearchFuture */
073    private Response response;
074
075    /** the done flag */
076    private boolean done;
077
078    /** a reference to hold the SearchResultDone response */
079    private SearchResultDone searchDoneResp;
080
081
082    /**
083     * Instantiates a new search cursor.
084     *
085     * @param future the future
086     * @param timeout the timeout
087     * @param timeUnit the time unit
088     */
089    public SearchCursorImpl( SearchFuture future, long timeout, TimeUnit timeUnit )
090    {
091        if ( IS_DEBUG )
092        {
093            LOG_CURSOR.debug( "Creating SearchCursorImpl {}", this );
094        }
095
096        this.future = future;
097        this.timeout = timeout;
098        this.timeUnit = timeUnit;
099    }
100
101
102    /**
103     * {@inheritDoc}
104     */
105    @Override
106    public boolean next() throws LdapException, CursorException
107    {
108        if ( done )
109        {
110            return false;
111        }
112
113        try
114        {
115            if ( future.isCancelled() )
116            {
117                response = null;
118                done = true;
119                return false;
120            }
121
122            response = future.get( timeout, timeUnit );
123        }
124        catch ( Exception e )
125        {
126            LdapException ldapException = new LdapException( LdapNetworkConnection.NO_RESPONSE_ERROR, e );
127
128            // Send an abandon request
129            if ( !future.isCancelled() )
130            {
131                future.cancel( true );
132            }
133
134            // close the cursor
135            try 
136            {
137                close( ldapException );
138            }
139            catch ( IOException ioe )
140            {
141                throw new LdapException( ioe.getMessage(), ioe );
142            }
143
144            throw ldapException;
145        }
146
147        if ( response == null )
148        {
149            future.cancel( true );
150
151            throw new LdapConnectionTimeOutException( LdapNetworkConnection.TIME_OUT_ERROR );
152        }
153
154        done = response instanceof SearchResultDone;
155
156        if ( done )
157        {
158            searchDoneResp = ( SearchResultDone ) response;
159
160            response = null;
161        }
162
163        return !done;
164    }
165
166
167    /**
168     * {@inheritDoc}
169     */
170    @Override
171    public Response get() throws InvalidCursorPositionException
172    {
173        if ( !available() )
174        {
175            throw new InvalidCursorPositionException();
176        }
177
178        return response;
179    }
180
181
182    /**
183     * {@inheritDoc}
184     */
185    @Override
186    public SearchResultDone getSearchResultDone()
187    {
188        return searchDoneResp;
189    }
190
191
192    /**
193     * {@inheritDoc}
194     */
195    @Override
196    public boolean available()
197    {
198        return response != null;
199    }
200
201
202    /**
203     * {@inheritDoc}
204     */
205    @Override
206    public void close() throws IOException
207    {
208        if ( IS_DEBUG )
209        {
210            LOG_CURSOR.debug( "Closing SearchCursorImpl {}", this );
211        }
212
213        close( null );
214    }
215
216
217    /**
218     * {@inheritDoc}
219     */
220    @Override
221    public void close( Exception cause ) throws IOException
222    {
223        if ( IS_DEBUG )
224        {
225            LOG_CURSOR.debug( "Closing SearchCursorImpl {}", this );
226        }
227
228        if ( done )
229        {
230            super.close();
231            return;
232        }
233
234        if ( !future.isCancelled() )
235        {
236            future.cancel( true );
237        }
238
239        if ( cause != null )
240        {
241            super.close( cause );
242        }
243        else
244        {
245            super.close();
246        }
247    }
248
249
250    // rest of all operations will throw UnsupportedOperationException
251
252    /**
253     * This operation is not supported in SearchCursor.
254     * {@inheritDoc}
255     */
256    @Override
257    public void after( Response element ) throws LdapException, CursorException
258    {
259        throw new UnsupportedOperationException( I18n.err( I18n.ERR_02014_UNSUPPORTED_OPERATION, getClass().getName()
260            .concat( "." ).concat( "after( Response element )" ) ) );
261    }
262
263
264    /**
265     * This operation is not supported in SearchCursor.
266     * {@inheritDoc}
267     */
268    @Override
269    public void afterLast() throws LdapException, CursorException
270    {
271        throw new UnsupportedOperationException( I18n.err( I18n.ERR_02014_UNSUPPORTED_OPERATION, getClass().getName()
272            .concat( "." ).concat( "afterLast()" ) ) );
273    }
274
275
276    /**
277     * This operation is not supported in SearchCursor.
278     * {@inheritDoc}
279     */
280    @Override
281    public void before( Response element ) throws LdapException, CursorException
282    {
283        throw new UnsupportedOperationException( I18n.err( I18n.ERR_02014_UNSUPPORTED_OPERATION, getClass().getName()
284            .concat( "." ).concat( "before( Response element )" ) ) );
285    }
286
287
288    /**
289     * This operation is not supported in SearchCursor.
290     * {@inheritDoc}
291     */
292    @Override
293    public void beforeFirst() throws LdapException, CursorException
294    {
295        throw new UnsupportedOperationException( I18n.err( I18n.ERR_02014_UNSUPPORTED_OPERATION, getClass().getName()
296            .concat( "." ).concat( "beforeFirst()" ) ) );
297    }
298
299
300    /**
301     * This operation is not supported in SearchCursor.
302     * {@inheritDoc}
303     */
304    @Override
305    public boolean first() throws LdapException, CursorException
306    {
307        throw new UnsupportedOperationException( I18n.err( I18n.ERR_02014_UNSUPPORTED_OPERATION, getClass().getName()
308            .concat( "." ).concat( "first()" ) ) );
309    }
310
311
312    /**
313     * This operation is not supported in SearchCursor.
314     * {@inheritDoc}
315     */
316    @Override
317    public boolean last() throws LdapException, CursorException
318    {
319        throw new UnsupportedOperationException( I18n.err( I18n.ERR_02014_UNSUPPORTED_OPERATION, getClass().getName()
320            .concat( "." ).concat( "last()" ) ) );
321    }
322
323
324    /**
325     * This operation is not supported in SearchCursor.
326     * {@inheritDoc}
327     */
328    @Override
329    public boolean previous() throws LdapException, CursorException
330    {
331        throw new UnsupportedOperationException( I18n.err( I18n.ERR_02014_UNSUPPORTED_OPERATION, getClass().getName()
332            .concat( "." ).concat( "previous()" ) ) );
333    }
334
335
336    /**
337     * {@inheritDoc}
338     */
339    @Override
340    public boolean isDone()
341    {
342        return done;
343    }
344
345
346    /**
347     * {@inheritDoc}
348     */
349    @Override
350    public boolean isReferral()
351    {
352        return response instanceof SearchResultReference;
353    }
354
355
356    /**
357     * {@inheritDoc}
358     */
359    @Override
360    public Referral getReferral() throws LdapException
361    {
362        if ( isReferral() )
363        {
364            return ( ( SearchResultReference ) response ).getReferral();
365        }
366
367        throw new LdapException();
368    }
369
370
371    /**
372     * {@inheritDoc}
373     */
374    @Override
375    public boolean isEntry()
376    {
377        return response instanceof SearchResultEntry;
378    }
379
380
381    /**
382     * {@inheritDoc}
383     */
384    @Override
385    public Entry getEntry() throws LdapException
386    {
387        if ( isEntry() )
388        {
389            return ( ( SearchResultEntry ) response ).getEntry();
390        }
391        
392        if ( isReferral() )
393        {
394            Referral referral = ( ( SearchResultReference ) response ).getReferral();
395            throw new LdapReferralException( referral.getLdapUrls() );
396        }
397
398        throw new LdapException();
399    }
400
401
402    /**
403     * {@inheritDoc}
404     */
405    @Override
406    public boolean isIntermediate()
407    {
408        return response instanceof IntermediateResponse;
409    }
410
411
412    /**
413     * {@inheritDoc}
414     */
415    @Override
416    public IntermediateResponse getIntermediate() throws LdapException
417    {
418        if ( isEntry() )
419        {
420            return ( IntermediateResponse ) response;
421        }
422
423        throw new LdapException();
424    }
425}