io.macgyver.core.scheduler.ScheduledTaskManager.java Source code

Java tutorial

Introduction

Here is the source code for io.macgyver.core.scheduler.ScheduledTaskManager.java

Source

/**
 * 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 io.macgyver.core.scheduler;

import java.io.IOException;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.base.Strings;
import com.google.common.collect.Maps;

import io.macgyver.core.Bootstrap;
import io.macgyver.core.Kernel;
import io.macgyver.core.resource.Resource;
import io.macgyver.core.resource.ResourceMatcher;
import io.macgyver.core.script.ExtensionResourceProvider;
import io.macgyver.neorx.rest.NeoRxClient;

public class ScheduledTaskManager implements ApplicationListener<ApplicationReadyEvent> {

    public static final String SCHEDULE_TOKEN = "#@Schedule";
    public static final String SCHEDULED_BY_SCRIPT = "script";
    public static final String SCHEDULED_BY_MANUAL = "manual";
    public static final String SCHEDULED_BY = "scheduledBy";
    public static final String ENABLED = "enabled";

    Logger logger = LoggerFactory.getLogger(ScheduledTaskManager.class);

    @Autowired
    NeoRxClient neo4j;

    boolean schedulerEnabled = true;

    ObjectMapper mapper = new ObjectMapper();

    public void scheduleInline(String id, String cron, String script) {
        scheduleInline(id, cron, script, "groovy");
    }

    protected void throwIllegalStateOnEmptyList(String it, List<JsonNode> list) {
        if (list.isEmpty()) {
            throw new IllegalStateException("ScheduledTask id=" + it + " does not exist");
        }
    }

    public void scheduleInline(String id, String cron, String script, String language) {
        String cypher = "merge (t:ScheduledTask {id:{id}}) set t.scheduledBy='manual', t.cron={cron}, t.inlineScript={script}, t.enabled=true, t.inlineScriptLanguage={language} return t";
        throwIllegalStateOnEmptyList(id,
                neo4j.execCypher(cypher, "id", id, "cron", cron, SCHEDULED_BY_SCRIPT, script, "language", language)
                        .toList().toBlocking().first());
    }

    public void updateSchedule(String id, String cron) {
        String cypher = "match (t:ScheduledTask {id:{id}}) set t.cron={cron} return t";
        throwIllegalStateOnEmptyList(id,
                neo4j.execCypher(cypher, "id", id, "cron", cron).toList().toBlocking().first());

    }

    public void disable(String id) {
        enable(id, false);
    }

    public void enable(String id) {
        enable(id, true);
    }

    public void enable(String id, boolean b) {
        String cypher = "match (t:ScheduledTask {id:{id}}) set t.enabled={enabled} return t";
        throwIllegalStateOnEmptyList(id,
                neo4j.execCypher(cypher, "id", id, ENABLED, b).toList().toBlocking().first());
    }

    public void scheduleManually(String id) {
        throwIllegalStateOnEmptyList(id,
                neo4j.execCypher("match (t:ScheduledTask {id:{id}}) set t.scheduledBy='manual' return t", "id", id)
                        .toList().toBlocking().first());
    }

    public void scheduleByScript(String id) {
        throwIllegalStateOnEmptyList(id, neo4j.execCypher(
                "match (t:ScheduledTask {id:{id}}) where length(t.script)>0 set t.scheduledBy='script' return t",
                "id", id).toList().toBlocking().first());
    }

    public boolean isEnabled(JsonNode config) {
        return schedulerEnabled && config.path(ENABLED).asBoolean(true);
    }

    public void setSchedulerEnabled(boolean b) {
        logger.info("setting global scheduler status: {}", b);
        this.schedulerEnabled = b;
    }

    public boolean isSchedulerEnabled() {
        return this.schedulerEnabled;
    }

    protected Map<String, JsonNode> loadScheduledScriptTasks() {

        Map<String, JsonNode> m = Maps.newConcurrentMap();
        String cypher = "match (s:ScheduledTask) where length(s.script)>0 return s";
        neo4j.execCypher(cypher).forEach(it -> {
            String name = it.path(SCHEDULED_BY_SCRIPT).asText();
            if (!Strings.isNullOrEmpty(name)) {
                m.put(name, it);
            }
        });
        return m;

    }

    protected boolean isScripptScheduledByScript(JsonNode n) {
        return n != null && n.path(SCHEDULED_BY).asText().equals(SCHEDULED_BY_SCRIPT);
    }

    protected boolean isScriptManuallyScheduled(JsonNode n) {
        return n != null && n.path(SCHEDULED_BY).asText().equals(SCHEDULED_BY_MANUAL);
    }

    class ScriptResourceMatcher implements ResourceMatcher {

        @Override
        public boolean matches(Resource r) {
            try {
                if (r.getPath().startsWith("scripts/")) {
                    return true;
                }
                return false;
            } catch (Exception e) {
                return false;
            }
        }

    }

    public void scan() throws IOException {

        Map<String, JsonNode> scriptMap = loadScheduledScriptTasks();

        Kernel.getInstance();
        ExtensionResourceProvider extensionLoader = Kernel.getApplicationContext()
                .getBean(ExtensionResourceProvider.class);
        long scanTime = System.currentTimeMillis();

        CrontabExpressionExtractor expressionExtractor = new CrontabExpressionExtractor();

        for (Resource r : extensionLoader.findResources(new ScriptResourceMatcher())) {
            if (logger.isDebugEnabled()) {
                logger.debug("evaluating {} to see if it can be scheduled", r);
            }
            String path = r.getPath();
            if (path != null && path.startsWith("scripts/scheduler/")) {

                ObjectNode descriptor = expressionExtractor.extractCronExpression(r).or(mapper.createObjectNode());

                boolean b = descriptor.path(ENABLED).asBoolean(true);

                if (isScriptManuallyScheduled(scriptMap.get(path))) {

                    // if the ScheduledTask node's scheduledBy attribute is set
                    // to SCHEDULED_BY_MANUAL in neo4j, do not update. This
                    // allows scripts enabled/cron attributes to be
                    // manually adjusted (outside of the script).

                    if (logger.isDebugEnabled()) {
                        logger.debug("manually scheduled script tassk will not be updated: {}", path);
                    }
                } else {

                    // Update the corresponding ScheduledTask node in neo4j. The
                    // cron4j TaskCollector will read this.
                    String cypher = "merge (s:ScheduledTask {id:{id}}) set s.script={id},s.scheduledBy='script', s.enabled={enabled}, s.cron={cron}, s.lastUpdateTs={ts} return s;";

                    neo4j.execCypher(cypher, "id", path, ENABLED, b, "cron", descriptor.path("cron").asText(), "ts",
                            scanTime);
                }

            }
        }

        // now remove all entries scheduled via script that were not just
        // updated

        if (logger.isDebugEnabled()) {
            logger.debug("removing old scheduled entries...");
        }
        String cypher = "match (s:ScheduledTask) where s.scheduledBy='script' and (s.lastUpdateTs is null or s.lastUpdateTs<{ts}) delete s";
        neo4j.execCypher(cypher, "ts", scanTime);

    }

    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        try {
            // this is a one-time migration to use "id" as the identity
            // attribute
            neo4j.execCypher(
                    "match (t:ScheduledTask) where exists(t.script) and (t.id<>t.script or not exists(t.id)) set t.id=t.script return t");
        } catch (RuntimeException e) {
            logger.warn("problem matching ScheduledTask nodes", e);
        }

        try {
            neo4j.execCypher("create index on :ScheduledTask(id)");
        } catch (RuntimeException e) {
            logger.warn("problem creating index on TaskState(id)");
        }

    }

}