mirror of
https://github.com/kestra-io/kestra.git
synced 2025-12-19 18:05:41 -05:00
Initial commit
This commit is contained in:
17
.gitignore
vendored
Normal file
17
.gitignore
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
Thumbs.db
|
||||
.DS_Store
|
||||
.gradle
|
||||
build/
|
||||
target/
|
||||
out/
|
||||
.idea
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.project
|
||||
.settings
|
||||
.classpath
|
||||
.attach*
|
||||
docker-compose.override.yml
|
||||
|
||||
cli/src/main/resources/application-override.yml
|
||||
139
build.gradle
Normal file
139
build.gradle
Normal file
@@ -0,0 +1,139 @@
|
||||
buildscript {
|
||||
ext {
|
||||
micronautVersion = "1.2.0"
|
||||
confluentVersion = "5.2.1"
|
||||
kafkaVersion = "2.3.0"
|
||||
avroVersion = "1.9.0"
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
// micronaut
|
||||
id "io.spring.dependency-management" version "1.0.6.RELEASE"
|
||||
id "java"
|
||||
id "net.ltgt.apt-eclipse" version "0.21"
|
||||
id "net.ltgt.apt-idea" version "0.21"
|
||||
id "com.github.johnrengelman.shadow" version "4.0.2"
|
||||
id "application"
|
||||
}
|
||||
|
||||
idea {
|
||||
module {
|
||||
downloadJavadoc = true
|
||||
downloadSources = true
|
||||
}
|
||||
}
|
||||
|
||||
/**********************************************************************************************************************\
|
||||
* Main
|
||||
**********************************************************************************************************************/
|
||||
mainClassName = "org.floworc.cli.App"
|
||||
sourceCompatibility = 11
|
||||
|
||||
dependencies {
|
||||
compile project(":cli")
|
||||
}
|
||||
|
||||
|
||||
/**********************************************************************************************************************\
|
||||
* All projects
|
||||
**********************************************************************************************************************/
|
||||
allprojects {
|
||||
group "org.floworc"
|
||||
version "0.1"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url "https://jcenter.bintray.com" }
|
||||
maven { url "http://packages.confluent.io/maven/" }
|
||||
}
|
||||
|
||||
// micronaut
|
||||
apply plugin:"java"
|
||||
apply plugin:"net.ltgt.apt-eclipse"
|
||||
apply plugin:"net.ltgt.apt-idea"
|
||||
apply plugin: "io.spring.dependency-management"
|
||||
|
||||
dependencyManagement {
|
||||
imports {
|
||||
mavenBom "io.micronaut:micronaut-bom:" + micronautVersion
|
||||
}
|
||||
}
|
||||
|
||||
configurations {
|
||||
// for dependencies that are needed for development only
|
||||
developmentOnly
|
||||
}
|
||||
|
||||
// dependencies
|
||||
dependencies {
|
||||
// utils
|
||||
runtime "ch.qos.logback:logback-classic:1.2.3"
|
||||
compile group: 'com.google.guava', name: 'guava', version: '27.1-jre'
|
||||
compileOnly 'org.projectlombok:lombok:1.18.8'
|
||||
annotationProcessor "org.projectlombok:lombok:1.18.8"
|
||||
|
||||
// micronaut
|
||||
annotationProcessor "io.micronaut:micronaut-inject-java"
|
||||
annotationProcessor "io.micronaut:micronaut-validation"
|
||||
compile "io.micronaut:micronaut-runtime"
|
||||
compile "io.micronaut:micronaut-inject"
|
||||
compile "io.micronaut:micronaut-validation"
|
||||
compile "info.picocli:picocli"
|
||||
compile "io.micronaut.configuration:micronaut-picocli"
|
||||
|
||||
// test
|
||||
testCompile "org.junit.jupiter:junit-jupiter-api"
|
||||
testRuntime "org.junit.jupiter:junit-jupiter-engine"
|
||||
testRuntime "org.junit.jupiter:junit-jupiter-params"
|
||||
testAnnotationProcessor "io.micronaut:micronaut-inject-java"
|
||||
testCompile "io.micronaut.test:micronaut-test-junit5"
|
||||
testCompile "io.micronaut:micronaut-inject-java"
|
||||
testImplementation 'org.hamcrest:hamcrest:2.1'
|
||||
testImplementation 'org.hamcrest:hamcrest-library:2.1'
|
||||
|
||||
// floworc
|
||||
compile group: 'com.devskiller.friendly-id', name: 'friendly-id', version: '1.1.0'
|
||||
}
|
||||
}
|
||||
|
||||
/**********************************************************************************************************************\
|
||||
* Micronaut
|
||||
**********************************************************************************************************************/
|
||||
tasks.withType(JavaCompile){
|
||||
options.encoding = "UTF-8"
|
||||
options.compilerArgs.add("-parameters")
|
||||
}
|
||||
|
||||
run.classpath += configurations.developmentOnly
|
||||
test.classpath += configurations.developmentOnly
|
||||
|
||||
run.jvmArgs(
|
||||
"-noverify",
|
||||
"-XX:TieredStopAtLevel=1",
|
||||
"-Dcom.sun.management.jmxremote",
|
||||
'-Dmicronaut.environments=dev,override'
|
||||
)
|
||||
/**********************************************************************************************************************\
|
||||
* Test
|
||||
**********************************************************************************************************************/
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
|
||||
testLogging {
|
||||
exceptionFormat = "full"
|
||||
}
|
||||
}
|
||||
|
||||
/**********************************************************************************************************************\
|
||||
* Jar
|
||||
**********************************************************************************************************************/
|
||||
jar {
|
||||
manifest {
|
||||
attributes "Main-Class": mainClassName
|
||||
}
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
mergeServiceFiles()
|
||||
}
|
||||
11
cli/build.gradle
Normal file
11
cli/build.gradle
Normal file
@@ -0,0 +1,11 @@
|
||||
sourceCompatibility = 11
|
||||
|
||||
dependencies {
|
||||
// micronaut
|
||||
compile "io.micronaut:micronaut-management"
|
||||
compile "io.micronaut:micronaut-http-client"
|
||||
compile "io.micronaut:micronaut-http-server-netty"
|
||||
|
||||
// modules
|
||||
compile project(":core")
|
||||
}
|
||||
26
cli/src/main/java/org/floworc/core/App.java
Normal file
26
cli/src/main/java/org/floworc/core/App.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package org.floworc.core;
|
||||
|
||||
import io.micronaut.configuration.picocli.PicocliRunner;
|
||||
import picocli.CommandLine;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
@CommandLine.Command(
|
||||
name = "floworc",
|
||||
version = "v0.1",
|
||||
header = "floworc client",
|
||||
mixinStandardHelpOptions = true,
|
||||
subcommands = {
|
||||
|
||||
}
|
||||
)
|
||||
public class App implements Callable<Object> {
|
||||
public static void main(String[] args) throws Exception {
|
||||
PicocliRunner.call(App.class, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object call() throws Exception {
|
||||
return PicocliRunner.call(App.class, "--help");
|
||||
}
|
||||
}
|
||||
3
cli/src/main/resources/application.yml
Normal file
3
cli/src/main/resources/application.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
micronaut:
|
||||
application:
|
||||
name: floworc
|
||||
47
cli/src/main/resources/logback.xml
Normal file
47
cli/src/main/resources/logback.xml
Normal file
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration debug="false">
|
||||
<!-- Remove logback startup log -->
|
||||
<statusListener class="ch.qos.logback.core.status.NopStatusListener" />
|
||||
<property name="pattern" value="%d{ISO8601} %highlight(%-5.5level) %magenta(%-12.12thread) %cyan(%-12.12logger{12}) %msg%n" />
|
||||
<withJansi>true</withJansi>
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<target>System.out</target>
|
||||
<immediateFlush>true</immediateFlush>
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>ERROR</level>
|
||||
<onMatch>DENY</onMatch>
|
||||
</filter>
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>WARN</level>
|
||||
<onMatch>DENY</onMatch>
|
||||
</filter>
|
||||
<encoder>
|
||||
<pattern>${pattern}</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="STDERR" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<target>System.err</target>
|
||||
<immediateFlush>true</immediateFlush>
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>WARN</level>
|
||||
</filter>
|
||||
<encoder>
|
||||
<pattern>${pattern}</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT" />
|
||||
<appender-ref ref="STDERR" />
|
||||
</root>
|
||||
|
||||
<logger name="org.apache" level="WARN" />
|
||||
<logger name="io.confluent" level="WARN" />
|
||||
|
||||
<!-- The configuration '%s' was supplied but isn't a known config. > https://github.com/apache/kafka/pull/5876 -->
|
||||
<logger name="org.apache.kafka.clients.producer.ProducerConfig" level="ERROR" />
|
||||
<logger name="org.apache.kafka.clients.admin.AdminClientConfig" level="ERROR" />
|
||||
<logger name="org.apache.kafka.clients.consumer.ConsumerConfig" level="ERROR" />
|
||||
</configuration>
|
||||
13
core/build.gradle
Normal file
13
core/build.gradle
Normal file
@@ -0,0 +1,13 @@
|
||||
sourceCompatibility = 11
|
||||
|
||||
dependencies {
|
||||
// serializers
|
||||
compile group: "org.apache.avro", name: "avro", version: avroVersion
|
||||
|
||||
// yaml
|
||||
compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.9.2'
|
||||
compile group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.9.9'
|
||||
|
||||
// validations
|
||||
compile group: 'org.hibernate.validator', name: 'hibernate-validator', version: '6.0.17.Final'
|
||||
}
|
||||
15
core/src/main/java/org/floworc/core/executions/Context.java
Normal file
15
core/src/main/java/org/floworc/core/executions/Context.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package org.floworc.core.executions;
|
||||
|
||||
import lombok.Data;
|
||||
import org.floworc.core.flows.State;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class Context {
|
||||
private Map<String, Object> inputs;
|
||||
|
||||
private Map<String, Object> outputs;
|
||||
|
||||
private State state;
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package org.floworc.core.executions;
|
||||
|
||||
import lombok.*;
|
||||
import lombok.experimental.Wither;
|
||||
import org.floworc.core.flows.State;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Value
|
||||
@Builder
|
||||
public class Execution {
|
||||
@NotNull
|
||||
private String id;
|
||||
|
||||
@NotNull
|
||||
private String flowId;
|
||||
|
||||
@Wither
|
||||
private List<TaskRun> taskRunList;
|
||||
|
||||
@Wither
|
||||
private Context context;
|
||||
|
||||
@NotNull
|
||||
private State state;
|
||||
|
||||
public Execution withState(State.Type state) {
|
||||
return new Execution(
|
||||
this.id,
|
||||
this.flowId,
|
||||
this.taskRunList,
|
||||
this.context,
|
||||
this.state.withState(state)
|
||||
);
|
||||
}
|
||||
|
||||
public TaskRun findTaskRunById(String id) {
|
||||
Optional<TaskRun> find = this.taskRunList
|
||||
.stream()
|
||||
.filter(task -> task.getId().equals(id))
|
||||
.findFirst();
|
||||
|
||||
if (!find.isPresent()) {
|
||||
throw new IllegalArgumentException("Can't find taskrun with id '" + id + "' on execution '" + this.id + "'");
|
||||
}
|
||||
|
||||
return find.get();
|
||||
}
|
||||
|
||||
public Execution withTaskRun(TaskRun taskRun) {
|
||||
ArrayList<TaskRun> newTaskRunList = new ArrayList<>(this.taskRunList);
|
||||
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
Collections.replaceAll(
|
||||
newTaskRunList,
|
||||
this.findTaskRunById(taskRun.getId()),
|
||||
taskRun
|
||||
);
|
||||
|
||||
return new Execution(
|
||||
this.id,
|
||||
this.flowId,
|
||||
newTaskRunList,
|
||||
this.context,
|
||||
this.state
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package org.floworc.core.executions;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.floworc.core.flows.State;
|
||||
import org.floworc.core.tasks.Task;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Slf4j
|
||||
public class ExecutionService {
|
||||
public static Optional<List<TaskRun>> getNexts(Execution execution, List<Task> tasks) {
|
||||
if (tasks.size() == 0) {
|
||||
throw new IllegalStateException("Invalid execution " + execution.getId() + " on flow " +
|
||||
execution.getFlowId() + " with 0 task"
|
||||
);
|
||||
}
|
||||
|
||||
// first one
|
||||
if (execution.getTaskRunList() == null) {
|
||||
return Optional.of(tasks.get(0).toTaskRun(execution));
|
||||
}
|
||||
|
||||
// all done
|
||||
long terminatedCount = execution
|
||||
.getTaskRunList()
|
||||
.stream()
|
||||
.filter(taskRun -> taskRun.getState().isTerninated())
|
||||
.count();
|
||||
|
||||
if (terminatedCount == tasks.size()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
// find first running
|
||||
Optional<TaskRun> firstRunning = execution
|
||||
.getTaskRunList()
|
||||
.stream()
|
||||
.filter(taskRun -> taskRun.getState().isRunning())
|
||||
.findFirst();
|
||||
|
||||
if (firstRunning.isPresent()) {
|
||||
return tasks
|
||||
.get(execution.getTaskRunList().indexOf(firstRunning.get()))
|
||||
.getChildTaskRun(execution);
|
||||
}
|
||||
|
||||
// reverse
|
||||
ArrayList<TaskRun> reverse = new ArrayList<>(execution.getTaskRunList());
|
||||
Collections.reverse(reverse);
|
||||
|
||||
// find last created
|
||||
|
||||
Optional<TaskRun> lastCreated = reverse
|
||||
.stream()
|
||||
.filter(taskRun -> taskRun.getState().getCurrent() == State.Type.CREATED)
|
||||
.findFirst();
|
||||
|
||||
if (lastCreated.isPresent()) {
|
||||
return Optional.of(new ArrayList<>());
|
||||
}
|
||||
|
||||
// find last termintated
|
||||
Optional<TaskRun> lastTerminated = reverse
|
||||
.stream()
|
||||
.filter(taskRun -> taskRun.getState().isTerninated())
|
||||
.findFirst();
|
||||
|
||||
if (lastTerminated.isPresent()) {
|
||||
if (lastTerminated.get().getState().getCurrent() == State.Type.FAILED) {
|
||||
log.warn("Must find errors path");
|
||||
return Optional.of(new ArrayList<>());
|
||||
} else {
|
||||
int index = execution.getTaskRunList().indexOf(lastTerminated.get());
|
||||
|
||||
if (tasks.size() > index - 1) {
|
||||
return Optional.of(tasks
|
||||
.get(index + 1)
|
||||
.toTaskRun(execution)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.of(new ArrayList<>());
|
||||
}
|
||||
}
|
||||
17
core/src/main/java/org/floworc/core/executions/LogEntry.java
Normal file
17
core/src/main/java/org/floworc/core/executions/LogEntry.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package org.floworc.core.executions;
|
||||
|
||||
import lombok.Data;
|
||||
import org.slf4j.event.Level;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
@Data
|
||||
public class LogEntry {
|
||||
Instant timestamp;
|
||||
|
||||
Level level;
|
||||
|
||||
String thread;
|
||||
|
||||
String message;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package org.floworc.core.executions;
|
||||
|
||||
public class MetricEntry {
|
||||
|
||||
}
|
||||
61
core/src/main/java/org/floworc/core/executions/TaskRun.java
Normal file
61
core/src/main/java/org/floworc/core/executions/TaskRun.java
Normal file
@@ -0,0 +1,61 @@
|
||||
package org.floworc.core.executions;
|
||||
|
||||
import com.devskiller.friendly_id.FriendlyId;
|
||||
import lombok.Builder;
|
||||
import lombok.Value;
|
||||
import org.floworc.core.flows.State;
|
||||
import org.floworc.core.tasks.Task;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Value
|
||||
@Builder
|
||||
public class TaskRun {
|
||||
private String id;
|
||||
|
||||
@NotNull
|
||||
private String executionId;
|
||||
|
||||
@NotNull
|
||||
private String flowId;
|
||||
|
||||
@NotNull
|
||||
private String taskId;
|
||||
|
||||
private Context context;
|
||||
|
||||
@Builder.Default
|
||||
private List<LogEntry> logs = new ArrayList<>();
|
||||
|
||||
@Builder.Default
|
||||
private List<MetricEntry> metrics = new ArrayList<>();
|
||||
|
||||
@NotNull
|
||||
private State state;
|
||||
|
||||
public TaskRun withState(State.Type state) {
|
||||
return new TaskRun(
|
||||
this.id,
|
||||
this.executionId,
|
||||
this.flowId,
|
||||
this.taskId,
|
||||
this.context,
|
||||
this.logs,
|
||||
this.metrics,
|
||||
this.state.withState(state)
|
||||
);
|
||||
}
|
||||
|
||||
public static TaskRun of(Execution execution, Task task) {
|
||||
return TaskRun.builder()
|
||||
.id(FriendlyId.createFriendlyId())
|
||||
.executionId(execution.getId())
|
||||
.flowId(execution.getFlowId())
|
||||
.taskId(task.getId())
|
||||
.state(new State())
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.floworc.core.executions;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Value;
|
||||
import lombok.experimental.Wither;
|
||||
import org.floworc.core.tasks.Task;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Value
|
||||
@Builder
|
||||
public class WorkerTask {
|
||||
@NotNull
|
||||
@Wither
|
||||
private TaskRun taskRun;
|
||||
|
||||
@NotNull
|
||||
private Task task;
|
||||
|
||||
public Logger logger() {
|
||||
return LoggerFactory.getLogger(
|
||||
"flow." + this.getTaskRun().getFlowId() + "." +
|
||||
this.getTask().getId()
|
||||
);
|
||||
}
|
||||
}
|
||||
51
core/src/main/java/org/floworc/core/flows/Flow.java
Normal file
51
core/src/main/java/org/floworc/core/flows/Flow.java
Normal file
@@ -0,0 +1,51 @@
|
||||
package org.floworc.core.flows;
|
||||
|
||||
import lombok.*;
|
||||
import org.floworc.core.tasks.Task;
|
||||
import org.floworc.core.triggers.Trigger;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Value
|
||||
@Builder
|
||||
public class Flow {
|
||||
@NotNull
|
||||
private String id;
|
||||
|
||||
@NotNull
|
||||
private String namespace;
|
||||
|
||||
@Valid
|
||||
private List<Inputs> inputs;
|
||||
|
||||
@Valid
|
||||
private List<Task> tasks;
|
||||
|
||||
@Valid
|
||||
private List<Task> errors;
|
||||
|
||||
@Valid
|
||||
private List<Trigger> triggers;
|
||||
|
||||
public Logger logger() {
|
||||
return LoggerFactory.getLogger("flow." + this.id);
|
||||
}
|
||||
|
||||
public Task findTaskById(String id) {
|
||||
Optional<Task> find = this.tasks
|
||||
.stream()
|
||||
.filter(task -> task.getId().equals(id))
|
||||
.findFirst();
|
||||
|
||||
if (!find.isPresent()) {
|
||||
throw new IllegalArgumentException("Can't find task with id '" + id + "' on flow '" + this.id + "'");
|
||||
}
|
||||
|
||||
return find.get();
|
||||
}
|
||||
}
|
||||
14
core/src/main/java/org/floworc/core/flows/Inputs.java
Normal file
14
core/src/main/java/org/floworc/core/flows/Inputs.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package org.floworc.core.flows;
|
||||
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Data
|
||||
public class Inputs {
|
||||
@NotNull
|
||||
private String name;
|
||||
|
||||
private Class type;
|
||||
}
|
||||
72
core/src/main/java/org/floworc/core/flows/State.java
Normal file
72
core/src/main/java/org/floworc/core/flows/State.java
Normal file
@@ -0,0 +1,72 @@
|
||||
package org.floworc.core.flows;
|
||||
|
||||
import lombok.Value;
|
||||
import org.floworc.core.executions.TaskRun;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Value
|
||||
public class State {
|
||||
private Type current;
|
||||
|
||||
private List<History> histories;
|
||||
|
||||
public State() {
|
||||
this.current = Type.CREATED;
|
||||
this.histories = new ArrayList<>();
|
||||
this.histories.add(new History(this.current, Instant.now()));
|
||||
}
|
||||
|
||||
public State(Type state, State actual) {
|
||||
this.current = state;
|
||||
this.histories = actual.histories;
|
||||
this.histories.add(new History(this.current, Instant.now()));
|
||||
}
|
||||
|
||||
public State withState(Type state) {
|
||||
return new State(state, this);
|
||||
}
|
||||
|
||||
public Duration duration() {
|
||||
return Duration.between(
|
||||
this.histories.get(0).getDate(),
|
||||
this.histories.size() > 1 ? this.histories.get(this.histories.size() - 1).getDate() : Instant.now()
|
||||
);
|
||||
}
|
||||
|
||||
public String humanDuration() {
|
||||
String duration = duration()
|
||||
.toString()
|
||||
.substring(2)
|
||||
.replaceAll("(\\d[HMS])(?!$)", " $1 ")
|
||||
.toLowerCase();
|
||||
|
||||
return duration.substring(0, duration.length() - 4) + "s";
|
||||
}
|
||||
|
||||
public boolean isTerninated() {
|
||||
return this.current == Type.SKIPPED || this.current == Type.FAILED || this.current == Type.SUCCESS;
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return this.current == Type.RUNNING;
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
CREATED,
|
||||
RUNNING,
|
||||
SUCCESS,
|
||||
FAILED,
|
||||
SKIPPED,
|
||||
PAUSED;
|
||||
}
|
||||
|
||||
@Value
|
||||
public static class History {
|
||||
private Type state;
|
||||
private Instant date;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.floworc.core.queues;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public interface QueueInterface <T> {
|
||||
boolean emit(QueueMessage<T> message);
|
||||
|
||||
void receive(Consumer<QueueMessage<T>> consumer);
|
||||
|
||||
void ack(QueueMessage<T> message);
|
||||
}
|
||||
14
core/src/main/java/org/floworc/core/queues/QueueMessage.java
Normal file
14
core/src/main/java/org/floworc/core/queues/QueueMessage.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package org.floworc.core.queues;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class QueueMessage <T> {
|
||||
private String key;
|
||||
|
||||
private T body;
|
||||
|
||||
|
||||
}
|
||||
11
core/src/main/java/org/floworc/core/queues/QueueName.java
Normal file
11
core/src/main/java/org/floworc/core/queues/QueueName.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package org.floworc.core.queues;
|
||||
|
||||
public enum QueueName {
|
||||
WORKERS,
|
||||
EXECUTIONS,
|
||||
WORKERS_RESULT;
|
||||
|
||||
public boolean isPubSub() {
|
||||
return this == EXECUTIONS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package org.floworc.core.queues.types;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.floworc.core.queues.QueueInterface;
|
||||
import org.floworc.core.queues.QueueMessage;
|
||||
import org.floworc.core.queues.QueueName;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@Slf4j
|
||||
public class LocalQueue <T> implements QueueInterface<T> {
|
||||
private static ThreadPoolExecutor poolExecutor = (ThreadPoolExecutor) Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
|
||||
private QueueName topic;
|
||||
private List<QueueMessage<T>> messages = new ArrayList<>();
|
||||
private List<Consumer<QueueMessage<T>>> consumers = new ArrayList<>();
|
||||
|
||||
public LocalQueue(QueueName topic) {
|
||||
this.topic = topic;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean emit(QueueMessage<T> message) {
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("New message: topic '{}', key '{}', value {}", this.topic, message.getKey(), message.getBody());
|
||||
}
|
||||
|
||||
this.messages.add(message);
|
||||
|
||||
if (this.consumers != null) {
|
||||
if (this.topic.isPubSub()) {
|
||||
this.consumers
|
||||
.forEach(consumers ->
|
||||
poolExecutor.execute(() ->
|
||||
consumers.accept(message)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
poolExecutor.execute(() -> {
|
||||
this.consumers.get((new Random()).nextInt(this.consumers.size())).accept(message);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(Consumer<QueueMessage<T>> consumer) {
|
||||
this.consumers.add(consumer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ack(QueueMessage<T> message) {
|
||||
this.messages.remove(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.floworc.core.repositories;
|
||||
|
||||
import org.floworc.core.executions.Execution;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ExecutionRepository {
|
||||
private RepositoryStorage repositoryStorage;
|
||||
|
||||
public ExecutionRepository(RepositoryStorage repositoryStorage) {
|
||||
this.repositoryStorage = repositoryStorage;
|
||||
}
|
||||
|
||||
public Execution getById(String id) {
|
||||
return this.repositoryStorage.getByKey(Execution.class, id);
|
||||
}
|
||||
|
||||
public List<Execution> getAll() {
|
||||
return this.repositoryStorage.getAll(Execution.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.floworc.core.repositories;
|
||||
|
||||
import org.floworc.core.flows.Flow;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface RepositoryInterface {
|
||||
Optional<Flow> getFlowById(String id);
|
||||
|
||||
List<Flow> getFlows();
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.floworc.core.repositories;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface RepositoryStorage {
|
||||
<T> T getByKey(Class<T> clz, String id);
|
||||
|
||||
<T> List<T> getAll(Class<T> clz);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.floworc.core.repositories.storages;
|
||||
|
||||
import org.floworc.core.repositories.RepositoryStorage;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class MemoryStorage implements RepositoryStorage {
|
||||
private Map<String, Map<String, Object>> storage = new HashMap<>();
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getByKey(Class<T> clz, String id) {
|
||||
return (T) this.storage.get(clz.getName()).get(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
|
||||
public <T> List<T> getAll(Class<T> clz) {
|
||||
return new ArrayList<>(this.storage.get(clz.getName()).values())
|
||||
.stream()
|
||||
.map(o -> (T) o)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.floworc.core.repositories.types;
|
||||
|
||||
import org.floworc.core.flows.Flow;
|
||||
import org.floworc.core.repositories.RepositoryInterface;
|
||||
import org.floworc.core.serializers.YamlFlowParser;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class LocalRepository implements RepositoryInterface {
|
||||
private File basePath;
|
||||
private static final YamlFlowParser yamlFlowParser = new YamlFlowParser();
|
||||
|
||||
public LocalRepository(File basePath) {
|
||||
this.basePath = basePath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Flow> getFlowById(String id) {
|
||||
File file = new File(this.basePath, id + ".yaml");
|
||||
try {
|
||||
return Optional.of(yamlFlowParser.parse(file));
|
||||
} catch (IOException e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Flow> getFlows() {
|
||||
try {
|
||||
return Files.list(this.basePath.toPath())
|
||||
.map(path -> this.getFlowById(path.toFile().getName()))
|
||||
.filter(Optional::isPresent)
|
||||
.map(Optional::get)
|
||||
.collect(Collectors.toList());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package org.floworc.core.runners;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.floworc.core.executions.Execution;
|
||||
import org.floworc.core.executions.TaskRun;
|
||||
import org.floworc.core.executions.WorkerTask;
|
||||
import org.floworc.core.queues.QueueInterface;
|
||||
import org.floworc.core.queues.QueueMessage;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Slf4j
|
||||
public class ExecutionState implements Runnable {
|
||||
private QueueInterface<Execution> executionQueue;
|
||||
private QueueInterface<WorkerTask> workerTaskResultQueue;
|
||||
private static ConcurrentHashMap<String, Execution> executions = new ConcurrentHashMap<>();
|
||||
|
||||
public ExecutionState(
|
||||
QueueInterface<Execution> executionQueue,
|
||||
QueueInterface<WorkerTask> workerTaskResultQueue
|
||||
) {
|
||||
this.executionQueue = executionQueue;
|
||||
this.workerTaskResultQueue = workerTaskResultQueue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
this.executionQueue.receive(message -> {
|
||||
Execution execution = message.getBody();
|
||||
|
||||
if (execution.getState().isTerninated()) {
|
||||
executions.remove(message.getKey());
|
||||
} else {
|
||||
executions.put(message.getKey(), execution);
|
||||
}
|
||||
});
|
||||
|
||||
this.workerTaskResultQueue.receive(message -> {
|
||||
TaskRun taskRun = message.getBody().getTaskRun();
|
||||
|
||||
if (!executions.containsKey(taskRun.getExecutionId())) {
|
||||
throw new RuntimeException("Unable to find execution '" + taskRun.getExecutionId() + "' on ExecutionState");
|
||||
}
|
||||
|
||||
Execution execution = executions.get(taskRun.getExecutionId());
|
||||
|
||||
Execution newExecution = execution.withTaskRun(taskRun);
|
||||
this.executionQueue.emit(
|
||||
QueueMessage.<Execution>builder()
|
||||
.key(newExecution.getId())
|
||||
.body(newExecution)
|
||||
.build()
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
125
core/src/main/java/org/floworc/core/runners/Executor.java
Normal file
125
core/src/main/java/org/floworc/core/runners/Executor.java
Normal file
@@ -0,0 +1,125 @@
|
||||
package org.floworc.core.runners;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.floworc.core.executions.Execution;
|
||||
import org.floworc.core.executions.ExecutionService;
|
||||
import org.floworc.core.executions.TaskRun;
|
||||
import org.floworc.core.executions.WorkerTask;
|
||||
import org.floworc.core.flows.Flow;
|
||||
import org.floworc.core.flows.State;
|
||||
import org.floworc.core.queues.QueueInterface;
|
||||
import org.floworc.core.queues.QueueMessage;
|
||||
import org.floworc.core.repositories.RepositoryInterface;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
public class Executor implements Runnable {
|
||||
private QueueInterface<Execution> executionQueue;
|
||||
private QueueInterface<WorkerTask> workerTaskQueue;
|
||||
private RepositoryInterface repository;
|
||||
|
||||
public Executor(
|
||||
QueueInterface<Execution> executionQueue,
|
||||
QueueInterface<WorkerTask> workerTaskQueue,
|
||||
RepositoryInterface repositoryInterface
|
||||
) {
|
||||
this.executionQueue = executionQueue;
|
||||
this.workerTaskQueue = workerTaskQueue;
|
||||
this.repository = repositoryInterface;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
this.executionQueue.receive(message -> {
|
||||
Execution execution = message.getBody();
|
||||
|
||||
if (!execution.getState().isTerninated()) {
|
||||
Flow flow = this.repository
|
||||
.getFlowById(execution.getFlowId())
|
||||
.orElseThrow(() -> new IllegalArgumentException("Invalid flow id '" + execution.getFlowId() + "'"));
|
||||
|
||||
ExecutionService.getNexts(execution, flow.getTasks())
|
||||
.ifPresentOrElse(
|
||||
nexts -> this.onNexts(flow, execution, nexts),
|
||||
() -> this.onEnd(flow, execution)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onNexts(Flow flow, Execution execution, List<TaskRun> nexts) {
|
||||
if (nexts.size() == 0) {
|
||||
flow.logger().trace(
|
||||
"[execution: {}] Call getNexts but no next found: {}",
|
||||
execution.getId(),
|
||||
execution
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
flow.logger().trace(
|
||||
"[execution: {}] Found nexts {}",
|
||||
execution.getId(),
|
||||
nexts
|
||||
);
|
||||
}
|
||||
|
||||
List<TaskRun> executionTasksRun;
|
||||
Execution newExecution;
|
||||
|
||||
if (execution.getTaskRunList() == null) {
|
||||
executionTasksRun = nexts;
|
||||
} else {
|
||||
executionTasksRun = new ArrayList<>(execution.getTaskRunList());
|
||||
executionTasksRun.addAll(nexts);
|
||||
}
|
||||
|
||||
newExecution = execution.withTaskRunList(executionTasksRun);
|
||||
|
||||
if (execution.getState().getCurrent() == State.Type.CREATED) {
|
||||
flow.logger().info(
|
||||
"[execution: {}] Flow started",
|
||||
execution.getId()
|
||||
);
|
||||
|
||||
newExecution = newExecution.withState(State.Type.RUNNING);
|
||||
}
|
||||
|
||||
this.executionQueue.emit(
|
||||
QueueMessage.<Execution>builder()
|
||||
.key(newExecution.getId())
|
||||
.body(newExecution)
|
||||
.build()
|
||||
);
|
||||
|
||||
final Execution finalNewExecution = newExecution;
|
||||
nexts.forEach(taskRun -> this.workerTaskQueue.emit(QueueMessage.<WorkerTask>builder()
|
||||
.key(finalNewExecution.getId())
|
||||
.body(WorkerTask.builder()
|
||||
.taskRun(taskRun)
|
||||
.task(flow.findTaskById(taskRun.getTaskId()))
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
));
|
||||
}
|
||||
|
||||
private void onEnd(Flow flow, Execution execution) {
|
||||
Execution newExecution = execution.withState(State.Type.SUCCESS);
|
||||
|
||||
flow.logger().info(
|
||||
"[execution: {}] Flow completed with state {} in {}",
|
||||
newExecution.getId(),
|
||||
newExecution.getState().getCurrent(),
|
||||
newExecution.getState().humanDuration()
|
||||
);
|
||||
|
||||
this.executionQueue.emit(
|
||||
QueueMessage.<Execution>builder()
|
||||
.key(newExecution.getId())
|
||||
.body(newExecution)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.floworc.core.runners;
|
||||
|
||||
import org.floworc.core.executions.Execution;
|
||||
import org.floworc.core.flows.Flow;
|
||||
|
||||
public interface RunnerInterface {
|
||||
void run();
|
||||
}
|
||||
62
core/src/main/java/org/floworc/core/runners/Worker.java
Normal file
62
core/src/main/java/org/floworc/core/runners/Worker.java
Normal file
@@ -0,0 +1,62 @@
|
||||
package org.floworc.core.runners;
|
||||
|
||||
import org.floworc.core.executions.WorkerTask;
|
||||
import org.floworc.core.flows.State;
|
||||
import org.floworc.core.queues.QueueInterface;
|
||||
import org.floworc.core.queues.QueueMessage;
|
||||
|
||||
public class Worker implements Runnable {
|
||||
private QueueInterface<WorkerTask> workerTaskQueue;
|
||||
private QueueInterface<WorkerTask> workerTaskResultQueue;
|
||||
|
||||
public Worker(QueueInterface<WorkerTask> workerTaskQueue, QueueInterface<WorkerTask> workerTaskResultQueue) {
|
||||
this.workerTaskQueue = workerTaskQueue;
|
||||
this.workerTaskResultQueue = workerTaskResultQueue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
this.workerTaskQueue.receive(message -> {
|
||||
this.run(message.getBody());
|
||||
});
|
||||
}
|
||||
|
||||
public void run(WorkerTask workerTask) {
|
||||
workerTask.logger().info(
|
||||
"[execution: {}] [taskrun: {}] Task started",
|
||||
workerTask.getTaskRun().getExecutionId(),
|
||||
workerTask.getTaskRun().getId()
|
||||
);
|
||||
|
||||
this.workerTaskResultQueue.emit(QueueMessage.<WorkerTask>builder()
|
||||
.key(workerTask.getTaskRun().getExecutionId())
|
||||
.body(workerTask.withTaskRun(workerTask.getTaskRun().withState(State.Type.RUNNING)))
|
||||
.build()
|
||||
);
|
||||
|
||||
try {
|
||||
workerTask.getTask().run();
|
||||
|
||||
this.workerTaskResultQueue.emit(QueueMessage.<WorkerTask>builder()
|
||||
.key(workerTask.getTaskRun().getExecutionId())
|
||||
.body(workerTask.withTaskRun(workerTask.getTaskRun().withState(State.Type.SUCCESS)))
|
||||
.build()
|
||||
);
|
||||
} catch (Exception e) {
|
||||
workerTask.logger().error("Failed task", e);
|
||||
|
||||
this.workerTaskResultQueue.emit(QueueMessage.<WorkerTask>builder()
|
||||
.key(workerTask.getTaskRun().getExecutionId())
|
||||
.body(workerTask.withTaskRun(workerTask.getTaskRun().withState(State.Type.FAILED)))
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
workerTask.logger().info(
|
||||
"[execution: {}] [taskrun: {}] Task completed in {}",
|
||||
workerTask.getTaskRun().getExecutionId(),
|
||||
workerTask.getTaskRun().getId(),
|
||||
workerTask.getTaskRun().getState().humanDuration()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package org.floworc.core.runners.types;
|
||||
|
||||
import com.devskiller.friendly_id.FriendlyId;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.floworc.core.executions.Execution;
|
||||
import org.floworc.core.executions.WorkerTask;
|
||||
import org.floworc.core.flows.Flow;
|
||||
import org.floworc.core.flows.State;
|
||||
import org.floworc.core.queues.QueueMessage;
|
||||
import org.floworc.core.queues.QueueName;
|
||||
import org.floworc.core.queues.types.LocalQueue;
|
||||
import org.floworc.core.repositories.types.LocalRepository;
|
||||
import org.floworc.core.runners.ExecutionState;
|
||||
import org.floworc.core.runners.Executor;
|
||||
import org.floworc.core.runners.RunnerInterface;
|
||||
import org.floworc.core.runners.Worker;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
@Slf4j
|
||||
public class StandAloneRunner implements RunnerInterface {
|
||||
private LocalQueue<Execution> executionQueue;
|
||||
private LocalQueue<WorkerTask> workerTaskQueue;
|
||||
private LocalQueue<WorkerTask> workerTaskResultQueue;
|
||||
private LocalRepository localRepository;
|
||||
private ThreadPoolExecutor poolExecutor;
|
||||
|
||||
public StandAloneRunner(File basePath) {
|
||||
this.executionQueue = new LocalQueue<>(QueueName.EXECUTIONS);
|
||||
this.workerTaskQueue = new LocalQueue<>(QueueName.WORKERS);
|
||||
this.workerTaskResultQueue = new LocalQueue<>(QueueName.WORKERS_RESULT);
|
||||
this.localRepository = new LocalRepository(basePath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
poolExecutor = (ThreadPoolExecutor) Executors.newFixedThreadPool(5);
|
||||
poolExecutor.execute(new Executor(
|
||||
this.executionQueue,
|
||||
this.workerTaskQueue,
|
||||
this.localRepository
|
||||
));
|
||||
|
||||
poolExecutor.execute(new ExecutionState(
|
||||
this.executionQueue,
|
||||
this.workerTaskResultQueue
|
||||
));
|
||||
|
||||
while(poolExecutor.getActiveCount() != poolExecutor.getCorePoolSize()) {
|
||||
poolExecutor.execute(new Worker(
|
||||
this.workerTaskQueue,
|
||||
this.workerTaskResultQueue
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Execution run(Flow flow) throws InterruptedException {
|
||||
this.run();
|
||||
|
||||
Execution execution = Execution.builder()
|
||||
.id(FriendlyId.createFriendlyId())
|
||||
.flowId(flow.getId())
|
||||
.state(new State())
|
||||
.build();
|
||||
|
||||
AtomicReference<Execution> receive = new AtomicReference<>();
|
||||
|
||||
this.executionQueue.receive(message -> {
|
||||
if (message.getBody().getState().isTerninated()) {
|
||||
receive.set(message.getBody());
|
||||
|
||||
this.poolExecutor.shutdownNow();
|
||||
}
|
||||
});
|
||||
|
||||
this.executionQueue.emit(
|
||||
QueueMessage.<Execution>builder()
|
||||
.key(execution.getId())
|
||||
.body(execution)
|
||||
.build()
|
||||
);
|
||||
|
||||
|
||||
this.poolExecutor.awaitTermination(5, TimeUnit.MINUTES);
|
||||
|
||||
return receive.get();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.floworc.core.serializers;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.floworc.core.flows.Flow;
|
||||
|
||||
import javax.validation.ConstraintViolation;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Getter
|
||||
public class InvalidDefinitionException extends IllegalArgumentException {
|
||||
private Set<ConstraintViolation<Flow>> violations;
|
||||
|
||||
private InvalidDefinitionException(String message, Set<ConstraintViolation<Flow>> violations) {
|
||||
super(message);
|
||||
this.violations = violations;
|
||||
}
|
||||
|
||||
public static InvalidDefinitionException of(Set<ConstraintViolation<Flow>> violations) {
|
||||
List<String> errors = violations.stream().map(Object::toString).collect(Collectors.toList());
|
||||
|
||||
return new InvalidDefinitionException(
|
||||
"Invalid Flow definitions with errors: \n- " + String.join("\n- ", errors),
|
||||
violations
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package org.floworc.core.serializers;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
|
||||
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator;
|
||||
import org.floworc.core.flows.Flow;
|
||||
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.Validation;
|
||||
import javax.validation.Validator;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
|
||||
public class YamlFlowParser {
|
||||
private static final ObjectMapper mapper = new ObjectMapper(new YAMLFactory())
|
||||
.registerModule(new Jdk8Module())
|
||||
.registerModule(new JavaTimeModule());
|
||||
|
||||
private static final Validator validator = Validation.byDefaultProvider()
|
||||
.configure()
|
||||
.messageInterpolator(new ParameterMessageInterpolator())
|
||||
.buildValidatorFactory()
|
||||
.getValidator();
|
||||
|
||||
public Flow parse(File file) throws IOException {
|
||||
Flow flow = mapper.readValue(file, Flow.class);
|
||||
|
||||
Set<ConstraintViolation<Flow>> violations = validator.validate(flow);
|
||||
|
||||
if (violations.size() > 0) {
|
||||
throw InvalidDefinitionException.of(violations);
|
||||
}
|
||||
|
||||
return flow;
|
||||
}
|
||||
}
|
||||
|
||||
14
core/src/main/java/org/floworc/core/tasks/Retry.java
Normal file
14
core/src/main/java/org/floworc/core/tasks/Retry.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package org.floworc.core.tasks;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
@Data
|
||||
public class Retry {
|
||||
private int limit;
|
||||
|
||||
private RetryIntervalType type;
|
||||
|
||||
private Duration interval;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package org.floworc.core.tasks;
|
||||
|
||||
public enum RetryIntervalType {
|
||||
CONSTANT,
|
||||
EXPONENTIAL
|
||||
}
|
||||
47
core/src/main/java/org/floworc/core/tasks/Task.java
Normal file
47
core/src/main/java/org/floworc/core/tasks/Task.java
Normal file
@@ -0,0 +1,47 @@
|
||||
package org.floworc.core.tasks;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import lombok.Data;
|
||||
import org.apache.avro.reflect.Nullable;
|
||||
import org.floworc.core.executions.Execution;
|
||||
import org.floworc.core.executions.TaskRun;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "type", visible = true)
|
||||
@Data
|
||||
abstract public class Task {
|
||||
@NotNull
|
||||
private String id;
|
||||
|
||||
private String type;
|
||||
|
||||
@Nullable
|
||||
private Retry retry;
|
||||
|
||||
private int timeout;
|
||||
|
||||
private List<Task> errors;
|
||||
|
||||
abstract public Void run() throws Exception;
|
||||
|
||||
public List<TaskRun> toTaskRun(Execution execution) {
|
||||
return Collections.singletonList(TaskRun.of(execution, this));
|
||||
}
|
||||
|
||||
public Optional<List<TaskRun>> getChildTaskRun(Execution execution) {
|
||||
return Optional.of(new ArrayList<>());
|
||||
}
|
||||
|
||||
public Optional<Task> findById(String id) {
|
||||
if (this.getId().equals(id)) {
|
||||
return Optional.of(this);
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
16
core/src/main/java/org/floworc/core/tasks/TaskResult.java
Normal file
16
core/src/main/java/org/floworc/core/tasks/TaskResult.java
Normal file
@@ -0,0 +1,16 @@
|
||||
package org.floworc.core.tasks;
|
||||
|
||||
import lombok.Data;
|
||||
import org.floworc.core.flows.State;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
@Data
|
||||
public class TaskResult {
|
||||
private State state;
|
||||
|
||||
private Instant start;
|
||||
|
||||
private Duration duration;
|
||||
}
|
||||
21
core/src/main/java/org/floworc/core/tasks/debugs/Echo.java
Normal file
21
core/src/main/java/org/floworc/core/tasks/debugs/Echo.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package org.floworc.core.tasks.debugs;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.floworc.core.tasks.Task;
|
||||
|
||||
@ToString(callSuper = true)
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@Slf4j
|
||||
public class Echo extends Task {
|
||||
private String format;
|
||||
|
||||
@Override
|
||||
public Void run() throws Exception {
|
||||
log.info(this.format);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
16
core/src/main/java/org/floworc/core/tasks/flows/Each.java
Normal file
16
core/src/main/java/org/floworc/core/tasks/flows/Each.java
Normal file
@@ -0,0 +1,16 @@
|
||||
package org.floworc.core.tasks.flows;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ToString(callSuper = true)
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@Slf4j
|
||||
public class Each extends Parallel {
|
||||
private List<String> values;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package org.floworc.core.tasks.flows;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.floworc.core.executions.Execution;
|
||||
import org.floworc.core.executions.ExecutionService;
|
||||
import org.floworc.core.executions.TaskRun;
|
||||
import org.floworc.core.tasks.Task;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@ToString(callSuper = true)
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@Slf4j
|
||||
public class Parallel extends Task {
|
||||
private Integer concurrent;
|
||||
|
||||
private List<Task> tasks;
|
||||
|
||||
@Override
|
||||
public Optional<Task> findById(String id) {
|
||||
Optional<Task> superFind = super.findById(id);
|
||||
if (superFind.isPresent()) {
|
||||
return superFind;
|
||||
}
|
||||
|
||||
return this.tasks
|
||||
.stream()
|
||||
.map(task -> task.findById(id))
|
||||
.filter(Optional::isPresent)
|
||||
.map(Optional::get)
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<List<TaskRun>> getChildTaskRun(Execution execution) {
|
||||
return ExecutionService.getNexts(execution, this.tasks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void run() {
|
||||
log.info("Starting '{}'", this.tasks);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
27
core/src/main/java/org/floworc/core/tasks/flows/Switch.java
Normal file
27
core/src/main/java/org/floworc/core/tasks/flows/Switch.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package org.floworc.core.tasks.flows;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.floworc.core.tasks.Task;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@ToString(callSuper = true)
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@Slf4j
|
||||
public class Switch extends Task {
|
||||
private Map<String, List<Task>> cases;
|
||||
|
||||
private List<Task> defaults;
|
||||
|
||||
@Override
|
||||
public Void run() {
|
||||
log.info("Starting '{}'", this);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
72
core/src/main/java/org/floworc/core/tasks/scripts/Bash.java
Normal file
72
core/src/main/java/org/floworc/core/tasks/scripts/Bash.java
Normal file
@@ -0,0 +1,72 @@
|
||||
package org.floworc.core.tasks.scripts;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import lombok.Value;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.floworc.core.tasks.Task;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@ToString(callSuper = true)
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Value
|
||||
@Slf4j
|
||||
public class Bash extends Task {
|
||||
private String[] commands;
|
||||
|
||||
@Override
|
||||
public Void run() throws Exception {
|
||||
List<String> commands = Arrays.asList("/bin/sh", "-c", String.join("\n", this.commands));
|
||||
|
||||
log.debug("Starting command [{}]", String.join("; ", this.commands));
|
||||
|
||||
ProcessBuilder processBuilder = new ProcessBuilder();
|
||||
processBuilder.command(commands);
|
||||
|
||||
Process process = processBuilder.start();
|
||||
readInput(process.getInputStream(), false);
|
||||
readInput(process.getErrorStream(), true);
|
||||
|
||||
// process.pid();
|
||||
|
||||
int exitCode = process.waitFor();
|
||||
|
||||
if (exitCode != 0) {
|
||||
throw new RuntimeException("Command failed with code " + exitCode);
|
||||
} else {
|
||||
log.debug("Command succeed with code " + exitCode);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void readInput(InputStream inputStream, boolean isStdErr) {
|
||||
Thread thread = new Thread(() -> {
|
||||
try {
|
||||
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
|
||||
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
|
||||
String line;
|
||||
while ((line = bufferedReader.readLine()) != null) {
|
||||
if (isStdErr) {
|
||||
log.warn(line);
|
||||
} else {
|
||||
log.info(line);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
|
||||
thread.setName("bash-log");
|
||||
thread.start();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.floworc.core.triggers;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
abstract public class Trigger {
|
||||
private String type;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.floworc.core.triggers.types;
|
||||
|
||||
import lombok.*;
|
||||
import org.floworc.core.triggers.Trigger;
|
||||
|
||||
@ToString(callSuper = true)
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
public class Schedule extends Trigger {
|
||||
private String expression;
|
||||
|
||||
private ScheduleBackfill backfill;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.floworc.core.triggers.types;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
@Data
|
||||
public class ScheduleBackfill {
|
||||
private Instant start;
|
||||
}
|
||||
23
core/src/test/java/org/floworc/core/Utils.java
Normal file
23
core/src/test/java/org/floworc/core/Utils.java
Normal file
@@ -0,0 +1,23 @@
|
||||
package org.floworc.core;
|
||||
|
||||
import org.floworc.core.flows.Flow;
|
||||
import org.floworc.core.serializers.YamlFlowParser;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
abstract public class Utils {
|
||||
private static final YamlFlowParser yamlFlowParser = new YamlFlowParser();
|
||||
|
||||
public static Flow parse(String path) throws IOException {
|
||||
URL resource = Utils.class.getClassLoader().getResource(path);
|
||||
assertNotNull(resource);
|
||||
|
||||
File file = new File(resource.getFile());
|
||||
|
||||
return yamlFlowParser.parse(file);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.floworc.core.runners;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.floworc.core.Utils;
|
||||
import org.floworc.core.executions.Execution;
|
||||
import org.floworc.core.flows.Flow;
|
||||
import org.floworc.core.runners.types.StandAloneRunner;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
|
||||
class StandAloneRunnerTest {
|
||||
private final StandAloneRunner runner = new StandAloneRunner(
|
||||
new File(Objects.requireNonNull(Utils.class.getClassLoader().getResource("flows")).toURI())
|
||||
);
|
||||
|
||||
StandAloneRunnerTest() throws URISyntaxException {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void run() throws IOException, InterruptedException {
|
||||
Flow flow = Utils.parse("flows/full.yaml");
|
||||
Execution execution = runner.run(flow);
|
||||
|
||||
assertThat(execution.getTaskRunList(), hasSize(4));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package org.floworc.core.serializers;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.floworc.core.Utils;
|
||||
import org.floworc.core.flows.Flow;
|
||||
import org.floworc.core.tasks.RetryIntervalType;
|
||||
import org.floworc.core.tasks.Task;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
class YamlFlowParserTest {
|
||||
@Test
|
||||
void parse() throws IOException {
|
||||
Flow flow = Utils.parse("flows/full.yaml");
|
||||
|
||||
assertThat(flow.getId(), is("full"));
|
||||
assertThat(flow.getTasks().size(), is(4));
|
||||
|
||||
// third with all optionnals
|
||||
Task optionnals = flow.getTasks().get(2);
|
||||
assertThat(optionnals.getTimeout(), is(1000));
|
||||
assertThat(optionnals.getRetry().getInterval().getSeconds(), is(900L));
|
||||
assertThat(optionnals.getRetry().getType(), is(RetryIntervalType.CONSTANT));
|
||||
assertThat(optionnals.getRetry().getLimit(), is(5));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validation() throws IOException {
|
||||
assertThrows(InvalidDefinitionException.class, () -> {
|
||||
Utils.parse("flows/invalid.yaml");
|
||||
});
|
||||
|
||||
try {
|
||||
Utils.parse("flows/invalid.yaml");
|
||||
} catch (InvalidDefinitionException e) {
|
||||
assertThat(e.getViolations().size(), is(3));
|
||||
}
|
||||
}
|
||||
}
|
||||
16
core/src/test/java/org/floworc/core/tasks/BashTest.java
Normal file
16
core/src/test/java/org/floworc/core/tasks/BashTest.java
Normal file
@@ -0,0 +1,16 @@
|
||||
package org.floworc.core.tasks;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.floworc.core.runners.types.StandAloneRunner;
|
||||
import org.floworc.core.tasks.scripts.Bash;
|
||||
|
||||
class BashTest {
|
||||
|
||||
@Test
|
||||
void run() throws Exception {
|
||||
Bash bash = new Bash(
|
||||
new String[]{"sleep 1", "curl www.google.fr > /dev/null", "echo 0", "sleep 1", "echo 1"}
|
||||
);
|
||||
bash.run();
|
||||
}
|
||||
}
|
||||
107
core/src/test/resources/flows/full.yaml
Normal file
107
core/src/test/resources/flows/full.yaml
Normal file
@@ -0,0 +1,107 @@
|
||||
id: full
|
||||
namespace: com.leroymerlin.data.supply
|
||||
|
||||
#triggers:
|
||||
#- type: schedule
|
||||
# expression: 42 4 1 * *
|
||||
# backfill:
|
||||
# start: 2018-01-01
|
||||
#
|
||||
|
||||
tasks:
|
||||
- id: 1st
|
||||
type: org.floworc.core.tasks.scripts.Bash
|
||||
commands:
|
||||
- 'echo "first"'
|
||||
|
||||
- id: 2nd
|
||||
type: org.floworc.core.tasks.debugs.Echo
|
||||
format: second {{todo}}
|
||||
|
||||
- id: 3rd
|
||||
type: org.floworc.core.tasks.debugs.Echo
|
||||
format: third all optionnal args {{todo}}
|
||||
timeout: 1000
|
||||
retry:
|
||||
limit: 5
|
||||
type: CONSTANT
|
||||
interval: PT15M
|
||||
|
||||
- id: 4th
|
||||
type: org.floworc.core.tasks.flows.Parallel
|
||||
concurrent: 3
|
||||
tasks:
|
||||
- id: 4th - 1st
|
||||
type: org.floworc.core.tasks.scripts.Bash
|
||||
commands:
|
||||
- echo "{{id}}"
|
||||
- id: 4th - 2nd
|
||||
type: org.floworc.core.tasks.scripts.Bash
|
||||
commands:
|
||||
- echo "{{id}}"
|
||||
- id: 4th - 3rd
|
||||
type: org.floworc.core.tasks.scripts.Bash
|
||||
commands:
|
||||
- echo "{{id}}"
|
||||
- id: 4th - 4th
|
||||
type: org.floworc.core.tasks.scripts.Bash
|
||||
commands:
|
||||
- echo "{{id}}"
|
||||
- id: 4th - 5th
|
||||
type: org.floworc.core.tasks.scripts.Bash
|
||||
commands:
|
||||
- echo "{{id}}"
|
||||
|
||||
#- type: python
|
||||
# commands:
|
||||
# - print(bla)
|
||||
#- type: each
|
||||
# values: {{selector}}
|
||||
# tasks:
|
||||
# - type: org.floworc.core.tasks.scripts.Bash
|
||||
# commands:
|
||||
# - echo {{item}}
|
||||
#
|
||||
#- type: switch
|
||||
# expression: {{selector}}
|
||||
# cases:
|
||||
# - value: hello
|
||||
# tasks:
|
||||
# - type: org.floworc.core.tasks.debugs.Echo
|
||||
# text: hello world
|
||||
# - value: bye
|
||||
# tasks:
|
||||
# - type: org.floworc.core.tasks.debugs.Echo
|
||||
# text: goodbye world
|
||||
# default:
|
||||
# - tasks:
|
||||
# -type: org.floworc.core.tasks.debugs.Echo
|
||||
# text: something else
|
||||
#
|
||||
#- type: branch
|
||||
# branches:
|
||||
# first:
|
||||
# - type: org.floworc.core.tasks.scripts.Bash
|
||||
# commands:
|
||||
# - echo "first branch - step 1"
|
||||
# - type: org.floworc.core.tasks.scripts.Bash
|
||||
# commands:
|
||||
# - echo "first branch - step 2"
|
||||
# second:
|
||||
# - type: org.floworc.core.tasks.scripts.Bash
|
||||
# commands:
|
||||
# - echo "second branch - step 1"
|
||||
# - type: org.floworc.core.tasks.scripts.Bash
|
||||
# commands:
|
||||
# - echo "second branch - step 2"
|
||||
#
|
||||
#- type: org.floworc.core.tasks.scripts.Bash
|
||||
# commands: sleep 5
|
||||
# timeout: 1
|
||||
# errors:
|
||||
# - type: org.floworc.core.tasks.scripts.Bash
|
||||
# commands:
|
||||
# - echo "first branch - step 1"
|
||||
# - type: org.floworc.core.tasks.scripts.Bash
|
||||
# commands:
|
||||
# - echo "first branch - step 2"
|
||||
4
core/src/test/resources/flows/invalid.yaml
Normal file
4
core/src/test/resources/flows/invalid.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
tasks:
|
||||
- type: org.floworc.core.tasks.scripts.Bash
|
||||
commands:
|
||||
- echo "bla"
|
||||
23
core/src/test/resources/logback.xml
Normal file
23
core/src/test/resources/logback.xml
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration debug="false">
|
||||
<!-- Remove logback startup log -->
|
||||
<statusListener class="ch.qos.logback.core.status.NopStatusListener" />
|
||||
<property name="pattern" value="%d{ISO8601} %highlight(%-5.5level) %magenta(%-12.12thread) %cyan(%-12.12logger{12}) %msg%n" />
|
||||
<withJansi>true</withJansi>
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<target>System.out</target>
|
||||
<immediateFlush>true</immediateFlush>
|
||||
<encoder>
|
||||
<pattern>${pattern}</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
|
||||
<root level="WARN">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
|
||||
<logger name="org.floworc" level="DEBUG" />
|
||||
<logger name="flow" level="DEBUG" />
|
||||
</configuration>
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
#Thu Apr 25 10:26:30 CEST 2019
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
|
||||
172
gradlew
vendored
Executable file
172
gradlew
vendored
Executable file
@@ -0,0 +1,172 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
84
gradlew.bat
vendored
Executable file
84
gradlew.bat
vendored
Executable file
@@ -0,0 +1,84 @@
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
2
lombok.config
Normal file
2
lombok.config
Normal file
@@ -0,0 +1,2 @@
|
||||
lombok.addLombokGeneratedAnnotation = true
|
||||
lombok.anyConstructor.addConstructorProperties = true
|
||||
4
settings.gradle
Normal file
4
settings.gradle
Normal file
@@ -0,0 +1,4 @@
|
||||
rootProject.name="floworc"
|
||||
|
||||
include 'cli'
|
||||
include 'core'
|
||||
Reference in New Issue
Block a user