NodeStructureRequest and NodeValueRequest fire ExternalRead twice
[simantics/platform.git] / bundles / org.simantics.db.layer0 / src / org / simantics / db / layer0 / variable / NodeStructureRequest.java
1 package org.simantics.db.layer0.variable;
2
3 import java.util.Collections;
4 import java.util.List;
5 import java.util.Map;
6
7 import org.simantics.databoard.util.ObjectUtils;
8 import org.simantics.db.ReadGraph;
9 import org.simantics.db.common.request.ParametrizedPrimitiveRead;
10 import org.simantics.db.exception.DatabaseException;
11 import org.simantics.db.layer0.variable.Variables.NodeStructure;
12 import org.simantics.db.procedure.Listener;
13 import org.simantics.simulator.variable.exceptions.NodeManagerException;
14 import org.slf4j.Logger;
15 import org.slf4j.LoggerFactory;
16
17 import gnu.trove.map.hash.THashMap;
18
19 @SuppressWarnings("rawtypes")
20 class NodeStructureRequest extends ParametrizedPrimitiveRead<VariableNode, NodeStructure> implements VariableNodeReadRunnable {
21
22     private static final Logger LOGGER = LoggerFactory.getLogger(NodeStructureRequest.class);
23
24     private Listener<NodeStructure> listener = null;
25     private NodeStructure value = Variables.PENDING_NODE_STRUCTURE;
26     private boolean wasRun = false;
27
28     static class Probe implements Runnable {
29
30         private VariableNode node;
31         public NodeStructure result;
32
33         public Probe(VariableNode node) {
34             this.node = node;
35         }
36
37         @SuppressWarnings("unchecked")
38         @Override
39         public void run() {
40             try {
41                 result = NodeStructureRequest.get(node);
42                 node.support.structureCache.put(node.node, result, 1000000000L);
43             } catch (NodeManagerException e) {
44                 e.printStackTrace();
45             }
46         }
47
48     }
49
50     public NodeStructureRequest(VariableNode node) {
51         super(node);
52     }
53
54     @SuppressWarnings("unchecked")
55     @Override
56     public void register(ReadGraph graph, final Listener<NodeStructure> procedure) {
57
58         if(procedure.isDisposed()) {
59
60             // We are not listening
61             NodeStructure result = (NodeStructure)parameter.support.structureCache.get(parameter.node);
62
63             if(result != null) {
64                 // Return cached value immediately
65                 procedure.execute(result);
66             } else {
67                 NodeStructureRequest.Probe probe = new Probe(parameter);
68                 parameter.support.manager.getRealm().asyncExec(probe);
69                 if(probe.result != null) {
70                     procedure.execute(probe.result);
71                 } else {
72                     procedure.execute(Variables.PENDING_NODE_STRUCTURE);
73                 }
74             }
75
76             return;
77
78         }
79
80         // We need to listen
81         listener = procedure;
82         // Register listening
83         parameter.support.manager.addNodeListener(parameter.node, this);
84         synchronized(this) {
85             if(!wasRun) {
86                 NodeStructure result = (NodeStructure)parameter.support.structureCache.get(parameter.node);
87                 if(result != null) {
88                     procedure.execute(result);
89                 } else {
90                     procedure.execute(Variables.PENDING_NODE_STRUCTURE);
91                 }
92             }
93         }
94
95     }
96
97     static class NodeListener implements VariableNodeReadRunnable {
98
99         private VariableNode node;
100         private NodeStructureRequest request;
101
102         public NodeListener(VariableNode node, NodeStructureRequest request) {
103             this.node = node;
104             this.request = request;
105         }
106
107         @SuppressWarnings("unchecked")
108         @Override
109         public void run() {
110             node.support.manager.addNodeListener(node.node, request);
111         }
112
113     }
114
115     @SuppressWarnings("unchecked")
116     @Override
117     public void unregistered() {
118         parameter.support.manager.removeNodeListener(parameter.node, this);
119         parameter.support.structureCache.removeListening(parameter.node);
120         listener = null;
121     }
122
123     @SuppressWarnings("unchecked")
124     public static NodeStructure get(VariableNode parameter) throws NodeManagerException {
125         List<?> children = parameter.support.manager.getChildren(parameter.node);
126         List<?> properties = parameter.support.manager.getProperties(parameter.node);
127         Map<String, Object> childMap = Collections.emptyMap();
128         Map<String, Object> propertyMap = childMap;
129         if(!children.isEmpty()) {
130             childMap = new THashMap<>(children.size());
131             for(Object o : children) {
132                 String name = parameter.support.manager.getName(o);
133                 childMap.put(name, o);
134             }
135         }
136         if(!properties.isEmpty()) {
137             propertyMap = new THashMap<>(properties.size());
138             for(Object o : properties) {
139                 String name = parameter.support.manager.getName(o);
140                 propertyMap.put(name, o);
141             }
142         }
143         return new NodeStructure(childMap, propertyMap);
144     }
145
146     @SuppressWarnings("unchecked")
147     @Override
148     public synchronized void run() {
149         try {
150             // Cache this value with infinite cache time since we are listening
151             NodeStructure newValue = get(parameter);
152             if (wasRun && ObjectUtils.objectEquals(value, newValue)) {
153                 //System.out.println("CACHE VALUE MATCH (" + newValue + ") for " + node.node);
154                 return;
155             }
156             value = newValue;
157             parameter.support.structureCache.put(parameter.node, value);
158         } catch (Throwable e) {
159             // Must catch everything to prevent DB client from getting stuck.
160             LOGGER.error("Error while computing node structure", e);
161             // Invoke the exception method of the listener
162             Listener<NodeStructure> listener = this.listener;
163             if (listener != null) {
164                 listener.exception(new DatabaseException("External data access error", e));
165                 wasRun = true;
166             }
167             return;
168         }
169
170         // Must always invoke an existing listener, regardless of earlier errors.
171         Listener<NodeStructure> listener = this.listener;
172         if (listener != null) {
173             listener.execute(value);
174             wasRun = true;
175         }
176     }
177
178     @Override
179     public String toString() {
180         return "NodeStructureRequest.run @ " + System.identityHashCode(this);
181     }
182
183 }