/**
 * Copyright 2014- Qubole Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.qubole.qds.sdk.java.client;

import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.qubole.qds.sdk.java.entities.ResultValue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;

/**
 * A utility for polling for a command result. Allows for blocking or
 * callback on a running query
 */
public class ResultLatch
{
    private final QdsClient client;
    private final String queryId;
    private final AtomicLong pollMs = new AtomicLong(DEFAULT_POLL_MS);

    private static final ExecutorService executorService = Executors.newCachedThreadPool(new ThreadFactoryBuilder().setNameFormat("ResultLatch-%d").setDaemon(true).build());

    private static final int DEFAULT_POLL_MS = 5000;

    private static final String STATUS_DONE = "done";
    private static final String STATUS_WAITING = "waiting";
    private static final String STATUS_RUNNING = "running";

    /**
     * @param client the client
     * @param queryId the query
     */
    public ResultLatch(QdsClient client, int queryId)
    {
        this(client, Integer.toString(queryId));
    }

    /**
     * @param client the client
     * @param queryId the query
     */
    public ResultLatch(QdsClient client, String queryId)
    {
        this.client = Preconditions.checkNotNull(client, "client cannot be null");
        this.queryId = Preconditions.checkNotNull(queryId, "queryId cannot be null").trim();
    }

    /**
     * Change the time that the query status is polled. The default
     * is {@link #DEFAULT_POLL_MS}.
     *
     * @param time polling time
     * @param unit time unit
     */
    public void setPollSleep(long time, TimeUnit unit)
    {
        pollMs.set(unit.toMillis(time));
    }

    /**
     * Callback interface.
     *
     * @see ResultLatch#callback(Callback)
     */
    public interface Callback
    {
        /**
         * Called when the result has been retrieved
         *
         * @param queryId the query
         * @param resultValue the result
         */
        public void result(String queryId, ResultValue resultValue);

        /**
         * Called if there was an error getting the result
         *
         * @param queryId the query
         * @param e the error
         */
        public void error(String queryId, Exception e);
    }

    /**
     * This method returns immediately and polls asynchronously for the result.
     * When the result is retrieved (or there is an error) the callback is called
     *
     * @param callback callback
     */
    public void callback(final Callback callback)
    {
        Runnable runnable = new Runnable()
        {
            @Override
            public void run()
            {
                try
                {
                    callback.result(queryId, awaitResult());
                }
                catch ( Exception e )
                {
                    callback.error(queryId, e);
                }
            }
        };
        executorService.submit(runnable);
    }

    /**
     * Await indefinitely for the result
     *
     * @throws Exception errors
     */
    public void await() throws Exception
    {
        await(0, null);
    }

    /**
     * Await up to a maximum time for the result
     *
     * @param maxWait max wait time
     * @param timeUnit time unit
     * @return true if the result is ready, false if not or time has run out
     * @throws Exception errors
     */
    public boolean await(long maxWait, TimeUnit timeUnit) throws Exception
    {
        boolean hasWait = (timeUnit != null);
        long startMs = System.currentTimeMillis();
        for(;;)
        {
            String status = client.command().status(queryId).invoke().get().getStatus();
            if ( status.equalsIgnoreCase(STATUS_DONE) )
            {
                break;
            }
            if ( !status.equalsIgnoreCase(STATUS_RUNNING) && !status.equalsIgnoreCase(STATUS_WAITING) )
            {
                throw new Exception(String.format("Bad status for query %s: %s", queryId, status));
            }

            if ( hasWait )
            {
                long elapsedMs = System.currentTimeMillis() - startMs;
                if ( elapsedMs >= timeUnit.toMillis(maxWait) )
                {
                    return false;
                }
            }
            Thread.sleep(pollMs.get());
        }
        return true;
    }

    /**
     * Await indefinitely for the result
     *
     * @return the result
     * @throws Exception errors
     */
    public ResultValue awaitResult() throws Exception
    {
        await();
        return getResultValue();
    }

    /**
     * Await up to a maximum time for the result
     *
     * @param maxWait max wait time
     * @param timeUnit time unit
     * @return the result
     * @throws Exception errors
     */
    public ResultValue awaitResult(long maxWait, TimeUnit timeUnit) throws Exception
    {
        if ( !await(maxWait, timeUnit) )
        {
            throw new TimeoutException();
        }
        return getResultValue();
    }

    private ResultValue getResultValue() throws InterruptedException, java.util.concurrent.ExecutionException
    {
        return client.command().results(queryId).invoke().get();
    }
}

@@ -1006,16 +1006,49 @@
 micLong;
+%0Aimport java.util.logging.Logger;
 %0A%0A/**%0A *
@@ -1490,24 +1490,110 @@
 .build());%0A%0A
+    private static final Logger log = Logger.getLogger(ResultLatch.class.getName());%0A%0A
     private 
@@ -1632,16 +1632,65 @@
  = 5000;
+%0A    private static final int MIN_POLL_MS = 1000;
 %0A%0A    pr
@@ -2621,32 +2621,372 @@
 nit unit)%0A    %7B%0A
+        if ( unit.toMillis(time) %3C MIN_POLL_MS )%0A        %7B%0A            log.warning(String.format(%22Poll interval cannot be less than %25d seconds. Setting it to %25d seconds.%22, TimeUnit.MILLISECONDS.toSeconds(MIN_POLL_MS), TimeUnit.MILLISECONDS.toSeconds(MIN_POLL_MS)));%0A            pollMs.set(MIN_POLL_MS);%0A        %7D%0A        else%0A        %7B%0A    
         pollMs.s
@@ -3006,24 +3006,34 @@
 lis(time));%0A
+        %7D%0A
     %7D%0A%0A    /

Set a limit on minimum polling interval. Pull Request #38