ru.runa.wf.logic.bot.WorkflowThreadPoolBotInvoker.java Source code

Java tutorial

Introduction

Here is the source code for ru.runa.wf.logic.bot.WorkflowThreadPoolBotInvoker.java

Source

/*
 * This file is part of the RUNA WFE project.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; version 2.1
 * of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 */
package ru.runa.wf.logic.bot;

import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import ru.runa.wfe.ConfigurationException;
import ru.runa.wfe.bot.Bot;
import ru.runa.wfe.bot.BotStation;
import ru.runa.wfe.bot.BotTask;
import ru.runa.wfe.bot.invoker.BotInvoker;
import ru.runa.wfe.commons.CoreErrorProperties;
import ru.runa.wfe.commons.Errors;
import ru.runa.wfe.security.AuthenticationException;
import ru.runa.wfe.service.delegate.Delegates;
import ru.runa.wfe.task.dto.WfTask;
import ru.runa.wfe.user.User;

public class WorkflowThreadPoolBotInvoker implements BotInvoker, Runnable {
    private final Log log = LogFactory.getLog(WorkflowThreadPoolBotInvoker.class);

    private static final long STUCK_TIMEOUT_SECONDS = BotStationResources.getStuckTimeoutInMinutes() * 60;

    private ScheduledThreadPoolExecutor executor;

    private long configurationVersion = -1;

    private final Map<Bot, WorkflowBotExecutor> botExecutors = Maps.newHashMap();

    private Future<?> botInvokerInvocation;

    private final Map<BotExecutionStatus, ScheduledFuture<?>> scheduledTasks = Maps.newConcurrentMap();

    private BotStation botStation;

    /**
     * Checking botInvokerInvocation.isDone() leads to run() method called only once per moment.
     */
    @Override
    public synchronized void invokeBots(BotStation botStation, boolean resetFailedDelay) {
        this.botStation = Delegates.getBotService().getBotStation(botStation.getId());
        if (botInvokerInvocation != null && !botInvokerInvocation.isDone()) {
            log.debug("botInvokerInvocation != null && !botInvokerInvocation.isDone()");
            return;
        }
        int poolSize = BotStationResources.getThreadPoolSize();
        if (executor == null) {
            log.debug(String.format("Creating new executor(ScheduledExecutorService),size %d", poolSize));
            executor = new ScheduledThreadPoolExecutor(poolSize, new BotNamedThreadFactory());

        } else {
            if (executor.getCorePoolSize() != poolSize) {
                log.debug(String.format("change core thread pool size from %d to %d", executor.getCorePoolSize(),
                        poolSize));
                executor.setCorePoolSize(poolSize);
            }
            if (executor.getMaximumPoolSize() != poolSize) {
                log.debug(String.format("change maximum thread pool size from %d to %d",
                        executor.getMaximumPoolSize(), poolSize));
                executor.setMaximumPoolSize(poolSize);
            }
        }
        checkStuckBots();
        botInvokerInvocation = executor.schedule(this, 1000, TimeUnit.MILLISECONDS);
        logBotsActivites();
        if (resetFailedDelay) {
            for (WorkflowBotExecutor botExecutor : botExecutors.values()) {
                botExecutor.resetFailedDelay();
            }
        }
    }

    private void checkStuckBots() {
        try {
            for (Iterator<Entry<BotExecutionStatus, ScheduledFuture<?>>> iter = scheduledTasks.entrySet()
                    .iterator(); iter.hasNext();) {
                Entry<BotExecutionStatus, ScheduledFuture<?>> entry = iter.next();
                if (entry.getValue().isDone()) {
                    iter.remove();
                    continue;
                }
                BotExecutionStatus executor = entry.getKey();
                if (executor.getExecutionStatus() == WorkflowBotTaskExecutionStatus.STARTED
                        && executor.getExecutionInSeconds() > STUCK_TIMEOUT_SECONDS) {
                    if (executor.interruptExecution()) {
                        iter.remove();
                    }
                }
            }
        } catch (Exception e) {
            log.warn("Stuck threads search/stop is throwing exception.", e);
        }
    }

    private void configure() {
        String botStationErrorMessage = CoreErrorProperties
                .getMessage(CoreErrorProperties.BOT_STATION_CONFIGURATION_ERROR, botStation.getName());
        try {
            if (botStation.getVersion() != configurationVersion) {
                log.info("Will update bots configuration.");
                String username = BotStationResources.getSystemUsername();
                String password = BotStationResources.getSystemPassword();
                User botStationUser = Delegates.getAuthenticationService().authenticateByLoginPassword(username,
                        password);
                Map<Bot, WorkflowBotExecutor> existingBotExecutors = Maps.newHashMap(botExecutors);
                botExecutors.clear();
                List<Bot> bots = Delegates.getBotService().getBots(botStationUser, botStation.getId());
                for (Bot bot : bots) {
                    String botErrorMessage = CoreErrorProperties
                            .getMessage(CoreErrorProperties.BOT_CONFIGURATION_ERROR, bot.getUsername());
                    try {
                        log.info("Configuring " + bot.getUsername());
                        User user = Delegates.getAuthenticationService()
                                .authenticateByLoginPassword(bot.getUsername(), bot.getPassword());
                        List<BotTask> tasks = Delegates.getBotService().getBotTasks(user, bot.getId());
                        if (existingBotExecutors.containsKey(bot)) {
                            WorkflowBotExecutor botExecutor = existingBotExecutors.get(bot);
                            botExecutor.reinitialize(bot, tasks);
                            botExecutors.put(bot, botExecutor);
                        } else {
                            botExecutors.put(bot, new WorkflowBotExecutor(user, bot, tasks));
                        }
                        Errors.removeSystemError(botErrorMessage);
                    } catch (Throwable th) {
                        log.error("Unable to configure " + bot, th);
                        Errors.addSystemError(new ConfigurationException(botErrorMessage, th));
                    }
                }
                configurationVersion = botStation.getVersion();
            } else {
                log.debug("bots configuration is up to date, version = " + botStation.getVersion());
            }
            Errors.removeSystemError(botStationErrorMessage);
        } catch (Throwable th) {
            log.error("Botstation configuration error", th);
            Errors.addSystemError(new ConfigurationException(botStationErrorMessage, th));
        }
    }

    /**
     * Checking botInvokerInvocation.isDone() in synchronized invokeBots method leads to run() method called only once per moment.
     */
    @Override
    public void run() {
        configure();
        if (executor == null) {
            log.warn("executor(ScheduledExecutorService) == null");
            return;
        }
        for (WorkflowBotExecutor botExecutor : botExecutors.values()) {
            try {
                if (botExecutor.getBot().isSequentialExecution()) {
                    scheduleSequentialBot(botExecutor);
                } else {
                    Set<WfTask> tasks = botExecutor.getNewTasks();
                    scheduleTasks(botExecutor, tasks);
                }
            } catch (AuthenticationException e) {
                configurationVersion = -1;
                log.error("BotRunner execution failed. Will recreate botstation settings and bots.", e);
            } catch (Exception e) {
                log.error("BotRunner execution failed.", e);
            }
        }
    }

    /**
     * Schedules all task for bot. Each parallel tasks scheduled as self. Sequential tasks is grouped for sequential execution.
     *
     * @param botExecutor
     *            Bot execution data.
     * @param tasks
     *            Tasks to schedule.
     */
    private void scheduleTasks(WorkflowBotExecutor botExecutor, Set<WfTask> tasks) {
        Map<String, List<WorkflowBotTaskExecutor>> sequentialTasks = Maps.newHashMap();
        for (WfTask task : tasks) {
            final String botTaskName = BotTaskConfigurationUtils.getBotTaskName(botExecutor.getUser(), task);
            BotTask botTaskConfiguration = botExecutor.getBotTasks().get(botTaskName);
            if (botTaskConfiguration == null) { // in spite of that, the task
                // should be started because it
                // is necessary to show the
                // error message
                log.error("No handler for bot task " + task.getName() + " in " + botExecutor.getBot());
            } else if (botTaskConfiguration.isSequentialExecution() && scheduledTasks.containsKey(
                    new WorkflowSequentialBotTaskExecutor(botExecutor.getBot(), botTaskConfiguration, null))) {
                continue;
            }
            WorkflowBotTaskExecutor botTaskExecutor = botExecutor.createBotTaskExecutor(task);
            if (botTaskConfiguration != null && botTaskConfiguration.isSequentialExecution()) {
                List<WorkflowBotTaskExecutor> botTasks = sequentialTasks.get(task.getName());
                if (botTasks == null) {
                    botTasks = Lists.newLinkedList();
                    sequentialTasks.put(task.getName(), botTasks);
                }
                botTasks.add(botTaskExecutor);
            } else {
                ScheduledFuture<?> future = executor.schedule(botTaskExecutor, 200, TimeUnit.MILLISECONDS);
                scheduledTasks.put(botTaskExecutor, future);
            }
        }
        for (String taskName : sequentialTasks.keySet()) {
            WorkflowSequentialBotTaskExecutor botTaskExecutor = new WorkflowSequentialBotTaskExecutor(
                    botExecutor.getBot(), botExecutor.getBotTasks().get(taskName), sequentialTasks.get(taskName));
            ScheduledFuture<?> future = executor.schedule(botTaskExecutor, 200, TimeUnit.MILLISECONDS);
            scheduledTasks.put(botTaskExecutor, future);
        }
    }

    /**
     * Schedules new tasks for sequential bot.
     *
     * @param botExecutor
     *            Component, used to create new bot task executors.
     */
    private void scheduleSequentialBot(WorkflowBotExecutor botExecutor) {
        if (scheduledTasks.containsKey(new WorkflowSequentialBotTaskExecutor(botExecutor.getBot(), null, null))) {
            return;
        }
        List<WorkflowBotTaskExecutor> tasksToExecute = Lists.newLinkedList();
        Set<WfTask> tasks = botExecutor.getNewTasks();
        for (WfTask task : tasks) {
            tasksToExecute.add(botExecutor.createBotTaskExecutor(task));
        }
        WorkflowSequentialBotTaskExecutor botTaskExecutor = new WorkflowSequentialBotTaskExecutor(
                botExecutor.getBot(), null, tasksToExecute);
        ScheduledFuture<?> future = executor.schedule(botTaskExecutor, 200, TimeUnit.MILLISECONDS);
        scheduledTasks.put(botTaskExecutor, future);
    }

    private void logBotsActivites() {
        BotLogger botLogger = BotStationResources.createBotLogger();
        if (botLogger == null) {
            return;
        }
        botLogger.logActivity();
    }

    /**
     * forked from java.util.concurrent.Executors.DefaultThreadFactory due to private access to this class
     */
    private static class BotNamedThreadFactory implements ThreadFactory {
        static final AtomicInteger poolNumber = new AtomicInteger(1), threadNumber = new AtomicInteger(1);
        final ThreadGroup group;

        final String namePrefix;

        BotNamedThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = s != null ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
            // adding custom prefix
            namePrefix = "bot-pool-" + poolNumber.getAndIncrement() + "-thread-";
        }

        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
            if (t.isDaemon()) {
                t.setDaemon(false);
            }
            if (t.getPriority() != Thread.NORM_PRIORITY) {
                t.setPriority(Thread.NORM_PRIORITY);
            }
            return t;
        }
    }

}