/*
 * Copyright © 2013-2019, The SeedStack authors <http://seedstack.org>
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

package org.seedstack.shed.misc;

import java.time.Duration;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RetryingExecutor implements Executor {
    private static final Logger LOGGER = LoggerFactory.getLogger(RetryingExecutor.class);
    private static final int DEFAULT_RETRY_DELAY = 10000;
    private final AtomicBoolean active = new AtomicBoolean(false);
    private final Timer timer = new Timer();
    private final String name;
    private volatile Duration retryDelay = Duration.ofMillis(DEFAULT_RETRY_DELAY);
    private volatile Thread thread;
    private volatile Runnable command;

    public RetryingExecutor() {
        this.name = "retry";
    }

    public RetryingExecutor(String name) {
        this.name = name;
    }

    public RetryingExecutor(String name, Duration retryDelay) {
        this.name = name;
        this.retryDelay = retryDelay;
    }

    public Duration getRetryDelay() {
        return retryDelay;
    }

    public void setRetryDelay(Duration retryDelay) {
        this.retryDelay = retryDelay;
    }

    public synchronized void start() {
        if (!active.getAndSet(true)) {
            startThread();
        }
    }

    public synchronized void stop() {
        if (active.getAndSet(false)) {
            timer.cancel();
            thread.interrupt();
        }
    }

    public synchronized void execute(Runnable command) {
        if (active.get()) {
            stop();
            this.command = command;
            start();
        } else {
            this.command = command;
        }
    }

    private void startThread() {
        thread = new Thread(() -> {
            LOGGER.debug("Retry executor {} is starting", getName());

            while (active.get()) {
                try {
                    command.run();
                } catch (Exception e) {
                    LOGGER.error("An exception occurred during {} command execution", getName(), e);
                    break;
                }
            }

            if (active.get()) {
                LOGGER.warn("Interruption of {} command execution. A retry is scheduled in {} ms",
                        getName(),
                        retryDelay.toMillis());
                try {
                    timer.schedule(new MyTimerTask(), retryDelay.toMillis());
                } catch (Exception e) {
                    LOGGER.error("Unable to schedule a retry of {} command execution", getName(), e);
                }
            } else {
                LOGGER.debug("Retry executor {} is stopping", getName());
            }
        });
        thread.setName(getName());
        thread.start();
    }

    private String getName() {
        return name + "-" + thread.getId();
    }

    private class MyTimerTask extends TimerTask {
        @Override
        public void run() {
            synchronized (RetryingExecutor.this) {
                if (!thread.isAlive()) {
                    startThread();
                } else {
                    timer.schedule(new MyTimerTask(), retryDelay.toMillis());
                }
            }
        }
    }
}

@@ -536,16 +536,178 @@
 ctory;%0A%0A
+/**%0A * Simple %7B@link Executor%7D that executes the command continuously in a loop and implements a delayed retry logic%0A * when the command throws an exception.%0A */%0A
 public c
@@ -1208,354 +1208,1125 @@
     
-public RetryingExecutor() %7B%0A        this.name = %22retry%22;%0A    %7D%0A%0A    public RetryingExecutor(String name) %7B%0A        this.name = name;%0A    %7D%0A%0A    public RetryingExecutor(String name, Duration retryDelay) %7B%0A        this.name = name;%0A        this.retryDelay = retryDelay;%0A    %7D%0A%0A    public Duration getRetryDelay() %7B%0A        return retryDelay;%0A    %7D%0A
+/**%0A     * Creates a %7B@link RetryingExecutor%7D with default parameters.%0A     */%0A    public RetryingExecutor() %7B%0A        this.name = %22retry%22;%0A    %7D%0A%0A    /**%0A     * Creates a %7B@link RetryingExecutor%7D with the specified name but otherwise default parameters.%0A     *%0A     * @param name the name of the executor (used for naming the executing thread).%0A     */%0A    public RetryingExecutor(String name) %7B%0A        this.name = name;%0A    %7D%0A%0A    /**%0A     * Creates a %7B@link RetryingExecutor%7D with the specified name and the the specified retry delay.%0A     *%0A     * @param name       the name of the executor (used for naming the executing thread).%0A     * @param retryDelay the delay to wait before attempting a retry.%0A     */%0A    public RetryingExecutor(String name, Duration retryDelay) %7B%0A        this.name = name;%0A        this.retryDelay = retryDelay;%0A    %7D%0A%0A    /**%0A     * @return the delay to wait before attempting a retry.%0A     */%0A    public Duration getRetryDelay() %7B%0A        return retryDelay;%0A    %7D%0A%0A    /**%0A     * Sets the delay to wai before attempting a retry.%0A     *%0A     * @param retryDelay the retry delay.%0A     */
 %0A   
@@ -2412,32 +2412,109 @@
 ryDelay;%0A    %7D%0A%0A
+    /**%0A     * Starts the thread executing the command continuously.%0A     */%0A
     public synch
@@ -2611,32 +2611,138 @@
        %7D%0A    %7D%0A%0A
+    /**%0A     * Stops the thread executing the command continuously, interrupting it if necessary.%0A     */%0A
     public synch
@@ -2875,24 +2875,172 @@
    %7D%0A    %7D%0A%0A
+    /**%0A     * Executes the specified command continuously if the executor is started.%0A     *%0A     * @param command the command to execute.%0A     */%0A
     public s

Add javadoc to executor