New implementation of server state based on StandardNodeManager 30/1830/5
authorJussi Koskela <jussi.koskela@semantum.fi>
Tue, 12 Jun 2018 11:41:51 +0000 (14:41 +0300)
committerTuukka Lehtonen <tuukka.lehtonen@semantum.fi>
Tue, 12 Jun 2018 20:12:58 +0000 (20:12 +0000)
gitlab #20

Change-Id: I51e56095fb1e76b4c67945eead07d1e9da54eed4

16 files changed:
bundles/org.simantics.document.server/META-INF/MANIFEST.MF
bundles/org.simantics.document.server/adapters.xml [new file with mode: 0644]
bundles/org.simantics.document.server/build.properties
bundles/org.simantics.document.server/src/org/simantics/document/server/Functions.java
bundles/org.simantics.document.server/src/org/simantics/document/server/state/State.java [new file with mode: 0644]
bundles/org.simantics.document.server/src/org/simantics/document/server/state/StateNode.java [new file with mode: 0644]
bundles/org.simantics.document.server/src/org/simantics/document/server/state/StateNodeManager.java [new file with mode: 0644]
bundles/org.simantics.document.server/src/org/simantics/document/server/state/StateNodeManagerSupport.java [new file with mode: 0644]
bundles/org.simantics.document.server/src/org/simantics/document/server/state/StatePropertyNode.java [new file with mode: 0644]
bundles/org.simantics.document.server/src/org/simantics/document/server/state/StateRealm.java [new file with mode: 0644]
bundles/org.simantics.document.server/src/org/simantics/document/server/state/StateRootNode.java [new file with mode: 0644]
bundles/org.simantics.document.server/src/org/simantics/document/server/state/StateSessionManager.java [new file with mode: 0644]
bundles/org.simantics.document.server/src/org/simantics/document/server/state/StateVariableBuilder.java [new file with mode: 0644]
bundles/org.simantics.modeling/adapters.xml
bundles/org.simantics.simulator.toolkit.db/src/org/simantics/simulator/toolkit/db/StandardSessionManager.java
bundles/org.simantics.simulator.toolkit/src/org/simantics/simulator/toolkit/StandardNodeManager.java

index 016ed9189530fa547d9cbf1d91c4f7fe11b6b256..a86daf09a2f593667a0bfa8a698da3c606e6e376 100644 (file)
@@ -23,7 +23,9 @@ Require-Bundle:
  org.simantics.modeling;bundle-version="1.1.1",
  org.simantics.document.server.io;visibility:=reexport,
  org.simantics.scl.db;bundle-version="0.1.3",
- org.slf4j.api
+ org.slf4j.api,
+ org.simantics.simulator.toolkit,
+ org.simantics.simulator.toolkit.db;visibility:=reexport
 Bundle-ActivationPolicy: lazy
 Bundle-Activator: org.simantics.document.server.Activator
 Export-Package: org.simantics.document.server,
@@ -31,6 +33,7 @@ Export-Package: org.simantics.document.server,
  org.simantics.document.server.client,
  org.simantics.document.server.handler,
  org.simantics.document.server.request,
- org.simantics.document.server.serverResponse
+ org.simantics.document.server.serverResponse,
+ org.simantics.document.server.state
 Import-Package: org.simantics.layer0.utils.direct
 Bundle-ClassPath: .
diff --git a/bundles/org.simantics.document.server/adapters.xml b/bundles/org.simantics.document.server/adapters.xml
new file mode 100644 (file)
index 0000000..7a14760
--- /dev/null
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Copyright (c) 2007, 2010 Association for Decentralized Information Management
+    in Industry THTH ry.
+    All rights reserved. This program and the accompanying materials
+    are made available under the terms of the Eclipse Public License v1.0
+    which accompanies this distribution, and is available at
+    http://www.eclipse.org/legal/epl-v10.html
+   
+    Contributors:
+        VTT Technical Research Centre of Finland - initial API and implementation
+ -->
+
+<adapters>
+    <target interface="org.simantics.db.layer0.variable.VariableBuilder">
+        <type uri="http://www.simantics.org/Modeling-1.2/SCLCommandSession" class="org.simantics.document.server.state.StateVariableBuilder" />
+    </target>
+</adapters>
\ No newline at end of file
index 1635b2b1f4001122f4aa145cc1ee1f9d0bb41310..af2f18a121b7a662d1ba7114b4580b57dafc1309 100644 (file)
@@ -3,6 +3,7 @@ bin.includes = META-INF/,\
                .,\
                plugin.xml,\
                webdefault.xml,\
-               scl/
+               scl/,\
+               adapters.xml
 src.includes = scl/
 source.. = src/
index 979be3f26b8e6e63e64f3cba73a81b55f12fa4b2..66cc0047c3389fe4e559486c76f8eb52bf50a8ab 100644 (file)
@@ -68,6 +68,9 @@ import org.simantics.document.server.request.ServerSCLHandlerValueRequest;
 import org.simantics.document.server.request.ServerSCLValueRequest;
 import org.simantics.document.server.serverResponse.ServerResponse;
 import org.simantics.document.server.serverResponse.SuccessResponse;
+import org.simantics.document.server.state.StateNodeManager;
+import org.simantics.document.server.state.StateRealm;
+import org.simantics.document.server.state.StateSessionManager;
 import org.simantics.modeling.ModelingResources;
 import org.simantics.modeling.scl.SCLRealm;
 import org.simantics.modeling.scl.SCLSessionManager;
@@ -1147,11 +1150,6 @@ public class Functions {
 
        } else {
 
-               String id = sclStateKey(graph, base, self, ref);
-
-               SCLRealm realm = SCLSessionManager.getOrCreateSCLRealm(base.getURI(graph) + "/__scl__");
-               realm.getConnection().setVariable(id, getSCLType(defaultValue), defaultValue);
-
                return defaultValue;
 
        }
@@ -1161,9 +1159,9 @@ public class Functions {
 
        String id = sclStateKey(graph, base, self, ref);
 
-       SCLRealm realm = SCLSessionManager.getOrCreateSCLRealm(base.getURI(graph)+"/__scl__");
-       realm.getConnection().setVariable(id, getSCLType(value), value);
-       realm.refreshVariablesSync();
+               StateRealm realm = (StateRealm) StateSessionManager.getInstance().getOrCreateRealm(graph, base.getURI(graph)+"/__scl__");
+               StateNodeManager nodeManager = (StateNodeManager) realm.getNodeManager();
+               nodeManager.setState(id, value);
 
     }
 
diff --git a/bundles/org.simantics.document.server/src/org/simantics/document/server/state/State.java b/bundles/org.simantics.document.server/src/org/simantics/document/server/state/State.java
new file mode 100644 (file)
index 0000000..9be1a49
--- /dev/null
@@ -0,0 +1,13 @@
+package org.simantics.document.server.state;
+
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.simantics.databoard.Bindings;
+import org.simantics.databoard.binding.Binding;
+import org.simantics.databoard.binding.mutable.Variant;
+
+public class State {
+       public static final Binding BINDING = Bindings.getBindingUnchecked(State.class);
+       public Map<String, Variant> properties = new TreeMap<>();
+}
diff --git a/bundles/org.simantics.document.server/src/org/simantics/document/server/state/StateNode.java b/bundles/org.simantics.document.server/src/org/simantics/document/server/state/StateNode.java
new file mode 100644 (file)
index 0000000..df82475
--- /dev/null
@@ -0,0 +1,7 @@
+package org.simantics.document.server.state;
+
+import org.simantics.simulator.toolkit.StandardNode;
+
+public class StateNode implements StandardNode {
+
+}
diff --git a/bundles/org.simantics.document.server/src/org/simantics/document/server/state/StateNodeManager.java b/bundles/org.simantics.document.server/src/org/simantics/document/server/state/StateNodeManager.java
new file mode 100644 (file)
index 0000000..0b48e27
--- /dev/null
@@ -0,0 +1,149 @@
+package org.simantics.document.server.state;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+import org.simantics.databoard.Bindings;
+import org.simantics.databoard.binding.Binding;
+import org.simantics.databoard.binding.error.BindingConstructionException;
+import org.simantics.databoard.binding.mutable.Variant;
+import org.simantics.databoard.serialization.RuntimeSerializerConstructionException;
+import org.simantics.databoard.serialization.SerializerConstructionException;
+import org.simantics.db.layer0.variable.NodeSupport;
+import org.simantics.simulator.toolkit.StandardNodeManager;
+import org.simantics.simulator.toolkit.StandardRealm;
+import org.simantics.simulator.variable.exceptions.NodeManagerException;
+import org.slf4j.LoggerFactory;
+
+public class StateNodeManager extends StandardNodeManager<StateNode, StateNodeManagerSupport> {
+
+       private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(StateNodeManager.class);
+
+       private NodeSupport<StateNode> support;
+
+       public StateNodeManager(StandardRealm<StateNode, StateNodeManagerSupport> realm, StateNode root) {
+               super(realm, root);
+       }
+
+       public void registerSupport(NodeSupport<StateNode> support) {
+               this.support = support;
+       }
+
+       @Override
+       public Set<String> getClassifications(StateNode node) throws NodeManagerException {
+               return Collections.emptySet();
+       }
+
+       @Override
+       public void refreshVariable(StateNode node) {
+               super.refreshVariable(node);
+               support.valueCache.clearExpired();
+               support.structureCache.clearExpired();
+       }
+
+       public void setState(String key, Object value) {
+               try {
+                       getRealm().syncExec(() -> {
+                               try {
+                                       StateRootNode rootNode = (StateRootNode) getRoot();
+                                       StatePropertyNode propertyNode = rootNode.getProperty(key);
+                                       if (propertyNode == null) {
+                                               setValue(rootNode.createProperty(key), value, Bindings.OBJECT);
+                                               refreshVariable(rootNode);
+                                       } else {
+                                               setValue(propertyNode, value, Bindings.OBJECT);
+                                       }
+                               } catch (NodeManagerException e) {
+                                       LOGGER.error("Failed to set state.", e);
+                               }
+                       });
+               } catch (InterruptedException e) {
+                       LOGGER.error("Setting state was interrupted.", e);
+               }
+       }
+
+       public byte[] serialize() {
+               final byte [][] result = new byte[1][];
+               try {
+                       getRealm().syncExec(() -> {
+                               State state = new State();
+                               StateRootNode root = (StateRootNode) getRoot();
+                               for (StateNode node : root.getProperties().values()) {
+                                       StatePropertyNode property = (StatePropertyNode)node;
+                                       try {
+                                               Binding binding = Bindings.getInstanceBinding(property.getValue());
+                                               if (binding != null) {
+                                                       state.properties.put(property.getName(), new Variant(binding, property.getValue()));
+                                               }
+                                       } catch (BindingConstructionException e) {
+                                               // skip
+                                       }
+                               }
+
+                               try {
+                                       result[0] = Bindings.getSerializerUnchecked(State.BINDING).serialize(state);
+                               } catch (RuntimeSerializerConstructionException | IOException e) {
+                               }
+                       });
+               } catch (InterruptedException e) {
+                       LOGGER.error("Serializing state was interrupted.", e);
+               }
+               return result[0];
+       }
+
+       public void deserialize(byte[] bytes) {
+               try {
+                       getRealm().syncExec(() -> {
+                               StateRootNode rootNode = (StateRootNode) getRoot();
+                               rootNode.clear();
+                               try {
+                                       State state = (State) Bindings.getSerializer(State.BINDING).deserialize(bytes);
+                                       for (Map.Entry<String, Variant> entry : state.properties.entrySet()) {
+                                               String key = entry.getKey();
+                                               Object value = entry.getValue().getValue();
+                                               try {
+                                                       setValue(rootNode.createProperty(key), value, Bindings.OBJECT);
+                                               } catch (NodeManagerException e) {
+                                                       LOGGER.error("Failed to deserialize state.", e);
+                                               }
+                                       }
+                                       refreshVariable(rootNode);
+                               } catch (IOException e1) {
+                                       LOGGER.error("Failed to deserialize state.", e1);
+                               } catch (SerializerConstructionException e1) {
+                                       LOGGER.error("Failed to deserialize state.", e1);
+                               }
+                       });
+               } catch (InterruptedException e) {
+                       LOGGER.error("Deserializing state was interrupted.", e);
+               }
+       }
+
+       public void clearState() {
+               try {
+                       getRealm().syncExec(() -> {
+                               StateRootNode rootNode = (StateRootNode) getRoot();
+                               rootNode.clear();
+                               refreshVariable(rootNode);
+                       });
+               } catch (InterruptedException e) {
+                       LOGGER.error("Clearing state was interrupted.", e);
+               }
+       }
+
+       public void removeState(String key) {
+               try {
+                       getRealm().syncExec(() -> {
+                               StateRootNode rootNode = (StateRootNode) getRoot();
+                               if (rootNode.removeProperty(key)) {
+                                       refreshVariable(rootNode);
+                               }
+                       });
+               } catch (InterruptedException e) {
+                       LOGGER.error("Removing state was interrupted.", e);
+               }
+       }
+
+}
diff --git a/bundles/org.simantics.document.server/src/org/simantics/document/server/state/StateNodeManagerSupport.java b/bundles/org.simantics.document.server/src/org/simantics/document/server/state/StateNodeManagerSupport.java
new file mode 100644 (file)
index 0000000..748d4f1
--- /dev/null
@@ -0,0 +1,57 @@
+package org.simantics.document.server.state;
+
+import java.util.Collections;
+import java.util.Map;
+
+import org.simantics.databoard.Bindings;
+import org.simantics.databoard.binding.Binding;
+import org.simantics.simulator.toolkit.StandardNodeManagerSupport;
+import org.simantics.simulator.variable.exceptions.NoValueException;
+import org.simantics.simulator.variable.exceptions.NodeManagerException;
+
+public class StateNodeManagerSupport implements StandardNodeManagerSupport<StateNode> {
+
+       @Override
+       public Object getEngineValue(StateNode node) throws NodeManagerException {
+               if (node instanceof StatePropertyNode) {
+                       return ((StatePropertyNode) node).getValue();
+               }
+               else throw new NoValueException();
+       }
+
+       @Override
+       public Binding getEngineBinding(StateNode node) throws NodeManagerException {
+               if (node instanceof StatePropertyNode) {
+                       return Bindings.OBJECT;
+               }
+               else throw new NoValueException();
+       }
+
+       @Override
+       public void setEngineValue(StateNode node, Object value) throws NodeManagerException {
+               if (node instanceof StatePropertyNode) {
+                       ((StatePropertyNode) node).setValue(value);
+               }
+               else throw new NodeManagerException();
+       }
+
+       @Override
+       public String getName(StateNode node) {
+               if (node instanceof StatePropertyNode) {
+                       return ((StatePropertyNode) node).getName();
+               } else return "@";
+       }
+
+       @Override
+       public Map<String, StateNode> getChildren(StateNode node) {
+               return Collections.emptyMap();
+       }
+
+       @Override
+       public Map<String, StateNode> getProperties(StateNode node) {
+               if (node instanceof StateRootNode) {
+                       return ((StateRootNode) node).getProperties();
+               } else return Collections.emptyMap();
+       }
+
+}
diff --git a/bundles/org.simantics.document.server/src/org/simantics/document/server/state/StatePropertyNode.java b/bundles/org.simantics.document.server/src/org/simantics/document/server/state/StatePropertyNode.java
new file mode 100644 (file)
index 0000000..4c0b410
--- /dev/null
@@ -0,0 +1,23 @@
+package org.simantics.document.server.state;
+
+public class StatePropertyNode extends StateNode {
+
+       public String name;
+       public Object value;
+
+       public StatePropertyNode(String name) {
+               this.name = name;
+       }
+
+       public String getName() {
+               return name;
+       }
+
+       public Object getValue() {
+               return value;
+       }
+
+       public void setValue(Object value) {
+               this.value = value;
+       }
+}
diff --git a/bundles/org.simantics.document.server/src/org/simantics/document/server/state/StateRealm.java b/bundles/org.simantics.document.server/src/org/simantics/document/server/state/StateRealm.java
new file mode 100644 (file)
index 0000000..2436bad
--- /dev/null
@@ -0,0 +1,46 @@
+package org.simantics.document.server.state;
+
+import java.util.function.Function;
+
+import org.simantics.simulator.toolkit.StandardNodeManager;
+import org.simantics.simulator.toolkit.StandardRealm;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class StateRealm extends StandardRealm<StateNode, StateNodeManagerSupport> {
+
+       private static final Logger LOGGER = LoggerFactory.getLogger(StateRealm.class);
+
+       protected StateRealm(StateNodeManagerSupport engine, String id) {
+               super(engine, id);
+       }
+
+       @Override
+       public Logger getLogger() {
+               return LOGGER;
+       }
+
+       @Override
+       protected StandardNodeManager<StateNode, StateNodeManagerSupport> createManager() {
+               return new StateNodeManager(this, new StateRootNode());
+       }
+
+       @Override
+       public void asyncExec(Function fun) {
+               try {
+                       syncExec(fun);
+               } catch (InterruptedException e) {
+                       LOGGER.error("Execution interrupted.", e);
+               }
+       }
+
+       @Override
+       public void asyncExec(Runnable runnable) {
+               try {
+                       syncExec(runnable);
+               } catch (InterruptedException e) {
+                       LOGGER.error("Execution interrupted.", e);
+               }
+       }
+
+}
diff --git a/bundles/org.simantics.document.server/src/org/simantics/document/server/state/StateRootNode.java b/bundles/org.simantics.document.server/src/org/simantics/document/server/state/StateRootNode.java
new file mode 100644 (file)
index 0000000..669cbb2
--- /dev/null
@@ -0,0 +1,32 @@
+package org.simantics.document.server.state;
+
+import java.util.Map;
+import java.util.TreeMap;
+
+public class StateRootNode extends StateNode {
+
+       public Map<String, StateNode> properties = new TreeMap<>();
+
+       public Map<String, StateNode> getProperties() {
+               return properties;
+       }
+
+       public StatePropertyNode getProperty(String key) {
+               return (StatePropertyNode) properties.get(key);
+       }
+
+       public StatePropertyNode createProperty(String key) {
+               StatePropertyNode propertyNode = new StatePropertyNode(key);
+               properties.put(key, propertyNode);
+               return propertyNode;
+       }
+
+       public boolean removeProperty(String key) {
+               return (properties.remove(key) != null);
+       }
+
+       public void clear() {
+               properties.clear();
+       }
+
+}
diff --git a/bundles/org.simantics.document.server/src/org/simantics/document/server/state/StateSessionManager.java b/bundles/org.simantics.document.server/src/org/simantics/document/server/state/StateSessionManager.java
new file mode 100644 (file)
index 0000000..267217c
--- /dev/null
@@ -0,0 +1,37 @@
+package org.simantics.document.server.state;
+
+import org.simantics.db.ReadGraph;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.db.layer0.variable.NodeSupport;
+import org.simantics.simulator.toolkit.StandardNodeManager;
+import org.simantics.simulator.toolkit.StandardRealm;
+import org.simantics.simulator.toolkit.db.StandardSessionManager;
+
+public class StateSessionManager extends StandardSessionManager<StateNode, StateNodeManagerSupport> {
+
+       private static StateSessionManager INSTANCE;
+
+       public static StateSessionManager getInstance() {
+               if(INSTANCE == null) {
+                       INSTANCE = new StateSessionManager();
+               }
+               return INSTANCE;
+       }
+
+       @Override
+       protected StateNodeManagerSupport createEngine(ReadGraph graph, String id) throws DatabaseException {
+               return new StateNodeManagerSupport();
+       }
+
+       @Override
+       protected StandardRealm<StateNode, StateNodeManagerSupport> createRealm(StateNodeManagerSupport engine, String id) {
+               return new StateRealm(engine, id);
+       }
+
+       @Override
+       public void registerNodeSupport(StandardNodeManager<StateNode, StateNodeManagerSupport> manager,
+                       NodeSupport<StateNode> support) {
+               ((StateNodeManager)manager).registerSupport(support);
+       }
+
+}
diff --git a/bundles/org.simantics.document.server/src/org/simantics/document/server/state/StateVariableBuilder.java b/bundles/org.simantics.document.server/src/org/simantics/document/server/state/StateVariableBuilder.java
new file mode 100644 (file)
index 0000000..404a66e
--- /dev/null
@@ -0,0 +1,23 @@
+package org.simantics.document.server.state;
+
+import org.simantics.db.ReadGraph;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.db.layer0.variable.NodeManagerVariableBuilder;
+import org.simantics.db.layer0.variable.NodeSupport;
+import org.simantics.document.server.state.StateNodeManager;
+import org.simantics.document.server.state.StateSessionManager;
+
+public class StateVariableBuilder extends NodeManagerVariableBuilder {
+
+       @Override
+       protected NodeSupport<?> getNodeSupport(ReadGraph graph, String sessionName) throws DatabaseException {
+               return StateSessionManager.getInstance().getOrCreateNodeSupport(graph, sessionName);
+       }
+
+       @Override
+       protected Object getRoot(ReadGraph graph, NodeSupport<?> support, String sessionName) {
+               StateNodeManager manager = (StateNodeManager)support.manager;
+               return manager.getRoot();
+       }
+
+}
\ No newline at end of file
index d96981fd4c1d2b788bb08c98661230208217dd66..ff53cb579b533673483a2cfc2e33da8093905d52 100644 (file)
                </type>
        </target>
 
-
-       <target interface="org.simantics.db.layer0.variable.VariableBuilder">
-               <type uri="http://www.simantics.org/Modeling-1.2/SCLCommandSession" class="org.simantics.modeling.scl.SCLVariableBuilder" />
-       </target>
-
        <target interface="org.simantics.db.layer0.migration.MigrationStep">
                <resource uri="http://www.simantics.org/Modeling-1.2/Migration/attachCreationInformationStep"
                        class="org.simantics.modeling.migration.AttachCreationInformationStep"
index 62389597a9916ea0509608592ee0bf72f83d740d..47b6782825a731de43ac32e00dbaccaa2a0ac2ca 100644 (file)
@@ -9,6 +9,7 @@ import org.simantics.db.common.request.ParametrizedPrimitiveRead;
 import org.simantics.db.exception.DatabaseException;
 import org.simantics.db.layer0.variable.NodeSupport;
 import org.simantics.db.procedure.Listener;
+import org.simantics.simulator.toolkit.StandardNodeManager;
 import org.simantics.simulator.toolkit.StandardNodeManagerSupport;
 import org.simantics.simulator.toolkit.StandardRealm;
 
@@ -87,12 +88,17 @@ public abstract class StandardSessionManager<Node, Engine extends StandardNodeMa
         }
     }
 
+    public void registerNodeSupport(StandardNodeManager<Node,Engine> realm, NodeSupport<Node> support) {
+    
+    }
+
     public NodeSupport<Node> getOrCreateNodeSupport(ReadGraph graph, String id) throws DatabaseException {
         synchronized(SUPPORTS) {
             NodeSupport<Node> result = SUPPORTS.get(id);
             if(result == null) {
                 StandardRealm<Node,Engine> realm = getOrCreateRealm(graph, id);
                 result = new NodeSupport<Node>(realm.getNodeManager());
+                registerNodeSupport(realm.getNodeManager(), result);
                 SUPPORTS.put(id, result);
             }
             return result;
index 87ae4f32f3b7394d1e8168c705c2584ba0ae3f8c..8f1d1cf53c9c2a0c29ce8a6fbefd499128646df7 100644 (file)
@@ -43,7 +43,7 @@ import gnu.trove.set.hash.THashSet;
 /**
  * StandardNodeManager gives default implementations to some methods
  * of NodeManager.
- * 
+ *
  * @author Antti Villberg
  */
 public abstract class StandardNodeManager<Node, Engine extends StandardNodeManagerSupport<Node>> implements NodeManager<Node> {
@@ -108,15 +108,15 @@ public abstract class StandardNodeManager<Node, Engine extends StandardNodeManag
                     return  System.identityHashCode(o1);
                 } else {
                     if(o1.equals(o2)) return 0;
-                    return System.identityHashCode(o1) - System.identityHashCode(o2);  
+                    return System.identityHashCode(o1) - System.identityHashCode(o2);
                 }
             }
         }
 
     };
 
-    THashMap<Node, Variant> valueCache = new THashMap<>(); 
-    protected THashMap<Node, THashSet<Runnable>> listeners = new THashMap<>(); 
+    THashMap<Node, Variant> valueCache = new THashMap<>();
+    protected THashMap<Node, THashSet<Runnable>> listeners = new THashMap<>();
 
     AtomicBoolean fireNodeListenersScheduled = new AtomicBoolean(false);
     Runnable fireNodeListeners = new Runnable() {
@@ -136,7 +136,7 @@ public abstract class StandardNodeManager<Node, Engine extends StandardNodeManag
         }
     };
 
-    Runnable clearValueCache = () -> valueCache.clear(); 
+    Runnable clearValueCache = () -> valueCache.clear();
 
     public StandardNodeManager(StandardRealm<Node,Engine> realm, Node root) {
         assert(realm != null);
@@ -263,6 +263,20 @@ public abstract class StandardNodeManager<Node, Engine extends StandardNodeManag
         }
     }
 
+    public void refreshVariable(Node node) {
+        realm.asyncExec(() -> {
+            valueCache.remove(node);
+            synchronized(listeners) {
+                THashSet<Runnable> runnables = listeners.get(node);
+                if (runnables != null) {
+                    for (Runnable r : runnables) {
+                        r.run();
+                    }
+                }
+            }
+        });
+    }
+
     public void refreshVariables() {
         realm.asyncExec(clearValueCache);
         fireNodeListeners();
@@ -327,7 +341,7 @@ public abstract class StandardNodeManager<Node, Engine extends StandardNodeManag
                 throw new NodeManagerException(e);
             }
         }
-        refreshVariables();
+        refreshVariable(node);
     }
 
     @Override
@@ -336,7 +350,7 @@ public abstract class StandardNodeManager<Node, Engine extends StandardNodeManag
             String id = getRealmId();
             int lastSlash = id.lastIndexOf("/");
             if(lastSlash == -1) throw new IllegalStateException("Invalid realm id " + id);
-            String name = id.substring(lastSlash+1); 
+            String name = id.substring(lastSlash+1);
             return name;
         } else {
             return realm.getEngine().getName(node);