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

Flatten MqttTopics to list instead of tree.

- Resolves issue #18 (using first solution)
- Also one topic may be used by multiple items
- An item can have no topic (stressed in relation with "?")
- Removed ParserTests as topic construction is not needed anymore
parent b7940ac3
No related branches found
No related tags found
No related merge requests found
Showing
with 41 additions and 233 deletions
......@@ -56,7 +56,7 @@ aspect Printing {
.addNonDefault("label", getLabel())
.addRequired("state", getStateAsString())
.addOptional("category", hasCategory(), () -> getCategory().getName())
.addOptional("topic", getTopic() != null, () -> getTopic().allParts())
.addOptional("topic", hasTopic(), () -> getTopic().getTopicString())
.addIds("controls", getControllingList())
.addNodes("metaData", getNumMetaData(), getMetaDataList(),
md -> "\"" + md.getKey() + "\":\"" + md.getValue() + "\"",
......@@ -85,7 +85,7 @@ aspect Printing {
.addRequired("id", getID())
.addNonDefault("label", getLabel())
.addOptional("category", hasCategory(), () -> getCategory().getName())
.addOptional("topic", getTopic() != null, () -> getTopic().allParts())
.addOptional("topic", hasTopic(), () -> getTopic().getTopicString())
.addIds("controls", getControllingList())
.addNodes("metaData", getNumMetaData(), getMetaDataList(),
md -> "\"" + md.getKey() + "\":\"" + md.getValue() + "\"",
......
......@@ -3,39 +3,25 @@ aspect MQTT {
// --- default values ---
private static final int MqttRoot.DEFAULT_PORT = 1883;
java.util.Set<String> MqttRoot.ignoredTopics = new java.util.HashSet<>();
//--- resolveTopic ---
syn java.util.Optional<MqttTopic> MqttRoot.resolveTopic(String topic) {
ensureCorrectPrefixes();
if (!topic.startsWith(getIncomingPrefix())) {
logger.debug("Topic '{}' does not start with incoming prefix '{}'", topic, getIncomingPrefix());
logger.warn("Topic '{}' does not start with incoming prefix '{}'", topic, getIncomingPrefix());
return java.util.Optional.empty();
}
topic = topic.substring(getIncomingPrefix().length());
String[] tokens = topic.split("/");
int tokenIndex = 0;
java.util.Optional<MqttTopic> result = check(tokens, 0, getTopics());
if (!result.isPresent() && !ignoredTopics.contains(topic)) {
logger.error("Could not resolve {}, ignoring it.", topic);
ignoredTopics.add(topic);
}
return result;
String suffix = topic.substring(getIncomingPrefix().length());
return resolveTopicSuffix(suffix);
}
java.util.Optional<MqttTopic> MqttRoot.check(String[] tokens, int tokenIndex, JastAddList<MqttTopic> topics) {
for (MqttTopic current : topics) {
if (tokens[tokenIndex].equals(current.getPart())) {
// topic part matches, move on or return if tokens are empty
++tokenIndex;
if (tokens.length == tokenIndex) {
return java.util.Optional.of(current);
} else {
return check(tokens, tokenIndex, current.getSubTopics());
}
//--- resolveTopicSuffix ---
syn java.util.Optional<MqttTopic> MqttRoot.resolveTopicSuffix(String suffix) {
for (MqttTopic current : getTopics()) {
if (current.getTopicString().equals(suffix)) {
return Optional.of(current);
}
}
return java.util.Optional.empty();
return Optional.empty();
}
public void MqttRoot.ensureCorrectPrefixes() {
......@@ -48,19 +34,10 @@ aspect MQTT {
}
//--- getIncomingTopic ---
syn String MqttTopic.getIncomingTopic() = getMqttRoot().getIncomingPrefix() + allParts();
syn String MqttTopic.getIncomingTopic() = getMqttRoot().getIncomingPrefix() + getTopicString();
//--- getOutgoingTopic ---
syn String MqttTopic.getOutgoingTopic() = getMqttRoot().getOutgoingPrefix() + allParts();
//--- allParts ---
inh String MqttTopic.allParts();
eq MqttTopic.getSubTopic(int i).allParts() {
return allParts() + "/" + getSubTopic(i).getPart();
}
eq MqttRoot.getTopic(int i).allParts() {
return getTopic(i).getPart();
}
syn String MqttTopic.getOutgoingTopic() = getMqttRoot().getOutgoingPrefix() + getTopicString();
//--- getMqttSender (should be cached) ---
cache MqttRoot.getMqttSender();
......@@ -78,7 +55,6 @@ aspect MQTT {
inh MqttRoot MqttTopic.getMqttRoot();
eq MqttRoot.getTopic().getMqttRoot() = this;
eq MqttTopic.getSubTopic().getMqttRoot() = getMqttRoot();
/**
* Sends the current state via MQTT.
......
// ---------------- MQTT ------------------------------
MqttRoot ::= Topic:MqttTopic* <IncomingPrefix:String> <OutgoingPrefix:String> [Host:ExternalHost] ;
MqttTopic ::= <Part:String> SubTopic:MqttTopic* ;
rel Item.Topic <-> MqttTopic.Item ;
MqttTopic ::= <TopicString:String> ;
rel Item.Topic? <-> MqttTopic.Item* ;
......@@ -42,7 +42,8 @@ public class MQTTUpdater implements AutoCloseable {
delegatee.setHost(host.getHostName(), host.getPort());
delegatee.setOnMessage((topicString, message) ->
root.getMqttRoot().resolveTopic(topicString).ifPresent(topic ->
itemUpdate(topic.getItem(), message)));
topic.getItems().forEach(
item -> itemUpdate(item, message))));
delegatee.setTopicsForSubscription(root.getMqttRoot().getIncomingPrefix() + "#");
delegatee.setQoSForSubscription(QoS.AT_LEAST_ONCE);
}
......
......@@ -140,7 +140,7 @@ public class EraserParserHelper {
if (elem instanceof ModelElement) {
return ((ModelElement) elem).getID();
} else if (elem instanceof MqttTopic) {
return safeAllParts((MqttTopic) elem);
return ((MqttTopic) elem).getTopicString();
} else if (elem instanceof DefaultChannelCategory) {
return ((DefaultChannelCategory) elem).getValue().name();
} else if (elem instanceof SimpleChannelCategory) {
......@@ -149,23 +149,6 @@ public class EraserParserHelper {
return elem.toString();
}
private String safeAllParts(MqttTopic elem) {
StringBuilder sb = new StringBuilder(elem.getPart());
ASTNode parent;
while (true) {
parent = elem.getParent();
if (parent == null) break;
assert parent instanceof List;
parent = parent.getParent();
if (parent == null || parent instanceof MqttRoot) {
break;
}
elem = (MqttTopic) parent;
sb.insert(0, elem.getPart() + "/");
}
return sb.toString();
}
private <Src extends ASTNode, Target extends ASTNode> void resolveList(
Map<String, Target> resolved, Map<Src, Iterable<String>> missing, BiConsumer<Src, Target> adder) {
missing.forEach(
......
......@@ -178,59 +178,18 @@ public class ParserUtils {
}
/**
* Create an hierarchical tree of topics for the topics parts, separated by "/", and assign the leaf topic to the item.
* @param item The item to which the leaf topic will be assigned to
* @param topicName The full topic name
* Create a topic for the given topic name and assign it to the given item.
* @param item The item to which the topic will be assigned to
* @param topicSuffix The full topic name
* @param root The model to operate on
*/
public static void createMqttTopic(Item item, String topicName, Root root) {
String[] parts = topicName.split("/");
String firstPart = parts[0];
MqttTopic firstTopic = null;
for (MqttTopic topic : root.getMqttRoot().getTopicList()) {
if (topic.getPart().equals(firstPart)) {
firstTopic = topic;
break;
}
}
if (firstTopic == null) {
// no matching topic found for first part. create one.
firstTopic = createTopic(firstPart, root);
}
MqttTopic lastTopic = processRemainingTopicParts(firstTopic, parts, 1);
item.setTopic(lastTopic);
}
private static MqttTopic processRemainingTopicParts(MqttTopic topic, String[] parts, int index) {
if (index >= parts.length) {
return topic;
}
for (MqttTopic subTopic : topic.getSubTopicList()) {
if (subTopic.getPart().equals(parts[index])) {
// matching part found
return processRemainingTopicParts(subTopic, parts, index + 1);
}
}
// no matching part was found. create remaining topics.
for (int currentIndex = index; currentIndex < parts.length; currentIndex++) {
MqttTopic newTopic = createSubTopic(parts[currentIndex]);
topic.addSubTopic(newTopic);
topic = newTopic;
}
return topic;
}
private static MqttTopic createSubTopic(String part) {
return createTopic(part, null);
}
private static MqttTopic createTopic(String part, Root root) {
public static void createMqttTopic(Item item, String topicSuffix, Root root) {
item.setTopic(root.getMqttRoot().resolveTopicSuffix(topicSuffix).orElseGet(() -> {
MqttTopic result = new MqttTopic();
result.setPart(part);
if (root != null) {
result.setTopicString(topicSuffix);
root.getMqttRoot().addTopic(result);
}
return result;
}));
}
}
......@@ -108,12 +108,7 @@ public class MqttTests {
NumberItem item1 = modelAB.item;
item1.setTopic(modelAB.secondTopic);
MqttTopic alternative = new MqttTopic();
alternative.setPart(alternativeFirstPart);
MqttTopic alternativeB = new MqttTopic();
alternativeB.setPart(secondPart);
alternative.addSubTopic(alternativeB);
modelAB.model.getRoot().getMqttRoot().addTopic(alternative);
MqttTopic alternativeB = createAndAddMqttTopic(modelAB.model.getRoot().getMqttRoot(),alternativeFirstPart + "/" + secondPart);
NumberItem item2 = TestUtils.addItemTo(modelAB.model, 0);
item2.setTopic(alternativeB);
......@@ -186,12 +181,8 @@ public class MqttTests {
// mqttRoot.setHostByName("localhost");
mqttRoot.setHost(ExternalHost.of(mqttBroker.getContainerIpAddress(), mqttBroker.getFirstMappedPort()));
}
MqttTopic a = new MqttTopic();
a.setPart(firstPart);
MqttTopic ab = new MqttTopic();
ab.setPart(secondPart);
a.addSubTopic(ab);
mqttRoot.addTopic(a);
MqttTopic a = createAndAddMqttTopic(mqttRoot, firstPart);
MqttTopic ab = createAndAddMqttTopic(mqttRoot, firstPart + "/" + secondPart);
mqttRoot.ensureCorrectPrefixes();
model.getRoot().setMqttRoot(mqttRoot);
return ModelItemAndTwoTopics.of(model, mai.item, a, ab);
......@@ -212,6 +203,13 @@ public class MqttTests {
}
}
private MqttTopic createAndAddMqttTopic(MqttRoot mqttRoot, String suffix) {
MqttTopic result = new MqttTopic();
result.setTopicString(suffix);
mqttRoot.addTopic(result);
return result;
}
private <T> void assertTimeoutEquals(long seconds, T expected, Supplier<T> actualProvider) throws InterruptedException {
if (expected == actualProvider.get()) {
// already matched right now. return immediately.
......
package de.tudresden.inf.st.eraser;
import de.tudresden.inf.st.eraser.util.TestUtils;
import de.tudresden.inf.st.eraser.util.TestUtils.ModelAndItem;
import de.tudresden.inf.st.eraser.jastadd.model.MqttRoot;
import de.tudresden.inf.st.eraser.jastadd.model.NumberItem;
import de.tudresden.inf.st.eraser.jastadd.model.Root;
import de.tudresden.inf.st.eraser.util.ParserUtils;
import org.junit.Assert;
import org.junit.Test;
/**
* Testing helper methods using in parsing.
*
* @author rschoene - Initial contribution
*/
public class ParserTests {
@Test
public void testCreateMqttTopicSimple() {
ModelAndItem mai = TestUtils.createModelAndItem(1);
Root root = mai.model.getRoot();
root.setMqttRoot(new MqttRoot());
NumberItem item = mai.item;
Assert.assertNull(item.getTopic());
String[] parts = { "one", "two", "three" };
String topicName = String.join("/", parts);
ParserUtils.createMqttTopic(item, topicName, root);
Assert.assertNotNull(item.getTopic());
MqttRoot mqttRoot = root.getMqttRoot();
Assert.assertEquals("There must be only one topic", 1, mqttRoot.getNumTopic());
Assert.assertEquals("First part is wrong",
parts[0], mqttRoot.getTopic(0).getPart());
Assert.assertEquals("First topic has wrong number of sub-topics",
1, mqttRoot.getTopic(0).getNumSubTopic());
Assert.assertEquals("Second part is wrong",
parts[1], mqttRoot.getTopic(0).getSubTopic(0).getPart());
Assert.assertEquals("Second topic has wrong number of sub-topics",
1, mqttRoot.getTopic(0).getSubTopic(0).getNumSubTopic());
Assert.assertEquals("Third part is wrong",
parts[2], mqttRoot.getTopic(0).getSubTopic(0).getSubTopic(0).getPart());
Assert.assertEquals("Third part is wrong object",
item.getTopic(), mqttRoot.getTopic(0).getSubTopic(0).getSubTopic(0));
Assert.assertEquals("Name does not match", topicName, item.getTopic().allParts());
}
@Test
public void testCreateMqttTopicTwoInterleavedTopics() {
ModelAndItem mai = TestUtils.createModelAndItem(1);
Root root = mai.model.getRoot();
root.setMqttRoot(new MqttRoot());
NumberItem item1 = mai.item;
NumberItem item2 = TestUtils.addItemTo(root.getOpenHAB2Model(), 3);
Assert.assertNull(item1.getTopic());
Assert.assertNull(item2.getTopic());
String[] parts = { "one", "two", "three" };
String otherPart2 = "222";
String topicName1 = String.join("/", parts);
String topicName2 = String.join("/", parts[0], otherPart2, parts[2]);
ParserUtils.createMqttTopic(item1, topicName1, root);
ParserUtils.createMqttTopic(item2, topicName2, root);
Assert.assertNotNull(item1.getTopic());
Assert.assertNotNull(item2.getTopic());
MqttRoot mqttRoot = root.getMqttRoot();
Assert.assertEquals("There must be only one topic", 1, mqttRoot.getNumTopic());
Assert.assertEquals("First part is wrong",
parts[0], mqttRoot.getTopic(0).getPart());
Assert.assertEquals("First topic has wrong number of sub-topics",
2, mqttRoot.getTopic(0).getNumSubTopic());
Assert.assertEquals("Second part for first item is wrong",
parts[1], mqttRoot.getTopic(0).getSubTopic(0).getPart());
Assert.assertEquals("Second part for first item is wrong",
otherPart2, mqttRoot.getTopic(0).getSubTopic(1).getPart());
Assert.assertEquals("Second topic for first item has wrong number of sub-topics",
1, mqttRoot.getTopic(0).getSubTopic(0).getNumSubTopic());
Assert.assertEquals("Third part for first item is wrong",
parts[2], mqttRoot.getTopic(0).getSubTopic(0).getSubTopic(0).getPart());
Assert.assertEquals("Second topic for second item has wrong number of sub-topics",
1, mqttRoot.getTopic(0).getSubTopic(1).getNumSubTopic());
Assert.assertEquals("Third part for second item is wrong",
parts[2], mqttRoot.getTopic(0).getSubTopic(1).getSubTopic(0).getPart());
Assert.assertEquals("Third part for first item is wrong object",
item1.getTopic(), mqttRoot.getTopic(0).getSubTopic(0).getSubTopic(0));
Assert.assertEquals("Third part for second item is wrong object",
item2.getTopic(), mqttRoot.getTopic(0).getSubTopic(1).getSubTopic(0));
Assert.assertEquals("Name for first item does not match", topicName1, item1.getTopic().allParts());
Assert.assertEquals("Name for second item does not match", topicName2, item2.getTopic().allParts());
}
}
......@@ -180,7 +180,7 @@ public class Application {
private SimpleItem wrapItem(Item item) {
return SimpleItem.of(item.getID(),
item.getLabel(),
item.getTopic() != null ? item.getTopic().allParts() : null,
item.getTopic() != null ? item.getTopic().getTopicString() : null,
item.isFrozen(),
item.isSendState(),
item.getControllingList().stream().map(Item::getID).collect(Collectors.toList()),
......
......@@ -83,11 +83,8 @@ public class IntegrationMain {
MqttRoot mqttRoot = new MqttRoot();
mqttRoot.setHostByName("localhost");
mqttRoot.setIncomingPrefix("oh2/out/");
MqttTopic irisTopic = new MqttTopic();
irisTopic.setPart("iris1_item");
MqttTopic irisStateTopic = new MqttTopic();
irisStateTopic.setPart("state");
irisTopic.addSubTopic(irisStateTopic);
irisStateTopic.setTopicString("iris1_item/state");
Item iris = null;
for (Item item : model.getOpenHAB2Model().items()) {
if (item.getID().equals("iris1_item")) {
......@@ -99,8 +96,7 @@ public class IntegrationMain {
logger.error("Could not find iris1. Exiting");
return;
}
irisStateTopic.setItem(iris);
mqttRoot.addTopic(irisTopic);
irisStateTopic.addItem(iris);
model.setMqttRoot(mqttRoot);
// JsonSerializer.write(model, "src/main/resources/openhab2-data.json");
JsonSerializer.write(model, "openhab2-data.json");
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment