From e44eb189fb451eec29a4b555153df79f482c75ed Mon Sep 17 00:00:00 2001
From: rschoene <rene.schoene@tu-dresden.de>
Date: Mon, 22 May 2023 16:36:26 +0200
Subject: [PATCH] WIP: restClient receiving for tokens.

- missing tests
- missing close
- missing disconnect
---
 pages/docs/extending.md                       |  6 +--
 .../src/main/jastadd/Analysis.jrag            |  2 +-
 .../src/main/jastadd/Handlers.jrag            |  1 +
 .../src/main/jastadd/Intermediate.jadd        | 26 +++++++++-
 .../jastadd/ragconnect/compiler/Compiler.java |  4 ++
 .../main/resources/RestClientHandler.mustache | 49 +++++++++++++++++++
 .../src/main/resources/handler.mustache       |  9 ++++
 .../main/resources/receiveDefinition.mustache | 16 ++++++
 .../main/resources/tokenComponent.mustache    |  5 ++
 9 files changed, 113 insertions(+), 5 deletions(-)
 create mode 100644 ragconnect.base/src/main/resources/RestClientHandler.mustache

diff --git a/pages/docs/extending.md b/pages/docs/extending.md
index 14fa8c8..4ed4ce1 100644
--- a/pages/docs/extending.md
+++ b/pages/docs/extending.md
@@ -5,9 +5,9 @@ To add a new communication protocol, the following locations have to be changed
 ### Within `ragconnect.base/src/main/resources`
 
 {% raw %}
-- Add a new handler `ABCHandler.jadd`, similar to the existing handlers. 
+- Add a new handler `ABCHandler.jadd`, similar to the existing handlers. A handler must have a constructor accepting a single String parameter, and must have a `close()` method cleaning up any held resources. 
 - In `handler.mustache`, add further methods if needed for handler usage in the application code (similar to `{{rootNodeName}}.{{SetupWaitUntilReadyMethodName}}` for `mqtt`)
-- In `receiveDefinition.mustache` and `sendDefinition.mustache`: add a new case in the switch statements defining the logic to happen upon connect and disconnect for both definitions. If the new protocol is close to a PUSH semantic, follow `mqtt`. If it is closer to PULL semantic, follow `rest`.
+- In `receiveDefinition.mustache` and `sendDefinition.mustache`: add a new case in the switch statements defining the logic to happen upon connect and disconnect for both definitions (that are four distinct locations). If the new protocol is close to a PUSH semantic, follow `mqtt`. If it is closer to PULL semantic, follow `restClient`.
 {% endraw %}
 
 ### Within `ragconnect.base/src/main/jastadd`
@@ -18,7 +18,7 @@ In `Handlers.jrag`: Add a new attribute `RagConnect.abcHandler()` returning the
 
 In `Compiler.java`:
 - Add a new choice for `--protocols` similar to the existing ones
-- Add a newly constructed handler in `setConfiguration`  with the needed fields (definition file name within `resources` directory, commonly `ABCHandler.jadd`; class name of the handler; unique name for the protocol; whether the handler is used, i.e., if it was given in `--protocols`)
+- Add a newly constructed handler in `setConfiguration`  with the needed fields (class name of the handler; unique name for the protocol (must be a valid Java identifier); whether the handler is used, i.e., if it was given in `--protocols`)
 
 Furthermore, new test cases are appreciated, see [below](#writing-tests).
 
diff --git a/ragconnect.base/src/main/jastadd/Analysis.jrag b/ragconnect.base/src/main/jastadd/Analysis.jrag
index e1ec0f2..407efa3 100644
--- a/ragconnect.base/src/main/jastadd/Analysis.jrag
+++ b/ragconnect.base/src/main/jastadd/Analysis.jrag
@@ -112,7 +112,7 @@ aspect Analysis {
     return !getDependencySourceDefinitionList().isEmpty() ||
               getTokenPortTargetList().stream()
                 .map(PortTarget::containingPortDefinition)
-                .anyMatch(PortDefinition::shouldNotResetValue);
+                .anyMatch(def -> def.shouldNotResetValue() || ragconnect().restClientHandler().getInUse());
   }
 
   // --- effectiveUsedAt ---
diff --git a/ragconnect.base/src/main/jastadd/Handlers.jrag b/ragconnect.base/src/main/jastadd/Handlers.jrag
index e64a8c5..b038c2a 100644
--- a/ragconnect.base/src/main/jastadd/Handlers.jrag
+++ b/ragconnect.base/src/main/jastadd/Handlers.jrag
@@ -2,6 +2,7 @@ aspect RagConnectHandlers {
   syn Handler RagConnect.javaHandler() = resolveHandlerByName("java");
   syn Handler RagConnect.mqttHandler() = resolveHandlerByName("mqtt");
   syn Handler RagConnect.restHandler() = resolveHandlerByName("rest");
+  syn Handler RagConnect.restClientHandler() = resolveHandlerByName("rest_client");
 
   private Handler RagConnect.resolveHandlerByName(String uniqueName) {
     for (Handler handler : getHandlerList()) {
diff --git a/ragconnect.base/src/main/jastadd/Intermediate.jadd b/ragconnect.base/src/main/jastadd/Intermediate.jadd
index 60fe493..feee69c 100644
--- a/ragconnect.base/src/main/jastadd/Intermediate.jadd
+++ b/ragconnect.base/src/main/jastadd/Intermediate.jadd
@@ -232,7 +232,8 @@ aspect MustacheMappingApplicationAndDefinition {
   eq MRelationSendDefinition.preemptiveReturn() = "return false;";
 
   eq MTokenReceiveDefinition.firstInputVarName() = "message";
-  eq MTokenReceiveDefinition.preemptiveExpectedValue() = getterMethodCall();
+  // if receiverName variable is set, use internal getter instead to avoid StackOverflow
+  eq MTokenReceiveDefinition.preemptiveExpectedValue() = "(" + getPortDefinition().receiverName() + " != null ? get" + getPortDefinition().token().internalName() + "() : " + getterMethodCall() + ")";
   eq MTokenReceiveDefinition.preemptiveReturn() = "return;";
 
   eq MTokenSendDefinition.firstInputVarName() = getterMethodCall();
@@ -525,6 +526,7 @@ aspect MustacheSendDefinition {
 
   syn boolean PortDefinition.relationPortWithListRole() = getPortTarget().relationPortWithListRole();
 
+  syn String PortDefinition.receiverName() = getPortTarget().receiverName();
   syn String PortDefinition.senderName() = getPortTarget().senderName();
 
   syn java.util.List<SendIncrementalObserverEntry> PortDefinition.sendIncrementalObserverEntries() {
@@ -583,6 +585,9 @@ containingPortDefinition().getIndexBasedListAccess());
   syn boolean PortTarget.relationPortWithListRole() = false;
   eq RelationPortTarget.relationPortWithListRole() = getRole().isListRole();
 
+  syn String PortTarget.receiverName() = ragconnect().internalRagConnectPrefix() + "_receiver_" + entityName();
+  eq ContextFreeTypePortTarget.receiverName() = null;
+
   syn String PortTarget.senderName() = ragconnect().internalRagConnectPrefix() + "_sender_" + entityName();
   eq ContextFreeTypePortTarget.senderName() = null;
 
@@ -645,6 +650,16 @@ aspect MustacheTokenComponent {
 
   syn String TokenComponent.javaType() = effectiveJavaTypeUse().prettyPrint();
 
+  syn PortDefinition TokenComponent.normalTokenReceiveDef() {
+    for (Component comp : meOwnedByOthers()) {
+      PortDefinition maybeResult = comp.asTokenComponent().directNormalTokenReceiveDef();
+      if (maybeResult != null) {
+        return maybeResult;
+      }
+    }
+    return directNormalTokenReceiveDef();
+  }
+
   syn PortDefinition TokenComponent.normalTokenSendDef() {
     for (Component comp : meOwnedByOthers()) {
       PortDefinition maybeResult = comp.asTokenComponent().directNormalTokenSendDef();
@@ -682,6 +697,15 @@ aspect MustacheTokenComponent {
   // > see MustacheSend for updateMethodName, writeMethodName
 
   // === attributes needed for computing above ones ===
+  syn PortDefinition TokenComponent.directNormalTokenReceiveDef() {
+    for (PortTarget target : getTokenPortTargetList()) {
+      if (target.isTokenPortTarget() && ragconnect().restClientHandler().getInUse()) {
+        return target.containingPortDefinition();
+      }
+    }
+    return null;
+  }
+
   syn PortDefinition TokenComponent.directNormalTokenSendDef() {
     for (PortTarget target : getTokenPortTargetList()) {
       if (target.isTokenPortTarget() && target.containingPortDefinition().shouldNotResetValue()) {
diff --git a/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java b/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java
index be81bfd..704e0ff 100644
--- a/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java
+++ b/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java
@@ -39,6 +39,7 @@ public class Compiler extends AbstractCompiler {
   private static final String OPTION_PROTOCOL_JAVA = "java";
   private static final String OPTION_PROTOCOL_MQTT = "mqtt";
   private static final String OPTION_PROTOCOL_REST = "rest";
+  private static final String OPTION_PROTOCOL_REST_CLIENT = "rest-client";
 
   public Compiler() {
     super("ragconnect", true);
@@ -183,6 +184,7 @@ public class Compiler extends AbstractCompiler {
             .addDefaultValue(OPTION_PROTOCOL_MQTT, "Enable MQTT")
             .addAcceptedValue(OPTION_PROTOCOL_JAVA, "Enable Java (experimental)")
             .addAcceptedValue(OPTION_PROTOCOL_REST, "Enable REST")
+            .addAcceptedValue(OPTION_PROTOCOL_REST_CLIENT, "Enable REST client (experimental)")
     );
     optionPrintYaml = addOption(
         new BooleanOption("printYaml", "Print out YAML instead of generating files and exit.")
@@ -341,6 +343,8 @@ public class Compiler extends AbstractCompiler {
     ragConnect.addHandler(new Handler("JavaHandler", "java", optionProtocols.hasValue(OPTION_PROTOCOL_JAVA)));
     ragConnect.addHandler(new Handler("MqttServerHandler", "mqtt", optionProtocols.hasValue(OPTION_PROTOCOL_MQTT)));
     ragConnect.addHandler(new Handler("RestServerHandler", "rest", optionProtocols.hasValue(OPTION_PROTOCOL_REST)));
+    ragConnect.addHandler(new Handler("RestClientServerHandler", "rest_client",
+            optionProtocols.hasValue(OPTION_PROTOCOL_REST_CLIENT)));
   }
 
   public String generateAspect(RagConnect ragConnect) {
diff --git a/ragconnect.base/src/main/resources/RestClientHandler.mustache b/ragconnect.base/src/main/resources/RestClientHandler.mustache
new file mode 100644
index 0000000..c6eb64b
--- /dev/null
+++ b/ragconnect.base/src/main/resources/RestClientHandler.mustache
@@ -0,0 +1,49 @@
+// dispatch depending on URI. might not be necessary
+public class RestClientServerHandler {
+  public RestClientServerHandler(String name) {
+    // TODO implement
+  }
+
+  RestClientHandler singleton = new RestClientHandler();
+  // TODO create handler based on URI
+  public RagConnectReceiver newReceiverFor(RagConnectToken connectToken, java.util.function.BiConsumer<String, byte[]> callback) {
+    return singleton.newReceiverFor(connectToken, callback);
+  }
+
+  public boolean disconnect(RagConnectToken connectToken) {
+    // TODO implement
+    return false;
+  }
+
+  public void close() {
+    // TODO implement
+  }
+}
+
+public class RestClientHandler {
+  public RagConnectReceiver newReceiverFor(RagConnectToken connectToken, java.util.function.BiConsumer<String, byte[]> callback) {
+    java.net.URI target = java.net.URI.create(connectToken.uri.toString().replaceFirst("restClient", "http"));
+    return () -> {
+      byte[] rawInput;
+      try {
+        rawInput = fetchFrom(target);
+      } catch (Exception e) {
+        {{logException}}("Exception when fetching from " + target, e);
+        return;
+      }
+      callback.accept("", rawInput);
+    };
+  }
+
+  byte[] fetchFrom(java.net.URI uri) throws java.io.IOException, InterruptedException {
+    // TODO the request should only be built once, and not every time the GET call is run
+    java.net.http.HttpRequest httpRequest = java.net.http.HttpRequest.newBuilder()
+            .uri(uri)
+            .GET()
+            .build();
+    java.net.http.HttpClient httpClient = java.net.http.HttpClient.newHttpClient();
+    java.net.http.HttpResponse<byte[]> response = httpClient.send(httpRequest,
+            java.net.http.HttpResponse.BodyHandlers.ofByteArray());
+    return response.body();
+  }
+}
diff --git a/ragconnect.base/src/main/resources/handler.mustache b/ragconnect.base/src/main/resources/handler.mustache
index 75bd14c..435a88c 100644
--- a/ragconnect.base/src/main/resources/handler.mustache
+++ b/ragconnect.base/src/main/resources/handler.mustache
@@ -46,6 +46,12 @@ aspect RagConnectHandler {
   {{/InUse}}
 {{/restHandler}}
 
+{{#restClientHandler}}
+  {{#InUse}}
+  {{> RestClientHandler}}
+  {{/InUse}}
+{{/restClientHandler}}
+
   class RagConnectToken {
     static java.util.concurrent.atomic.AtomicLong counter = new java.util.concurrent.atomic.AtomicLong(0);
     final long id;
@@ -191,4 +197,7 @@ aspect RagConnectHandler {
       java.util.Optional.ofNullable(publishers.get(index)).ifPresent(publisher -> publisher.setLastValue(value));
     }
   }
+
+  interface RagConnectReceiver extends Runnable {
+  }
 }
diff --git a/ragconnect.base/src/main/resources/receiveDefinition.mustache b/ragconnect.base/src/main/resources/receiveDefinition.mustache
index b2e05ce..42526d8 100644
--- a/ragconnect.base/src/main/resources/receiveDefinition.mustache
+++ b/ragconnect.base/src/main/resources/receiveDefinition.mustache
@@ -1,3 +1,5 @@
+private RagConnectReceiver {{parentTypeName}}.{{receiverName}} = null;
+
 {{#typeIsList}}
 {{#IndexBasedListAccess}}
 private int {{parentTypeName}}.{{resolveInListMethodName}}(String topic) {
@@ -123,6 +125,14 @@ private boolean {{parentTypeName}}.{{internalConnectMethodName}}(String {{connec
       break;
   {{/InUse}}
   {{/restHandler}}
+  {{#restClientHandler}}
+  {{#InUse}}
+    case "restClient":
+      {{receiverName}} = {{attributeName}}().newReceiverFor(connectToken, consumer);
+      success = {{receiverName}} != null;
+      break;
+  {{/InUse}}
+  {{/restClientHandler}}
     default:
       {{logError}}("Unknown protocol '{{log_}}'.", scheme);
       success = false;
@@ -161,6 +171,12 @@ public boolean {{parentTypeName}}.{{disconnectMethodName}}(String {{connectParam
     break;
   {{/InUse}}
   {{/restHandler}}
+  {{#restClientHandler}}
+  {{#InUse}}
+    case "rest": disconnectingMethod = {{attributeName}}()::disconnect;
+    break;
+  {{/InUse}}
+  {{/restClientHandler}}
     default:
       {{logError}}("Unknown protocol '{{log_}}' in '{{log_}}' for disconnecting {{parentTypeName}}.{{entityName}}",
         scheme, {{connectParameterName}});
diff --git a/ragconnect.base/src/main/resources/tokenComponent.mustache b/ragconnect.base/src/main/resources/tokenComponent.mustache
index 3d90c34..ee669d8 100644
--- a/ragconnect.base/src/main/resources/tokenComponent.mustache
+++ b/ragconnect.base/src/main/resources/tokenComponent.mustache
@@ -18,5 +18,10 @@ public {{parentTypeName}} {{parentTypeName}}.set{{Name}}({{javaType}} value) {
 }
 
 public {{javaType}} {{parentTypeName}}.get{{Name}}() {
+  {{#normalTokenReceiveDef}}
+  if ({{receiverName}} != null) {
+    {{receiverName}}.run();
+  }
+  {{/normalTokenReceiveDef}}
   return get{{internalName}}();
 }
-- 
GitLab