From 5f7e37d7b26832b3b512f9dda310cb9bc92c93fb Mon Sep 17 00:00:00 2001
From: Marcin Chrzanowski <marcin.j.chrzanowski@gmail.com>
Date: Fri, 27 Dec 2019 21:08:44 +0100
Subject: Create new zones with UpdateAttributes message

---
 .../cloudatlas/agent/messages/StanikMessage.java   |  3 +-
 .../agent/messages/UpdateAttributesMessage.java    | 23 +++++++++++
 .../edu/mimuw/cloudatlas/agent/modules/Stanik.java | 48 ++++++++++++++++++++++
 .../java/pl/edu/mimuw/cloudatlas/model/ZMI.java    | 17 ++++++++
 .../mimuw/cloudatlas/agent/modules/StanikTest.java | 38 +++++++++++++++++
 5 files changed, 128 insertions(+), 1 deletion(-)
 create mode 100644 src/main/java/pl/edu/mimuw/cloudatlas/agent/messages/UpdateAttributesMessage.java

(limited to 'src')

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
index d2b3064..8661c90 100644
--- a/src/main/java/pl/edu/mimuw/cloudatlas/agent/messages/StanikMessage.java
+++ b/src/main/java/pl/edu/mimuw/cloudatlas/agent/messages/StanikMessage.java
@@ -5,7 +5,8 @@ import pl.edu.mimuw.cloudatlas.agent.modules.ModuleType;
 
 public abstract class StanikMessage extends AgentMessage {
     public enum Type {
-        GET_HIERARCHY
+        GET_HIERARCHY,
+        UPDATE_ATTRIBUTES
     }
 
     private Type type;
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/modules/Stanik.java b/src/main/java/pl/edu/mimuw/cloudatlas/agent/modules/Stanik.java
index b8db08a..a457a94 100644
--- a/src/main/java/pl/edu/mimuw/cloudatlas/agent/modules/Stanik.java
+++ b/src/main/java/pl/edu/mimuw/cloudatlas/agent/modules/Stanik.java
@@ -1,9 +1,17 @@
 package pl.edu.mimuw.cloudatlas.agent.modules;
 
+import java.util.Map.Entry;
+
 import pl.edu.mimuw.cloudatlas.agent.messages.AgentMessage;
 import pl.edu.mimuw.cloudatlas.agent.messages.GetHierarchyMessage;
 import pl.edu.mimuw.cloudatlas.agent.messages.HierarchyMessage;
 import pl.edu.mimuw.cloudatlas.agent.messages.StanikMessage;
+import pl.edu.mimuw.cloudatlas.agent.messages.UpdateAttributesMessage;
+import pl.edu.mimuw.cloudatlas.model.AttributesMap;
+import pl.edu.mimuw.cloudatlas.model.Attribute;
+import pl.edu.mimuw.cloudatlas.model.PathName;
+import pl.edu.mimuw.cloudatlas.model.Value;
+import pl.edu.mimuw.cloudatlas.model.ValueString;
 import pl.edu.mimuw.cloudatlas.model.ZMI;
 
 public class Stanik extends Module {
@@ -19,6 +27,9 @@ public class Stanik extends Module {
             case GET_HIERARCHY:
                 handleGetHierarchy((GetHierarchyMessage) message);
                 break;
+            case UPDATE_ATTRIBUTES:
+                handleUpdateAttributes((UpdateAttributesMessage) message);
+                break;
             default:
                 throw new InvalidMessageType("This type of message cannot be handled by Stanik");
         }
@@ -28,4 +39,41 @@ public class Stanik extends Module {
         HierarchyMessage response = new HierarchyMessage("", message.getRequestingModule(), 0, message.getRequestId(), hierarchy.clone());
         sendMessage(response);
     }
+
+    public void handleUpdateAttributes(UpdateAttributesMessage message) {
+        try {
+            addMissingZones(new PathName(message.getPathName()));
+            ZMI zone = hierarchy.findDescendant(message.getPathName());
+            for (Entry<Attribute, Value> entry : zone.getAttributes()) {
+                Attribute attribute = entry.getKey();
+                Value newValue = message.getAttributes().getOrNull(attribute);
+                if (newValue == null) {
+                    zone.getAttributes().remove(attribute);
+                }
+            }
+            for (Entry<Attribute, Value> entry : message.getAttributes()) {
+                zone.getAttributes().addOrChange(entry.getKey(), entry.getValue());
+            }
+        } catch (ZMI.NoSuchZoneException e) {
+            System.out.println("ERROR: zone should exist after being added");
+        }
+    }
+
+    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()));
+                parent.addSon(newSon);
+            }
+        } catch (ZMI.NoSuchZoneException e) {
+            System.out.println("ERROR: zone should exist after being added");
+        }
+    }
+
+    public ZMI getHierarchy() {
+        return hierarchy;
+    }
 }
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/modules/StanikTest.java b/src/test/java/pl/edu/mimuw/cloudatlas/agent/modules/StanikTest.java
index 1c1e216..31b701d 100644
--- a/src/test/java/pl/edu/mimuw/cloudatlas/agent/modules/StanikTest.java
+++ b/src/test/java/pl/edu/mimuw/cloudatlas/agent/modules/StanikTest.java
@@ -7,11 +7,13 @@ import pl.edu.mimuw.cloudatlas.agent.messages.AgentMessage;
 import pl.edu.mimuw.cloudatlas.agent.messages.GetHierarchyMessage;
 import pl.edu.mimuw.cloudatlas.agent.messages.HierarchyMessage;
 import pl.edu.mimuw.cloudatlas.agent.messages.ResponseMessage;
+import pl.edu.mimuw.cloudatlas.agent.messages.UpdateAttributesMessage;
 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.ValueString;
 import pl.edu.mimuw.cloudatlas.model.ZMI;
 
 import org.junit.Before;
@@ -65,4 +67,40 @@ public class StanikTest {
         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"));
+        UpdateAttributesMessage message = new UpdateAttributesMessage("test_msg", 0, "/", attributes);
+        stanik.handleTyped(message);
+        AttributesMap actualAttributes = stanik.getHierarchy().getAttributes();
+        assertEquals(2, countAttributes(actualAttributes));
+        assertEquals(new ValueInt(1337l), actualAttributes.get("foo"));
+        assertEquals(new ValueString("baz"), actualAttributes.get("bar"));
+    }
+
+    @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"));
+        UpdateAttributesMessage message = new UpdateAttributesMessage("test_msg", 0, "/new", attributes);
+        stanik.handleTyped(message);
+        AttributesMap actualAttributes = stanik.getHierarchy().findDescendant("/new").getAttributes();
+        assertEquals(3, countAttributes(actualAttributes));
+        assertEquals(new ValueInt(1337l), actualAttributes.getOrNull("foo"));
+        assertEquals(new ValueString("baz"), actualAttributes.getOrNull("bar"));
+    }
+
+    public int countAttributes(AttributesMap attributes) {
+        int count = 0;
+        for (Entry<Attribute, Value> attribute : attributes) {
+            count++;
+        }
+
+        return count;
+    }
 }
-- 
cgit v1.2.3