m-chrzan.xyz
aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin <marcin.j.chrzanowski@gmail.com>2019-12-29 16:50:05 +0100
committerGitHub <noreply@github.com>2019-12-29 16:50:05 +0100
commit7f03e1ddbd73831499d63eee1075b0b1226fa173 (patch)
tree55bd25630a090c91e1a4362b68e32da22389dd1d
parent8b076ed54b692381a3c1410b704bdad33ad5ca0b (diff)
parentc3121bfa6c00682173a7f1fba9d4c524b8ef517e (diff)
Merge pull request #77 from m-chrzan/stanik
Stanik
-rw-r--r--src/main/java/pl/edu/mimuw/cloudatlas/agent/messages/GetStateMessage.java22
-rw-r--r--src/main/java/pl/edu/mimuw/cloudatlas/agent/messages/ResponseMessage.java31
-rw-r--r--src/main/java/pl/edu/mimuw/cloudatlas/agent/messages/StanikMessage.java27
-rw-r--r--src/main/java/pl/edu/mimuw/cloudatlas/agent/messages/StateMessage.java29
-rw-r--r--src/main/java/pl/edu/mimuw/cloudatlas/agent/messages/UpdateAttributesMessage.java23
-rw-r--r--src/main/java/pl/edu/mimuw/cloudatlas/agent/messages/UpdateQueriesMessage.java21
-rw-r--r--src/main/java/pl/edu/mimuw/cloudatlas/agent/modules/Module.java10
-rw-r--r--src/main/java/pl/edu/mimuw/cloudatlas/agent/modules/ModuleType.java4
-rw-r--r--src/main/java/pl/edu/mimuw/cloudatlas/agent/modules/Stanik.java167
-rw-r--r--src/main/java/pl/edu/mimuw/cloudatlas/model/ZMI.java17
-rw-r--r--src/test/java/pl/edu/mimuw/cloudatlas/agent/MockExecutor.java24
-rw-r--r--src/test/java/pl/edu/mimuw/cloudatlas/agent/modules/StanikTest.java211
12 files changed, 585 insertions, 1 deletions
diff --git a/src/main/java/pl/edu/mimuw/cloudatlas/agent/messages/GetStateMessage.java b/src/main/java/pl/edu/mimuw/cloudatlas/agent/messages/GetStateMessage.java
new file mode 100644
index 0000000..63392e8
--- /dev/null
+++ b/src/main/java/pl/edu/mimuw/cloudatlas/agent/messages/GetStateMessage.java
@@ -0,0 +1,22 @@
+package pl.edu.mimuw.cloudatlas.agent.messages;
+
+import pl.edu.mimuw.cloudatlas.agent.modules.ModuleType;
+
+public class GetStateMessage extends StanikMessage {
+ private ModuleType requestingModule;
+ private long requestId;
+
+ public GetStateMessage(String messageId, long timestamp, ModuleType requestingModule, long requestId) {
+ super(messageId, timestamp, Type.GET_STATE);
+ this.requestingModule = requestingModule;
+ this.requestId = requestId;
+ }
+
+ public ModuleType getRequestingModule() {
+ return requestingModule;
+ }
+
+ public long getRequestId() {
+ return requestId;
+ }
+}
diff --git a/src/main/java/pl/edu/mimuw/cloudatlas/agent/messages/ResponseMessage.java b/src/main/java/pl/edu/mimuw/cloudatlas/agent/messages/ResponseMessage.java
new file mode 100644
index 0000000..02b3337
--- /dev/null
+++ b/src/main/java/pl/edu/mimuw/cloudatlas/agent/messages/ResponseMessage.java
@@ -0,0 +1,31 @@
+package pl.edu.mimuw.cloudatlas.agent.messages;
+
+import pl.edu.mimuw.cloudatlas.agent.modules.Module;
+import pl.edu.mimuw.cloudatlas.agent.modules.ModuleType;
+
+public abstract class ResponseMessage extends AgentMessage {
+ public enum Type {
+ STATE
+ }
+
+ Type type;
+ long requestId;
+
+ public ResponseMessage(String messageId, ModuleType destinationModule, long timestamp, Type type, long requestId) {
+ super(messageId, destinationModule, timestamp);
+ this.type = type;
+ this.requestId = requestId;
+ }
+
+ public void callMe(Module module) throws InterruptedException, Module.InvalidMessageType {
+ module.handleTyped(this);
+ }
+
+ public long getRequestId() {
+ return requestId;
+ }
+
+ public Type getType() {
+ return type;
+ }
+}
diff --git a/src/main/java/pl/edu/mimuw/cloudatlas/agent/messages/StanikMessage.java b/src/main/java/pl/edu/mimuw/cloudatlas/agent/messages/StanikMessage.java
new file mode 100644
index 0000000..b23f6e0
--- /dev/null
+++ b/src/main/java/pl/edu/mimuw/cloudatlas/agent/messages/StanikMessage.java
@@ -0,0 +1,27 @@
+package pl.edu.mimuw.cloudatlas.agent.messages;
+
+import pl.edu.mimuw.cloudatlas.agent.modules.Module;
+import pl.edu.mimuw.cloudatlas.agent.modules.ModuleType;
+
+public abstract class StanikMessage extends AgentMessage {
+ public enum Type {
+ GET_STATE,
+ UPDATE_ATTRIBUTES,
+ UPDATE_QUERIES
+ }
+
+ private Type type;
+
+ public StanikMessage(String messageId, long timestamp, Type type) {
+ super(messageId, ModuleType.STATE, timestamp);
+ this.type = type;
+ }
+
+ public Type getType() {
+ return type;
+ }
+
+ public void callMe(Module module) throws InterruptedException, Module.InvalidMessageType {
+ module.handleTyped(this);
+ }
+}
diff --git a/src/main/java/pl/edu/mimuw/cloudatlas/agent/messages/StateMessage.java b/src/main/java/pl/edu/mimuw/cloudatlas/agent/messages/StateMessage.java
new file mode 100644
index 0000000..f7f490b
--- /dev/null
+++ b/src/main/java/pl/edu/mimuw/cloudatlas/agent/messages/StateMessage.java
@@ -0,0 +1,29 @@
+package pl.edu.mimuw.cloudatlas.agent.messages;
+
+import java.util.Map;
+import java.util.Map.Entry;
+
+import pl.edu.mimuw.cloudatlas.agent.modules.ModuleType;
+import pl.edu.mimuw.cloudatlas.model.Attribute;
+import pl.edu.mimuw.cloudatlas.model.ValueQuery;
+import pl.edu.mimuw.cloudatlas.model.ValueTime;
+import pl.edu.mimuw.cloudatlas.model.ZMI;
+
+public class StateMessage extends ResponseMessage {
+ private ZMI zmi;
+ private Map<Attribute, Entry<ValueQuery, ValueTime>> queries;
+
+ public StateMessage(String messageId, ModuleType destinationModule, long timestamp, long requestId, ZMI zmi, Map<Attribute, Entry<ValueQuery, ValueTime>> queries) {
+ super(messageId, destinationModule, timestamp, Type.STATE, requestId);
+ this.zmi = zmi;
+ this.queries = queries;
+ }
+
+ public ZMI getZMI() {
+ return zmi;
+ }
+
+ public Map<Attribute, Entry<ValueQuery, ValueTime>> getQueries() {
+ return queries;
+ }
+}
diff --git a/src/main/java/pl/edu/mimuw/cloudatlas/agent/messages/UpdateAttributesMessage.java b/src/main/java/pl/edu/mimuw/cloudatlas/agent/messages/UpdateAttributesMessage.java
new file mode 100644
index 0000000..7e41631
--- /dev/null
+++ b/src/main/java/pl/edu/mimuw/cloudatlas/agent/messages/UpdateAttributesMessage.java
@@ -0,0 +1,23 @@
+package pl.edu.mimuw.cloudatlas.agent.messages;
+
+import pl.edu.mimuw.cloudatlas.agent.modules.ModuleType;
+import pl.edu.mimuw.cloudatlas.model.AttributesMap;
+
+public class UpdateAttributesMessage extends StanikMessage {
+ private String pathName;
+ private AttributesMap attributes;
+
+ public UpdateAttributesMessage(String messageId, long timestamp, String pathName, AttributesMap attributes) {
+ super(messageId, timestamp, Type.UPDATE_ATTRIBUTES);
+ this.pathName = pathName;
+ this.attributes = attributes;
+ }
+
+ public String getPathName() {
+ return pathName;
+ }
+
+ public AttributesMap getAttributes() {
+ return attributes;
+ }
+}
diff --git a/src/main/java/pl/edu/mimuw/cloudatlas/agent/messages/UpdateQueriesMessage.java b/src/main/java/pl/edu/mimuw/cloudatlas/agent/messages/UpdateQueriesMessage.java
new file mode 100644
index 0000000..58ad55a
--- /dev/null
+++ b/src/main/java/pl/edu/mimuw/cloudatlas/agent/messages/UpdateQueriesMessage.java
@@ -0,0 +1,21 @@
+package pl.edu.mimuw.cloudatlas.agent.messages;
+
+import java.util.Map;
+import java.util.Map.Entry;
+
+import pl.edu.mimuw.cloudatlas.model.Attribute;
+import pl.edu.mimuw.cloudatlas.model.ValueQuery;
+import pl.edu.mimuw.cloudatlas.model.ValueTime;
+
+public class UpdateQueriesMessage extends StanikMessage {
+ private Map<Attribute, Entry<ValueQuery, ValueTime>> queries;
+
+ public UpdateQueriesMessage(String messageId, long timestamp, Map<Attribute, Entry<ValueQuery, ValueTime>> queries) {
+ super(messageId, timestamp, Type.UPDATE_QUERIES);
+ this.queries = queries;
+ }
+
+ public Map<Attribute, Entry<ValueQuery, ValueTime>> getQueries() {
+ return queries;
+ }
+}
diff --git a/src/main/java/pl/edu/mimuw/cloudatlas/agent/modules/Module.java b/src/main/java/pl/edu/mimuw/cloudatlas/agent/modules/Module.java
index a50a95f..d0bf083 100644
--- a/src/main/java/pl/edu/mimuw/cloudatlas/agent/modules/Module.java
+++ b/src/main/java/pl/edu/mimuw/cloudatlas/agent/modules/Module.java
@@ -3,7 +3,9 @@ package pl.edu.mimuw.cloudatlas.agent.modules;
import pl.edu.mimuw.cloudatlas.agent.Executor;
import pl.edu.mimuw.cloudatlas.agent.messages.AgentMessage;
import pl.edu.mimuw.cloudatlas.agent.messages.TimerSchedulerMessage;
+import pl.edu.mimuw.cloudatlas.agent.messages.ResponseMessage;
import pl.edu.mimuw.cloudatlas.agent.messages.RMIMessage;
+import pl.edu.mimuw.cloudatlas.agent.messages.StanikMessage;
/*
* A Module is a (potentially stateful) event handler.
@@ -34,6 +36,14 @@ public abstract class Module {
throw new InvalidMessageType("Got an RMIMessage in module " + moduleType.toString());
}
+ public void handleTyped(StanikMessage message) throws InterruptedException, InvalidMessageType {
+ throw new InvalidMessageType("Got a StanikMessage in module " + moduleType.toString());
+ }
+
+ public void handleTyped(ResponseMessage message) throws InterruptedException, InvalidMessageType {
+ throw new InvalidMessageType("Got a ResponseMessage in module " + moduleType.toString());
+ }
+
public void setExecutor(Executor executor) {
this.executor = executor;
}
diff --git a/src/main/java/pl/edu/mimuw/cloudatlas/agent/modules/ModuleType.java b/src/main/java/pl/edu/mimuw/cloudatlas/agent/modules/ModuleType.java
index ff4a92e..d221f06 100644
--- a/src/main/java/pl/edu/mimuw/cloudatlas/agent/modules/ModuleType.java
+++ b/src/main/java/pl/edu/mimuw/cloudatlas/agent/modules/ModuleType.java
@@ -8,5 +8,7 @@ public enum ModuleType {
GOSSIP_IN,
GOSSIP_OUT,
STATE,
- QUERY
+ QUERY,
+ // for testing
+ TEST
}
diff --git a/src/main/java/pl/edu/mimuw/cloudatlas/agent/modules/Stanik.java b/src/main/java/pl/edu/mimuw/cloudatlas/agent/modules/Stanik.java
new file mode 100644
index 0000000..e8721b3
--- /dev/null
+++ b/src/main/java/pl/edu/mimuw/cloudatlas/agent/modules/Stanik.java
@@ -0,0 +1,167 @@
+package pl.edu.mimuw.cloudatlas.agent.modules;
+
+import java.util.Iterator;
+import java.util.HashMap;
+import java.util.Map.Entry;
+
+import pl.edu.mimuw.cloudatlas.agent.messages.AgentMessage;
+import pl.edu.mimuw.cloudatlas.agent.messages.GetStateMessage;
+import pl.edu.mimuw.cloudatlas.agent.messages.StateMessage;
+import pl.edu.mimuw.cloudatlas.agent.messages.StanikMessage;
+import pl.edu.mimuw.cloudatlas.agent.messages.UpdateAttributesMessage;
+import pl.edu.mimuw.cloudatlas.agent.messages.UpdateQueriesMessage;
+import pl.edu.mimuw.cloudatlas.model.Attribute;
+import pl.edu.mimuw.cloudatlas.model.AttributesMap;
+import pl.edu.mimuw.cloudatlas.model.PathName;
+import pl.edu.mimuw.cloudatlas.model.Type;
+import pl.edu.mimuw.cloudatlas.model.TypePrimitive;
+import pl.edu.mimuw.cloudatlas.model.Value;
+import pl.edu.mimuw.cloudatlas.model.ValueBoolean;
+import pl.edu.mimuw.cloudatlas.model.ValueQuery;
+import pl.edu.mimuw.cloudatlas.model.ValueString;
+import pl.edu.mimuw.cloudatlas.model.ValueTime;
+import pl.edu.mimuw.cloudatlas.model.ZMI;
+
+public class Stanik extends Module {
+ private class InvalidUpdateAttributesMessage extends Exception {
+ public InvalidUpdateAttributesMessage(String message) {
+ super(message);
+ }
+ }
+
+ private ZMI hierarchy;
+ private HashMap<Attribute, Entry<ValueQuery, ValueTime>> queries;
+
+ public Stanik() {
+ super(ModuleType.STATE);
+ hierarchy = new ZMI();
+ queries = new HashMap<Attribute, Entry<ValueQuery, ValueTime>>();
+ hierarchy.getAttributes().add("timestamp", new ValueTime(0l));
+ }
+
+ public void handleTyped(StanikMessage message) throws InterruptedException, InvalidMessageType {
+ switch(message.getType()) {
+ case GET_STATE:
+ handleGetState((GetStateMessage) message);
+ break;
+ case UPDATE_ATTRIBUTES:
+ handleUpdateAttributes((UpdateAttributesMessage) message);
+ break;
+ case UPDATE_QUERIES:
+ handleUpdateQueries((UpdateQueriesMessage) message);
+ break;
+ default:
+ throw new InvalidMessageType("This type of message cannot be handled by Stanik");
+ }
+ }
+
+ public void handleGetState(GetStateMessage message) throws InterruptedException {
+ StateMessage response = new StateMessage("", message.getRequestingModule(), 0, message.getRequestId(), hierarchy.clone(), (HashMap<Attribute, Entry<ValueQuery, ValueTime>>) queries.clone());
+ sendMessage(response);
+ }
+
+ public void handleUpdateAttributes(UpdateAttributesMessage message) {
+ try {
+ validateUpdateAttributesMessage(message);
+ addMissingZones(new PathName(message.getPathName()));
+ ZMI zone = hierarchy.findDescendant(message.getPathName());
+ AttributesMap attributes = zone.getAttributes();
+ if (valueLower(attributes.get("timestamp"), message.getAttributes().get("timestamp"))) {
+ transferAttributes(message.getAttributes(), attributes);
+ } else {
+ System.out.println("DEBUG: not applying update with older attributes");
+ }
+ } catch (InvalidUpdateAttributesMessage e) {
+ System.out.println("ERROR: invalid UpdateAttributesMessage " + e.getMessage());
+ } catch (ZMI.NoSuchZoneException e) {
+ System.out.println("ERROR: zone should exist after being added");
+ }
+ }
+
+ public void handleUpdateQueries(UpdateQueriesMessage message) {
+ for (Entry<Attribute, Entry<ValueQuery, ValueTime>> entry : message.getQueries().entrySet()) {
+ Attribute attribute = entry.getKey();
+ ValueTime timestamp = entry.getValue().getValue();
+ Entry<ValueQuery, ValueTime> currentTimestampedQuery = queries.get(attribute);
+ if (currentTimestampedQuery == null || valueLower(currentTimestampedQuery.getValue(), timestamp)) {
+ queries.put(entry.getKey(), entry.getValue());
+ }
+ }
+ }
+
+ private boolean valueLower(Value a, Value b) {
+ return ((ValueBoolean) a.isLowerThan(b)).getValue();
+ }
+
+ private void validateUpdateAttributesMessage(UpdateAttributesMessage message) throws InvalidUpdateAttributesMessage {
+ validateZoneName(message);
+ validateHasTimeStamp(message);
+ }
+
+ private void validateZoneName(UpdateAttributesMessage message) throws InvalidUpdateAttributesMessage {
+ Value name = message.getAttributes().getOrNull("name");
+ if (message.getPathName().equals("/")) {
+ if (name != null && !name.isNull()) {
+ throw new InvalidUpdateAttributesMessage("The root zone should have a null name");
+ }
+ } else {
+ if (valueNonNullOfType(name, TypePrimitive.STRING)) {
+ ValueString nameString = (ValueString) name;
+ String expectedName = (new PathName(message.getPathName())).getSingletonName();
+ if (!nameString.getValue().equals(expectedName)) {
+ throw new InvalidUpdateAttributesMessage("The zone's name attribute should match its path name");
+ }
+ } else {
+ throw new InvalidUpdateAttributesMessage("Zone attributes should have a name attribute of type String");
+ }
+ }
+ }
+
+ private void validateHasTimeStamp(UpdateAttributesMessage message) throws InvalidUpdateAttributesMessage {
+ if (!valueNonNullOfType(message.getAttributes().getOrNull("timestamp"), TypePrimitive.TIME)) {
+ throw new InvalidUpdateAttributesMessage("Zone attriutes should have a timestamp attribute of type Time");
+ }
+ }
+
+ private boolean valueNonNullOfType(Value value, Type type) {
+ return value != null && !value.isNull() && value.getType().isCompatible(type);
+ }
+
+ private void transferAttributes(AttributesMap fromAttributes, AttributesMap toAttributes) {
+ Iterator<Entry<Attribute, Value>> iterator = toAttributes.iterator();
+ while (iterator.hasNext()) {
+ Entry<Attribute, Value> entry = iterator.next();
+ Attribute attribute = entry.getKey();
+ Value newValue = fromAttributes.getOrNull(attribute);
+ if (newValue == null) {
+ iterator.remove();
+ }
+ }
+ for (Entry<Attribute, Value> entry : fromAttributes) {
+ toAttributes.addOrChange(entry.getKey(), entry.getValue());
+ }
+ }
+
+ private void addMissingZones(PathName path) {
+ try {
+ if (!hierarchy.descendantExists(path)) {
+ addMissingZones(path.levelUp());
+ ZMI parent = hierarchy.findDescendant(path.levelUp());
+ ZMI newSon = new ZMI(parent);
+ newSon.getAttributes().add("name", new ValueString(path.getSingletonName()));
+ newSon.getAttributes().add("timestamp", new ValueTime(0l));
+ parent.addSon(newSon);
+ }
+ } catch (ZMI.NoSuchZoneException e) {
+ System.out.println("ERROR: zone should exist after being added");
+ }
+ }
+
+ public ZMI getHierarchy() {
+ return hierarchy;
+ }
+
+ public HashMap<Attribute, Entry<ValueQuery, ValueTime>> getQueries() {
+ return queries;
+ }
+}
diff --git a/src/main/java/pl/edu/mimuw/cloudatlas/model/ZMI.java b/src/main/java/pl/edu/mimuw/cloudatlas/model/ZMI.java
index 7f2f604..54fbf43 100644
--- a/src/main/java/pl/edu/mimuw/cloudatlas/model/ZMI.java
+++ b/src/main/java/pl/edu/mimuw/cloudatlas/model/ZMI.java
@@ -111,6 +111,23 @@ public class ZMI implements Cloneable, Serializable {
return descendant;
}
+ public boolean descendantExists(PathName path) {
+ try {
+ findDescendant(path);
+ return true;
+ } catch (NoSuchZoneException e) {
+ return false;
+ }
+ }
+
+ /*
+ * Convenient version of findDescendant that takes String representation of
+ * path.
+ */
+ public ZMI findDescendant(String pathString) throws NoSuchZoneException {
+ return findDescendant(new PathName(pathString));
+ }
+
/**
* Gets the list of sons of this ZMI. Modifying a value in the returned list will cause an exception.
*
diff --git a/src/test/java/pl/edu/mimuw/cloudatlas/agent/MockExecutor.java b/src/test/java/pl/edu/mimuw/cloudatlas/agent/MockExecutor.java
new file mode 100644
index 0000000..1da50ad
--- /dev/null
+++ b/src/test/java/pl/edu/mimuw/cloudatlas/agent/MockExecutor.java
@@ -0,0 +1,24 @@
+package pl.edu.mimuw.cloudatlas.agent;
+
+import java.util.concurrent.LinkedBlockingQueue;
+
+import pl.edu.mimuw.cloudatlas.agent.modules.Module;
+import pl.edu.mimuw.cloudatlas.agent.messages.AgentMessage;
+
+/*
+ * Instead of passing messages to an EventBus, this one just collects them
+ * locally for inspection in unit tests.
+ */
+public class MockExecutor extends Executor {
+ public LinkedBlockingQueue<AgentMessage> messagesToPass;
+
+ public MockExecutor(Module module) {
+ super(module);
+ messagesToPass = new LinkedBlockingQueue<AgentMessage>();
+ }
+
+ @Override
+ public void passMessage(AgentMessage message) throws InterruptedException {
+ messagesToPass.put(message);
+ }
+}
diff --git a/src/test/java/pl/edu/mimuw/cloudatlas/agent/modules/StanikTest.java b/src/test/java/pl/edu/mimuw/cloudatlas/agent/modules/StanikTest.java
new file mode 100644
index 0000000..aeaf19f
--- /dev/null
+++ b/src/test/java/pl/edu/mimuw/cloudatlas/agent/modules/StanikTest.java
@@ -0,0 +1,211 @@
+package pl.edu.mimuw.cloudatlas.agent.modules;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.AbstractMap.SimpleImmutableEntry;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import pl.edu.mimuw.cloudatlas.agent.messages.AgentMessage;
+import pl.edu.mimuw.cloudatlas.agent.messages.GetStateMessage;
+import pl.edu.mimuw.cloudatlas.agent.messages.StateMessage;
+import pl.edu.mimuw.cloudatlas.agent.messages.ResponseMessage;
+import pl.edu.mimuw.cloudatlas.agent.messages.UpdateAttributesMessage;
+import pl.edu.mimuw.cloudatlas.agent.messages.UpdateQueriesMessage;
+import pl.edu.mimuw.cloudatlas.agent.MockExecutor;
+import pl.edu.mimuw.cloudatlas.model.Attribute;
+import pl.edu.mimuw.cloudatlas.model.AttributesMap;
+import pl.edu.mimuw.cloudatlas.model.Value;
+import pl.edu.mimuw.cloudatlas.model.ValueInt;
+import pl.edu.mimuw.cloudatlas.model.ValueQuery;
+import pl.edu.mimuw.cloudatlas.model.ValueString;
+import pl.edu.mimuw.cloudatlas.model.ValueTime;
+import pl.edu.mimuw.cloudatlas.model.ZMI;
+
+import org.junit.Before;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class StanikTest {
+ private Stanik stanik;
+ private MockExecutor executor;
+
+ @Before
+ public void setupEventBus() {
+ stanik = new Stanik();
+ executor = new MockExecutor(stanik);
+ }
+
+ @Test
+ public void getEmptyState() throws Exception {
+ GetStateMessage message = new GetStateMessage("test_msg", 0, ModuleType.TEST, 42);
+ stanik.handleTyped(message);
+ assertEquals(1, executor.messagesToPass.size());
+ ResponseMessage receivedMessage = (ResponseMessage) executor.messagesToPass.take();
+ assertEquals(ModuleType.TEST, receivedMessage.getDestinationModule());
+ assertEquals(ResponseMessage.Type.STATE, receivedMessage.getType());
+ assertEquals(42, receivedMessage.getRequestId());
+ StateMessage stateMessage = (StateMessage) receivedMessage;
+ ZMI zmi = stateMessage.getZMI();
+ assertNull(zmi.getFather());
+ assertTrue(zmi.getSons().isEmpty());
+ assertEquals(1, iterableSize(zmi.getAttributes()));
+ Map<Attribute, Entry<ValueQuery, ValueTime>> queries = stateMessage.getQueries();
+ assertEquals(0, iterableSize(queries.keySet()));
+ }
+
+ @Test
+ public void hierarchyIsDeepCopy() throws Exception {
+ GetStateMessage message = new GetStateMessage("test_msg", 0, ModuleType.TEST, 42);
+ stanik.handleTyped(message);
+ StateMessage receivedMessage = (StateMessage) executor.messagesToPass.poll();
+ assertNotNull(receivedMessage);
+ AttributesMap attributes = receivedMessage.getZMI().getAttributes();
+ assertNull(attributes.getOrNull("foo"));
+ attributes.add("foo", new ValueInt(1337l));
+
+ GetStateMessage newMessage = new GetStateMessage("test_msg2", 123, ModuleType.TEST, 43);
+ stanik.handleTyped(newMessage);
+ StateMessage newReceivedMessage = (StateMessage) executor.messagesToPass.poll();
+ AttributesMap newAttributes = newReceivedMessage.getZMI().getAttributes();
+ assertNull(newAttributes.getOrNull("foo"));
+ }
+
+ @Test
+ public void updateRootAttributes() throws Exception {
+ AttributesMap attributes = new AttributesMap();
+ attributes.add("foo", new ValueInt(1337l));
+ attributes.add("bar", new ValueString("baz"));
+ attributes.add("timestamp", new ValueTime("2012/12/21 04:20:00.000"));
+ UpdateAttributesMessage message = new UpdateAttributesMessage("test_msg", 0, "/", attributes);
+ stanik.handleTyped(message);
+ AttributesMap actualAttributes = stanik.getHierarchy().getAttributes();
+ assertEquals(3, iterableSize(actualAttributes));
+ assertEquals(new ValueInt(1337l), actualAttributes.get("foo"));
+ assertEquals(new ValueString("baz"), actualAttributes.get("bar"));
+ assertEquals(new ValueTime("2012/12/21 04:20:00.000"), actualAttributes.getOrNull("timestamp"));
+ }
+
+ @Test
+ public void updateWithNewZone() throws Exception {
+ AttributesMap attributes = new AttributesMap();
+ attributes.add("foo", new ValueInt(1337l));
+ attributes.add("bar", new ValueString("baz"));
+ attributes.add("name", new ValueString("new"));
+ attributes.add("timestamp", new ValueTime("2012/12/21 04:20:00.000"));
+ UpdateAttributesMessage message = new UpdateAttributesMessage("test_msg", 0, "/new", attributes);
+ stanik.handleTyped(message);
+ AttributesMap actualAttributes = stanik.getHierarchy().findDescendant("/new").getAttributes();
+ assertEquals(4, iterableSize(actualAttributes));
+ assertEquals(new ValueInt(1337l), actualAttributes.getOrNull("foo"));
+ assertEquals(new ValueString("baz"), actualAttributes.getOrNull("bar"));
+ assertEquals(new ValueString("new"), actualAttributes.getOrNull("name"));
+ assertEquals(new ValueTime("2012/12/21 04:20:00.000"), actualAttributes.getOrNull("timestamp"));
+ }
+
+ @Test
+ public void updateWithRemovedAttributes() throws Exception {
+ AttributesMap attributes = new AttributesMap();
+ attributes.add("foo", new ValueInt(1337l));
+ attributes.add("bar", new ValueString("baz"));
+ UpdateAttributesMessage message = new UpdateAttributesMessage("test_msg", 0, "/", attributes);
+ attributes.add("timestamp", new ValueTime("2012/12/21 04:20:00.000"));
+ stanik.handleTyped(message);
+
+ AttributesMap newAttributes = new AttributesMap();
+ newAttributes.add("timestamp", new ValueTime("2012/12/21 04:20:42.000"));
+ newAttributes.add("foo", new ValueInt(1338l));
+ UpdateAttributesMessage newMessage = new UpdateAttributesMessage("test_msg2", 0, "/", newAttributes);
+ stanik.handleTyped(newMessage);
+
+ AttributesMap actualAttributes = stanik.getHierarchy().getAttributes();
+ assertEquals(2, iterableSize(actualAttributes));
+ assertEquals(new ValueInt(1338l), actualAttributes.getOrNull("foo"));
+ assertEquals(new ValueTime("2012/12/21 04:20:42.000"), actualAttributes.getOrNull("timestamp"));
+ }
+
+ @Test
+ public void dontApplyUpdateWithOlderTimestamp() throws Exception {
+ AttributesMap attributes = new AttributesMap();
+ attributes.add("foo", new ValueInt(1337l));
+ attributes.add("timestamp", new ValueTime("2012/12/21 04:20:00.000"));
+ UpdateAttributesMessage message = new UpdateAttributesMessage("test_msg", 0, "/", attributes);
+ stanik.handleTyped(message);
+
+ AttributesMap oldAttributes = new AttributesMap();
+ oldAttributes.add("foo", new ValueInt(1336l));
+ oldAttributes.add("timestamp", new ValueTime("2012/12/21 04:19:00.000"));
+ UpdateAttributesMessage newMessage = new UpdateAttributesMessage("test_msg2", 0, "/", oldAttributes);
+ stanik.handleTyped(newMessage);
+
+ AttributesMap actualAttributes = stanik.getHierarchy().getAttributes();
+ assertEquals(2, iterableSize(actualAttributes));
+ assertEquals(new ValueInt(1337l), actualAttributes.getOrNull("foo"));
+ assertEquals(new ValueTime("2012/12/21 04:20:00.000"), actualAttributes.getOrNull("timestamp"));
+ }
+
+ @Test
+ public void addQuery() throws Exception {
+ HashMap<Attribute, Entry<ValueQuery, ValueTime>> queries = new HashMap<Attribute, Entry<ValueQuery, ValueTime>>();
+ queries.put(new Attribute("&query"), new SimpleImmutableEntry(new ValueQuery("SELECT 1 AS one"), new ValueTime(42l)));
+ UpdateQueriesMessage message = new UpdateQueriesMessage("test_msg", 0, queries);
+ stanik.handleTyped(message);
+
+ HashMap<Attribute, Entry<ValueQuery, ValueTime>> actualQueries = stanik.getQueries();
+ assertEquals(1, iterableSize(actualQueries.keySet()));
+ assertTrue(actualQueries.containsKey(new Attribute("&query")));
+ Entry<ValueQuery, ValueTime> timestampedQuery = actualQueries.get(new Attribute("&query"));
+ assertEquals(new ValueTime(42l), timestampedQuery.getValue());
+ assertEquals(new ValueQuery("SELECT 1 AS one"), timestampedQuery.getKey());
+ }
+
+ @Test
+ public void updateQueries() throws Exception {
+ HashMap<Attribute, Entry<ValueQuery, ValueTime>> queries = new HashMap<Attribute, Entry<ValueQuery, ValueTime>>();
+ queries.put(new Attribute("&query1"), new SimpleImmutableEntry(new ValueQuery("SELECT 1 AS one"), new ValueTime(42l)));
+ queries.put(new Attribute("&query3"), new SimpleImmutableEntry(new ValueQuery("SELECT 23 AS x"), new ValueTime(43l)));
+ queries.put(new Attribute("&query4"), new SimpleImmutableEntry(new ValueQuery("SELECT 1000 AS foo"), new ValueTime(43l)));
+ UpdateQueriesMessage message = new UpdateQueriesMessage("test_msg", 0, queries);
+ stanik.handleTyped(message);
+
+ HashMap<Attribute, Entry<ValueQuery, ValueTime>> otherQueries = new HashMap<Attribute, Entry<ValueQuery, ValueTime>>();
+ otherQueries.put(new Attribute("&query1"), new SimpleImmutableEntry(new ValueQuery("SELECT 2 AS one"), new ValueTime(41l)));
+ otherQueries.put(new Attribute("&query2"), new SimpleImmutableEntry(new ValueQuery("SELECT 42 AS answer"), new ValueTime(39l)));
+ otherQueries.put(new Attribute("&query3"), new SimpleImmutableEntry(new ValueQuery("SELECT 17 AS y"), new ValueTime(44l)));
+ UpdateQueriesMessage otherMessage = new UpdateQueriesMessage("test_msg", 0, otherQueries);
+ stanik.handleTyped(otherMessage);
+
+ HashMap<Attribute, Entry<ValueQuery, ValueTime>> actualQueries = stanik.getQueries();
+ assertEquals(4, iterableSize(actualQueries.keySet()));
+ assertTrue(actualQueries.containsKey(new Attribute("&query1")));
+ assertTrue(actualQueries.containsKey(new Attribute("&query2")));
+ assertTrue(actualQueries.containsKey(new Attribute("&query3")));
+ assertTrue(actualQueries.containsKey(new Attribute("&query4")));
+
+ Entry<ValueQuery, ValueTime> timestampedQuery1 = actualQueries.get(new Attribute("&query1"));
+ assertEquals(new ValueTime(42l), timestampedQuery1.getValue());
+ assertEquals(new ValueQuery("SELECT 1 AS one"), timestampedQuery1.getKey());
+
+ Entry<ValueQuery, ValueTime> timestampedQuery2 = actualQueries.get(new Attribute("&query2"));
+ assertEquals(new ValueTime(39l), timestampedQuery2.getValue());
+ assertEquals(new ValueQuery("SELECT 42 AS answer"), timestampedQuery2.getKey());
+
+ Entry<ValueQuery, ValueTime> timestampedQuery3 = actualQueries.get(new Attribute("&query3"));
+ assertEquals(new ValueTime(44l), timestampedQuery3.getValue());
+ assertEquals(new ValueQuery("SELECT 17 AS y"), timestampedQuery3.getKey());
+
+ Entry<ValueQuery, ValueTime> timestampedQuery4 = actualQueries.get(new Attribute("&query4"));
+ assertEquals(new ValueTime(43l), timestampedQuery4.getValue());
+ assertEquals(new ValueQuery("SELECT 1000 AS foo"), timestampedQuery4.getKey());
+ }
+
+ public <T> int iterableSize(Iterable<T> iterable) {
+ int count = 0;
+ for (T attribute : iterable) {
+ count++;
+ }
+
+ return count;
+ }
+}