All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.kojan.workflow.TaskExecutor Maven / Gradle / Ivy

/*-
 * Copyright (c) 2021-2023 Red Hat, 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 io.kojan.workflow;

import io.kojan.workflow.model.Artifact;
import io.kojan.workflow.model.Parameter;
import io.kojan.workflow.model.Result;
import io.kojan.workflow.model.Task;
import io.kojan.workflow.model.TaskOutcome;
import io.kojan.xml.XMLException;
import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * @author Mikolaj Izdebski
 */
class TaskExecutor extends Thread implements TaskExecutionContext {
    private final WorkflowExecutor wfe;
    private final TaskHandlerFactory handlerFactory;
    private final Task task;
    private final List dependencies;
    private final String resultId;
    private final Path resultDir;
    private final Path workDir;
    private final List artifacts = new ArrayList<>();

    public TaskExecutor(
            WorkflowExecutor wfe,
            TaskHandlerFactory handlerFactory,
            Task task,
            List dependencies) {
        this.wfe = wfe;
        this.handlerFactory = handlerFactory;
        this.task = task;
        this.dependencies = Collections.unmodifiableList(dependencies);

        try {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            md.update(getTask().getHandler().getBytes());
            md.update(Byte.MIN_VALUE);
            for (Parameter param : getTask().getParameters()) {
                md.update(param.getName().getBytes());
                md.update(Byte.MIN_VALUE);
                md.update(param.getValue().getBytes());
                md.update(Byte.MIN_VALUE);
            }
            for (FinishedTask dependency : getDependencies()) {
                md.update(dependency.getResult().getId().getBytes());
                md.update(Byte.MIN_VALUE);
            }
            byte[] digest = md.digest();
            this.resultId =
                    new BigInteger(1, digest)
                            .setBit(digest.length << 3)
                            .toString(16)
                            .substring(1)
                            .toUpperCase();
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalStateException(e);
        }

        this.resultDir = wfe.getStorage().getResultDir(task, resultId);
        this.workDir = wfe.getStorage().getWorkDir(task, resultId);
    }

    public Task getTask() {
        return task;
    }

    public List getDependencies() {
        return dependencies;
    }

    public Path getWorkDir() {
        return workDir;
    }

    public Path getResultDir() {
        return resultDir;
    }

    public List getDependencyArtifacts(String type) throws TaskTermination {
        List artifacts = new ArrayList<>();

        for (FinishedTask dependency : getDependencies()) {
            for (Artifact dependencyArtifact : dependency.getResult().getArtifacts()) {
                if (dependencyArtifact.getType().equals(type)) {
                    artifacts.add(dependency.getArtifact(dependencyArtifact));
                }
            }
        }

        if (artifacts.isEmpty()) {
            TaskTermination.error(
                    task + " was expected to have a dependency artifact of type " + type);
        }

        return artifacts;
    }

    public Path getDependencyArtifact(String type) throws TaskTermination {
        List artifacts = getDependencyArtifacts(type);

        if (artifacts.size() > 1) {
            TaskTermination.error(
                    task + " was expected to have only one dependency artifact of type " + type);
        }

        return artifacts.iterator().next();
    }

    public Path addArtifact(String type, String name) {
        Artifact artifact = new Artifact(type, name);
        artifacts.add(artifact);
        return resultDir.resolve(artifact.getName());
    }

    private void deleteDirectoryIfExists(Path dir) throws IOException {
        if (dir != null && Files.isDirectory(dir)) {
            Files.walk(dir)
                    .map(Path::toFile)
                    .sorted((o1, o2) -> -o1.compareTo(o2))
                    .forEach(File::delete);
        }
    }

    private void initializeTaskDirectories() throws TaskTermination {
        try {
            Files.createDirectories(resultDir.getParent());
            deleteDirectoryIfExists(resultDir);
            Files.createDirectory(resultDir);
            Files.createDirectories(workDir.getParent());
            deleteDirectoryIfExists(workDir);
            Files.createDirectory(workDir);
        } catch (IOException e) {
            throw TaskTermination.error(
                    "I/O error when creating task directories: " + e.getMessage());
        }
    }

    private void cleanupTaskDirectories() throws TaskTermination {
        try {
            deleteDirectoryIfExists(workDir);
        } catch (IOException e) {
            throw TaskTermination.error(
                    "I/O error when deleting task work directory: " + e.getMessage());
        }
    }

    private TaskTermination handleTask() {
        try {
            initializeTaskDirectories();

            try {
                TaskHandler handler = handlerFactory.createTaskHandler(task);
                handler.handleTask(this);
                throw TaskTermination.error("Task did not set explicit outcome");
            } finally {
                cleanupTaskDirectories();
            }
        } catch (TaskTermination termination) {
            return termination;
        }
    }

    @Override
    public void run() {
        if (Files.isRegularFile(resultDir.resolve("stamp"))) {
            try {
                Result cachedResult = Result.readFromXML(resultDir.resolve("result.xml"));

                // All dependency tasks completed before cached result was even
                // started?
                if (getDependencies().stream()
                        .allMatch(
                                dep ->
                                        dep.getResult()
                                                        .getTimeFinished()
                                                        .compareTo(cachedResult.getTimeStarted())
                                                <= 0)) {
                    FinishedTask finishedTask =
                            new FinishedTask(getTask(), cachedResult, resultDir);
                    wfe.stateChangeFromPendingToFinished(finishedTask);
                    return;
                }
            } catch (IOException | XMLException e) {
                throw new RuntimeException(e);
            }
        }

        try {
            wfe.getThrottle().acquireCapacity(task);
            wfe.stateChangeFromPendingToRunning(task);

            LocalDateTime timeStarted = LocalDateTime.now();
            TaskTermination termination = handleTask();
            LocalDateTime timeFinished = LocalDateTime.now();

            Result result =
                    new Result(
                            resultId,
                            task.getId(),
                            artifacts,
                            termination.getOutcome(),
                            termination.getMessage(),
                            timeStarted,
                            timeFinished);
            if (result.getOutcome() == TaskOutcome.SUCCESS) {
                try {
                    result.writeToXML(resultDir.resolve("result.xml"));
                    Files.createFile(resultDir.resolve("stamp"));
                } catch (IOException | XMLException e) {
                    throw new RuntimeException(e);
                }
            }
            FinishedTask finishedTask = new FinishedTask(getTask(), result, resultDir);
            wfe.stateChangeFromRunningToFinished(finishedTask);
        } finally {
            wfe.getThrottle().releaseCapacity(task);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy