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

Resolve "New Handler: REST client"

parent 784cf03d
No related branches found
No related tags found
2 merge requests!39Version 1.1.0,!38Resolve "New Handler: REST client"
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