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