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 */
020package org.apache.directory.ldap.client.api.future;
021
022import java.util.concurrent.TimeUnit;
023
024import org.apache.directory.api.ldap.model.message.Response;
025import org.apache.directory.ldap.client.api.LdapConnection;
026
027/**
028 * A Future implementation used in LdapConnection operations for operations
029 * that only get one single response.
030 *
031 * @param <R> The result type returned by this Future's <tt>get</tt> method
032 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
033 */
034public abstract class UniqueResponseFuture<R extends Response> implements ResponseFuture<R>
035{
036    /** The response */
037    private R response;
038
039    /** flag to determine if this future is cancelled */
040    protected boolean cancelled = false;
041
042    /** If the request has been cancelled because of an exception  it will be stored here */
043    protected Throwable cause;
044
045    /** The messageID for this future */
046    protected int messageId;
047
048    /** The connection used by the request */
049    protected LdapConnection connection;
050    
051    /** A flag set to TRUE when the response has been received */
052    private volatile boolean done = false;
053
054    /**
055     * Creates a new instance of UniqueResponseFuture.
056     *
057     * @param connection The LdapConnection used by the request
058     * @param messageId The associated message ID
059     */
060    public UniqueResponseFuture( LdapConnection connection, int messageId )
061    {
062        this.messageId = messageId;
063        this.connection = connection;
064    }
065
066
067    /**
068     * {@inheritDoc}
069     * @throws InterruptedException if the operation has been cancelled by client
070     */
071    @Override
072    public synchronized R get() throws InterruptedException
073    {
074        while ( !done && !cancelled )
075        {
076            wait();
077        }
078        
079        return response;
080    }
081
082
083    /**
084     * {@inheritDoc}
085     * @throws InterruptedException if the operation has been cancelled by client
086     */
087    @Override
088    public synchronized R get( long timeout, TimeUnit unit ) throws InterruptedException
089    {
090        wait( unit.toMillis( timeout ) );
091        
092        return response;
093    }
094
095
096    /**
097     * Set the associated Response in this Future
098     * 
099     * @param response The response to add into the Future
100     * @throws InterruptedException if the operation has been cancelled by client
101     */
102    public synchronized void set( R response ) throws InterruptedException
103    {
104        this.response = response;
105        
106        done = response != null;
107        
108        notifyAll();
109    }
110
111
112    /**
113     * {@inheritDoc}
114     */
115    @Override
116    public boolean cancel( boolean mayInterruptIfRunning )
117    {
118        if ( cancelled )
119        {
120            return cancelled;
121        }
122
123        // set the cancel flag first
124        cancelled = true;
125
126        // Send an abandonRequest only if this future exists
127        if ( !connection.isRequestCompleted( messageId ) )
128        {
129            connection.abandon( messageId );
130        }
131        
132        // Notify the future
133        try
134        { 
135            set( null );
136        }
137        catch ( InterruptedException ie )
138        {
139            // Nothing we can do
140        }
141
142        return cancelled;
143    }
144
145
146    /**
147     * {@inheritDoc}
148     */
149    @Override
150    public boolean isCancelled()
151    {
152        return cancelled;
153    }
154
155
156    /**
157     * This operation is not supported in this implementation of Future.
158     * 
159     * {@inheritDoc}
160     */
161    @Override
162    public boolean isDone()
163    {
164        return done;
165    }
166
167
168    /**
169     * @return the cause
170     */
171    public Throwable getCause()
172    {
173        return cause;
174    }
175
176
177    /**
178     * Associate a cause to the ResponseFuture
179     * @param cause the cause to set
180     */
181    public void setCause( Throwable cause )
182    {
183        this.cause = cause;
184    }
185
186
187    /**
188     * Cancel the Future
189     *
190     */
191    public void cancel()
192    {
193        // set the cancel flag first
194        cancelled = true;
195        
196        // Notify the future
197        try
198        { 
199            set( null );
200        }
201        catch ( InterruptedException ie )
202        {
203            // Nothing we can do
204        }
205    }
206
207
208    /**
209     * {@inheritDoc}
210     */
211    @Override
212    public String toString()
213    {
214        StringBuilder sb = new StringBuilder();
215
216        sb.append( "[msgId : " ).append( messageId ).append( ", " );
217        sb.append( "Canceled :" ).append( cancelled ).append( "]" );
218
219        return sb.toString();
220    }
221}