diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..e0f15db
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "java.configuration.updateBuildConfiguration": "automatic"
+}
\ No newline at end of file
diff --git a/src/services/examples/backend.json b/src/services/examples/backend.json
new file mode 100644
index 0000000..b35e42f
--- /dev/null
+++ b/src/services/examples/backend.json
@@ -0,0 +1,25 @@
+{
+ "type": "java",
+ "endpoints": {
+ "http": {
+ "/cart/checkout": ["sleep,1000", "sql://backend-db/?query=SELECT * from carts", ["http://ext-payment-1/pay", "http://ext-payment-2/pay"]],
+ "/cart/add": ["sql://backend-db/?query=SELECT * from carts"],
+ "error": [{"call": "error,500,Cart not found", "probability": 0.5}],
+ "/data": ["sleep,50", "log,warn,do some logging", "log,more logging", {
+ "call": "data",
+ "id": "price",
+ "type": "double",
+ "value": [1,2,3]
+ }, {
+ "call": "data",
+ "id": "randomThingy",
+ "chance": "integer,min:5,max:13"
+ }, {
+ "call": "data",
+ "id": "stuff",
+ "chance": "letter,casing:upper"
+ }]
+ }
+ },
+ "name": "backend"
+}
diff --git a/src/services/examples/database.json b/src/services/examples/database.json
new file mode 100644
index 0000000..0b7dcbd
--- /dev/null
+++ b/src/services/examples/database.json
@@ -0,0 +1 @@
+{"type":"mysql","databases":{"shop":{"carts":["id","name","value"],"customers":["id","name","email"]}},"name":"backend-db"}
diff --git a/src/services/examples/frontend.json b/src/services/examples/frontend.json
new file mode 100644
index 0000000..ff506d4
--- /dev/null
+++ b/src/services/examples/frontend.json
@@ -0,0 +1,41 @@
+{
+ "type": "nodejs",
+ "options": {
+ "connectionDelay": 500,
+ "httpLibrary": "request-promise"
+ },
+ "endpoints": {
+ "http": {
+ "/logo.png": ["sleep,1000"],
+ "/script.js": ["sleep,5000"],
+ "/checkout": ["http://backend/cart/checkout", "image,logo.png", "script,script.js","ajax,cache"],
+ "/addtocart": ["http://backend/cart/add", "slow,1500"],
+ "/cache": [["sleep,5000","sleep,5"], "cache,1024"],
+ "/schedule": ["sleep,50", {
+ "call": "error,500,oops",
+ "schedule": "0-30 * * * * * *"
+ }],
+ "/test": [{
+ "call": "http://localhost:9876/cache",
+ "catchExceptions": false,
+ "remoteTimeout": 2000
+ }],
+ "/data": ["sleep,50", "log,warn,do some logging", "log,more logging", {
+ "call": "data",
+ "id": "price",
+ "type": "double",
+ "value": [1,2,3]
+ }, {
+ "call": "data",
+ "id": "randomThingy",
+ "chance": "integer,min:5,max:13"
+ }, {
+ "call": "data",
+ "id": "stuff",
+ "chance": "letter,casing:upper"
+ }],
+ "/script": ["code,sample.js"]
+ }
+ },
+ "name": "frontend"
+}
diff --git a/src/services/java/.gitignore b/src/services/java/.gitignore
new file mode 100644
index 0000000..8a33059
--- /dev/null
+++ b/src/services/java/.gitignore
@@ -0,0 +1,7 @@
+.idea
+*.class
+target/maven-*
+extras/*.jar
+target/
+node.log
+javanode.iml
\ No newline at end of file
diff --git a/src/services/java/Dockerfile b/src/services/java/Dockerfile
new file mode 100644
index 0000000..8aeb48d
--- /dev/null
+++ b/src/services/java/Dockerfile
@@ -0,0 +1,15 @@
+FROM maven:3-jdk-11 AS build
+WORKDIR /home/app
+COPY src /home/app/src
+COPY pom.xml /home/app
+RUN mvn -f /home/app/pom.xml clean package
+
+FROM openjdk:11-jre
+RUN mkdir -p /app/dependency-jars
+WORKDIR /app
+COPY --from=build /home/app/target /app/
+COPY entrypoint.sh /entrypoint.sh
+RUN chmod +x /entrypointk8s.sh
+COPY extras/* /app/
+EXPOSE 8080
+CMD ["/entrypoint.sh"]
diff --git a/src/services/java/entrypoint.sh b/src/services/java/entrypoint.sh
new file mode 100755
index 0000000..08bb671
--- /dev/null
+++ b/src/services/java/entrypoint.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+echo java -jar "javanode-1.0-SNAPSHOT.jar" 8080
+APP_CONFIG="$(
+
+ 4.0.0
+ com.cisco.open.appsimulator.java
+ javanode
+ jar
+ 1.0-SNAPSHOT
+ javanode
+
+ 11.0.0.beta3
+
+
+
+ jakarta.servlet
+ jakarta.servlet-api
+ 5.0.0
+
+
+ mysql
+ mysql-connector-java
+ 5.1.49
+
+
+ org.mongodb
+ mongo-java-driver
+ 3.12.7
+
+
+ org.eclipse.jetty
+ jetty-server
+ ${jettyVersion}
+
+
+ org.eclipse.jetty
+ jetty-servlet
+ ${jettyVersion}
+
+
+ org.eclipse.jetty
+ jetty-client
+ ${jettyVersion}
+
+
+ javax.json
+ javax.json-api
+ 1.1
+
+
+ org.glassfish
+ javax.json
+ 1.1
+
+
+ net.sf.ehcache
+ ehcache
+ 2.10.5
+
+
+ org.slf4j
+ slf4j-api
+ 2.0.16
+
+
+ ch.qos.logback
+ logback-classic
+ 1.5.12
+
+
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ 1.1
+
+
+
+ java
+
+
+
+
+ com.cisco.open.appsimulator.JavaNode
+
+ 8080
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 3.1.0
+
+
+
+ true
+ com.cisco.open.appsimulator.JavaNode
+ dependency-jars/
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+ 2.5.1
+
+
+ copy-dependencies
+ package
+
+ copy-dependencies
+
+
+
+ runtime
+ ${project.build.directory}/dependency-jars/
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.8.0
+
+ 11
+
+
+
+
+
diff --git a/src/services/java/run.sh b/src/services/java/run.sh
new file mode 100755
index 0000000..5bbba48
--- /dev/null
+++ b/src/services/java/run.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+env APP_CONFIG="$(<../examples/backend.json)" mvn -e compile exec:java
diff --git a/src/services/java/src/main/java/com/cisco/open/appsimulator/HttpException.java b/src/services/java/src/main/java/com/cisco/open/appsimulator/HttpException.java
new file mode 100644
index 0000000..14d4cbc
--- /dev/null
+++ b/src/services/java/src/main/java/com/cisco/open/appsimulator/HttpException.java
@@ -0,0 +1,17 @@
+package com.cisco.open.appsimulator;
+
+import java.io.IOException;
+
+public class HttpException extends IOException
+{
+ protected int code;
+
+ public HttpException(int code, String message) {
+ super(message);
+ this.code = code;
+ }
+
+ public int getCode() {
+ return this.code;
+ }
+}
diff --git a/src/services/java/src/main/java/com/cisco/open/appsimulator/JavaNode.java b/src/services/java/src/main/java/com/cisco/open/appsimulator/JavaNode.java
new file mode 100644
index 0000000..60ed9b7
--- /dev/null
+++ b/src/services/java/src/main/java/com/cisco/open/appsimulator/JavaNode.java
@@ -0,0 +1,337 @@
+package com.cisco.open.appsimulator;
+
+import net.sf.ehcache.Element;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.servlet.ServletHandler;
+
+import javax.json.*;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.net.HttpURLConnection;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.concurrent.ThreadLocalRandom;
+
+import net.sf.ehcache.Cache;
+import net.sf.ehcache.CacheManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.core.util.StatusPrinter;
+
+public class JavaNode {
+
+ protected static Cache cache;
+
+ private static final Logger logger = LoggerFactory.getLogger(JavaNode.class);
+
+ public static void main(String[] args) throws Exception {
+
+ int port = 8080;
+ //CacheManager cacheManager;
+
+ if (args.length > 0) {
+ port = Integer.parseInt(args[0]);
+ }
+
+ LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
+ StatusPrinter.print(loggerContext);
+
+ logger.info("Reading AppConfig");
+ String envAppConfig = System.getenv("APP_CONFIG");
+ logger.info("Using config {}", envAppConfig);
+ if (envAppConfig == null) {
+ envAppConfig = "{\"endpoints\":{\"http\":{}}}";
+ }
+
+ JsonReader jsonReader = Json.createReader(new StringReader(envAppConfig));
+ JsonObject config = jsonReader.readObject();
+
+ CacheManager cm = CacheManager.getInstance();
+
+ cm.addCache("cache1");
+
+ cache = cm.getCache("cache1");
+
+ logger.info("Starting Server on port {}", port);
+ Server server = new Server(port);
+ ServletHandler handler = new ServletHandler();
+ server.setHandler(handler);
+ logger.info("NodeServlet setConfig");
+ NodeServlet.setConfig(config);
+ logger.info("add Servlet mapping");
+ handler.addServletWithMapping(NodeServlet.class, "/*");
+
+ server.start();
+ logger.info("Join server");
+ server.join();
+ }
+
+ // @SuppressWarnings("serial")
+ public static class NodeServlet extends HttpServlet {
+ protected static JsonObject config;
+ protected static JsonObject endpoints;
+
+ public static void setConfig(JsonObject config) {
+ NodeServlet.config = config;
+ NodeServlet.endpoints = config.getJsonObject("endpoints").getJsonObject("http");
+ logger.info("Config: {}", config.toString());
+ logger.info("Config: {}", config.getJsonObject("endpoints").getJsonObject("http").toString());
+ }
+
+ protected String buildResponse(int timeout) {
+ long start = System.currentTimeMillis();
+ long finish = start;
+ String response = "";
+ while (finish - start < timeout) {
+ response += " ";
+ finish = System.currentTimeMillis();
+ }
+ return response.length() + "slow response";
+ }
+
+ protected String loadFromCache(int timeout) {
+ long start = System.currentTimeMillis();
+ long finish = start;
+ int i = 0;
+ Integer element = Integer.valueOf(0);
+ while (finish - start < timeout) {
+ i++;
+ element = Integer.valueOf(i);
+ cache.putIfAbsent(new Element(element, i));
+ finish = System.currentTimeMillis();
+ }
+ return "Cache result: " + cache.get(element).toString();
+ }
+
+ protected String queryDatabase(String call, boolean catchExceptions, int remoteTimeout)
+ throws IOException {
+ try {
+ String url = "jdbc:my" + call.split("\\?")[0] + "?useSSL=false";
+ Connection connection = DriverManager.getConnection(url, "root", "root");
+
+ PreparedStatement stmt = connection.prepareStatement(call.split("\\?query=")[1]);
+ stmt.execute();
+
+ connection.close();
+ } catch (SQLException e) {
+ if (catchExceptions) {
+ return e.getMessage();
+ }
+ throw new IOException(e.getMessage());
+ }
+ return "Database query executed: " + call;
+ }
+
+ protected String callRemote(String call, boolean catchExceptions, int remoteTimeout)
+ throws IOException {
+ try {
+ URL url = new URL(call);
+ // return new Scanner( url.openStream() ).useDelimiter( "\\Z" ).next();
+ HttpURLConnection con = (HttpURLConnection) url.openConnection();
+
+ con.setConnectTimeout(remoteTimeout);
+ con.setReadTimeout(remoteTimeout);
+ con.setRequestProperty("Content-Type", "application/json");
+
+ BufferedReader in = new BufferedReader(
+ new InputStreamReader(con.getInputStream()));
+ String inputLine;
+ StringBuffer response = new StringBuffer();
+
+ while ((inputLine = in.readLine()) != null) {
+ response.append(inputLine);
+ }
+ in.close();
+
+ return response.toString();
+
+ } catch (IOException e) {
+ if (catchExceptions) {
+ return e.getMessage();
+ }
+ throw e;
+ }
+ }
+
+ protected String logMessage(String level, String msg) {
+ switch (level) {
+ case "warn":
+ logger.warn(msg);
+ break;
+ case "error":
+ logger.error(msg);
+ break;
+ case "debug":
+ logger.debug(msg);
+ break;
+ case "trace":
+ logger.trace(msg);
+ break;
+ default:
+ logger.info(msg);
+ }
+ return "Logged (" + level + "): " + msg;
+ }
+
+ protected String processCall(String call, boolean catchExceptions, int remoteTimeout)
+ throws IOException {
+ logger.info("Processing call: {}", call);
+ if (call.startsWith("sleep")) {
+ int timeout = Integer.parseInt(call.split(",")[1].trim());
+ try {
+ Thread.sleep(timeout);
+ } catch (InterruptedException e) {
+
+ }
+ return "Slept for " + timeout;
+ }
+
+ if (call.startsWith("log")) {
+ String[] log = call.split(",");
+ if (log.length > 2) {
+ return this.logMessage(log[1], log[2]);
+ }
+ return this.logMessage("info", log[1]);
+ }
+
+ if (call.startsWith("slow")) {
+ int timeout = Integer.parseInt(call.split(",")[1]);
+ return this.buildResponse(timeout);
+ }
+
+ if (call.startsWith("cache")) {
+ int timeout = Integer.parseInt(call.split(",")[1]);
+ return this.loadFromCache(timeout);
+ }
+
+ if (call.startsWith("http://")) {
+ return this.callRemote(call, catchExceptions, remoteTimeout);
+ }
+ if (call.startsWith("sql://")) {
+ return this.queryDatabase(call, catchExceptions, remoteTimeout);
+ }
+ if (call.startsWith("error")) {
+ throw new HttpException(500, "error");
+ }
+ if (call.startsWith("image")) {
+ String src = call.split(",")[1];
+ return "
";
+ }
+ if (call.startsWith("script")) {
+ String src = call.split(",")[1];
+ return "";
+ }
+ if (call.startsWith("ajax")) {
+ String src = call.split(",")[1];
+ return "";
+ }
+ return ":" + call + " is not supported";
+ }
+
+ protected String processData(JsonObject data) {
+ return "Data not processed: not implemented";
+ }
+
+ protected String preProcessCall(JsonValue call) throws IOException {
+
+ boolean catchExceptions = true;
+ int remoteTimeout = Integer.MAX_VALUE;
+
+ if (call.getValueType() == JsonValue.ValueType.ARRAY) {
+ JsonArray arr = (JsonArray) call;
+ int index = ThreadLocalRandom.current().nextInt(arr.size());
+ call = arr.get(index);
+ }
+ if (call.getValueType() == JsonValue.ValueType.OBJECT) {
+ JsonObject obj = (JsonObject) call;
+ call = obj.getJsonString("call");
+
+ if (((JsonString) call).getString().startsWith("data")) {
+ return this.processData(obj);
+ }
+
+ if (obj.containsKey("probability")) {
+ double probability = obj.getJsonNumber("probability").doubleValue();
+ if (probability * 100 < ThreadLocalRandom.current().nextInt(100)) {
+ return call + " was not probable";
+ }
+ }
+ if (obj.containsKey("catchExceptions")) {
+ catchExceptions = obj.getBoolean("catchExceptions");
+ }
+ if (obj.containsKey("remoteTimeout")) {
+ remoteTimeout = obj.getInt("remoteTimeout");
+ }
+ }
+ return this.processCall(((JsonString) call).getString(), catchExceptions, remoteTimeout);
+ }
+
+ public void handleEndpoint(HttpServletResponse response, JsonArray endpoint) throws IOException {
+ response.setStatus(HttpServletResponse.SC_OK);
+
+ StringBuilder result = new StringBuilder();
+
+ for (JsonValue entry : endpoint) {
+ result.append(this.preProcessCall(entry));
+ }
+
+ response.getWriter().println(result);
+ }
+
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ String endpoint = request.getRequestURI();
+ logger.info("Endpoint: {}", endpoint);
+
+ String contentType = request.getContentType();
+
+ if ("application/json".equals(contentType)
+ || "javascript".equals(request.getParameter("output"))) {
+ response.setContentType(contentType);
+ } else if (contentType == null) {
+ response.setContentType("text/html;charset=utf-8");
+ }
+
+ if (request.getParameter("uniqueSessionId") != null) {
+ // not implemented
+ }
+
+ try {
+ if (NodeServlet.endpoints.containsKey(endpoint)) {
+ logger.info("handling endpoint {} now containsKey", endpoint);
+ this.handleEndpoint(response, NodeServlet.endpoints.getJsonArray(endpoint));
+ } else if (NodeServlet.endpoints.containsKey(endpoint.substring(1))) {
+ logger.info("handling endpoint {} now substring", endpoint);
+ this.handleEndpoint(response,
+ NodeServlet.endpoints.getJsonArray(endpoint.substring(1)));
+ } else {
+ logger.info("handling endpoint {} notfound", endpoint);
+ response.setStatus(HttpServletResponse.SC_NOT_FOUND);
+ response.getWriter().println(404);
+ }
+ } catch (HttpException e) {
+ response.setStatus(e.getCode());
+ response.getWriter().println(e.getMessage());
+ } catch (IOException e) {
+ response.setStatus(500);
+ response.getWriter().println(e.getMessage());
+ }
+ }
+
+ }
+
+}
diff --git a/src/services/java/src/main/resources/logback.xml b/src/services/java/src/main/resources/logback.xml
new file mode 100644
index 0000000..686aa46
--- /dev/null
+++ b/src/services/java/src/main/resources/logback.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+ %date{ISO8601} [%thread] %-5level %logger - %msg%n
+
+
+
+
+
+ ${LOG_DIRECTORY:-.}/node.log
+
+
+ %date{ISO8601} [%thread] %-5level %logger - %msg%n
+
+
+
+
+
+
+
+
+
\ No newline at end of file