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
Branches
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:
    image: openjdk:11
    stage: build
    script:
    - ./gradlew --console=plain --no-daemon assemble jar
    - ./gradlew --console=plain --no-daemon assemble testClasses jar
    artifacts:
    paths:
    - "ragconnect.base/build/libs/ragconnect-*.jar"
    ......
    ......@@ -6,16 +6,16 @@ Additional options are as follows.
    ## Table with available options
    | Name | Required (Default) | Description |
    |---|---|---|
    |------------------------------|--------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
    | `--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. |
    | `--verbose` | No (false) | Print more messages while compiling. |
    | `--logReads` | No (false) | Enable logging for every received message. |
    | `--logWrites` | No (false) | Enable logging for every sent message. |
    | `--logIncremental` | No (false) | Enable logging for observer in incremental dependency tracking. |
    | `--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). |
    | `--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) |
    ......@@ -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.
    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
    #### Automatic dependency tracking
    ......@@ -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]
    [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
    ### 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).
    ......
    # 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:
    - "Using RagConnect (by Example)": using.md
    - "RagConnect Specification Language": dsl.md
    - "Compiler options": compiler.md
    - "Communication Protocols": handlers.md
    - "Use Cases": use_cases.md
    - "Inner workings": inner-workings.md
    - "Evaluation Metrics: Lines of Code": cloc.md
    ......
    ......@@ -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 ---
    ......
    ......@@ -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("restClient");
    private Handler RagConnect.resolveHandlerByName(String uniqueName) {
    for (Handler handler : getHandlerList()) {
    ......
    ......@@ -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,16 @@ aspect MustacheTokenComponent {
    // > see MustacheSend for updateMethodName, writeMethodName
    // === 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() {
    for (PortTarget target : getTokenPortTargetList()) {
    if (target.isTokenPortTarget() && target.containingPortDefinition().shouldNotResetValue()) {
    ......
    ......@@ -233,6 +233,15 @@ aspect IntermediateToYAML {
    }
    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 {
    ......
    ......@@ -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 = "restClient";
    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("RestClientHandler", "restClient",
    optionProtocols.hasValue(OPTION_PROTOCOL_REST_CLIENT)));
    }
    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 {
    return handler;
    }
    public boolean newPUTConnection(RagConnectToken connectToken, java.util.function.Consumer<String> callback) {
    tokensForRemoval.put(connectToken, callback);
    resolveHandler(connectToken.uri).newPUTConnection(connectToken.uri.getPath(), callback);
    public boolean newPUTConnection(RagConnectToken connectToken, java.util.function.BiConsumer<String, byte[]> consumer) {
    tokensForRemoval.put(connectToken, consumer);
    resolveHandler(connectToken.uri).newPUTConnection(connectToken.uri.getPath(), consumer);
    return true;
    }
    public boolean newGETConnection(RagConnectToken connectToken, SupplierWithException<String> supplier) {
    public boolean newGETConnection(RagConnectToken connectToken, SupplierWithException<byte[]> supplier) {
    tokensForRemoval.put(connectToken, supplier);
    resolveHandler(connectToken.uri).newGETConnection(connectToken.uri.getPath(), supplier);
    return true;
    ......@@ -52,8 +52,8 @@ public class RestHandler {
    private int port;
    /** Dispatch knowledge */
    private final java.util.Map<String, java.util.List<java.util.function.Consumer<String>>> callbacks;
    private final java.util.Map<String, SupplierWithException<String>> suppliers;
    private final java.util.Map<String, java.util.List<java.util.function.BiConsumer<String, byte[]>>> callbacks;
    private final java.util.Map<String, SupplierWithException<byte[]>> suppliers;
    public RestHandler() {
    this.port = DEFAULT_PORT;
    ......@@ -64,23 +64,24 @@ public class RestHandler {
    public RestHandler setPort(int port) {
    this.port = port;
    start();
    spark.Spark.path("", () -> spark.Spark.before((request, response) -> {{logDebug}}("Request on {{log_}}: {{log_}}", request.pathInfo(), request.bodyAsBytes())));
    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)) {
    callbacks.get(path).add(callback);
    } else {
    // 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);
    callbacks.put(path, callbackList);
    spark.Spark.put(path, (request, response) -> {
    String content = request.body();
    byte[] content = request.bodyAsBytes();
    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 {
    f.accept(content);
    f.accept(path, content);
    } catch (Exception e) {
    errors.add(e.getMessage());
    }
    ......@@ -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) {
    {{logWarn}}("Overriding existing supplier for '{{log_}}'", path);
    }
    ......
    ......@@ -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 {
    }
    }
    private RagConnectReceiver {{parentTypeName}}.{{receiverName}} = null;
    {{#typeIsList}}
    {{#IndexBasedListAccess}}
    private int {{parentTypeName}}.{{resolveInListMethodName}}(String topic) {
    ......@@ -116,13 +118,18 @@ private boolean {{parentTypeName}}.{{internalConnectMethodName}}(String {{connec
    {{#restHandler}}
    {{#InUse}}
    case "rest":
    success = {{attributeName}}().newPUTConnection(connectToken, input -> {
    // TODO wildcard-topic not supported yet
    consumer.accept("", input.getBytes());
    });
    success = {{attributeName}}().newPUTConnection(connectToken, consumer);
    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 +168,15 @@ public boolean {{parentTypeName}}.{{disconnectMethodName}}(String {{connectParam
    break;
    {{/InUse}}
    {{/restHandler}}
    {{#restClientHandler}}
    {{#InUse}}
    case "restClient": disconnectingMethod = token -> {
    {{receiverName}} = null;
    return true;
    };
    break;
    {{/InUse}}
    {{/restClientHandler}}
    default:
    {{logError}}("Unknown protocol '{{log_}}' in '{{log_}}' for disconnecting {{parentTypeName}}.{{entityName}}",
    scheme, {{connectParameterName}});
    ......
    ......@@ -54,12 +54,28 @@ public boolean {{parentTypeName}}.{{connectMethodName}}(String {{connectParamete
    case "rest": {
    success = {{attributeName}}().newGETConnection(connectToken, () -> {
    {{updateMethodName}}({{#IndexBasedListAccess}}index{{/IndexBasedListAccess}});
    return new String({{lastValueGetterCall}});
    return {{lastValueGetterCall}};
    });
    break;
    }
    {{/InUse}}
    {{/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:
    {{logError}}("Unknown protocol '{{log_}}'.", scheme);
    success = false;
    ......@@ -121,6 +137,13 @@ public boolean {{parentTypeName}}.{{disconnectMethodName}}(String {{connectParam
    break;
    {{/InUse}}
    {{/restHandler}}
    {{#restClientHandler}}
    {{#InUse}}
    case "restClient":
    disconnectingMethod = {{senderName}}::remove;
    break;
    {{/InUse}}
    {{/restClientHandler}}
    default:
    {{logError}}("Unknown protocol '{{log_}}' in '{{log_}}' for disconnecting {{parentTypeName}}.{{entityName}}",
    scheme, {{connectParameterName}});
    ......@@ -165,9 +188,6 @@ protected void {{parentTypeName}}.{{writeMethodName}}({{#IndexBasedListAccess}}i
    {{#needForwarding}}
    syn {{{forwardingType}}} {{parentTypeName}}.{{forwardingName}}({{#IndexBasedListAccess}}int index{{/IndexBasedListAccess}}) {
    {{#relationPortWithListRole}}
    // for (var element : {{realGetterMethodCall}}) {
    // element.{{touchedTerminalsMethodName}}();
    // }
    {{realGetterMethodCall}}.stream().forEach(element -> element.{{touchedTerminalsMethodName}}());
    return {{realGetterMethodCall}};
    {{/relationPortWithListRole}}
    ......
    ......@@ -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}}();
    }
    ......@@ -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 ---
    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