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 *     https://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        // no need to wait if already done or cancelled
091        if ( !done && !cancelled )
092        {
093            wait( unit.toMillis( timeout ) );
094        }
095
096        return response;
097    }
098
099
100    /**
101     * Set the associated Response in this Future
102     * 
103     * @param response The response to add into the Future
104     * @throws InterruptedException if the operation has been cancelled by client
105     */
106    public synchronized void set( R response ) throws InterruptedException
107    {
108        this.response = response;
109
110        done = response != null;
111
112        notifyAll();
113    }
114
115
116    /**
117     * {@inheritDoc}
118     */
119    @Override
120    public boolean cancel( boolean mayInterruptIfRunning )
121    {
122        if ( cancelled )
123        {
124            return cancelled;
125        }
126
127        // set the cancel flag first
128        cancelled = true;
129
130        // Send an abandonRequest only if this future exists
131        if ( !connection.isRequestCompleted( messageId ) )
132        {
133            connection.abandon( messageId );
134        }
135        
136        // Notify the future
137        try
138        { 
139            set( null );
140        }
141        catch ( InterruptedException ie )
142        {
143            // Nothing we can do
144        }
145
146        return cancelled;
147    }
148
149
150    /**
151     * {@inheritDoc}
152     */
153    @Override
154    public boolean isCancelled()
155    {
156        return cancelled;
157    }
158
159
160    /**
161     * This operation is not supported in this implementation of Future.
162     * 
163     * {@inheritDoc}
164     */
165    @Override
166    public boolean isDone()
167    {
168        return done;
169    }
170
171
172    /**
173     * @return the cause
174     */
175    public Throwable getCause()
176    {
177        return cause;
178    }
179
180
181    /**
182     * Associate a cause to the ResponseFuture
183     * @param cause the cause to set
184     */
185    public void setCause( Throwable cause )
186    {
187        this.cause = cause;
188    }
189
190
191    /**
192     * Cancel the Future
193     *
194     */
195    public void cancel()
196    {
197        // set the cancel flag first
198        cancelled = true;
199        
200        // Notify the future
201        try
202        { 
203            set( null );
204        }
205        catch ( InterruptedException ie )
206        {
207            // Nothing we can do
208        }
209    }
210
211
212    /**
213     * {@inheritDoc}
214     */
215    @Override
216    public String toString()
217    {
218        StringBuilder sb = new StringBuilder();
219
220        sb.append( "[msgId : " ).append( messageId ).append( ", " );
221        sb.append( "Canceled :" ).append( cancelled ).append( "]" );
222
223        return sb.toString();
224    }
225}