diff --git a/.gitignore b/.gitignore index a78076875b36829500e0d58bf05998ee5ade757a..78aba4bb88ae15cc77fd1bb1b454f37c145e3839 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ .classpath .idea/ .gradle/ +build/ diff --git a/ragconnect.base/src/main/jastadd/Analysis.jrag b/ragconnect.base/src/main/jastadd/Analysis.jrag index 623a3f0ad8c0457d8d0606bf70dea0adfea89ea4..30159bc3149d419953be27e9a0801faa46acdde3 100644 --- a/ragconnect.base/src/main/jastadd/Analysis.jrag +++ b/ragconnect.base/src/main/jastadd/Analysis.jrag @@ -1,13 +1,14 @@ aspect Analysis { // --- lookupTokenEndpointDefinition --- - inh TokenEndpointDefinition TokenEndpointDefinition.lookupTokenEndpointDefinition(TokenComponent token); - eq RagConnect.getEndpointDefinition().lookupTokenEndpointDefinition(TokenComponent token) { + inh java.util.List<TokenEndpointDefinition> TokenEndpointDefinition.lookupTokenEndpointDefinitions(TokenComponent token); + eq RagConnect.getEndpointDefinition().lookupTokenEndpointDefinitions(TokenComponent token) { + java.util.List<TokenEndpointDefinition> result = new java.util.ArrayList<>(); for (EndpointDefinition def : getEndpointDefinitionList()) { if (def.isTokenEndpointDefinition() && def.asTokenEndpointDefinition().getToken().equals(token)) { - return def.asTokenEndpointDefinition(); + result.add(def.asTokenEndpointDefinition()); } } - return null; + return result; } // --- lookupDependencyDefinition --- @@ -22,6 +23,70 @@ aspect Analysis { } // --- isAlreadyDefined --- - syn boolean TokenEndpointDefinition.isAlreadyDefined() = lookupTokenEndpointDefinition(getToken()) != this; + syn boolean TokenEndpointDefinition.isAlreadyDefined() { + java.util.List<TokenEndpointDefinition> definitions = lookupTokenEndpointDefinitions(getToken()); + java.util.Set<String> protocols = definitions + .stream() + .map(TokenEndpointDefinition::protocol) + .collect(java.util.stream.Collectors.toSet()); + return definitions.size() > protocols.size(); + } syn boolean DependencyDefinition.isAlreadyDefined() = lookupDependencyDefinition(getSource().containingTypeDecl(), getID()) != this; + + // --- protocol --- + syn String TokenEndpointDefinition.protocol(); + eq ReceiveFromMqttDefinition.protocol() = "mqtt"; + eq SendToMqttDefinition.protocol() = "mqtt"; + eq ReceiveFromRestDefinition.protocol() = "rest"; + eq SendToRestDefinition.protocol() = "rest"; + + // --- usesPROTOCOL --- + syn boolean RagConnect.usesMqtt() = !mqttEndpointDefinitions().isEmpty(); + syn boolean RagConnect.usesRest() = !restEndpointDefinitions().isEmpty(); + + // --- mqttEndpointDefinitions --- + coll java.util.List<TokenEndpointDefinition> RagConnect.mqttEndpointDefinitions() [new java.util.ArrayList<>()] root RagConnect; + ReceiveFromMqttDefinition contributes this + to RagConnect.mqttEndpointDefinitions() + for ragconnect(); + SendToMqttDefinition contributes this + to RagConnect.mqttEndpointDefinitions() + for ragconnect(); + + // --- restEndpointDefinitions --- + coll java.util.List<TokenEndpointDefinition> RagConnect.restEndpointDefinitions() [new java.util.ArrayList<>()] root RagConnect; + ReceiveFromRestDefinition contributes this + to RagConnect.restEndpointDefinitions() + for ragconnect(); + SendToRestDefinition contributes this + to RagConnect.restEndpointDefinitions() + for ragconnect(); + + syn boolean MappingDefinitionType.assignableTo(JavaTypeUse target); + eq JavaMappingDefinitionType.assignableTo(JavaTypeUse target) = getType().assignableTo(target); + eq JavaArrayMappingDefinitionType.assignableTo(JavaTypeUse target) { + if (!target.getName().endsWith("[]")) { return false; } + return getType().assignableTo(new SimpleJavaTypeUse(target.getName().replace("[]", ""))); + } + syn boolean JavaTypeUse.assignableTo(JavaTypeUse target) { + // target var = this; + return target.primitivePrettyPrint().equals(this.primitivePrettyPrint()); + } + syn String JavaTypeUse.primitivePrettyPrint() { + switch(getName()) { + case "int": + case "Integer": return "int"; + case "short": + case "Short": return "short"; + case "long": + case "Long": return "long"; + case "float": + case "Float": return "float"; + case "double": + case "Double": return "double"; + case "char": + case "Character": return "char"; + default: return getName(); + } + } } diff --git a/ragconnect.base/src/main/jastadd/Errors.jrag b/ragconnect.base/src/main/jastadd/Errors.jrag index cf001ac9d3b64181b58e9b1c461cba6e316d1370..fe2fcd59efd365174ca2803affebc34819cf3d7e 100644 --- a/ragconnect.base/src/main/jastadd/Errors.jrag +++ b/ragconnect.base/src/main/jastadd/Errors.jrag @@ -23,9 +23,9 @@ aspect Errors { when effectiveMappings().get(0) == null to RagConnect.errors(); - ReceiveTokenEndpointDefinition contributes error("to-type of last mapping (" + effectiveMappings().get(effectiveMappings().size() - 1).getToType().prettyPrint() + ") does not match type of the Token (" + getToken().effectiveJavaTypeUse().prettyPrint() + ")!") - when !getToken().effectiveJavaTypeUse().prettyPrint().equals( - effectiveMappings().get(effectiveMappings().size() - 1).getToType().prettyPrint()) + ReceiveTokenEndpointDefinition contributes error("to-type of last mapping (" + effectiveMappings().get(effectiveMappings().size() - 1).getToType().prettyPrint() + ") not assignable to type of the Token (" + getToken().effectiveJavaTypeUse().prettyPrint() + ")!") + when !effectiveMappings().get(effectiveMappings().size() - 1).getToType().assignableTo( + getToken().effectiveJavaTypeUse()) to RagConnect.errors(); SendTokenEndpointDefinition contributes error("Sending target token must be an NTA token!") diff --git a/ragconnect.base/src/main/jastadd/MustacheNodes.relast b/ragconnect.base/src/main/jastadd/MustacheNodes.relast index 1a35853e84701475c11632e7b31a31458b8de729..73d1b318e03c68d2bd243264b3d9863548657e02 100644 --- a/ragconnect.base/src/main/jastadd/MustacheNodes.relast +++ b/ragconnect.base/src/main/jastadd/MustacheNodes.relast @@ -1,7 +1,7 @@ //TypeComponentMustache ; //rel TypeComponentMustache.TypeComponent -> TypeComponent ; -MRagConnect ::= ReceiveDefinition:MReceiveDefinition* SendDefinition:MSendDefinition* MappingDefinition:MMappingDefinition* DependencyDefinition:MDependencyDefinition* RootTypeComponent:MTypeComponent* TokenComponent:MTokenComponent*; +MRagConnect ::= ReceiveDefinition:MReceiveDefinition* PushSendDefinition:MSendDefinition* PullSendDefinition:MSendDefinition* MappingDefinition:MMappingDefinition* DependencyDefinition:MDependencyDefinition* RootTypeComponent:MTypeComponent* TokenComponent:MTokenComponent*; abstract MEndpointDefinition ::= InnerMappingDefinition:MInnerMappingDefinition*; MReceiveDefinition : MEndpointDefinition; MSendDefinition : MEndpointDefinition; diff --git a/ragconnect.base/src/main/jastadd/Util.jadd b/ragconnect.base/src/main/jastadd/Util.jadd new file mode 100644 index 0000000000000000000000000000000000000000..3b59b13c53a8d0e1ca2a05feba43294797a4577e --- /dev/null +++ b/ragconnect.base/src/main/jastadd/Util.jadd @@ -0,0 +1,5 @@ +aspect Util { + static String ASTNode.capitalize(String s) { + return Character.toUpperCase(s.charAt(0)) + s.substring(1); + } +} diff --git a/ragconnect.base/src/main/jastadd/backend/Generation.jadd b/ragconnect.base/src/main/jastadd/backend/Generation.jadd index 512750e268fea52e0c0d46284e55fb3d66054ec1..35d1fcd6c65b37e0f6344df233310a371fbabb60 100644 --- a/ragconnect.base/src/main/jastadd/backend/Generation.jadd +++ b/ragconnect.base/src/main/jastadd/backend/Generation.jadd @@ -31,13 +31,18 @@ aspect AttributesForMustache { eq MRagConnect.getChild().mqttHandlerField() = mqttHandlerField(); eq MRagConnect.getRootTypeComponent(int i).isFirst() = i == 0; + syn boolean MRagConnect.usesMqtt() = getRagConnect().usesMqtt(); syn String MRagConnect.mqttHandlerAttribute() = getRagConnect().mqttHandlerAttribute(); syn String MRagConnect.mqttHandlerField() = getRagConnect().mqttHandlerField(); syn String MRagConnect.mqttSetHostMethod() = getRagConnect().mqttSetHostMethod(); syn String MRagConnect.mqttWaitUntilReadyMethod() = getRagConnect().mqttWaitUntilReadyMethod(); syn String MRagConnect.mqttCloseMethod() = getRagConnect().mqttCloseMethod(); + syn boolean MRagConnect.usesRest() = getRagConnect().usesRest(); syn String MRagConnect.restHandlerAttribute() = getRagConnect().restHandlerAttribute(); + syn String MRagConnect.restHandlerField() = getRagConnect().restHandlerField(); + syn String MRagConnect.restSetPortMethod() = getRagConnect().restSetPortMethod(); + syn String MRagConnect.restCloseMethod() = getRagConnect().restCloseMethod(); // --- MEndpointDefinition --- syn String MEndpointDefinition.preemptiveExpectedValue(); @@ -51,6 +56,8 @@ aspect AttributesForMustache { inh String MEndpointDefinition.mqttHandlerAttribute(); + syn String MEndpointDefinition.connectParameterName() = endpointDef().connectParameterName(); + syn String MEndpointDefinition.newConnectionMethod() = endpointDef().newConnectionMethod(); syn String MEndpointDefinition.handlerAttribute() = endpointDef().handlerAttribute(); syn String MEndpointDefinition.connectMethod() = endpointDef().connectMethod(); syn TokenComponent MEndpointDefinition.token() = endpointDef().getToken(); @@ -88,9 +95,12 @@ aspect AttributesForMustache { eq MReceiveDefinition.endpointDef() = getReceiveTokenEndpointDefinition(); eq MReceiveDefinition.firstInputVarName() = "message"; + syn String MReceiveDefinition.newConnectionMethod() = getReceiveTokenEndpointDefinition().newConnectionMethod(); + syn String MReceiveDefinition.connectParameterName() = getReceiveTokenEndpointDefinition().connectParameterName(); + // --- MSendDefinition --- eq MSendDefinition.preemptiveExpectedValue() = lastValue(); - eq MSendDefinition.preemptiveReturn() = "return false;"; + eq MSendDefinition.preemptiveReturn() = getSendTokenEndpointDefinition().preemptiveReturn(); eq MSendDefinition.endpointDef() = getSendTokenEndpointDefinition(); eq MSendDefinition.firstInputVarName() = "get" + tokenName() + "()"; @@ -99,6 +109,7 @@ aspect AttributesForMustache { syn String MSendDefinition.updateMethod() = getSendTokenEndpointDefinition().updateMethod(); syn String MSendDefinition.writeMethod() = getSendTokenEndpointDefinition().writeMethod(); syn String MSendDefinition.tokenResetMethod() = getSendTokenEndpointDefinition().tokenResetMethod(); + syn boolean MSendDefinition.isPush() = getSendTokenEndpointDefinition().isPush(); // --- MMappingDefinition --- syn String MMappingDefinition.toType() = getMappingDefinition().getToType().prettyPrint(); @@ -112,7 +123,7 @@ aspect AttributesForMustache { syn String MDependencyDefinition.dependencyMethod() = getDependencyDefinition().dependencyMethod(); syn String MDependencyDefinition.sourceParentTypeName() = getDependencyDefinition().getSource().containingTypeDecl().getName(); syn String MDependencyDefinition.internalRelationPrefix() = getDependencyDefinition().internalRelationPrefix(); - syn nta MEndpointDefinition MDependencyDefinition.targetEndpointDefinition() { + syn nta MSendDefinition MDependencyDefinition.targetEndpointDefinition() { return getDependencyDefinition().targetEndpointDefinition().toMustache(); } @@ -134,7 +145,12 @@ aspect AttributesForMustache { result.setRagConnect(this); for (EndpointDefinition def : getEndpointDefinitionList()) { if (def.isSendTokenEndpointDefinition()) { - result.addSendDefinition(def.asSendTokenEndpointDefinition().toMustache()); + SendTokenEndpointDefinition sendDef = def.asSendTokenEndpointDefinition(); + if (sendDef.isPush()) { + result.addPushSendDefinition(sendDef.toMustache()); + } else { + result.addPullSendDefinition(sendDef.toMustache()); + } } else { result.addReceiveDefinition(def.asReceiveTokenEndpointDefinition().toMustache()); } @@ -166,34 +182,18 @@ aspect AttributesForMustache { addInnerMappingDefinition(inner); } } - syn lazy MReceiveDefinition ReceiveTokenEndpointDefinition.toMustache(); - eq ReceiveFromMqttDefinition.toMustache() { + syn lazy MReceiveDefinition ReceiveTokenEndpointDefinition.toMustache() { MReceiveDefinition result = new MReceiveDefinition(); result.setReceiveTokenEndpointDefinition(this); result.addInnerMappings(); return result; } - eq ReceiveFromRestDefinition.toMustache() { - MReceiveDefinition result = new MReceiveDefinition(); - System.err.println("REST not implemented!"); - result.setReceiveTokenEndpointDefinition(this); - result.addInnerMappings(); - return result; - } - syn lazy MSendDefinition SendTokenEndpointDefinition.toMustache(); - eq SendToMqttDefinition.toMustache() { + syn lazy MSendDefinition SendTokenEndpointDefinition.toMustache() { MSendDefinition result = new MSendDefinition(); result.setSendTokenEndpointDefinition(this); result.addInnerMappings(); return result; } - eq SendToRestDefinition.toMustache() { - MSendDefinition result = new MSendDefinition(); - System.err.println("REST not implemented!"); - result.setSendTokenEndpointDefinition(this); - result.addInnerMappings(); - return result; - } syn lazy MMappingDefinition MappingDefinition.toMustache() { MMappingDefinition result = new MMappingDefinition(); result.setMappingDefinition(this); @@ -224,7 +224,7 @@ aspect AspectGeneration { syn String TokenComponent.internalName() = getDependencySourceDefinitionList().isEmpty() ? externalName() : "_internal_" + getName(); syn String TokenComponent.externalName() = getName(); - syn String TokenEndpointDefinition.connectMethod() = "connect" + getToken().getName(); + syn String TokenEndpointDefinition.connectMethod() = "connect" + getToken().getName() + (lookupTokenEndpointDefinitions(getToken()).size() > 1 ? "Via" + capitalize(protocol()) : ""); syn String SendTokenEndpointDefinition.sendTopic() = "_topic_" + getToken().getName(); syn String SendTokenEndpointDefinition.lastValue() = "_lastValue" + getToken().getName(); syn String SendTokenEndpointDefinition.updateMethod() = "_update_" + getToken().getName(); @@ -237,6 +237,7 @@ aspect AspectGeneration { syn String DependencyDefinition.internalRelationPrefix() = "_internal_" + getID(); syn String DependencyDefinition.internalTokenName() = getSource().internalName(); + // -- MQTT names -- syn String RagConnect.mqttHandlerAttribute() = "_mqttHandler"; syn String RagConnect.mqttHandlerField() = "_mqttHandler"; @@ -244,15 +245,40 @@ aspect AspectGeneration { syn String RagConnect.mqttWaitUntilReadyMethod() = "MqttWaitUntilReady"; syn String RagConnect.mqttCloseMethod() = "MqttCloseConnections"; + // -- REST names -- + syn String RagConnect.restSetPortMethod() = "RestSetPort"; + syn String RagConnect.restCloseMethod() = "RestCloseConnections"; + syn String RagConnect.restHandlerAttribute() = "_restHandler"; syn String RagConnect.restHandlerField() = "_restHandler"; + // -- endpoint names -- syn String TokenEndpointDefinition.handlerAttribute(); eq ReceiveFromMqttDefinition.handlerAttribute() = mqttHandlerAttribute(); eq SendToMqttDefinition.handlerAttribute() = mqttHandlerAttribute(); eq ReceiveFromRestDefinition.handlerAttribute() = restHandlerAttribute(); eq SendToRestDefinition.handlerAttribute() = restHandlerAttribute(); + syn String TokenEndpointDefinition.newConnectionMethod(); + eq ReceiveFromMqttDefinition.newConnectionMethod() = "newConnection"; + eq SendToMqttDefinition.newConnectionMethod() = null; + eq ReceiveFromRestDefinition.newConnectionMethod() = "newPUTConnection"; + eq SendToRestDefinition.newConnectionMethod() = "newGETConnection"; + + syn String TokenEndpointDefinition.connectParameterName(); + eq ReceiveFromMqttDefinition.connectParameterName() = "topic"; + eq SendToMqttDefinition.connectParameterName() = "topic"; + eq ReceiveFromRestDefinition.connectParameterName() = "path"; + eq SendToRestDefinition.connectParameterName() = "path"; + + syn String SendTokenEndpointDefinition.preemptiveReturn(); + eq SendToMqttDefinition.preemptiveReturn() = "return false;"; + eq SendToRestDefinition.preemptiveReturn() = "throw e;"; // e is Exception variable + + syn boolean SendTokenEndpointDefinition.isPush(); + eq SendToMqttDefinition.isPush() = true; + eq SendToRestDefinition.isPush() = false; + // naming copy attributes // --- mqttHandlerAttribute --- inh String EndpointDefinition.mqttHandlerAttribute(); diff --git a/ragconnect.base/src/main/jastadd/backend/Mappings.jrag b/ragconnect.base/src/main/jastadd/backend/Mappings.jrag index d5f79046be4271f93a954061c28978aa19ba63b9..0410cc289d9e69fc27b343ac162cdd6b60bcb9d5 100644 --- a/ragconnect.base/src/main/jastadd/backend/Mappings.jrag +++ b/ragconnect.base/src/main/jastadd/backend/Mappings.jrag @@ -1,25 +1,7 @@ aspect DefaultMappings { - private DefaultMappingDefinition RagConnect.baseDefaultMappingDefinitionFromBytes(String typeName) { - DefaultMappingDefinition result = new DefaultMappingDefinition(); - result.setID("_DefaultBytesTo" + Character.toUpperCase(typeName.charAt(0)) + typeName.substring(1) + "Mapping"); - result.setFromType(new JavaArrayMappingDefinitionType(new SimpleJavaTypeUse("byte"))); - result.setFromVariableName("bytes"); - result.setToType(new JavaMappingDefinitionType(new SimpleJavaTypeUse(typeName))); - return result; - } - - private DefaultMappingDefinition RagConnect.baseDefaultMappingDefinitionToBytes(String typeName) { - DefaultMappingDefinition result = new DefaultMappingDefinition(); - result.setID("_Default" + Character.toUpperCase(typeName.charAt(0)) + typeName.substring(1) + "ToBytesMapping"); - result.setFromType(new JavaMappingDefinitionType(new SimpleJavaTypeUse(typeName))); - result.setFromVariableName("input"); - result.setToType(new JavaArrayMappingDefinitionType(new SimpleJavaTypeUse("byte"))); - return result; - } - private String RagConnect.baseDefaultMappingTypeNamePart(String typeName) { - return Character.toUpperCase(typeName.charAt(0)) + typeName.substring(1).replace("[]", "s"); + return capitalize(typeName).replace("[]", "s"); } private MappingDefinitionType RagConnect.baseDefaultMappingTypeFromName(String typeName) { @@ -38,103 +20,61 @@ aspect DefaultMappings { return result; } - syn nta DefaultMappingDefinition RagConnect.defaultBytesToIntMapping() { - DefaultMappingDefinition result = baseDefaultMappingDefinitionFromBytes("int"); - result.setContent("return java.nio.ByteBuffer.wrap(bytes).getInt();"); - return result; - } + syn nta DefaultMappingDefinition RagConnect.defaultBytesToIntMapping() = baseDefaultMappingDefinition( + "byte[]", "int", "return java.nio.ByteBuffer.wrap(input).getInt();"); + syn nta DefaultMappingDefinition RagConnect.defaultBytesToShortMapping() = baseDefaultMappingDefinition( + "byte[]", "short", "return java.nio.ByteBuffer.wrap(input).getShort();"); + syn nta DefaultMappingDefinition RagConnect.defaultBytesToLongMapping() = baseDefaultMappingDefinition( + "byte[]", "long", "return java.nio.ByteBuffer.wrap(input).getLong();"); + syn nta DefaultMappingDefinition RagConnect.defaultBytesToFloatMapping() = baseDefaultMappingDefinition( + "byte[]", "float", "return java.nio.ByteBuffer.wrap(input).getFloat();"); + syn nta DefaultMappingDefinition RagConnect.defaultBytesToDoubleMapping() = baseDefaultMappingDefinition( + "byte[]", "double", "return java.nio.ByteBuffer.wrap(input).getDouble();"); + syn nta DefaultMappingDefinition RagConnect.defaultBytesToCharMapping() = baseDefaultMappingDefinition( + "byte[]", "char", "return java.nio.ByteBuffer.wrap(input).getChar();"); + syn nta DefaultMappingDefinition RagConnect.defaultBytesToStringMapping() = baseDefaultMappingDefinition( + "byte[]", "String", "return new String(input);"); - syn nta DefaultMappingDefinition RagConnect.defaultBytesToShortMapping() { - DefaultMappingDefinition result = baseDefaultMappingDefinitionFromBytes("short"); - result.setContent("return java.nio.ByteBuffer.wrap(bytes).getShort();"); - return result; - } - - syn nta DefaultMappingDefinition RagConnect.defaultBytesToLongMapping() { - DefaultMappingDefinition result = baseDefaultMappingDefinitionFromBytes("long"); - result.setContent("return java.nio.ByteBuffer.wrap(bytes).getLong();"); - return result; - } - - syn nta DefaultMappingDefinition RagConnect.defaultBytesToFloatMapping() { - DefaultMappingDefinition result = baseDefaultMappingDefinitionFromBytes("float"); - result.setContent("return java.nio.ByteBuffer.wrap(bytes).getFloat();"); - return result; - } - - syn nta DefaultMappingDefinition RagConnect.defaultBytesToDoubleMapping() { - DefaultMappingDefinition result = baseDefaultMappingDefinitionFromBytes("double"); - result.setContent("return java.nio.ByteBuffer.wrap(bytes).getDouble();"); - return result; - } - - syn nta DefaultMappingDefinition RagConnect.defaultBytesToCharMapping() { - DefaultMappingDefinition result = baseDefaultMappingDefinitionFromBytes("char"); - result.setContent("return java.nio.ByteBuffer.wrap(bytes).getChar();"); - return result; - } - - syn nta DefaultMappingDefinition RagConnect.defaultBytesToStringMapping() { - DefaultMappingDefinition result = baseDefaultMappingDefinitionFromBytes("String"); - result.setContent("return new String(bytes);"); - return result; - } - - syn nta DefaultMappingDefinition RagConnect.defaultIntToBytesMapping() { - DefaultMappingDefinition result = baseDefaultMappingDefinitionToBytes("int"); - result.setContent("return java.nio.ByteBuffer.allocate(4).putInt(input).array();"); - return result; - } - - syn nta DefaultMappingDefinition RagConnect.defaultShortToBytesMapping() { - DefaultMappingDefinition result = baseDefaultMappingDefinitionToBytes("short"); - result.setContent("return java.nio.ByteBuffer.allocate(2).putShort(input).array();"); - return result; - } - - syn nta DefaultMappingDefinition RagConnect.defaultLongToBytesMapping() { - DefaultMappingDefinition result = baseDefaultMappingDefinitionToBytes("long"); - result.setContent("return java.nio.ByteBuffer.allocate(8).putLong(input).array();"); - return result; - } - - syn nta DefaultMappingDefinition RagConnect.defaultFloatToBytesMapping() { - DefaultMappingDefinition result = baseDefaultMappingDefinitionToBytes("float"); - result.setContent("return java.nio.ByteBuffer.allocate(4).putFloat(input).array();"); - return result; - } - - syn nta DefaultMappingDefinition RagConnect.defaultDoubleToBytesMapping() { - DefaultMappingDefinition result = baseDefaultMappingDefinitionToBytes("double"); - result.setContent("return java.nio.ByteBuffer.allocate(8).putDouble(input).array();"); - return result; - } - - syn nta DefaultMappingDefinition RagConnect.defaultCharToBytesMapping() { - DefaultMappingDefinition result = baseDefaultMappingDefinitionToBytes("char"); - result.setContent("return java.nio.ByteBuffer.allocate(2).putChar(input).array();"); - return result; - } - - syn nta DefaultMappingDefinition RagConnect.defaultStringToBytesMapping() { - DefaultMappingDefinition result = baseDefaultMappingDefinitionToBytes("String"); - result.setContent("return input.getBytes();"); - return result; - } + syn nta DefaultMappingDefinition RagConnect.defaultIntToBytesMapping() = baseDefaultMappingDefinition( + "int", "byte[]", "return java.nio.ByteBuffer.allocate(Integer.BYTES).putInt(input).array();"); + syn nta DefaultMappingDefinition RagConnect.defaultShortToBytesMapping() = baseDefaultMappingDefinition( + "short", "byte[]", "return java.nio.ByteBuffer.allocate(Short.BYTES).putShort(input).array();"); + syn nta DefaultMappingDefinition RagConnect.defaultLongToBytesMapping() = baseDefaultMappingDefinition( + "long", "byte[]", "return java.nio.ByteBuffer.allocate(Long.BYTES).putLong(input).array();"); + syn nta DefaultMappingDefinition RagConnect.defaultFloatToBytesMapping() = baseDefaultMappingDefinition( + "float", "byte[]", "return java.nio.ByteBuffer.allocate(Float.BYTES).putFloat(input).array();"); + syn nta DefaultMappingDefinition RagConnect.defaultDoubleToBytesMapping() = baseDefaultMappingDefinition( + "double", "byte[]", "return java.nio.ByteBuffer.allocate(Double.BYTES).putDouble(input).array();"); + syn nta DefaultMappingDefinition RagConnect.defaultCharToBytesMapping() = baseDefaultMappingDefinition( + "char", "byte[]", "return java.nio.ByteBuffer.allocate(Character.BYTES).putChar(input).array();"); + syn nta DefaultMappingDefinition RagConnect.defaultStringToBytesMapping() = baseDefaultMappingDefinition( + "String", "byte[]", "return input.getBytes();"); - syn nta DefaultMappingDefinition RagConnect.defaultStringToIntMapping() = baseDefaultMappingDefinition("String", "int", "return Integer.parseInteger(input);"); - syn nta DefaultMappingDefinition RagConnect.defaultStringToShortMapping() = baseDefaultMappingDefinition("String", "short", "return Short.parseShort(input);"); - syn nta DefaultMappingDefinition RagConnect.defaultStringToLongMapping() = baseDefaultMappingDefinition("String", "long", "return Long.parseLong(input);"); - syn nta DefaultMappingDefinition RagConnect.defaultStringToFloatMapping() = baseDefaultMappingDefinition("String", "float", "return Float.parseFloat(input);"); - syn nta DefaultMappingDefinition RagConnect.defaultStringToDoubleMapping() = baseDefaultMappingDefinition("String", "double", "return Double.parseDouble(input);"); - syn nta DefaultMappingDefinition RagConnect.defaultStringToCharMapping() = baseDefaultMappingDefinition("String", "char", "return input.charAt(0);"); + syn nta DefaultMappingDefinition RagConnect.defaultStringToIntMapping() = baseDefaultMappingDefinition( + "String", "int", "return Integer.parseInt(input);"); + syn nta DefaultMappingDefinition RagConnect.defaultStringToShortMapping() = baseDefaultMappingDefinition( + "String", "short", "return Short.parseShort(input);"); + syn nta DefaultMappingDefinition RagConnect.defaultStringToLongMapping() = baseDefaultMappingDefinition( + "String", "long", "return Long.parseLong(input);"); + syn nta DefaultMappingDefinition RagConnect.defaultStringToFloatMapping() = baseDefaultMappingDefinition( + "String", "float", "return Float.parseFloat(input);"); + syn nta DefaultMappingDefinition RagConnect.defaultStringToDoubleMapping() = baseDefaultMappingDefinition( + "String", "double", "return Double.parseDouble(input);"); + syn nta DefaultMappingDefinition RagConnect.defaultStringToCharMapping() = baseDefaultMappingDefinition( + "String", "char", "return input.charAt(0);"); - syn nta DefaultMappingDefinition RagConnect.defaultIntToStringMapping() = baseDefaultMappingDefinition("int", "String", "return String.valueOf(input);"); - syn nta DefaultMappingDefinition RagConnect.defaultShortToStringMapping() = baseDefaultMappingDefinition("int", "String", "return String.valueOf(input);"); - syn nta DefaultMappingDefinition RagConnect.defaultLongToStringMapping() = baseDefaultMappingDefinition("int", "String", "return String.valueOf(input);"); - syn nta DefaultMappingDefinition RagConnect.defaultFloatToStringMapping() = baseDefaultMappingDefinition("int", "String", "return String.valueOf(input);"); - syn nta DefaultMappingDefinition RagConnect.defaultDoubleToStringMapping() = baseDefaultMappingDefinition("int", "String", "return String.valueOf(input);"); - syn nta DefaultMappingDefinition RagConnect.defaultCharToStringMapping() = baseDefaultMappingDefinition("int", "String", "return String.valueOf(input);"); + syn nta DefaultMappingDefinition RagConnect.defaultIntToStringMapping() = baseDefaultMappingDefinition( + "int", "String", "return String.valueOf(input);"); + syn nta DefaultMappingDefinition RagConnect.defaultShortToStringMapping() = baseDefaultMappingDefinition( + "short", "String", "return String.valueOf(input);"); + syn nta DefaultMappingDefinition RagConnect.defaultLongToStringMapping() = baseDefaultMappingDefinition( + "long", "String", "return String.valueOf(input);"); + syn nta DefaultMappingDefinition RagConnect.defaultFloatToStringMapping() = baseDefaultMappingDefinition( + "float", "String", "return String.valueOf(input);"); + syn nta DefaultMappingDefinition RagConnect.defaultDoubleToStringMapping() = baseDefaultMappingDefinition( + "double", "String", "return String.valueOf(input);"); + syn nta DefaultMappingDefinition RagConnect.defaultCharToStringMapping() = baseDefaultMappingDefinition( + "char", "String", "return String.valueOf(input);"); } aspect Mappings { @@ -300,7 +240,7 @@ aspect Mappings { syn java.util.List<MappingDefinition> RagConnect.allMappingDefinitions() { java.util.List<MappingDefinition> result = new java.util.ArrayList<>(); getMappingDefinitionList().iterator().forEachRemaining(result::add); - // byte[] converstion + // byte[] conversion result.add(defaultBytesToIntMapping()); result.add(defaultBytesToShortMapping()); result.add(defaultBytesToLongMapping()); @@ -315,7 +255,7 @@ aspect Mappings { result.add(defaultDoubleToBytesMapping()); result.add(defaultCharToBytesMapping()); result.add(defaultStringToBytesMapping()); - // string converstion + // string conversion result.add(defaultStringToIntMapping()); result.add(defaultStringToShortMapping()); result.add(defaultStringToLongMapping()); diff --git a/ragconnect.base/src/main/jastadd/parser/RagConnect.parser b/ragconnect.base/src/main/jastadd/parser/RagConnect.parser index 0f897afc1992544d7736ccee4d884b934e7f0227..56a8dc4cc7f3396e778a529e981c1aefb302e18b 100644 --- a/ragconnect.base/src/main/jastadd/parser/RagConnect.parser +++ b/ragconnect.base/src/main/jastadd/parser/RagConnect.parser @@ -10,6 +10,10 @@ RagConnect ragconnect private Iterable<String> makeMappingDefs(ArrayList<?> raw_mapping_defs) { return () -> raw_mapping_defs.stream().map(raw -> ((Symbol) raw).value.toString()).iterator(); } + private TokenEndpointDefinition enableAlwaysApply(TokenEndpointDefinition def) { + def.setAlwaysApply(true); + return def; + } :} ; EndpointDefinition endpoint_definition @@ -29,10 +33,10 @@ EndpointDefinition endpoint_definition EndpointDefinition endpoint_definition_type = RECEIVE token_ref {: return new ReceiveFromMqttDefinition().setToken(token_ref); :} | RECEIVE token_ref VIA MQTT {: return new ReceiveFromMqttDefinition().setToken(token_ref); :} - | RECEIVE token_ref VIA REST {: return new ReceiveFromRestDefinition().setToken(token_ref); :} + | RECEIVE token_ref VIA REST {: return enableAlwaysApply(new ReceiveFromRestDefinition()).setToken(token_ref); :} | SEND token_ref {: return new SendToMqttDefinition().setToken(token_ref); :} | SEND token_ref VIA MQTT {: return new SendToMqttDefinition().setToken(token_ref); :} - | SEND token_ref VIA REST {: return new SendToRestDefinition().setToken(token_ref); :} + | SEND token_ref VIA REST {: return enableAlwaysApply(new SendToRestDefinition()).setToken(token_ref); :} ; TokenComponent token_ref diff --git a/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java b/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java index 8b33412c7994b5443088de6249273b9b83374414..5160ab6611f8a8f177555e55e511153b95a9659b 100644 --- a/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java +++ b/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java @@ -14,9 +14,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; -import java.util.Collection; -import java.util.MissingResourceException; -import java.util.ResourceBundle; +import java.util.*; public class Compiler extends AbstractCompiler { @@ -62,18 +60,26 @@ public class Compiler extends AbstractCompiler { } printMessage("Writing output files"); - // copy MqttHandler into outputDir - final String mqttHandlerFileName = "MqttHandler.jadd"; - try { - InputStream inputStream = Compiler.class.getClassLoader().getResourceAsStream(mqttHandlerFileName); - if (inputStream == null) { - throw new CompilerException("Could not open " + mqttHandlerFileName); + final List<String> handlers = new ArrayList<>(); + if (ragConnect.usesMqtt()) { + handlers.add("MqttHandler.jadd"); + } + if (ragConnect.usesRest()) { + handlers.add("RestHandler.jadd"); + } + // copy handlers into outputDir + for (String handlerFileName : handlers) { + try { + InputStream inputStream = Compiler.class.getClassLoader().getResourceAsStream(handlerFileName); + if (inputStream == null) { + throw new CompilerException("Could not open " + handlerFileName); + } + Files.copy(inputStream, + getConfiguration().outputDir().toPath().resolve(handlerFileName), + StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + throw new CompilerException("Could not copy " + handlerFileName, e); } - Files.copy(inputStream, - getConfiguration().outputDir().toPath().resolve(mqttHandlerFileName), - StandardCopyOption.REPLACE_EXISTING); - } catch (IOException e) { - throw new CompilerException("Could not copy " + mqttHandlerFileName, e); } for (GrammarFile grammarFile : ragConnect.getProgram().getGrammarFileList()) { Path outputFile = getConfiguration().outputDir().toPath().resolve(grammarFile.getFileName()); @@ -90,7 +96,7 @@ public class Compiler extends AbstractCompiler { new Compiler().run(args); } catch (CompilerException e) { System.err.println(e.getMessage()); - System.exit(-1); + System.exit(1); } } @@ -162,6 +168,7 @@ public class Compiler extends AbstractCompiler { parseGrammar(program, filename); break; case "connect": + case "ragconnect": // process ragConnect RagConnect parsedRagConnect = parseRagConnect(program, filename); mergeRagConnectDefinitions(ragConnect, parsedRagConnect); diff --git a/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/SimpleMain.java b/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/SimpleMain.java index ac1109fef423ed58ebbbdfcaeacbdca42745e341..9da13bf479b6fe16be8c935b06cf1e9ad3283308 100644 --- a/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/SimpleMain.java +++ b/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/SimpleMain.java @@ -21,6 +21,17 @@ public class SimpleMain { // --- just testing byte[] conversion --- public static void testing() { + System.out.println("---"); + try { + Class<?> clazz = Class.forName("java.util.List"); + System.out.println("clazz.getName() = " + clazz.getName()); + System.out.println(Integer.class.isAssignableFrom(Integer.class)); + System.out.println(new SimpleJavaTypeUse("int").assignableTo(new SimpleJavaTypeUse("Integer"))); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + System.out.println("---"); + byte[] bytes; int i = 1; double d = 2.3d; @@ -102,8 +113,8 @@ public class SimpleMain { } public static void main(String[] args) { -// testing(); - createManualAST(); + testing(); +// createManualAST(); } private static void createManualAST() { diff --git a/ragconnect.base/src/main/resources/MqttHandler.jadd b/ragconnect.base/src/main/resources/MqttHandler.jadd index 493d8e4452e81c5dbfa45a522a6366345908cdcc..1161211ffb0b90eaf26c7d27d709d71f7a6369c8 100644 --- a/ragconnect.base/src/main/resources/MqttHandler.jadd +++ b/ragconnect.base/src/main/resources/MqttHandler.jadd @@ -83,7 +83,7 @@ public class MqttHandler { String topicString = topic.toString(); java.util.List<java.util.function.Consumer<byte[]>> callbackList = callbacks.get(topicString); if (callbackList == null || callbackList.isEmpty()) { - logger.debug("Got a message, but no callback to call. Forgot to unsubscribe?"); + logger.debug("Got a message, but no callback to call. Forgot to subscribe?"); } else { byte[] message = body.toByteArray(); // System.out.println("message = " + Arrays.toString(message)); diff --git a/ragconnect.base/src/main/resources/RestHandler.jadd b/ragconnect.base/src/main/resources/RestHandler.jadd new file mode 100644 index 0000000000000000000000000000000000000000..24118f04c563fe0b44105bfe674e269c76e7466f --- /dev/null +++ b/ragconnect.base/src/main/resources/RestHandler.jadd @@ -0,0 +1,99 @@ +aspect RestHandler { +/** + * Helper class to receive updates and publishes information via REST. + * @author rschoene - Initial contribution + */ +public class RestHandler { + private static final int DEFAULT_PORT = 4567; + + private final org.apache.logging.log4j.Logger logger; + private final String name; + private int port; + private final java.util.concurrent.CountDownLatch exitCondition; + /** 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; + + public RestHandler() { + this("RagConnect"); + } + + public RestHandler(String name) { + this.logger = org.apache.logging.log4j.LogManager.getLogger(RestHandler.class); + this.name = name; + this.port = DEFAULT_PORT; + this.exitCondition = new java.util.concurrent.CountDownLatch(1); + this.callbacks = new java.util.HashMap<>(); + this.suppliers = new java.util.HashMap<>(); + } + + public RestHandler setPort(int port) { + this.port = port; + start(); + return this; + } + + public void newPUTConnection(String path, java.util.function.Consumer<String> 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<>(); + callbackList.add(callback); + callbacks.put(path, callbackList); + spark.Spark.put(path, (request, response) -> { + String content = request.body(); + java.util.Set<String> errors = new java.util.HashSet<>(); + for (java.util.function.Consumer<String> f : callbackList) { + try { + f.accept(content); + } catch (Exception e) { + errors.add(e.getMessage()); + } + } + if (errors.isEmpty()) { + return "OK"; + } else { + return makeError(response, 500, errors.stream().collect(java.util.stream.Collectors.joining("\n", "The folloing errors happened: [", "]"))); + } + }); + } + } + + public void newGETConnection(String path, SupplierWithException<String> supplier) { + if (suppliers.get(path) != null) { + logger.warn("Overriding existing supplier for '{}'", path); + } + suppliers.put(path, supplier); + spark.Spark.get(path, (request, response) -> { + try { + return supplier.get(); + } catch (Exception e) { + return makeError(response, 500, e.getMessage()); + } + }); + } + + private String makeError(spark.Response response, int statusCode, String message) { + response.status(statusCode); + return message; + } + + public void start() { + logger.info("Starting REST server at {}", this.port); + spark.Spark.port(this.port); + spark.Spark.init(); + spark.Spark.awaitInitialization(); + } + + public void close() { + spark.Spark.stop(); + spark.Spark.awaitStop(); + } + +} +@FunctionalInterface +public interface SupplierWithException<T> { + public T get() throws Exception; +} +} diff --git a/ragconnect.base/src/main/resources/mappingApplication.mustache b/ragconnect.base/src/main/resources/mappingApplication.mustache index 95ce2fe29329d87f880497b37697d50c8c0687be..cbfac221a210037dce1f9881699edad5c68776a2 100644 --- a/ragconnect.base/src/main/resources/mappingApplication.mustache +++ b/ragconnect.base/src/main/resources/mappingApplication.mustache @@ -1,7 +1,7 @@ {{lastDefinitionToType}} {{resultVarPrefix}}{{lastDefinitionName}}; try { {{#InnerMappingDefinitions}} - {{^last}}{{ToType}} {{/last}}{{resultVarPrefix}}{{methodName}} = {{methodName}}({{inputVarName}});{{!inputVarName has to be computed beforehand}} + {{^last}}{{ToType}} {{/last}}{{resultVarPrefix}}{{methodName}} = {{methodName}}({{inputVarName}}); {{/InnerMappingDefinitions}} } catch (Exception e) { e.printStackTrace(); diff --git a/ragconnect.base/src/main/resources/mqtt.mustache b/ragconnect.base/src/main/resources/mqtt.mustache index dbdb85e7954dbf50ae7e150ffc43dd4cbda0fd80..95b253a9b4b4e46fda738a66499b8e2a7cc3707d 100644 --- a/ragconnect.base/src/main/resources/mqtt.mustache +++ b/ragconnect.base/src/main/resources/mqtt.mustache @@ -1,5 +1,5 @@ aspect MQTT { - private String {{rootNodeName}}.MqttName() { return "Ros2Rag"; } + private String {{rootNodeName}}.MqttName() { return "RagConnectMQTT"; } private MqttHandler {{rootNodeName}}.{{mqttHandlerField}} = new MqttHandler(MqttName()); public void {{rootNodeName}}.{{mqttSetHostMethod}}(String host) throws java.io.IOException { {{mqttHandlerField}}.setHost(host); @@ -20,4 +20,7 @@ aspect MQTT { {{#first}}inh MqttHandler ASTNode.{{mqttHandlerAttribute}}();{{/first}} eq {{rootNodeName}}.get{{name}}().{{mqttHandlerAttribute}}() = {{mqttHandlerField}}; {{/getRootTypeComponents}} + {{^getRootTypeComponents}} + syn MqttHandler {{rootNodeName}}.{{mqttHandlerAttribute}}() = {{mqttHandlerField}}; + {{/getRootTypeComponents}} } diff --git a/ragconnect.base/src/main/resources/ragconnect.mustache b/ragconnect.base/src/main/resources/ragconnect.mustache index 0b455283977cf812a47b5266ee593c6597e97cc9..24e25418d07766f0ac881683080a622687ceff8e 100644 --- a/ragconnect.base/src/main/resources/ragconnect.mustache +++ b/ragconnect.base/src/main/resources/ragconnect.mustache @@ -1,12 +1,21 @@ -{{> mqtt}} +{{#usesMqtt}} + {{> mqtt}} +{{/usesMqtt}} +{{#usesRest}} + {{> rest}} +{{/usesRest}} aspect ROS2RAG { {{#ReceiveDefinitions}} {{> receiveDefinition}} {{/ReceiveDefinitions}} - {{#SendDefinitions}} - {{> sendDefinition}} - {{/SendDefinitions}} + {{#PushSendDefinitions}} + {{> sendDefinitionPush}} + {{/PushSendDefinitions}} + + {{#PullSendDefinitions}} + {{> sendDefinitionPull}} + {{/PullSendDefinitions}} {{#MappingDefinitions}} {{> mappingDefinition}} diff --git a/ragconnect.base/src/main/resources/receiveDefinition.mustache b/ragconnect.base/src/main/resources/receiveDefinition.mustache index c02f5e5f49f6a9bebf0f6a1e7498ec743024afb3..31a08c737349b6c2d16f14a0fab50065d2c01a35 100644 --- a/ragconnect.base/src/main/resources/receiveDefinition.mustache +++ b/ragconnect.base/src/main/resources/receiveDefinition.mustache @@ -1,8 +1,8 @@ - public void {{parentTypeName}}.{{connectMethod}}(String topic) { - {{handlerAttribute}}().newConnection(topic, message -> { + public void {{parentTypeName}}.{{connectMethod}}(String {{connectParameterName}}) { + {{handlerAttribute}}().{{newConnectionMethod}}({{connectParameterName}}, message -> { {{> mappingApplication}} {{#loggingEnabledForReads}} - System.out.println("[Receive] " + topic + " -> {{tokenName}} = " + {{lastResult}}); + System.out.println("[Receive] " + {{connectParameterName}} + " -> {{tokenName}} = " + {{lastResult}}); {{/loggingEnabledForReads}} set{{tokenName}}({{lastResult}}); }); diff --git a/ragconnect.base/src/main/resources/rest.mustache b/ragconnect.base/src/main/resources/rest.mustache new file mode 100644 index 0000000000000000000000000000000000000000..fb77601c38467e0f3f1bd1f01993db410670ce16 --- /dev/null +++ b/ragconnect.base/src/main/resources/rest.mustache @@ -0,0 +1,18 @@ +aspect REST { + private String {{rootNodeName}}.RestName() { return "RagConnectREST"; } + private RestHandler {{rootNodeName}}.{{restHandlerField}} = new RestHandler(RestName()); + public void {{rootNodeName}}.{{restSetPortMethod}}(int port) { + {{restHandlerField}}.setPort(port); + } + public void {{rootNodeName}}.{{restCloseMethod}}() { + {{restHandlerField}}.close(); + } + + {{#getRootTypeComponents}} + {{#first}}inh RestHandler ASTNode.{{restHandlerAttribute}}();{{/first}} + eq {{rootNodeName}}.get{{name}}().{{restHandlerAttribute}}() = {{restHandlerField}}; + {{/getRootTypeComponents}} + {{^getRootTypeComponents}} + syn RestHandler {{rootNodeName}}.{{restHandlerAttribute}}() = {{restHandlerField}}; + {{/getRootTypeComponents}} +} diff --git a/ragconnect.base/src/main/resources/sendDefinitionPull.mustache b/ragconnect.base/src/main/resources/sendDefinitionPull.mustache new file mode 100644 index 0000000000000000000000000000000000000000..598846d86f9fc67167de7896f42c30cd0cfc3360 --- /dev/null +++ b/ragconnect.base/src/main/resources/sendDefinitionPull.mustache @@ -0,0 +1,7 @@ + public void {{parentTypeName}}.{{connectMethod}}(String {{connectParameterName}}, boolean writeCurrentValue) { + {{handlerAttribute}}().{{newConnectionMethod}}({{connectParameterName}}, () -> { + {{tokenResetMethod}}(); + {{> mappingApplication}} + return {{lastResult}}; + }); + } diff --git a/ragconnect.base/src/main/resources/sendDefinition.mustache b/ragconnect.base/src/main/resources/sendDefinitionPush.mustache similarity index 80% rename from ragconnect.base/src/main/resources/sendDefinition.mustache rename to ragconnect.base/src/main/resources/sendDefinitionPush.mustache index 874a342512326434d365563f5c1de93a609c5029..3f75b18274d687d306f7778f9846aeca592fa86f 100644 --- a/ragconnect.base/src/main/resources/sendDefinition.mustache +++ b/ragconnect.base/src/main/resources/sendDefinitionPush.mustache @@ -1,8 +1,8 @@ private String {{parentTypeName}}.{{sendTopic}} = null; private byte[] {{parentTypeName}}.{{lastValue}} = null; - public void {{parentTypeName}}.{{connectMethod}}(String topic, boolean writeCurrentValue) { - {{sendTopic}} = topic; + public void {{parentTypeName}}.{{connectMethod}}(String {{connectParameterName}}, boolean writeCurrentValue) { + {{sendTopic}} = {{connectParameterName}}; {{updateMethod}}(); if (writeCurrentValue) { {{writeMethod}}(); diff --git a/ragconnect.base/src/main/resources/tokenComponent.mustache b/ragconnect.base/src/main/resources/tokenComponent.mustache index e6a136d1c8de08b6dce9104fd46fafa5d91b67b6..729f442aebfc67ee2bbf5e559a9946405a275260 100644 --- a/ragconnect.base/src/main/resources/tokenComponent.mustache +++ b/ragconnect.base/src/main/resources/tokenComponent.mustache @@ -3,9 +3,11 @@ {{#DependencyDefinitions}} for ({{targetParentTypeName}} target : get{{internalRelationPrefix}}TargetList()) { {{#targetEndpointDefinition}} + {{#isPush}} if (target.{{updateMethod}}()) { target.{{writeMethod}}(); } + {{/isPush}} {{/targetEndpointDefinition}} } {{/DependencyDefinitions}} diff --git a/ragconnect.tests/build.gradle b/ragconnect.tests/build.gradle index 4cb42ea9965f71777280c355df42a8cde31ab47d..96b3d74270fae3c825621c5e5d199b6559105510 100644 --- a/ragconnect.tests/build.gradle +++ b/ragconnect.tests/build.gradle @@ -25,7 +25,16 @@ dependencies { testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.4.0' testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.4.0' testImplementation group: 'org.assertj', name: 'assertj-core', version: '3.12.1' + + // mqtt testImplementation group: 'org.fusesource.mqtt-client', name: 'mqtt-client', version: '1.15' + + // rest and client + testImplementation group: 'com.sparkjava', name: 'spark-core', version: '2.9.2' + testImplementation group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: '2.11.2' + testImplementation group: 'org.glassfish.jersey.core', name: 'jersey-client', version: '2.31' + testImplementation group: 'org.glassfish.jersey.inject', name: 'jersey-hk2', version: '2.31' + testImplementation group: 'net.sf.beaver', name: 'beaver-rt', version: '0.9.11' api group: 'com.google.protobuf', name: 'protobuf-java', version: '3.0.0' } @@ -97,7 +106,7 @@ task compileExampleTest(type: RelastTest) { 'src/test/02-after-ragconnect/example/RagConnect.jadd' } -testClasses.dependsOn compileExampleTest +compileTestJava.dependsOn compileExampleTest compileExampleTest.dependsOn preprocessExampleTest // --- Test: default-only-read --- @@ -113,8 +122,7 @@ task preprocessDefaultOnlyReadTest(type: JavaExec, group: 'verification') { args '--o=src/test/02-after-ragconnect/defaultOnlyRead', 'src/test/01-input/defaultOnlyRead/Test.relast', 'src/test/01-input/defaultOnlyRead/Test.connect', - '--rootNode=A', - '--verbose' + '--rootNode=A' } task compileDefaultOnlyReadTest(type: RelastTest) { @@ -128,7 +136,7 @@ task compileDefaultOnlyReadTest(type: RelastTest) { 'src/test/02-after-ragconnect/defaultOnlyRead/RagConnect.jadd' } -testClasses.dependsOn compileDefaultOnlyReadTest +compileTestJava.dependsOn compileDefaultOnlyReadTest compileDefaultOnlyReadTest.dependsOn preprocessDefaultOnlyReadTest // --- Test: default-only-write --- @@ -144,8 +152,7 @@ task preprocessDefaultOnlyWriteTest(type: JavaExec, group: 'verification') { args '--o=src/test/02-after-ragconnect/defaultOnlyWrite', 'src/test/01-input/defaultOnlyWrite/Test.relast', 'src/test/01-input/defaultOnlyWrite/Test.connect', - '--rootNode=A', - '--verbose' + '--rootNode=A' } task compileDefaultOnlyWriteTest(type: RelastTest) { @@ -160,7 +167,7 @@ task compileDefaultOnlyWriteTest(type: RelastTest) { 'src/test/02-after-ragconnect/defaultOnlyWrite/RagConnect.jadd' } -testClasses.dependsOn compileDefaultOnlyWriteTest +compileTestJava.dependsOn compileDefaultOnlyWriteTest compileDefaultOnlyWriteTest.dependsOn preprocessDefaultOnlyWriteTest // --- Test: read1write2 --- @@ -192,7 +199,7 @@ task compileRead1Write2Test(type: RelastTest) { 'src/test/02-after-ragconnect/read1write2/RagConnect.jadd' } -testClasses.dependsOn compileRead1Write2Test +compileTestJava.dependsOn compileRead1Write2Test compileRead1Write2Test.dependsOn preprocessRead1Write2Test // --- Test: read2write1 --- @@ -208,7 +215,7 @@ task preprocessRead2Write1Test(type: JavaExec, group: 'verification') { args '--o=src/test/02-after-ragconnect/read2write1', 'src/test/01-input/read2write1/Test.relast', 'src/test/01-input/read2write1/Test.connect', - '--rootNode=A', '--verbose', + '--rootNode=A', '--logReads', '--logWrites' } @@ -224,7 +231,7 @@ task compileRead2Write1Test(type: RelastTest) { 'src/test/02-after-ragconnect/read2write1/RagConnect.jadd' } -testClasses.dependsOn compileRead2Write1Test +compileTestJava.dependsOn compileRead2Write1Test compileRead2Write1Test.dependsOn preprocessRead2Write1Test // --- Test: via --- @@ -232,6 +239,7 @@ task preprocessViaTest(type: JavaExec, group: 'verification') { doFirst { delete 'src/test/02-after-ragconnect/via/Test.relast', 'src/test/02-after-ragconnect/via/MqttHandler.java', + 'src/test/02-after-ragconnect/via/RestHandler.java', 'src/test/02-after-ragconnect/via/RagConnect.jadd' } @@ -253,8 +261,9 @@ task compileViaTest(type: RelastTest) { packageName = 'via.ast' moreInputFiles 'src/test/01-input/via/Test.jadd', 'src/test/02-after-ragconnect/via/MqttHandler.jadd', + 'src/test/02-after-ragconnect/via/RestHandler.jadd', 'src/test/02-after-ragconnect/via/RagConnect.jadd' } -testClasses.dependsOn compileViaTest +compileTestJava.dependsOn compileViaTest compileViaTest.dependsOn preprocessViaTest diff --git a/ragconnect.tests/src/test/01-input/via/Test.connect b/ragconnect.tests/src/test/01-input/via/Test.connect index 2f39f088672c170a424dd1707bbbfcd24e169c4d..938490169415b2ce8f3da325ae2a1c768030921f 100644 --- a/ragconnect.tests/src/test/01-input/via/Test.connect +++ b/ragconnect.tests/src/test/01-input/via/Test.connect @@ -2,27 +2,33 @@ receive A.Mqtt2MqttInput via mqtt using MarkMqttInput ; receive A.Rest2RestInput via rest using MarkRestInput ; receive A.Mqtt2RestInput via mqtt using MarkMqttInput ; receive A.Rest2MqttInput via rest using MarkRestInput ; +receive A.Both2BothInput via mqtt using MarkMqttInput ; +receive A.Both2BothInput via rest using MarkRestInput ; send A.Mqtt2MqttOutput via mqtt using MarkMqttOutput ; send A.Rest2RestOutput via rest using MarkRestOutput ; send A.Mqtt2RestOutput via rest using MarkRestOutput ; send A.Rest2MqttOutput via mqtt using MarkMqttOutput ; +send A.Both2RestOutput via rest using MarkRestOutput ; +send A.Both2MqttOutput via mqtt using MarkMqttOutput ; A.Mqtt2MqttOutput canDependOn A.Mqtt2MqttInput as dependencyMqtt2Mqtt ; A.Rest2RestOutput canDependOn A.Rest2RestInput as dependencyRest2Rest ; A.Mqtt2RestOutput canDependOn A.Mqtt2RestInput as dependencyMqtt2Rest ; A.Rest2MqttOutput canDependOn A.Rest2MqttInput as dependencyRest2Mqtt ; +A.Both2RestOutput canDependOn A.Both2BothInput as dependencyBoth2Rest ; +A.Both2MqttOutput canDependOn A.Both2BothInput as dependencyBoth2Mqtt ; MarkMqttInput maps String s to String {: - return "FromMqtt" + s; + return "FromMqtt-" + s; :} MarkRestInput maps String s to String {: - return "FromRest" + s; + return "FromRest-" + s; :} MarkMqttOutput maps String s to String {: - return s + "ToMqtt"; + return s + "-ToMqtt"; :} MarkRestOutput maps String s to String {: - return s + "ToRest"; + return s + "-ToRest"; :} diff --git a/ragconnect.tests/src/test/01-input/via/Test.jadd b/ragconnect.tests/src/test/01-input/via/Test.jadd index 3c4313fb9b799f12d73ec1ea10279b579a5e388e..9d25387d26e651fe25a937a56a863311b4b7b69a 100644 --- a/ragconnect.tests/src/test/01-input/via/Test.jadd +++ b/ragconnect.tests/src/test/01-input/via/Test.jadd @@ -1,6 +1,8 @@ aspect Computation { - syn lazy String A.getMqtt2MqttOutput() = getMqtt2MqttInput() + "M2M" ; - syn lazy String A.getRest2RestOutput() = getRest2RestInput() + "R2R" ; - syn lazy String A.getMqtt2RestOutput() = getMqtt2RestInput() + "M2R" ; - syn lazy String A.getRest2MqttOutput() = getRest2MqttInput() + "R2M" ; + syn lazy String A.getMqtt2MqttOutput() = getMqtt2MqttInput() + "-M2M" ; + syn lazy String A.getRest2RestOutput() = getRest2RestInput() + "-R2R" ; + syn lazy String A.getMqtt2RestOutput() = getMqtt2RestInput() + "-M2R" ; + syn lazy String A.getRest2MqttOutput() = getRest2MqttInput() + "-R2M" ; + syn lazy String A.getBoth2MqttOutput() = getBoth2BothInput() + "-B2M" ; + syn lazy String A.getBoth2RestOutput() = getBoth2BothInput() + "-B2R" ; } diff --git a/ragconnect.tests/src/test/01-input/via/Test.relast b/ragconnect.tests/src/test/01-input/via/Test.relast index 6868ed853c2c06a60798db23c153deb200f6c647..1e707fbbc04b1204381351d0c38e826fcbcc8b80 100644 --- a/ragconnect.tests/src/test/01-input/via/Test.relast +++ b/ragconnect.tests/src/test/01-input/via/Test.relast @@ -1,4 +1,5 @@ A ::= <Mqtt2MqttInput> /<Mqtt2MqttOutput>/ <Rest2RestInput> /<Rest2RestOutput>/ <Mqtt2RestInput> /<Mqtt2RestOutput>/ - <Rest2MqttInput> /<Rest2MqttOutput>/; + <Rest2MqttInput> /<Rest2MqttOutput>/ + <Both2BothInput> /<Both2MqttOutput>/ /<Both2RestOutput>/; diff --git a/ragconnect.tests/src/test/java/org/jastadd/ros2rag/tests/AbstractMqttTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractMqttTest.java similarity index 95% rename from ragconnect.tests/src/test/java/org/jastadd/ros2rag/tests/AbstractMqttTest.java rename to ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractMqttTest.java index 7b7051348445d10305afb7618dedb0264adf9a8d..efaa302508d351f8d1fce557b49634527b54ac6e 100644 --- a/ragconnect.tests/src/test/java/org/jastadd/ros2rag/tests/AbstractMqttTest.java +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractMqttTest.java @@ -1,4 +1,4 @@ -package org.jastadd.ros2rag.tests; +package org.jastadd.ragconnect.tests; import defaultOnlyRead.ast.MqttHandler; import org.junit.jupiter.api.BeforeAll; diff --git a/ragconnect.tests/src/test/java/org/jastadd/ros2rag/tests/DefaultOnlyReadTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/DefaultOnlyReadTest.java similarity index 99% rename from ragconnect.tests/src/test/java/org/jastadd/ros2rag/tests/DefaultOnlyReadTest.java rename to ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/DefaultOnlyReadTest.java index 36795127d7a10763e4aceac43e90acb0680b4c15..8482c308f29b8381bc698efac91c69e2e94a8d9f 100644 --- a/ragconnect.tests/src/test/java/org/jastadd/ros2rag/tests/DefaultOnlyReadTest.java +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/DefaultOnlyReadTest.java @@ -1,4 +1,4 @@ -package org.jastadd.ros2rag.tests; +package org.jastadd.ragconnect.tests; import defaultOnlyRead.ast.A; import defaultOnlyRead.ast.BoxedTypes; diff --git a/ragconnect.tests/src/test/java/org/jastadd/ros2rag/tests/DefaultOnlyWriteTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/DefaultOnlyWriteTest.java similarity index 99% rename from ragconnect.tests/src/test/java/org/jastadd/ros2rag/tests/DefaultOnlyWriteTest.java rename to ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/DefaultOnlyWriteTest.java index df75fa7a2594a46556b4a099354ef7d69859bb4c..11fe3d9f240ea595d2c9c5825148c555272ba8cc 100644 --- a/ragconnect.tests/src/test/java/org/jastadd/ros2rag/tests/DefaultOnlyWriteTest.java +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/DefaultOnlyWriteTest.java @@ -1,4 +1,4 @@ -package org.jastadd.ros2rag.tests; +package org.jastadd.ragconnect.tests; import defaultOnlyWrite.ast.A; import defaultOnlyWrite.ast.BoxedTypesSyn; diff --git a/ragconnect.tests/src/test/java/org/jastadd/ros2rag/tests/Errors.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Errors.java similarity index 82% rename from ragconnect.tests/src/test/java/org/jastadd/ros2rag/tests/Errors.java rename to ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Errors.java index be45a8080cc251191c62fcbc3b1793129a45dee3..17d853fba71c2ee46c6c6c7b5424958af0a5f8d2 100644 --- a/ragconnect.tests/src/test/java/org/jastadd/ros2rag/tests/Errors.java +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Errors.java @@ -1,4 +1,4 @@ -package org.jastadd.ros2rag.tests; +package org.jastadd.ragconnect.tests; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -15,8 +15,8 @@ import java.util.Collections; import java.util.List; import java.util.stream.Collectors; -import static org.jastadd.ros2rag.tests.TestUtils.exec; -import static org.jastadd.ros2rag.tests.TestUtils.readFile; +import static org.jastadd.ragconnect.tests.TestUtils.exec; +import static org.jastadd.ragconnect.tests.TestUtils.readFile; import static org.junit.jupiter.api.Assertions.assertTrue; class Errors { @@ -24,7 +24,7 @@ class Errors { private static final Logger logger = LogManager.getLogger(Errors.class); private static final String FILENAME_PATTERN = "$FILENAME"; private static final String INPUT_DIRECTORY = "./src/test/01-input/errors/"; - private static final String OUTPUT_DIRECTORY = "./src/test/02-after-ros2rag/errors/"; + private static final String OUTPUT_DIRECTORY = "./src/test/02-after-ragconnect/errors/"; @BeforeAll public static void createOutputDirectory() { @@ -40,21 +40,21 @@ class Errors { @SuppressWarnings("SameParameterValue") private void test(String name, String rootNode) throws IOException { String grammarFile = INPUT_DIRECTORY + name + ".relast"; - String ros2ragFile = INPUT_DIRECTORY + name + ".ros2rag"; + String ragconnectFile = INPUT_DIRECTORY + name + ".connect"; String outFile = OUTPUT_DIRECTORY + name + ".out"; String expectedFile = INPUT_DIRECTORY + name + ".expected"; try { logger.debug("user.dir: {}", System.getProperty("user.dir")); String[] args = { - "--outputDir=" + OUTPUT_DIRECTORY, - "--inputGrammar=" + grammarFile, - "--inputRos2Rag=" + ros2ragFile, + "--o=" + OUTPUT_DIRECTORY, + grammarFile, + ragconnectFile, "--rootNode=" + rootNode, "--verbose" }; int returnValue = exec(Compiler.class, args, new File(outFile)); - Assertions.assertEquals(1, returnValue, "Ros2Rag did not return with value 1"); + Assertions.assertEquals(1, returnValue, "RagConnect did not return with value 1"); } catch (IOException | InterruptedException e) { e.printStackTrace(); } @@ -78,7 +78,7 @@ class Errors { // FIXME errors not handled correctly at the moment // Assertions.assertLinesMatch(expectedList, outList); - logger.info("ros2rag for " + name + " returned:\n{}", out); + logger.info("ragconnect for " + name + " returned:\n{}", out); } } diff --git a/ragconnect.tests/src/test/java/org/jastadd/ros2rag/tests/ExampleTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ExampleTest.java similarity index 99% rename from ragconnect.tests/src/test/java/org/jastadd/ros2rag/tests/ExampleTest.java rename to ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ExampleTest.java index 9b1ebceb541bddd4cafc3648b10c940af79bc952..1a58b683d55b0401485c85caca8105914e6cf67f 100644 --- a/ragconnect.tests/src/test/java/org/jastadd/ros2rag/tests/ExampleTest.java +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ExampleTest.java @@ -1,4 +1,4 @@ -package org.jastadd.ros2rag.tests; +package org.jastadd.ragconnect.tests; import com.google.protobuf.InvalidProtocolBufferException; import config.Config.RobotConfig; diff --git a/ragconnect.tests/src/test/java/org/jastadd/ros2rag/tests/Read1Write2Test.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Read1Write2Test.java similarity index 99% rename from ragconnect.tests/src/test/java/org/jastadd/ros2rag/tests/Read1Write2Test.java rename to ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Read1Write2Test.java index 4e21608a9cee5d8a2c3b18f64ef198760d982874..595976330654d05ae6e7a37dd8514fe901b9164f 100644 --- a/ragconnect.tests/src/test/java/org/jastadd/ros2rag/tests/Read1Write2Test.java +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Read1Write2Test.java @@ -1,4 +1,4 @@ -package org.jastadd.ros2rag.tests; +package org.jastadd.ragconnect.tests; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; diff --git a/ragconnect.tests/src/test/java/org/jastadd/ros2rag/tests/Read2Write1Test.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Read2Write1Test.java similarity index 99% rename from ragconnect.tests/src/test/java/org/jastadd/ros2rag/tests/Read2Write1Test.java rename to ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Read2Write1Test.java index a7cf6c7822aa9b8f77cad90551bf2bca05e149a3..5f61867b5a254a92090a4b6d502100a356f12ddd 100644 --- a/ragconnect.tests/src/test/java/org/jastadd/ros2rag/tests/Read2Write1Test.java +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Read2Write1Test.java @@ -1,4 +1,4 @@ -package org.jastadd.ros2rag.tests; +package org.jastadd.ragconnect.tests; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; diff --git a/ragconnect.tests/src/test/java/org/jastadd/ros2rag/tests/TestUtils.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TestUtils.java similarity index 97% rename from ragconnect.tests/src/test/java/org/jastadd/ros2rag/tests/TestUtils.java rename to ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TestUtils.java index 05db73e8790a25dd9d730ea637e8be500d568d5f..38616caf60907ff0b17da55d420180f00f1a225c 100644 --- a/ragconnect.tests/src/test/java/org/jastadd/ros2rag/tests/TestUtils.java +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TestUtils.java @@ -1,4 +1,4 @@ -package org.jastadd.ros2rag.tests; +package org.jastadd.ragconnect.tests; import java.io.File; import java.io.IOException; diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ViaTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ViaTest.java new file mode 100644 index 0000000000000000000000000000000000000000..8f1a8394960fa4d3ab37aeaf345e4f266b157e02 --- /dev/null +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ViaTest.java @@ -0,0 +1,179 @@ +package org.jastadd.ragconnect.tests; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import via.ast.A; +import via.ast.MqttHandler; + +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.MediaType; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Test case "via". + * + * @author rschoene - Initial contribution + */ +@Tag("rest") +public class ViaTest extends AbstractMqttTest { + + private static final int REST_PORT = 9002; + + private static final String TOPIC_MQTT_2_MQTT_RECEIVE = "mqtt2mqtt/receive"; + private static final String PATH_REST_2_REST_RECEIVE = "rest2rest/receive"; + private static final String TOPIC_MQTT_2_REST_RECEIVE = "mqtt2rest/receive"; + private static final String PATH_REST_2_MQTT_RECEIVE = "rest2mqtt/receive"; + + private static final String TOPIC_MQTT_2_MQTT_SEND = "mqtt2mqtt/send"; + private static final String PATH_REST_2_REST_SEND = "rest2rest/send"; + private static final String PATH_MQTT_2_REST_SEND = "mqtt2rest/send"; + private static final String TOPIC_REST_2_MQTT_SEND = "rest2mqtt/send"; + private static final String REST_SERVER_BASE_URL = "http://localhost:" + REST_PORT + "/"; + + private MqttHandler handler; + private A model; + private ReceiverData dataMqtt2Mqtt; + private ReceiverData dataRest2Mqtt; + private WebTarget dataRest2Rest; + private WebTarget dataMqtt2Rest; + private WebTarget senderRest2Rest; + private WebTarget senderRest2Mqtt; + + @AfterEach + public void closeConnections() { + if (handler != null) { + handler.close(); + } + if (model != null) { + model.MqttCloseConnections(); + model.RestCloseConnections(); + } + } + + @Test + public void buildModel() { + createModel(); + } + + @Test + public void communicateSendInitialValue() throws IOException, InterruptedException { + createModel(); + setupReceiverAndConnect(true); + + // check initial value + TestUtils.waitForMqtt(); + checkData(1, "100-M2M-ToMqtt", "200-R2R-ToRest", "300-M2R-ToRest", 1, "400-R2M-ToMqtt"); + + sendData("101", "201", "301", "401"); + + // check new value + TestUtils.waitForMqtt(); + checkData(2, "FromMqtt-101-M2M-ToMqtt", "FromRest-201-R2R-ToRest", "FromMqtt-301-M2R-ToRest", 2, "FromRest-401-R2M-ToMqtt"); + } + + @Test + public void communicateOnlyUpdatedValue() throws IOException, InterruptedException { + createModel(); + setupReceiverAndConnect(false); + + // check initial value + TestUtils.waitForMqtt(); + checkData(0, null, "200-R2R-ToRest", "300-M2R-ToRest", 0, null); + + sendData("111", "211", "311", "411"); + + // check new value + TestUtils.waitForMqtt(); + checkData(1, "FromMqtt-111-M2M-ToMqtt", "FromRest-211-R2R-ToRest", "FromMqtt-311-M2R-ToRest", 1, "FromRest-411-R2M-ToMqtt"); + } + + private void sendData(String inputMqtt2Mqtt, String inputRest2Rest, String inputMqtt2Rest, String inputRest2Mqtt) { + handler.publish(TOPIC_MQTT_2_MQTT_RECEIVE, inputMqtt2Mqtt.getBytes()); + senderRest2Rest.request().put(Entity.entity(inputRest2Rest, MediaType.TEXT_PLAIN_TYPE)); + handler.publish(TOPIC_MQTT_2_REST_RECEIVE, inputMqtt2Rest.getBytes()); + senderRest2Mqtt.request().put(Entity.entity(inputRest2Mqtt, MediaType.TEXT_PLAIN_TYPE)); + } + + private void checkData(int numberOfMqtt2MqttValues, String mqtt2MqttValue, String rest2RestValue, String mqtt2RestValue, int numberOfRest2MqttValues, String rest2MqttValue) { + dataMqtt2Mqtt.assertEqualData(numberOfMqtt2MqttValues, mqtt2MqttValue); + dataRest2Mqtt.assertEqualData(numberOfRest2MqttValues, rest2MqttValue); + assertEquals(rest2RestValue, readRest2Rest()); + assertEquals(mqtt2RestValue, readMqtt2Rest()); + } + + private String readRest2Rest() { + return dataRest2Rest.request().get().readEntity(String.class); + } + + private String readMqtt2Rest() { + return dataMqtt2Rest.request().get().readEntity(String.class); + } + + private void createModel() { + // Setting value for Input without dependencies does not trigger any updates + model = new A(); + model.setMqtt2MqttInput("100"); + model.setRest2RestInput("200"); + model.setMqtt2RestInput("300"); + model.setRest2MqttInput("400"); + } + + private void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException { + model.MqttSetHost(TestUtils.getMqttHost()); + model.RestSetPort(REST_PORT); + assertTrue(model.MqttWaitUntilReady(2, TimeUnit.SECONDS)); + + handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost()); + assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS)); + + model.addDependencyMqtt2Mqtt(model); + model.addDependencyRest2Rest(model); + model.addDependencyMqtt2Rest(model); + model.addDependencyRest2Mqtt(model); + + dataMqtt2Mqtt = new ReceiverData(); + dataRest2Mqtt = new ReceiverData(); + + handler.newConnection(TOPIC_MQTT_2_MQTT_SEND, bytes -> { + dataMqtt2Mqtt.numberOfStringValues += 1; + dataMqtt2Mqtt.lastStringValue = new String(bytes); + }); + handler.newConnection(TOPIC_REST_2_MQTT_SEND, bytes -> { + dataRest2Mqtt.numberOfStringValues += 1; + dataRest2Mqtt.lastStringValue = new String(bytes); + }); + + Client client = ClientBuilder.newClient(); + dataRest2Rest = client.target(REST_SERVER_BASE_URL + PATH_REST_2_REST_SEND); + dataMqtt2Rest = client.target(REST_SERVER_BASE_URL + PATH_MQTT_2_REST_SEND); + senderRest2Rest = client.target(REST_SERVER_BASE_URL + PATH_REST_2_REST_RECEIVE); + senderRest2Mqtt = client.target(REST_SERVER_BASE_URL + PATH_REST_2_MQTT_RECEIVE); + + model.connectMqtt2MqttInput(TOPIC_MQTT_2_MQTT_RECEIVE); + model.connectMqtt2MqttOutput(TOPIC_MQTT_2_MQTT_SEND, writeCurrentValue); + model.connectMqtt2RestInput(TOPIC_MQTT_2_REST_RECEIVE); + model.connectMqtt2RestOutput(PATH_MQTT_2_REST_SEND, writeCurrentValue); + model.connectRest2MqttInput(PATH_REST_2_MQTT_RECEIVE); + model.connectRest2MqttOutput(TOPIC_REST_2_MQTT_SEND, writeCurrentValue); + model.connectRest2RestInput(PATH_REST_2_REST_RECEIVE); + model.connectRest2RestOutput(PATH_REST_2_REST_SEND, writeCurrentValue); + } + + private static class ReceiverData { + String lastStringValue; + int numberOfStringValues = 0; + + public void assertEqualData(int expectedNumberOfValues, String expectedLastValue) { + assertEquals(expectedNumberOfValues, this.numberOfStringValues); + assertEquals(expectedLastValue, this.lastStringValue); + } + } +}