Renamied stuff & fixed js execution

This commit is contained in:
HWienhold 2023-10-03 19:58:47 +02:00
parent 48d03f4137
commit 028a5d54bc
20 changed files with 340 additions and 68 deletions

View File

@ -34,7 +34,12 @@
<dependency>
<groupId>org.javadelight</groupId>
<artifactId>delight-nashorn-sandbox</artifactId>
<version>0.3.2</version>
<version>0.4.2</version>
</dependency>
<dependency>
<groupId>org.openjdk.nashorn</groupId>
<artifactId>nashorn-core</artifactId>
<version>15.4</version>
</dependency>
</dependencies>
</project>

View File

@ -12,10 +12,12 @@ import com.indu.jsinjectionservice.service.JsExecutionService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@RestController
@RequestMapping("/api/js")
@RequiredArgsConstructor
@Slf4j
public class JsExecutionController {
private final JsExecutionService service;
@ -29,4 +31,9 @@ public class JsExecutionController {
return new ResponseEntity<String>( e.getLocalizedMessage(),HttpStatus.I_AM_A_TEAPOT);
}
}
@PostMapping("/log")
public void logFromJs(@RequestBody String message) {
log.info("JS log: {}", message);
}
}

View File

@ -0,0 +1,19 @@
package com.indu.jsinjectionservice.sanbox;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public enum AllowedClasses {
URL(java.net.URL.class),
DataOutputStream(java.io.DataOutputStream.class),
BufferedReader(java.io.BufferedReader.class),
InputStreamReader(java.io.InputStreamReader.class),
StringBuilder(java.lang.StringBuilder.class),
HttpURLConnection(java.net.HttpURLConnection.class);
private Class<?> value;
}

View File

@ -0,0 +1,85 @@
package com.indu.jsinjectionservice.sanbox;
public class JsRestService {
public static final String BASE = "http://localhost:8080";
public static String getLoggerFkt() {
return postRequestFunction("log","msg", "'/api/js/log'", "{ msg : msg }");
}
public static String getCallerFkt() {
return getRequestFunction("getCaller", "getEndpoint() + '/' + getCallerId()");
}
public static String getEndpointGetter(String api) {
return getFunction("getEndpoint", ret('"'+api+'"'));
}
public static String getCallerIdGetter(String id) {
return getFunction("getCallerId", ret('"'+id+'"'));
}
public static String getRequestFunction(String fkt, String endpoint) {
return "function " + fkt +"() {\r\n"
+ " try {\r\n"
+ " var url = '" + BASE + "'+"+endpoint+"; \r\n"
+ " var urlObj = new java.net.URL(url);\r\n"
+ " var connection = urlObj.openConnection();\r\n"
+ " \r\n"
+ " connection.setRequestMethod('GET');\r\n"
+ " connection.setRequestProperty('Content-Type', 'application/json; charset=utf-8');\r\n"
+ "\r\n"
+ " var inputStream = new java.io.BufferedReader(new java.io.InputStreamReader(connection.getInputStream()));\r\n"
+ " var response = new java.lang.StringBuilder();\r\n"
+ " var inputLine;\r\n"
+ "\r\n"
+ " while ((inputLine = inputStream.readLine()) !== null) {\r\n"
+ " response.append(inputLine);\r\n"
+ " }\r\n"
+ " inputStream.close();\r\n"
+ "\r\n"
+ " return response.toString();\r\n"
+ " } catch (e) {\r\n"
+ " log(e);\r\n"
+ " return null; \r\n"
+ " }\r\n"
+ "}\r\n";
}
public static String postRequestFunction(String fkt, String args, String endpoint, String body) {
return "function "+fkt+"("+args+")"+" {\r\n"
+ " var url = new java.net.URL('"+BASE+"'+"+ endpoint+ ");\r\n"
+ " var httpRequest = url.openConnection();\r\n"
+ " \r\n"
+ " httpRequest.setRequestMethod('POST');\r\n"
+ " httpRequest.setRequestProperty('Content-Type', 'application/json; charset=utf-8');\r\n"
+ " httpRequest.setDoOutput(true);\r\n"
+ "\r\n"
+ " var outputStream = new java.io.DataOutputStream(httpRequest.getOutputStream());\r\n"
+ " outputStream.writeBytes(JSON.stringify(" + body + "));\r\n"
+ " outputStream.flush();\r\n"
+ " outputStream.close();\r\n"
+ "\r\n"
+ " var responseCode = httpRequest.getResponseCode();\r\n"
+ " if (responseCode === java.net.HttpURLConnection.HTTP_OK) {\r\n"
+ " print('Message sent successfully!');\r\n"
+ " } else {\r\n"
+ " print('Error sending message. Response code: ' + responseCode);\r\n"
+ " }\r\n"
+ "}\r\n";
}
private static String ret(String var) {
return "return " + var + ";\r\n";
}
private static String getFunction (String name, String body, String... args) {
StringBuilder functionCode = new StringBuilder();
functionCode.append("function " + name);
functionCode.append("(" + String.join(", ", args ) +")");
functionCode.append("{\r\n\t" + body +"\r\n}");
return functionCode.toString() + ";";
}
}

View File

@ -0,0 +1,5 @@
package com.indu.jsinjectionservice.sanbox;
public class SanboxHandler {
}

View File

@ -1,12 +1,12 @@
package com.indu.jsinjectionservice.service;
import java.util.concurrent.Executors;
import javax.script.ScriptException;
import org.springframework.stereotype.Service;
import com.indu.jsinjectionservice.dto.JsExectutionRequest;
import com.indu.jsinjectionservice.sanbox.AllowedClasses;
import com.indu.jsinjectionservice.sanbox.JsRestService;
import delight.nashornsandbox.NashornSandbox;
import delight.nashornsandbox.exceptions.ScriptCPUAbuseException;
@ -20,31 +20,57 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j
public class JsExecutionService {
private final JsGenerationService generator;
public void executeInSandbox(@Valid JsExectutionRequest request) throws ScriptCPUAbuseException, ScriptException {
NashornSandbox sandbox = initializeSandbox();
sandbox.eval( generator.getCallerIdGetter(request.getCallerId()));
sandbox.eval( generator.getEndpointGetter(request.getEndpoint()));
sandbox.eval( generator.getFetchCallerFunction());
addHelperFkt(sandbox, request);
sandbox.eval( request.getScript()) ;
}
private void addHelperFkt(NashornSandbox sandbox , JsExectutionRequest request) throws ScriptCPUAbuseException, ScriptException {
sandbox.eval( JsRestService.getCallerIdGetter(request.getCallerId()) );
sandbox.eval( JsRestService.getEndpointGetter(request.getEndpoint()) );
sandbox.eval( JsRestService.getLoggerFkt() );
sandbox.eval( JsRestService.getCallerFkt() );
}
private void addAllowedJavaFkt(NashornSandbox sandbox) {
for( AllowedClasses cls : AllowedClasses.values()) {
sandbox.allow(cls.getValue());
}
}
private void restrictRessources(NashornSandbox sandbox) {
// sandbox.setMaxCPUTime(100);
// sandbox.setMaxMemory(50 * 1024);
// sandbox.allowNoBraces(false);
// sandbox.setMaxPreparedStatements(30);
// sandbox.setExecutor(Executors.newSingleThreadExecutor());
}
private NashornSandbox initializeSandbox() {
// NashornSandbox sandbox = NashornSandboxes.create();
NashornSandbox sandbox = new NashornSandboxImpl() {
@Override
public Object eval(String js) throws ScriptCPUAbuseException, ScriptException {
log.info(String.format("Running command \"%s\"", js));
return super.eval(js);
}
};
sandbox.setMaxCPUTime(100);
sandbox.setMaxMemory(50 * 1024);
sandbox.allowNoBraces(false);
sandbox.setMaxPreparedStatements(30);
sandbox.setExecutor(Executors.newSingleThreadExecutor());
NashornSandbox sandbox = getSandbox();
restrictRessources(sandbox);
addAllowedJavaFkt(sandbox);
return sandbox;
}
private NashornSandbox getSandbox() {
// return NashornSandboxes.create();
//debug
return new NashornSandboxImpl() {
@Override
public Object eval(String js) {
log.info("Running command \"{}\"", js);
try {
Object foo = super.eval(js);
log.info("res " + foo);
return foo;
} catch (Exception e) {
log.info(e.getMessage());
}
return "";
}
};
}
}

View File

@ -7,22 +7,68 @@ public class JsGenerationService {
// TODO: maybe add update parent whatever ...
public String getEndpointGetter(String api) {
return getFunction("getEndpoint", ret(api));
return getFunction("getEndpoint", ret('"'+api+'"'));
}
public String getCallerIdGetter(String id) {
return getFunction("getCallerId", ret(id));
return getFunction("getCallerId", ret('"'+id+'"'));
}
public String getFetchCallerFunction() {
// for now assume the other functions already added...
return "async " + getFunction("getCaller", getFetchBody());
return "function getCaller() {\r\n"
+ " try {\r\n"
+ " var url = getEndpoint() + '/' + getCallerId(); \r\n"
+ " var urlObj = new java.net.URL(url);\r\n"
+ " var connection = urlObj.openConnection();\r\n"
+ " \r\n"
+ " connection.setRequestMethod('GET');\r\n"
+ " connection.setRequestProperty('Content-Type', 'application/json; charset=utf-8');\r\n"
+ "\r\n"
+ " var inputStream = new java.io.BufferedReader(new java.io.InputStreamReader(connection.getInputStream()));\r\n"
+ " var response = new java.lang.StringBuilder();\r\n"
+ " var inputLine;\r\n"
+ "\r\n"
+ " while ((inputLine = inputStream.readLine()) !== null) {\r\n"
+ " response.append(inputLine);\r\n"
+ " }\r\n"
+ " inputStream.close();\r\n"
+ "\r\n"
+ " return response.toString();\r\n"
+ " } catch (e) {\r\n"
+ " log(e);\r\n"
+ " return null; // Handle the error in your application logic\r\n"
+ " }\r\n"
+ "}\r\n";
}
public String getFetchCallerFunction_k() {
// for now assume the other functions already added...
return "function getCaller() {\r\n"
+ " var url = new java.net.URL('http://localhost:8080/' +getEndpoint() + '/' + getCallerId()); \r\n"
+ "\r\n"
+ " var httpRequest = url.openConnection();\r\n"
+ " httpRequest.setRequestMethod('GET');\r\n"
+ " httpRequest.setRequestProperty('Content-Type', 'application/json; charset=utf-8');\r\n"
+ "\r\n"
+ " var inputStream = new java.io.BufferedReader(new java.io.InputStreamReader(httpRequest.getInputStream()));\r\n"
+ " var response = new java.lang.StringBuilder();\r\n"
+ " var inputLine;\r\n"
+ "\r\n"
+ " while ((inputLine = inputStream.readLine()) !== null) {\r\n"
+ " response.append(inputLine);\r\n"
+ " }\r\n"
+ " inputStream.close();\r\n"
+ "\r\n"
+ " return response.toString();\r\n"
+ "}";
}
private static String getFetchBody() {
StringBuilder body = new StringBuilder();
body.append("const response = await fetch( getEndpoint() );\n");
body.append("cost data = await response.json();\n");
body.append("const data = await response.json();\n");
return trycatch(body.toString() + ret("data"));
}
@ -32,7 +78,7 @@ public class JsGenerationService {
functionCode.append("function " + name);
functionCode.append("(" + String.join(", ", args ) +")");
functionCode.append("{" + body +"}");
return functionCode.toString();
return functionCode.toString() + ";";
}
private static String ret(String var) {
@ -42,7 +88,7 @@ public class JsGenerationService {
private static String trycatch ( String tryBody, String catchBody, String finallyBody) {
StringBuilder builder = new StringBuilder();
builder.append(" try { " + tryBody + " } ");
builder.append((" catch (error) { " + catchBody) == null ? "console.error(error);" : catchBody+ " } ");
builder.append(" catch (error) { " + ((catchBody == null) ? "console.error(error);" : catchBody)+ " } ");
if ( finallyBody != null) {
builder.append("finally { " + finallyBody + " } ");
}
@ -56,4 +102,28 @@ public class JsGenerationService {
private static String trycatch ( String tryBody) {
return trycatch(tryBody, null);
}
public static String loggerFkt() {
return "function log(message) {\r\n"
+ " var url = new java.net.URL('http://localhost:8080/api/js/log');\r\n"
+ " var httpRequest = url.openConnection();\r\n"
+ " \r\n"
+ " httpRequest.setRequestMethod('POST');\r\n"
+ " httpRequest.setRequestProperty('Content-Type', 'application/json; charset=utf-8');\r\n"
+ " httpRequest.setDoOutput(true);\r\n"
+ "\r\n"
+ " var outputStream = new java.io.DataOutputStream(httpRequest.getOutputStream());\r\n"
+ " outputStream.writeBytes(JSON.stringify({ message: message }));\r\n"
+ " outputStream.flush();\r\n"
+ " outputStream.close();\r\n"
+ "\r\n"
+ " var responseCode = httpRequest.getResponseCode();\r\n"
+ " if (responseCode === java.net.HttpURLConnection.HTTP_OK) {\r\n"
+ " print('Message sent successfully!');\r\n"
+ " } else {\r\n"
+ " print('Error sending message. Response code: ' + responseCode);\r\n"
+ " }\r\n"
+ "}\r\n";
}
}

View File

@ -45,6 +45,7 @@ public class TaskController {
@GetMapping(path="/{id}")
public TaskResponse getTaskById(@PathVariable String id) {
log.info("Got get request for {}", id);
return service.getById(id);
}
@ -60,6 +61,7 @@ public class TaskController {
return service.updateState(id, taskRequest);
}
@PostMapping(path="{id}/activate")
@PatchMapping(path="{id}/activate")
public TaskResponse setActivate(@PathVariable String id, @Valid @RequestBody EditorData editor) {
return service.activateTask(id, editor);

View File

@ -19,12 +19,12 @@ public class TaskMapper {
private final TaskRepository repo;
// TODO: Do it yourseltf?!, or at least test
@Mapping(target="beforeTaskId", source="beforeTask.id")
@Mapping(target="afterTaskId", source="afterTask.id")
@Mapping(target="previousTaskId", source="previousTask.id")
@Mapping(target="followingTaskId", source="followingTask.id")
public TaskResponse taskToResponse(Task task) {
return TaskResponse.builder().data(task.getData())
.afterTaskId(task.getAfterTask() == null ? null : task.getAfterTask().getId())
.beforeTaskId(task.getBeforeTask() == null ? null : task.getBeforeTask().getId())
.followingTaskId(task.getFollowingTask() == null ? null : task.getFollowingTask().getId())
.previousTaskId(task.getPreviousTask() == null ? null : task.getPreviousTask().getId())
.state(task.getState())
.id(task.getId())
// .editor(task.getEditor() == null ? null : task.getEditor().getEditor())
@ -33,8 +33,8 @@ public class TaskMapper {
public Task requestToTask(TaskRequest taskRequest) {
Task task = Task.builder().data(taskRequest.getData())
.afterTask(taskRequest.getAfterTaskId() == null ? null : repo.findById(taskRequest.getAfterTaskId()).orElseThrow())
.beforeTask(taskRequest.getBeforeTaskId() == null ? null : repo.findById(taskRequest.getBeforeTaskId()).orElseThrow())
.followingTask(taskRequest.getFollowingTaskId() == null ? null : repo.findById(taskRequest.getFollowingTaskId()).orElseThrow())
.previousTask(taskRequest.getPreviousTaskId() == null ? null : repo.findById(taskRequest.getPreviousTaskId()).orElseThrow())
.state(taskRequest.getState())
.editor(taskRequest.getEditor() == null
? SystemEditor.addSystemChanges()
@ -48,11 +48,11 @@ public class TaskMapper {
}
public void updateTaskFromRequest(TaskRequest request, Task task) {
if(request.getAfterTaskId() != null) {
task.setAfterTask(repo.findById(request.getAfterTaskId()).orElseThrow());
if(request.getFollowingTaskId() != null) {
task.setFollowingTask(repo.findById(request.getFollowingTaskId()).orElseThrow());
}
if(request.getBeforeTaskId() != null) {
task.setBeforeTask(repo.findById(request.getBeforeTaskId()).orElseThrow());
if(request.getPreviousTaskId() != null) {
task.setPreviousTask(repo.findById(request.getPreviousTaskId()).orElseThrow());
}
if(request.getParentId() != null) {
task.setParentId(request.getParentId());

View File

@ -28,7 +28,7 @@ public class TaskRequest {
private Date lastEdited;
private String beforeTaskId;
private String afterTaskId;
private String previousTaskId;
private String followingTaskId;
private String parentId;
}

View File

@ -17,8 +17,8 @@ public class TaskResponse {
private String id;
private TaskState state;
private String beforeTaskId;
private String afterTaskId;
private String previousTaskId;
private String followingTaskId;
private String parentId;
private Map<String, String> data;

View File

@ -35,9 +35,9 @@ public class Task {
@DBRef(lazy = true)
private Task beforeTask;
private Task previousTask;
@DBRef(lazy = true)
private Task afterTask;
private Task followingTask;
private String parentId;

View File

@ -26,11 +26,16 @@ public class JsExecutionService {
}
private void executeScript(Task task, String s) {
log.info("start script. task is following");
// log.info(task.toString());
log.info(s);
JsScriptDto body = JsScriptDto.builder()
.callerId(task.getId())
.script(s).build();
log.info("body" + body.toString());
webClientBuilder.build().post()
.uri("http://localhost:8080/api/js")
.bodyValue(JsScriptDto.builder()
.callerId(task.getId())
.script(s))
.bodyValue(body)
.retrieve()
.bodyToMono(Object.class)
.doOnNext(res->log.info(String.format("Successfully run scrip %s", res) ))

View File

@ -7,7 +7,6 @@ import org.springframework.stereotype.Service;
import com.indu.taskservice.dto.EditorData;
import com.indu.taskservice.dto.TaskStateRequest;
import com.indu.taskservice.exception.StateTransitionNotAllowedException;
import com.indu.taskservice.exception.TaskChangedException;
import com.indu.taskservice.model.AbortAction;
import com.indu.taskservice.model.Task;
import com.indu.taskservice.model.TaskState;
@ -28,9 +27,10 @@ public class TaskFlowService {
public Task updateState(String id, @Valid TaskStateRequest taskRequest) {
Task task = taskService.getTaskById(id);
if (!task.getEditor().getLastEdited().equals(taskRequest.getEditor().getLastEdited())) {
throw new TaskChangedException();
}
//TODO
// if (!task.getEditor().getLastEdited().equals(taskRequest.getEditor().getLastEdited())) {
// throw new TaskChangedException();
// }
log.info(task.getState().name());
if (!TaskStateTransitionValidator.isTransitionAllowed(task.getState(), taskRequest.getState())) {
throw new StateTransitionNotAllowedException(task.getState(), taskRequest.getState());
@ -72,10 +72,10 @@ public class TaskFlowService {
private boolean previousTasksDone(Task task) {
// TDB: would it be enough to check just the last one? this way one could in one workflow set different values for checkprevious
if( (task.getBeforeTask() != null) && !task.getBeforeTask().getState().equals(TaskState.DONE)) {
if( (task.getPreviousTask() != null) && !task.getPreviousTask().getState().equals(TaskState.DONE)) {
return false;
}
return (task.getBeforeTask() == null) || previousTasksDone( task.getBeforeTask());
return (task.getPreviousTask() == null) || previousTasksDone( task.getPreviousTask());
}
public void activateTask(Task task) {
@ -113,8 +113,8 @@ public class TaskFlowService {
// step back
task.setState(TaskState.WAITING);
repository.save(task);
if ( task.getBeforeTask() != null) {
activateTask(task.getBeforeTask());
if ( task.getPreviousTask() != null) {
activateTask(task.getPreviousTask());
} else {
// Errorohandling, notify workflow resp.
}
@ -123,12 +123,12 @@ public class TaskFlowService {
}
private void abortAllFollowing(Task task) {
Task nextTask = task.getAfterTask();
Task nextTask = task.getFollowingTask();
while(nextTask != null) {
// wont call set abort bc. we dont know whats the abort action there woud be. this workflow "path" should (for now) just abort all following
nextTask.setState(TaskState.ABORT);
repository.save(nextTask);
nextTask = nextTask.getAfterTask();
nextTask = nextTask.getFollowingTask();
}
notifyWorkflowAboutAbort();
}
@ -148,18 +148,18 @@ public class TaskFlowService {
if (!task.validateData()) {
throw new StateTransitionNotAllowedException("Data did not validate");
}
if (task.getAfterTask() != null) {
activateTask(task.getAfterTask());
if (task.getFollowingTask() != null) {
activateTask(task.getFollowingTask());
} else { // TODO: notify workflow
}
}
public void setReady(Task task) {
if (task.getTemplate().isCheckPreviousBeforeActivate()
&& !task.getBeforeTask().getState().equals(TaskState.DONE)) {
&& !task.getPreviousTask().getState().equals(TaskState.DONE)) {
throw new StateTransitionNotAllowedException("Cannot set state to DONE for " + task.getTemplate().getName()
+ " bc bf task " + task.getBeforeTask().getTemplate().getName() + " has state "
+ task.getBeforeTask().getState().name());
+ " bc bf task " + task.getPreviousTask().getTemplate().getName() + " has state "
+ task.getPreviousTask().getState().name());
}
}
}

View File

@ -102,14 +102,14 @@ public class TaskService {
public Task setFollowUpTaskId(String id, String followUpTaskId) {
Task task = getById(id);
task.setAfterTask(getById(followUpTaskId));
task.setFollowingTask(getById(followUpTaskId));
repository.save(task);
return task;
}
public Task setPreviousTask(String id, String previousId) {
Task task = getById(id);
task.setBeforeTask(getById(previousId));
task.setPreviousTask(getById(previousId));
repository.save(task);
return task;
}

View File

@ -0,0 +1,22 @@
package com.indu.workflowservice.dto;
import java.util.Date;
import com.indu.workflowservice.model.User;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class EditorData {
@NotNull
private User editor;
@NotNull
private Date lastEdited;
}

View File

@ -19,8 +19,8 @@ public class TaskDto {
private TaskState state;
private Map<String, String> data;
private String beforeTaskId;
private String afterTaskId;
private String previousTaskId;
private String followingTaskId;
private String parentId;
}

View File

@ -0,0 +1,17 @@
package com.indu.workflowservice.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class User {
private String firstName;
private String lastName;
private String id;
}

View File

@ -1,9 +1,13 @@
package com.indu.workflowservice.repository;
import java.util.Date;
import org.springframework.stereotype.Repository;
import org.springframework.web.reactive.function.client.WebClient;
import com.indu.workflowservice.dto.EditorData;
import com.indu.workflowservice.dto.TaskDto;
import com.indu.workflowservice.model.User;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -60,6 +64,11 @@ public class TaskRepository {
webClientBuilder.build()
.post()
.uri("http://localhost:8080/api/tasks" , uri-> uri.pathSegment(id, "activate").build())
.bodyValue(
EditorData.builder().lastEdited(new Date()).editor(
User.builder().lastName("system").firstName("system").id("---").build()
).build()
)
.retrieve()
.bodyToMono(TaskDto.class)
.doOnNext(res->log.info(String.format("Successfully updated %s.", id) ))

View File

@ -26,10 +26,10 @@ public class TaskService {
*/
public void addReferences(TaskDto previousTask, TaskDto followUpTask) {
// TODO: maybe add endpoint of parent, this way one could use "workflow-service" as some kind of interface and change this
previousTask.setAfterTaskId(followUpTask.getId());
previousTask.setFollowingTaskId(followUpTask.getId());
taskRepository.update(previousTask);
followUpTask.setBeforeTaskId(previousTask.getId());
followUpTask.setPreviousTaskId(previousTask.getId());
taskRepository.update(followUpTask);
}