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