Skip to content
Snippets Groups Projects
Commit ca03befd authored by René Schöne's avatar René Schöne
Browse files

Merge branch '61-new-handler-rest-client' into 'dev'

Resolve "New Handler: REST client"

Closes #61

See merge request !38
parents 784cf03d 25c8e819
No related branches found
No related tags found
2 merge requests!39Version 1.1.0,!38Resolve "New Handler: REST client"
Pipeline #17176 passed
Pipeline: RagConnect Dev Pages

#17177

    Showing
    with 425 additions and 80 deletions
    ...@@ -22,7 +22,7 @@ build: ...@@ -22,7 +22,7 @@ build:
    image: openjdk:11 image: openjdk:11
    stage: build stage: build
    script: script:
    - ./gradlew --console=plain --no-daemon assemble jar - ./gradlew --console=plain --no-daemon assemble testClasses jar
    artifacts: artifacts:
    paths: paths:
    - "ragconnect.base/build/libs/ragconnect-*.jar" - "ragconnect.base/build/libs/ragconnect-*.jar"
    ......
    ...@@ -6,16 +6,16 @@ Additional options are as follows. ...@@ -6,16 +6,16 @@ Additional options are as follows.
    ## Table with available options ## Table with available options
    | Name | Required (Default) | Description | | Name | Required (Default) | Description |
    |---|---|---| |------------------------------|--------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
    | `--rootNode` | Yes | Root node in the base grammar. | | `--rootNode` | Yes | Root node in the base grammar. |
    | `--protocols` | No (`mqtt`) | Protocols to enable, currently available: `java` (experimental), `mqtt`, `rest`. | | `--protocols` | No (`mqtt`) | Protocols to enable, one of `java` (experimental), `mqtt`, `rest`, see [Handlers][handlers] for details. |
    | `--printYaml` | No (false) | Print out YAML instead of generating files. | | `--printYaml` | No (false) | Print out YAML instead of generating files. |
    | `--verbose` | No (false) | Print more messages while compiling. | | `--verbose` | No (false) | Print more messages while compiling. |
    | `--logReads` | No (false) | Enable logging for every received message. | | `--logReads` | No (false) | Enable logging for every received message. |
    | `--logWrites` | No (false) | Enable logging for every sent message. | | `--logWrites` | No (false) | Enable logging for every sent message. |
    | `--logIncremental` | No (false) | Enable logging for observer in incremental dependency tracking. | | `--logIncremental` | No (false) | Enable logging for observer in incremental dependency tracking. |
    | `--logTarget` | No (`console`) | Logging target to use, currently available: `console, slf4j`. | | `--logTarget` | No (`console`) | Logging target to use, currently available: `console, slf4j`. |
    | `--experimental-jastadd-329` | No (false) | Use tracing events `INC_FLUSH_START` and `INC_FLUSH_END` ([JastAdd issue #329][jastadd-issue-329]), see [section about automatic dependency tracking](using.md#dependency-tracking-automatically-derived). | | `--experimental-jastadd-329` | No (false) | Use tracing events `INC_FLUSH_START` and `INC_FLUSH_END` ([JastAdd issue #329][jastadd-issue-329]), see [section about automatic dependency tracking][automatic-dependency-tracking]. |
    | `--incremental` | No (false) | Enables incremental dependency tracking (if `tracing` is also set appropriately). | | `--incremental` | No (false) | Enables incremental dependency tracking (if `tracing` is also set appropriately). |
    | `--tracing[=flush]` | No (false) | Enables incremental dependency tracking (if `incremental` is also set appropriately). | | `--tracing[=flush]` | No (false) | Enables incremental dependency tracking (if `incremental` is also set appropriately). |
    | `--version` | No (false) | Print version info and exit (reused JastAdd option) | | `--version` | No (false) | Print version info and exit (reused JastAdd option) |
    ...@@ -29,44 +29,6 @@ Their type is deduced by the file extension (`ast` and `relast` for input gramma ...@@ -29,44 +29,6 @@ Their type is deduced by the file extension (`ast` and `relast` for input gramma
    Using RagConnect itself does not introduce dependencies. Using RagConnect itself does not introduce dependencies.
    However, depending on the selected protocols and/or used features, additional dependencies are required when using the generated code. However, depending on the selected protocols and/or used features, additional dependencies are required when using the generated code.
    ### Communication protocol characteristics
    #### Java
    - Protocol identifier: `java`
    - URI scheme: `java://<ignored-host>[:ignored-port]/<topic>`
    - the value for host and port are always ignored, but are necessary to form a legal URI
    - No required runtime dependencies
    - Additional remarks:
    - First leading slash not included in topic.
    - Currently, the default mappings are applied, which requires a consumer to expect `byte[]` (instead of a more intuitive token or node value). This might change in future versions.
    #### MQTT
    - Protocol identifier: `mqtt`
    - URI scheme: `mqtt://<broker-host>[:port]/<topic>`
    - Default port: 1883
    - Type for mapping definitions: `byte[]`
    - Required runtime dependencies:
    - `group: 'org.fusesource.mqtt-client', name: 'mqtt-client', version: '1.15'`
    - Additional remarks:
    - First leading slash not included in topic.
    - Mqtt is selected by default, so this dependency therefore is required "by default".
    - Might work with other versions of `org.fusesource.mqtt-client.mqtt.client` as well.
    #### REST
    - Protocol identifier: `rest`
    - URI scheme: `rest://localhost[:port]/<path>`
    - Default port: 4567
    - Type for mapping definitions: `String`
    - Required runtime dependencies:
    - `group: 'com.sparkjava', name: 'spark-core', version: '2.9.3'`
    - Additional remarks:
    - Host is always `localhost`.
    - Might work with newer versions of `com.sparkjava.spark-core` as well.
    - For debugging, it is beneficial to include an implementation for [SLF4J][slf4j].
    ### Used features ### Used features
    #### Automatic dependency tracking #### Automatic dependency tracking
    ...@@ -116,4 +78,5 @@ However, depending on the selected protocols and/or used features, additional de ...@@ -116,4 +78,5 @@ However, depending on the selected protocols and/or used features, additional de
    - Additionally, a slf4j binding is required, see [the slf4j user manual][slf4j] - Additionally, a slf4j binding is required, see [the slf4j user manual][slf4j]
    [jastadd-issue-329]: https://bitbucket.org/jastadd/jastadd2/issues/329/add-event-for-completion-of-flush [jastadd-issue-329]: https://bitbucket.org/jastadd/jastadd2/issues/329/add-event-for-completion-of-flush
    [slf4j]: https://www.slf4j.org/manual.html [automatic-dependency-tracking]: using.md#dependency-tracking-automatically-derived
    [handlers]: handlers.md
    ...@@ -5,9 +5,9 @@ To add a new communication protocol, the following locations have to be changed ...@@ -5,9 +5,9 @@ To add a new communication protocol, the following locations have to be changed
    ### Within `ragconnect.base/src/main/resources` ### Within `ragconnect.base/src/main/resources`
    {% raw %} {% 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 `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 %} {% endraw %}
    ### Within `ragconnect.base/src/main/jastadd` ### Within `ragconnect.base/src/main/jastadd`
    ...@@ -18,7 +18,7 @@ In `Handlers.jrag`: Add a new attribute `RagConnect.abcHandler()` returning the ...@@ -18,7 +18,7 @@ In `Handlers.jrag`: Add a new attribute `RagConnect.abcHandler()` returning the
    In `Compiler.java`: In `Compiler.java`:
    - Add a new choice for `--protocols` similar to the existing ones - 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). Furthermore, new test cases are appreciated, see [below](#writing-tests).
    ......
    # Communication Protocol Characteristics (Handlers)
    ## Java (experimental)
    Uses Java methods to supply values (receive) and for callbacks (send).
    - Protocol identifier: `java`
    - URI scheme: `java://<ignored-host>[:ignored-port]/<topic>`
    - the value for host and port are always ignored, but are necessary to form a legal URI
    - No required runtime dependencies
    - Receive behaviour: Use the generated method `ragconnectJavaPush` to pass a value to the receiving port.
    - Send behaviour: When the value to be sent changes, previously registered callbacks are invoked.
    - Additional remarks:
    - First leading slash not included in topic.
    - Currently, the default mappings are applied, which requires a consumer to expect `byte[]` (instead of a more intuitive token or node value). This might change in future versions.
    ## MQTT
    Use an MQTT broker to receive and send messages.
    - Protocol identifier: `mqtt`
    - URI scheme: `mqtt://<broker-host>[:port]/<topic>`
    - Default port: 1883
    - Type for mapping definitions: `byte[]`
    - Required runtime dependencies:
    - `group: 'org.fusesource.mqtt-client', name: 'mqtt-client', version: '1.15'`
    - Receive behaviour: Upon connection, instruct the MQTT broker to listen for messages on some topic and pass the value of those messages to the receiving port.
    - Send behaviour: When the value to be sent changes, publish a message to the topic specified upon connection.
    - Additional remarks:
    - First leading slash not included in topic.
    - Mqtt is selected by default, so this dependency therefore is required "by default".
    - Might work with other versions of `org.fusesource.mqtt-client.mqtt.client` as well.
    ## REST Server
    Create a new REST server with its own target routes.
    - Protocol identifier: `rest`
    - URI scheme: `rest://localhost[:port]/<path>`
    - Default port: 4567
    - Type for mapping definitions: `byte[]`
    - Required runtime dependencies:
    - `group: 'com.sparkjava', name: 'spark-core', version: '2.9.3'`
    - Receive behaviour: Upon connection, create a new PUT connection and pass the value of every call to this PUT route to the receiving port.
    - Send behaviour: Upon connection, create a new GET connection and serve the latest value at this GET route.
    - Additional remarks:
    - Host is always `localhost`.
    - Targets to be invoked need to replace `rest` with `http`
    - Might work with newer versions of `com.sparkjava.spark-core` as well.
    - For debugging, it is beneficial to include an implementation for [SLF4J][slf4j].
    ## REST Client
    Invoke REST routes to fetch and send values.
    - Protocol identifier: `restClient`
    - URI scheme: `restClient://localhost[:port]/<path>`
    - Default port: 80
    - Type for mapping definitions: `byte[]`
    - No required runtime dependencies
    - Receive behaviour: Whenever the accessor is called, a GET request fetches the latest data and returns this data.
    - Send behaviour: When the value to be sent changes, a PUT request sends this data.
    - Additional remarks:
    - Invoked target replaces `restClient` with `http`
    - **Important constraint**: Receiving ports are only supported for tokens, since they interrupt the getter method!
    [slf4j]: https://www.slf4j.org/manual.html
    ...@@ -7,6 +7,7 @@ nav: ...@@ -7,6 +7,7 @@ nav:
    - "Using RagConnect (by Example)": using.md - "Using RagConnect (by Example)": using.md
    - "RagConnect Specification Language": dsl.md - "RagConnect Specification Language": dsl.md
    - "Compiler options": compiler.md - "Compiler options": compiler.md
    - "Communication Protocols": handlers.md
    - "Use Cases": use_cases.md - "Use Cases": use_cases.md
    - "Inner workings": inner-workings.md - "Inner workings": inner-workings.md
    - "Evaluation Metrics: Lines of Code": cloc.md - "Evaluation Metrics: Lines of Code": cloc.md
    ......
    ...@@ -112,7 +112,7 @@ aspect Analysis { ...@@ -112,7 +112,7 @@ aspect Analysis {
    return !getDependencySourceDefinitionList().isEmpty() || return !getDependencySourceDefinitionList().isEmpty() ||
    getTokenPortTargetList().stream() getTokenPortTargetList().stream()
    .map(PortTarget::containingPortDefinition) .map(PortTarget::containingPortDefinition)
    .anyMatch(PortDefinition::shouldNotResetValue); .anyMatch(def -> def.shouldNotResetValue() || ragconnect().restClientHandler().getInUse());
    } }
    // --- effectiveUsedAt --- // --- effectiveUsedAt ---
    ......
    ...@@ -2,6 +2,7 @@ aspect RagConnectHandlers { ...@@ -2,6 +2,7 @@ aspect RagConnectHandlers {
    syn Handler RagConnect.javaHandler() = resolveHandlerByName("java"); syn Handler RagConnect.javaHandler() = resolveHandlerByName("java");
    syn Handler RagConnect.mqttHandler() = resolveHandlerByName("mqtt"); syn Handler RagConnect.mqttHandler() = resolveHandlerByName("mqtt");
    syn Handler RagConnect.restHandler() = resolveHandlerByName("rest"); syn Handler RagConnect.restHandler() = resolveHandlerByName("rest");
    syn Handler RagConnect.restClientHandler() = resolveHandlerByName("restClient");
    private Handler RagConnect.resolveHandlerByName(String uniqueName) { private Handler RagConnect.resolveHandlerByName(String uniqueName) {
    for (Handler handler : getHandlerList()) { for (Handler handler : getHandlerList()) {
    ......
    ...@@ -232,7 +232,8 @@ aspect MustacheMappingApplicationAndDefinition { ...@@ -232,7 +232,8 @@ aspect MustacheMappingApplicationAndDefinition {
    eq MRelationSendDefinition.preemptiveReturn() = "return false;"; eq MRelationSendDefinition.preemptiveReturn() = "return false;";
    eq MTokenReceiveDefinition.firstInputVarName() = "message"; 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 MTokenReceiveDefinition.preemptiveReturn() = "return;";
    eq MTokenSendDefinition.firstInputVarName() = getterMethodCall(); eq MTokenSendDefinition.firstInputVarName() = getterMethodCall();
    ...@@ -525,6 +526,7 @@ aspect MustacheSendDefinition { ...@@ -525,6 +526,7 @@ aspect MustacheSendDefinition {
    syn boolean PortDefinition.relationPortWithListRole() = getPortTarget().relationPortWithListRole(); syn boolean PortDefinition.relationPortWithListRole() = getPortTarget().relationPortWithListRole();
    syn String PortDefinition.receiverName() = getPortTarget().receiverName();
    syn String PortDefinition.senderName() = getPortTarget().senderName(); syn String PortDefinition.senderName() = getPortTarget().senderName();
    syn java.util.List<SendIncrementalObserverEntry> PortDefinition.sendIncrementalObserverEntries() { syn java.util.List<SendIncrementalObserverEntry> PortDefinition.sendIncrementalObserverEntries() {
    ...@@ -583,6 +585,9 @@ containingPortDefinition().getIndexBasedListAccess()); ...@@ -583,6 +585,9 @@ containingPortDefinition().getIndexBasedListAccess());
    syn boolean PortTarget.relationPortWithListRole() = false; syn boolean PortTarget.relationPortWithListRole() = false;
    eq RelationPortTarget.relationPortWithListRole() = getRole().isListRole(); 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(); syn String PortTarget.senderName() = ragconnect().internalRagConnectPrefix() + "_sender_" + entityName();
    eq ContextFreeTypePortTarget.senderName() = null; eq ContextFreeTypePortTarget.senderName() = null;
    ...@@ -645,6 +650,16 @@ aspect MustacheTokenComponent { ...@@ -645,6 +650,16 @@ aspect MustacheTokenComponent {
    syn String TokenComponent.javaType() = effectiveJavaTypeUse().prettyPrint(); 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() { syn PortDefinition TokenComponent.normalTokenSendDef() {
    for (Component comp : meOwnedByOthers()) { for (Component comp : meOwnedByOthers()) {
    PortDefinition maybeResult = comp.asTokenComponent().directNormalTokenSendDef(); PortDefinition maybeResult = comp.asTokenComponent().directNormalTokenSendDef();
    ...@@ -682,6 +697,16 @@ aspect MustacheTokenComponent { ...@@ -682,6 +697,16 @@ aspect MustacheTokenComponent {
    // > see MustacheSend for updateMethodName, writeMethodName // > see MustacheSend for updateMethodName, writeMethodName
    // === attributes needed for computing above ones === // === attributes needed for computing above ones ===
    syn PortDefinition TokenComponent.directNormalTokenReceiveDef() {
    for (PortTarget target : getTokenPortTargetList()) {
    if (target.isTokenPortTarget() && !target.containingPortDefinition().getSend() &&
    ragconnect().restClientHandler().getInUse()) {
    return target.containingPortDefinition();
    }
    }
    return null;
    }
    syn PortDefinition TokenComponent.directNormalTokenSendDef() { syn PortDefinition TokenComponent.directNormalTokenSendDef() {
    for (PortTarget target : getTokenPortTargetList()) { for (PortTarget target : getTokenPortTargetList()) {
    if (target.isTokenPortTarget() && target.containingPortDefinition().shouldNotResetValue()) { if (target.isTokenPortTarget() && target.containingPortDefinition().shouldNotResetValue()) {
    ......
    ...@@ -233,6 +233,15 @@ aspect IntermediateToYAML { ...@@ -233,6 +233,15 @@ aspect IntermediateToYAML {
    } }
    return sb; return sb;
    } }
    // FIXME: remove refine once fixed in upstream mustache
    refine Helpers protected SimpleElement ComplexElement.makeStringElement(String value) {
    // simple test, check for special characters
    return containsAny(value, ":#,[{\"\n") ?
    StringElement.of(value.replace("\n", "\\n").replace("\"", "\\\"")) :
    ValueElement.of(value);
    }
    } }
    aspect Navigation { aspect Navigation {
    ......
    ...@@ -39,6 +39,7 @@ public class Compiler extends AbstractCompiler { ...@@ -39,6 +39,7 @@ public class Compiler extends AbstractCompiler {
    private static final String OPTION_PROTOCOL_JAVA = "java"; private static final String OPTION_PROTOCOL_JAVA = "java";
    private static final String OPTION_PROTOCOL_MQTT = "mqtt"; private static final String OPTION_PROTOCOL_MQTT = "mqtt";
    private static final String OPTION_PROTOCOL_REST = "rest"; private static final String OPTION_PROTOCOL_REST = "rest";
    private static final String OPTION_PROTOCOL_REST_CLIENT = "restClient";
    public Compiler() { public Compiler() {
    super("ragconnect", true); super("ragconnect", true);
    ...@@ -183,6 +184,7 @@ public class Compiler extends AbstractCompiler { ...@@ -183,6 +184,7 @@ public class Compiler extends AbstractCompiler {
    .addDefaultValue(OPTION_PROTOCOL_MQTT, "Enable MQTT") .addDefaultValue(OPTION_PROTOCOL_MQTT, "Enable MQTT")
    .addAcceptedValue(OPTION_PROTOCOL_JAVA, "Enable Java (experimental)") .addAcceptedValue(OPTION_PROTOCOL_JAVA, "Enable Java (experimental)")
    .addAcceptedValue(OPTION_PROTOCOL_REST, "Enable REST") .addAcceptedValue(OPTION_PROTOCOL_REST, "Enable REST")
    .addAcceptedValue(OPTION_PROTOCOL_REST_CLIENT, "Enable REST client (experimental)")
    ); );
    optionPrintYaml = addOption( optionPrintYaml = addOption(
    new BooleanOption("printYaml", "Print out YAML instead of generating files and exit.") new BooleanOption("printYaml", "Print out YAML instead of generating files and exit.")
    ...@@ -341,6 +343,8 @@ public class Compiler extends AbstractCompiler { ...@@ -341,6 +343,8 @@ public class Compiler extends AbstractCompiler {
    ragConnect.addHandler(new Handler("JavaHandler", "java", optionProtocols.hasValue(OPTION_PROTOCOL_JAVA))); 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("MqttServerHandler", "mqtt", optionProtocols.hasValue(OPTION_PROTOCOL_MQTT)));
    ragConnect.addHandler(new Handler("RestServerHandler", "rest", optionProtocols.hasValue(OPTION_PROTOCOL_REST))); ragConnect.addHandler(new Handler("RestServerHandler", "rest", optionProtocols.hasValue(OPTION_PROTOCOL_REST)));
    ragConnect.addHandler(new Handler("RestClientHandler", "restClient",
    optionProtocols.hasValue(OPTION_PROTOCOL_REST_CLIENT)));
    } }
    public String generateAspect(RagConnect ragConnect) { public String generateAspect(RagConnect ragConnect) {
    ......
    public class RestClientHandler {
    private java.net.http.HttpClient httpClient;
    public RestClientHandler(String name) {
    httpClient = java.net.http.HttpClient.newHttpClient();
    }
    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"));
    java.net.http.HttpRequest httpRequest = java.net.http.HttpRequest.newBuilder(target)
    .GET()
    .build();
    return () -> {
    byte[] rawInput;
    try {
    rawInput = fetchFrom(httpRequest);
    } catch (Exception e) {
    {{logException}}("Exception when fetching from " + target, e);
    return;
    }
    callback.accept("", rawInput);
    };
    }
    byte[] fetchFrom(java.net.http.HttpRequest httpRequest) throws java.io.IOException, InterruptedException {
    return httpClient.send(httpRequest, java.net.http.HttpResponse.BodyHandlers.ofByteArray()).body();
    }
    public RestClientPreparedRequest preparePut(java.net.URI uri) {
    java.net.URI target = java.net.URI.create(uri.toString().replaceFirst("restClient", "http"));
    java.net.http.HttpRequest.Builder httpRequestBuilder = java.net.http.HttpRequest.newBuilder(target);
    return new RestClientPreparedRequest(httpRequestBuilder);
    }
    void sendRequestAsync(RestClientPreparedRequest preparedRequest, byte[] message) {
    java.net.http.HttpRequest httpRequest = preparedRequest.builder
    .PUT(java.net.http.HttpRequest.BodyPublishers.ofByteArray(message))
    .build();
    httpClient.sendAsync(httpRequest, java.net.http.HttpResponse.BodyHandlers.discarding());
    }
    void sendRequestSync(RestClientPreparedRequest preparedRequest, byte[] message) {
    java.net.http.HttpRequest httpRequest = preparedRequest.builder
    .PUT(java.net.http.HttpRequest.BodyPublishers.ofByteArray(message))
    .build();
    java.net.http.HttpResponse response;
    try {
    response = httpClient.send(httpRequest, java.net.http.HttpResponse.BodyHandlers.ofString());
    {{logDebug}}("Response for message to {{log_}} is {{log_}} {{log_}}",
    httpRequest, response.statusCode(), response.body());
    } catch (java.io.IOException | InterruptedException e) {
    {{logException}}("Exception while sending {{log_}}: {{log_}}", httpRequest, e);
    }
    }
    public void close() {
    // empty
    }
    }
    public class RestClientPreparedRequest {
    java.net.http.HttpRequest.Builder builder;
    RestClientPreparedRequest(java.net.http.HttpRequest.Builder builder) {
    this.builder = builder;
    }
    }
    ...@@ -20,13 +20,13 @@ public class RestServerHandler { ...@@ -20,13 +20,13 @@ public class RestServerHandler {
    return handler; return handler;
    } }
    public boolean newPUTConnection(RagConnectToken connectToken, java.util.function.Consumer<String> callback) { public boolean newPUTConnection(RagConnectToken connectToken, java.util.function.BiConsumer<String, byte[]> consumer) {
    tokensForRemoval.put(connectToken, callback); tokensForRemoval.put(connectToken, consumer);
    resolveHandler(connectToken.uri).newPUTConnection(connectToken.uri.getPath(), callback); resolveHandler(connectToken.uri).newPUTConnection(connectToken.uri.getPath(), consumer);
    return true; return true;
    } }
    public boolean newGETConnection(RagConnectToken connectToken, SupplierWithException<String> supplier) { public boolean newGETConnection(RagConnectToken connectToken, SupplierWithException<byte[]> supplier) {
    tokensForRemoval.put(connectToken, supplier); tokensForRemoval.put(connectToken, supplier);
    resolveHandler(connectToken.uri).newGETConnection(connectToken.uri.getPath(), supplier); resolveHandler(connectToken.uri).newGETConnection(connectToken.uri.getPath(), supplier);
    return true; return true;
    ...@@ -52,8 +52,8 @@ public class RestHandler { ...@@ -52,8 +52,8 @@ public class RestHandler {
    private int port; private int port;
    /** Dispatch knowledge */ /** Dispatch knowledge */
    private final java.util.Map<String, java.util.List<java.util.function.Consumer<String>>> callbacks; private final java.util.Map<String, java.util.List<java.util.function.BiConsumer<String, byte[]>>> callbacks;
    private final java.util.Map<String, SupplierWithException<String>> suppliers; private final java.util.Map<String, SupplierWithException<byte[]>> suppliers;
    public RestHandler() { public RestHandler() {
    this.port = DEFAULT_PORT; this.port = DEFAULT_PORT;
    ...@@ -64,23 +64,24 @@ public class RestHandler { ...@@ -64,23 +64,24 @@ public class RestHandler {
    public RestHandler setPort(int port) { public RestHandler setPort(int port) {
    this.port = port; this.port = port;
    start(); start();
    spark.Spark.path("", () -> spark.Spark.before((request, response) -> {{logDebug}}("Request on {{log_}}: {{log_}}", request.pathInfo(), request.bodyAsBytes())));
    return this; return this;
    } }
    public void newPUTConnection(String path, java.util.function.Consumer<String> callback) { public void newPUTConnection(String path, java.util.function.BiConsumer<String, byte[]> callback) {
    if (callbacks.containsKey(path)) { if (callbacks.containsKey(path)) {
    callbacks.get(path).add(callback); callbacks.get(path).add(callback);
    } else { } else {
    // setup path // setup path
    java.util.List<java.util.function.Consumer<String>> callbackList = new java.util.ArrayList<>(); java.util.List<java.util.function.BiConsumer<String, byte[]>> callbackList = new java.util.ArrayList<>();
    callbackList.add(callback); callbackList.add(callback);
    callbacks.put(path, callbackList); callbacks.put(path, callbackList);
    spark.Spark.put(path, (request, response) -> { spark.Spark.put(path, (request, response) -> {
    String content = request.body(); byte[] content = request.bodyAsBytes();
    java.util.Set<String> errors = new java.util.HashSet<>(); java.util.Set<String> errors = new java.util.HashSet<>();
    for (java.util.function.Consumer<String> f : callbackList) { for (java.util.function.BiConsumer<String, byte[]> f : callbackList) {
    try { try {
    f.accept(content); f.accept(path, content);
    } catch (Exception e) { } catch (Exception e) {
    errors.add(e.getMessage()); errors.add(e.getMessage());
    } }
    ...@@ -94,7 +95,7 @@ public class RestHandler { ...@@ -94,7 +95,7 @@ public class RestHandler {
    } }
    } }
    public void newGETConnection(String path, SupplierWithException<String> supplier) { public void newGETConnection(String path, SupplierWithException<byte[]> supplier) {
    if (suppliers.get(path) != null) { if (suppliers.get(path) != null) {
    {{logWarn}}("Overriding existing supplier for '{{log_}}'", path); {{logWarn}}("Overriding existing supplier for '{{log_}}'", path);
    } }
    ......
    ...@@ -46,6 +46,12 @@ aspect RagConnectHandler { ...@@ -46,6 +46,12 @@ aspect RagConnectHandler {
    {{/InUse}} {{/InUse}}
    {{/restHandler}} {{/restHandler}}
    {{#restClientHandler}}
    {{#InUse}}
    {{> RestClientHandler}}
    {{/InUse}}
    {{/restClientHandler}}
    class RagConnectToken { class RagConnectToken {
    static java.util.concurrent.atomic.AtomicLong counter = new java.util.concurrent.atomic.AtomicLong(0); static java.util.concurrent.atomic.AtomicLong counter = new java.util.concurrent.atomic.AtomicLong(0);
    final long id; final long id;
    ...@@ -191,4 +197,7 @@ aspect RagConnectHandler { ...@@ -191,4 +197,7 @@ aspect RagConnectHandler {
    java.util.Optional.ofNullable(publishers.get(index)).ifPresent(publisher -> publisher.setLastValue(value)); java.util.Optional.ofNullable(publishers.get(index)).ifPresent(publisher -> publisher.setLastValue(value));
    } }
    } }
    interface RagConnectReceiver extends Runnable {
    }
    } }
    private RagConnectReceiver {{parentTypeName}}.{{receiverName}} = null;
    {{#typeIsList}} {{#typeIsList}}
    {{#IndexBasedListAccess}} {{#IndexBasedListAccess}}
    private int {{parentTypeName}}.{{resolveInListMethodName}}(String topic) { private int {{parentTypeName}}.{{resolveInListMethodName}}(String topic) {
    ...@@ -116,13 +118,18 @@ private boolean {{parentTypeName}}.{{internalConnectMethodName}}(String {{connec ...@@ -116,13 +118,18 @@ private boolean {{parentTypeName}}.{{internalConnectMethodName}}(String {{connec
    {{#restHandler}} {{#restHandler}}
    {{#InUse}} {{#InUse}}
    case "rest": case "rest":
    success = {{attributeName}}().newPUTConnection(connectToken, input -> { success = {{attributeName}}().newPUTConnection(connectToken, consumer);
    // TODO wildcard-topic not supported yet
    consumer.accept("", input.getBytes());
    });
    break; break;
    {{/InUse}} {{/InUse}}
    {{/restHandler}} {{/restHandler}}
    {{#restClientHandler}}
    {{#InUse}}
    case "restClient":
    {{receiverName}} = {{attributeName}}().newReceiverFor(connectToken, consumer);
    success = {{receiverName}} != null;
    break;
    {{/InUse}}
    {{/restClientHandler}}
    default: default:
    {{logError}}("Unknown protocol '{{log_}}'.", scheme); {{logError}}("Unknown protocol '{{log_}}'.", scheme);
    success = false; success = false;
    ...@@ -161,6 +168,15 @@ public boolean {{parentTypeName}}.{{disconnectMethodName}}(String {{connectParam ...@@ -161,6 +168,15 @@ public boolean {{parentTypeName}}.{{disconnectMethodName}}(String {{connectParam
    break; break;
    {{/InUse}} {{/InUse}}
    {{/restHandler}} {{/restHandler}}
    {{#restClientHandler}}
    {{#InUse}}
    case "restClient": disconnectingMethod = token -> {
    {{receiverName}} = null;
    return true;
    };
    break;
    {{/InUse}}
    {{/restClientHandler}}
    default: default:
    {{logError}}("Unknown protocol '{{log_}}' in '{{log_}}' for disconnecting {{parentTypeName}}.{{entityName}}", {{logError}}("Unknown protocol '{{log_}}' in '{{log_}}' for disconnecting {{parentTypeName}}.{{entityName}}",
    scheme, {{connectParameterName}}); scheme, {{connectParameterName}});
    ......
    ...@@ -54,12 +54,28 @@ public boolean {{parentTypeName}}.{{connectMethodName}}(String {{connectParamete ...@@ -54,12 +54,28 @@ public boolean {{parentTypeName}}.{{connectMethodName}}(String {{connectParamete
    case "rest": { case "rest": {
    success = {{attributeName}}().newGETConnection(connectToken, () -> { success = {{attributeName}}().newGETConnection(connectToken, () -> {
    {{updateMethodName}}({{#IndexBasedListAccess}}index{{/IndexBasedListAccess}}); {{updateMethodName}}({{#IndexBasedListAccess}}index{{/IndexBasedListAccess}});
    return new String({{lastValueGetterCall}}); return {{lastValueGetterCall}};
    }); });
    break; break;
    } }
    {{/InUse}} {{/InUse}}
    {{/restHandler}} {{/restHandler}}
    {{#restClientHandler}}
    {{#InUse}}
    case "restClient":
    RestClientPreparedRequest preparedRequest = {{attributeName}}().preparePut(uri);
    {{senderName}}.add(() -> {
    {{! sync or async could be selected via options, see issue #62 }}
    {{attributeName}}().sendRequestSync(preparedRequest, {{lastValueGetterCall}});
    }{{#IndexBasedListAccess}}, index{{/IndexBasedListAccess}}, connectToken);
    {{updateMethodName}}({{#IndexBasedListAccess}}index{{/IndexBasedListAccess}});
    if (writeCurrentValue) {
    {{writeMethodName}}({{#IndexBasedListAccess}}index, {{/IndexBasedListAccess}}connectToken);
    }
    success = true;
    break;
    {{/InUse}}
    {{/restClientHandler}}
    default: default:
    {{logError}}("Unknown protocol '{{log_}}'.", scheme); {{logError}}("Unknown protocol '{{log_}}'.", scheme);
    success = false; success = false;
    ...@@ -121,6 +137,13 @@ public boolean {{parentTypeName}}.{{disconnectMethodName}}(String {{connectParam ...@@ -121,6 +137,13 @@ public boolean {{parentTypeName}}.{{disconnectMethodName}}(String {{connectParam
    break; break;
    {{/InUse}} {{/InUse}}
    {{/restHandler}} {{/restHandler}}
    {{#restClientHandler}}
    {{#InUse}}
    case "restClient":
    disconnectingMethod = {{senderName}}::remove;
    break;
    {{/InUse}}
    {{/restClientHandler}}
    default: default:
    {{logError}}("Unknown protocol '{{log_}}' in '{{log_}}' for disconnecting {{parentTypeName}}.{{entityName}}", {{logError}}("Unknown protocol '{{log_}}' in '{{log_}}' for disconnecting {{parentTypeName}}.{{entityName}}",
    scheme, {{connectParameterName}}); scheme, {{connectParameterName}});
    ...@@ -165,9 +188,6 @@ protected void {{parentTypeName}}.{{writeMethodName}}({{#IndexBasedListAccess}}i ...@@ -165,9 +188,6 @@ protected void {{parentTypeName}}.{{writeMethodName}}({{#IndexBasedListAccess}}i
    {{#needForwarding}} {{#needForwarding}}
    syn {{{forwardingType}}} {{parentTypeName}}.{{forwardingName}}({{#IndexBasedListAccess}}int index{{/IndexBasedListAccess}}) { syn {{{forwardingType}}} {{parentTypeName}}.{{forwardingName}}({{#IndexBasedListAccess}}int index{{/IndexBasedListAccess}}) {
    {{#relationPortWithListRole}} {{#relationPortWithListRole}}
    // for (var element : {{realGetterMethodCall}}) {
    // element.{{touchedTerminalsMethodName}}();
    // }
    {{realGetterMethodCall}}.stream().forEach(element -> element.{{touchedTerminalsMethodName}}()); {{realGetterMethodCall}}.stream().forEach(element -> element.{{touchedTerminalsMethodName}}());
    return {{realGetterMethodCall}}; return {{realGetterMethodCall}};
    {{/relationPortWithListRole}} {{/relationPortWithListRole}}
    ......
    ...@@ -18,5 +18,10 @@ public {{parentTypeName}} {{parentTypeName}}.set{{Name}}({{javaType}} value) { ...@@ -18,5 +18,10 @@ public {{parentTypeName}} {{parentTypeName}}.set{{Name}}({{javaType}} value) {
    } }
    public {{javaType}} {{parentTypeName}}.get{{Name}}() { public {{javaType}} {{parentTypeName}}.get{{Name}}() {
    {{#normalTokenReceiveDef}}
    if ({{receiverName}} != null) {
    {{receiverName}}.run();
    }
    {{/normalTokenReceiveDef}}
    return get{{internalName}}(); return get{{internalName}}();
    } }
    ...@@ -712,6 +712,28 @@ task compileJavaIncremental(type: RagConnectTest) { ...@@ -712,6 +712,28 @@ task compileJavaIncremental(type: RagConnectTest) {
    } }
    } }
    // --- Test: rest-client-server ---
    task compileRestClientServerTest(type: RagConnectTest) {
    ragconnect {
    outputDir = file('src/test/02-after-ragconnect/restClientServer')
    inputFiles = [file('src/test/01-input/restClientServer/Test.relast'),
    file('src/test/01-input/restClientServer/Test.connect')]
    rootNode = 'Root'
    protocols = ['rest','restClient']
    extraOptions = defaultRagConnectOptionsAnd(['--experimental-jastadd-329'])
    }
    relast {
    useJastAddNames = true
    grammarName = 'src/test/03-after-relast/restClientServer/restClientServer'
    serializer = 'jackson'
    }
    jastadd {
    jastAddList = 'JastAddList'
    packageName = 'restClientServer.ast'
    extraOptions = JASTADD_INCREMENTAL_OPTIONS_TRACING_FULL
    }
    }
    // --- Task order --- // --- Task order ---
    classes.dependsOn(':ragconnect.base:jar') classes.dependsOn(':ragconnect.base:jar')
    ......
    send SenderRoot.SimpleValue ;
    receive ReceiverRoot.ReceivedValue ;
    send SenderRoot.SingleA ;
    receive ReceiverRoot.SomeA ;
    Root ::= SenderRoot ReceiverRoot;
    SenderRoot ::= <SimpleValue:int> SingleA:A ;
    ReceiverRoot ::= <ReceivedValue:int> SomeA:A ;
    A ::= <Value> Inner ;
    Inner ::= <InnerValue> ;
    package org.jastadd.ragconnect.tests;
    import org.jastadd.ragconnect.tests.utils.TestChecker;
    import org.junit.jupiter.api.AfterEach;
    import org.junit.jupiter.api.BeforeEach;
    import org.junit.jupiter.api.Disabled;
    import org.junit.jupiter.api.Test;
    import restClientServer.ast.*;
    import java.io.IOException;
    import static org.assertj.core.api.Assertions.assertThat;
    /**
    * Testing RestClient.
    *
    * @author rschoene - Initial contribution
    */
    public class RestClientServerTest extends RagConnectTest {
    private Root root;
    private TestChecker checker;
    private static final String TOPIC_NT_A_VALUE = "nt/a/value";
    private static final String TOPIC_NT_A_INNER_VALUE = "nt/a/inner/value";
    @Test
    public void testSimpleSenderRest() throws IOException {
    root.getSenderRoot().setSimpleValue(1);
    assertThat(root.getReceiverRoot().connectReceivedValue("restClient://localhost:4567/serve-simple")).isTrue();
    assertThat(root.getSenderRoot().connectSimpleValue("rest://localhost:4567/serve-simple", true)).isTrue();
    assertThat(root.getReceiverRoot().getReceivedValue()).isEqualTo(1);
    root.getSenderRoot().setSimpleValue(2);
    assertThat(root.getReceiverRoot().getReceivedValue()).isEqualTo(2);
    }
    @Test
    public void testSimpleSenderRestClient() throws IOException {
    root.getSenderRoot().setSimpleValue(1);
    assertThat(root.getReceiverRoot().connectReceivedValue("rest://localhost:4567/put-simple")).isTrue();
    assertThat(root.getSenderRoot().connectSimpleValue("restClient://localhost:4567/put-simple", true)).isTrue();
    assertThat(root.getReceiverRoot().getReceivedValue()).isEqualTo(1);
    root.getSenderRoot().setSimpleValue(2);
    assertThat(root.getReceiverRoot().getReceivedValue()).isEqualTo(2);
    }
    @Test
    public void testNonterminalSenderRest() throws IOException {
    A a = new A().setValue("a1");
    a.setInner(new Inner().setInnerValue("1"));
    root.getSenderRoot().setSingleA(a);
    checker.put(TOPIC_NT_A_VALUE, "a1")
    .put(TOPIC_NT_A_INNER_VALUE, "1");
    assertThat(root.getReceiverRoot().connectSomeA("rest://localhost:4567/put-nt")).isTrue();
    assertThat(root.getSenderRoot().connectSingleA("restClient://localhost:4567/put-nt", true)).isTrue();
    communicateNonterminal();
    }
    @Test
    @Disabled("Receiving nonterminals using restClient is not supported yet")
    public void testNonterminalSenderRestClient() throws IOException {
    A a = new A().setValue("a1");
    a.setInner(new Inner().setInnerValue("1"));
    root.getSenderRoot().setSingleA(a);
    assertThat(root.getReceiverRoot().connectSomeA("restClient://localhost:4567/serve-nt")).isTrue();
    assertThat(root.getSenderRoot().connectSingleA("rest://localhost:4567/serve-nt", true)).isTrue();
    communicateNonterminal();
    }
    private void communicateNonterminal() {
    A a = root.getSenderRoot().getSingleA();
    checker.check();
    a.setValue("a23");
    checker.put(TOPIC_NT_A_VALUE, "a23").check();
    a.getInner().setInnerValue("abc");
    checker.put(TOPIC_NT_A_INNER_VALUE, "abc").check();
    }
    @BeforeEach
    public void createModel() {
    root = new Root();
    root.setReceiverRoot(new ReceiverRoot());
    root.setSenderRoot(new SenderRoot());
    checker = new TestChecker().setActualNumberOfValues(() -> 0);
    checker.setCheckForString(TOPIC_NT_A_VALUE, (name, expected) ->
    assertThat(someAValue()).describedAs(name).isEqualTo(expected))
    .setCheckForString(TOPIC_NT_A_INNER_VALUE, (name, expected) ->
    assertThat(someAInnerValueOrNull()).describedAs(name).isEqualTo(expected))
    ;
    }
    private String someAValue() {
    if (root.getReceiverRoot().getSomeA() == null) {
    return null;
    }
    return root.getReceiverRoot().getSomeA().getValue();
    }
    private String someAInnerValueOrNull() {
    if (root.getReceiverRoot().getSomeA() == null || root.getReceiverRoot().getSomeA().getInner() == null) {
    return null;
    }
    return root.getReceiverRoot().getSomeA().getInner().getInnerValue();
    }
    @AfterEach
    public void close() {
    root.ragconnectCloseConnections();
    }
    }
    0% Loading or .
    You are about to add 0 people to the discussion. Proceed with caution.
    Please register or to comment