com.haulmont.cuba.core.app.scheduling.RunnerBean.java Source code

Java tutorial

Introduction

Here is the source code for com.haulmont.cuba.core.app.scheduling.RunnerBean.java

Source

/*
 * Copyright (c) 2008-2016 Haulmont.
 *
 * 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.haulmont.cuba.core.app.scheduling;

import com.haulmont.cuba.core.EntityManager;
import com.haulmont.cuba.core.Persistence;
import com.haulmont.cuba.core.Transaction;
import com.haulmont.cuba.core.app.MiddlewareStatisticsAccumulator;
import com.haulmont.cuba.core.app.ServerConfig;
import com.haulmont.cuba.core.app.ServerInfoAPI;
import com.haulmont.cuba.core.app.scheduled.MethodParameterInfo;
import com.haulmont.cuba.core.entity.ScheduledExecution;
import com.haulmont.cuba.core.entity.ScheduledTask;
import com.haulmont.cuba.core.entity.SchedulingType;
import com.haulmont.cuba.core.global.*;
import com.haulmont.cuba.core.sys.AppContext;
import com.haulmont.cuba.core.sys.SecurityContext;
import com.haulmont.cuba.security.app.UserSessionsAPI;
import com.haulmont.cuba.security.auth.AuthenticationManager;
import com.haulmont.cuba.security.auth.SystemUserCredentials;
import com.haulmont.cuba.security.global.LoginException;
import com.haulmont.cuba.security.global.UserSession;
import org.apache.commons.lang.BooleanUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Standard implementation of {@link Runner} interface used by {@link Scheduling} to run scheduled tasks.
 */
@Component(Runner.NAME)
public class RunnerBean implements Runner {

    private static final Logger log = LoggerFactory.getLogger(RunnerBean.class);

    protected ExecutorService executorService;

    @Inject
    protected SchedulingAPI scheduling;

    @Inject
    protected ServerInfoAPI serverInfo;

    @Inject
    protected Persistence persistence;

    @Inject
    protected TimeSource timeSource;

    @Inject
    protected AuthenticationManager authenticationManager;

    @Inject
    protected UserSessionsAPI userSessions;

    @Inject
    protected Scripting scripting;

    @Inject
    protected MiddlewareStatisticsAccumulator statisticsCounter;

    @Inject
    protected Metadata metadata;

    @Inject
    protected Configuration configuration;

    protected Map<String, UUID> userSessionIds = new ConcurrentHashMap<>();

    @PostConstruct
    public void init() {
        int nThreads = configuration.getConfig(ServerConfig.class).getSchedulingThreadPoolSize();
        executorService = Executors.newFixedThreadPool(nThreads, new ThreadFactory() {
            private final AtomicInteger threadNumber = new AtomicInteger(1);

            @Override
            public Thread newThread(@Nonnull Runnable r) {
                Thread thread = new Thread(r, "ScheduledRunnerThread-" + threadNumber.getAndIncrement());
                thread.setDaemon(true);
                return thread;
            }
        });
    }

    @Override
    public void runTask(ScheduledTask task, final long now, final @Nullable UserSession userSession) {
        // It's better not to pass an entity instance in managed state to another thread
        final ScheduledTask taskCopy = metadata.getTools().copy(task);

        executorService.submit(() -> {
            log.debug("{}: running", taskCopy);
            try {
                boolean runConcurrent = scheduling.setRunning(taskCopy, true);
                if (!runConcurrent) {
                    try {
                        setSecurityContext(taskCopy, userSession);
                        ScheduledExecution execution = registerExecutionStart(taskCopy, now);
                        statisticsCounter.incCubaScheduledTasksCount();
                        try {
                            Object result = executeTask(taskCopy);
                            registerExecutionFinish(taskCopy, execution, result);
                        } catch (Throwable throwable) {
                            registerExecutionFinish(taskCopy, execution, throwable);
                            throw throwable;
                        }
                    } finally {
                        scheduling.setRunning(taskCopy, false);
                        scheduling.setFinished(task);
                    }
                } else {
                    log.info("Detected concurrent task execution: {}, skip it", taskCopy);
                }
            } catch (Throwable throwable) {
                log.error("Error running {}", taskCopy, throwable);
            }
        });
    }

    protected void setSecurityContext(ScheduledTask task, @Nullable UserSession userSession) throws LoginException {
        if (userSession == null) {
            UUID sessionId = userSessionIds.get(task.getUserName());
            userSession = sessionId == null ? null : userSessions.getAndRefresh(sessionId);
            if (userSession == null) {
                userSession = authenticationManager.login(new SystemUserCredentials(task.getUserName()))
                        .getSession();
                userSessionIds.put(task.getUserName(), userSession.getId());
            }
        }
        AppContext.setSecurityContext(new SecurityContext(userSession));
    }

    protected ScheduledExecution registerExecutionStart(ScheduledTask task, long now) {
        if (!BooleanUtils.isTrue(task.getLogStart()) && !BooleanUtils.isTrue(task.getSingleton())
                && task.getSchedulingType() != SchedulingType.FIXED_DELAY)
            return null;

        log.trace("{}: registering execution start", task);

        Transaction tx = persistence.createTransaction();
        try {
            EntityManager em = persistence.getEntityManager();

            ScheduledExecution execution = metadata.create(ScheduledExecution.class);
            execution.setTask(em.getReference(ScheduledTask.class, task.getId()));
            execution.setStartTime(new Date(now));
            execution.setServer(serverInfo.getServerId());

            em.persist(execution);
            tx.commit();

            return execution;
        } finally {
            tx.end();
        }
    }

    protected void registerExecutionFinish(ScheduledTask task, ScheduledExecution execution, Object result) {
        if ((!BooleanUtils.isTrue(task.getLogFinish()) && !BooleanUtils.isTrue(task.getSingleton())
                && task.getSchedulingType() != SchedulingType.FIXED_DELAY) || execution == null)
            return;

        log.trace("{}: registering execution finish", task);
        Transaction tx = persistence.createTransaction();
        try {
            EntityManager em = persistence.getEntityManager();
            execution = em.merge(execution);
            execution.setFinishTime(timeSource.currentTimestamp());
            if (result != null)
                execution.setResult(result.toString());

            tx.commit();
        } finally {
            tx.end();
        }
    }

    protected Object executeTask(ScheduledTask task) {
        switch (task.getDefinedBy()) {
        case BEAN: {
            log.trace("{}: invoking bean", task);
            Object bean = AppBeans.get(task.getBeanName());
            try {
                List<MethodParameterInfo> methodParams = task.getMethodParameters();
                Class[] paramTypes = new Class[methodParams.size()];
                Object[] paramValues = new Object[methodParams.size()];

                for (int i = 0; i < methodParams.size(); i++) {
                    paramTypes[i] = methodParams.get(i).getType();
                    paramValues[i] = methodParams.get(i).getValue();
                }

                Method method = bean.getClass().getMethod(task.getMethodName(), paramTypes);
                return method.invoke(bean, paramValues);
            } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }

        case CLASS: {
            try {
                Class taskClass = scripting.loadClass(task.getClassName());
                Callable callable = (Callable) taskClass.newInstance();
                return callable.call();
            } catch (InstantiationException | IllegalAccessException e) {
                throw new RuntimeException(
                        String.format("An error occurred while instantiating class %s.", task.getClassName()), e);
            } catch (Exception e) {
                throw new RuntimeException(String.format(
                        "An error occurred while running method call() of class %s.", task.getClassName()), e);
            }
        }

        case SCRIPT: {
            return scripting.runGroovyScript(task.getScriptName(), Collections.<String, Object>emptyMap());
        }

        default: {
            throw new IllegalStateException(
                    String.format("\"Defined by\" field has illegal value: %s. Task id: [%s].", task.getDefinedBy(),
                            task.getId()));
        }
        }
    }
}