Merge commit 'e87f096'
[simantics/platform.git] / bundles / org.simantics.structural.synchronization.client / src / org / simantics / structural / synchronization / Synchronizer.java
1 package org.simantics.structural.synchronization;\r
2 \r
3 import gnu.trove.map.hash.TObjectIntHashMap;\r
4 import gnu.trove.set.hash.THashSet;\r
5 \r
6 import java.util.ArrayList;\r
7 import java.util.Collection;\r
8 import java.util.Collections;\r
9 import java.util.List;\r
10 \r
11 import org.eclipse.core.runtime.IProgressMonitor;\r
12 import org.simantics.db.ReadGraph;\r
13 import org.simantics.db.Resource;\r
14 import org.simantics.db.common.procedure.adapter.TransientCacheAsyncListener;\r
15 import org.simantics.db.exception.CancelTransactionException;\r
16 import org.simantics.db.exception.DatabaseException;\r
17 import org.simantics.db.layer0.exception.MissingVariableValueException;\r
18 import org.simantics.db.layer0.request.ResourceToPossibleVariable;\r
19 import org.simantics.db.layer0.variable.RVI;\r
20 import org.simantics.db.layer0.variable.Variable;\r
21 import org.simantics.db.service.ManagementSupport;\r
22 import org.simantics.layer0.Layer0;\r
23 import org.simantics.scl.runtime.SCLContext;\r
24 import org.simantics.structural.stubs.StructuralResource2;\r
25 import org.simantics.structural.synchronization.protocol.ChildInfo;\r
26 import org.simantics.structural.synchronization.protocol.Connection;\r
27 import org.simantics.structural.synchronization.protocol.SerializedVariable;\r
28 import org.simantics.structural.synchronization.protocol.SynchronizationEventHandler;\r
29 import org.simantics.structural.synchronization.protocol.SynchronizationException;\r
30 import org.simantics.structural2.variables.VariableConnectionPointDescriptor;\r
31 \r
32 public class Synchronizer {\r
33     \r
34     public static boolean TRACE = false;\r
35     \r
36     ReadGraph graph;\r
37     Layer0 L0;\r
38     StructuralResource2 STR;\r
39     THashSet<String> visitedTypes = new THashSet<String>();\r
40     IProgressMonitor monitor;\r
41     \r
42     double workDone = 0.0;\r
43     int workDoneInteger;\r
44     int maxWork;\r
45     \r
46     public Synchronizer(ReadGraph graph) {\r
47         this.graph = graph;\r
48         L0 = Layer0.getInstance(graph);\r
49         STR = StructuralResource2.getInstance(graph);\r
50     }\r
51     \r
52     public void setMonitor(IProgressMonitor monitor, int maxWork) {\r
53         this.monitor = monitor;\r
54         this.maxWork = maxWork;\r
55         this.workDoneInteger = 0;\r
56     }\r
57     \r
58     private void didWork(double amount) {\r
59         workDone += amount;\r
60         //System.out.println(workDone);\r
61         if(monitor != null) {\r
62             int t = (int)(workDone * maxWork);\r
63             if(t > workDoneInteger) {\r
64                 monitor.worked(t-workDoneInteger);\r
65                 workDoneInteger = t;\r
66             }\r
67         }\r
68     }\r
69 \r
70     ChildInfo mapChild(Variable child) throws DatabaseException {\r
71         String name = child.getName(graph);\r
72         RVI rvi = child.getRVI(graph);\r
73         return new ChildInfo(name, rvi.toString());\r
74     }\r
75 \r
76     Collection<ChildInfo> mapChildren(SynchronizationEventHandler handler, Collection<Variable> children) throws DatabaseException {\r
77         ArrayList<ChildInfo> result = new ArrayList<ChildInfo>(children.size());\r
78         for(Variable child : children) {\r
79             if(child.getPossibleType(graph, STR.Component) != null)\r
80                 try {\r
81                     result.add(mapChild(child));\r
82                 } catch(Exception e) {\r
83                     handler.reportProblem("Failed to get ChildInfo for " + child + ".", e);\r
84                 }\r
85         }\r
86         return result;\r
87     }\r
88 \r
89     Collection<Connection> mapConnections(SynchronizationEventHandler handler, Variable child) throws DatabaseException {\r
90         ArrayList<Connection> result = new ArrayList<Connection>();\r
91         for(Variable conn : child.getProperties(graph, StructuralResource2.URIs.SynchronizedConnectionRelation)) {\r
92             String name = conn.getName(graph);\r
93             org.simantics.structural2.variables.Connection vc = conn.getValue(graph);\r
94             Collection<VariableConnectionPointDescriptor> connectionPoints = vc.getConnectionPointDescriptors(graph, null);\r
95             List<String> cps = new ArrayList<String>(connectionPoints.size());\r
96             for(VariableConnectionPointDescriptor desc : connectionPoints) {\r
97                 if(desc.isFlattenedFrom(graph, conn)) continue;\r
98                 if(!desc.hasClassification(graph, StructuralResource2.URIs.ProvidingConnectionRelation)) continue;\r
99                 String cpRef = desc.getRelativeRVI(graph, child);\r
100                 cps.add(cpRef);\r
101             }\r
102             \r
103             result.add(new Connection(name, cps));\r
104             \r
105         }\r
106         \r
107         return result;\r
108         \r
109     }\r
110     \r
111     private SerializedVariable serialize(SynchronizationEventHandler handler, Variable var, String name) throws DatabaseException {\r
112         try {\r
113                 SerializedVariable result = new SerializedVariable(name, var.getVariantValue(graph));\r
114                 for(Variable prop : var.getProperties(graph, StructuralResource2.URIs.SynchronizedRelation)) {\r
115                         String pName = prop.getName(graph);\r
116                         SerializedVariable v = serialize(handler, prop, pName);\r
117                         if(v != null) result.addProperty(pName, v);\r
118                 }\r
119                 return result;\r
120         } catch (MissingVariableValueException e) {\r
121             handler.reportProblem("Failed to read " + name + ". " + e.getMessage());\r
122             \r
123             Throwable cur = e;\r
124             while((cur = cur.getCause()) != null) {\r
125                 if(!(cur instanceof MissingVariableValueException)) {\r
126                     handler.reportProblem(cur.getMessage());\r
127                     break;\r
128                 }\r
129             }\r
130         } catch (Exception e) {\r
131                 handler.reportProblem("Failed to serialize " + name + ": " + e.getMessage(), e);\r
132         }\r
133         \r
134         return null;\r
135         \r
136     }\r
137 \r
138     Collection<SerializedVariable> mapProperties(SynchronizationEventHandler handler, Variable child) throws DatabaseException {\r
139         ArrayList<SerializedVariable> result = new ArrayList<SerializedVariable>();\r
140         for(Variable prop : child.getProperties(graph, StructuralResource2.URIs.SynchronizedRelation)) {\r
141                 SerializedVariable serialized = serialize(handler, prop, prop.getName(graph));\r
142                 if(serialized != null) result.add(serialized);\r
143         }\r
144         return result;\r
145     }\r
146 \r
147     /**\r
148      * Assumes that the variable points to a composite.\r
149      */\r
150     public void fullSynchronization(Variable variable, SynchronizationEventHandler handler) throws DatabaseException {\r
151         long duration = 0;\r
152         if(TRACE) {\r
153             System.out.println("fullSynchronization " + variable.getURI(graph));\r
154             duration -= System.nanoTime();\r
155         }\r
156         SCLContext context = SCLContext.getCurrent();\r
157         Object oldGraph = context.put("graph", graph);\r
158         try {\r
159             handler.beginSynchronization();\r
160             synchronizationRec(variable, handler, null, 1.0);\r
161             handler.endSynchronization();\r
162         } finally {\r
163             context.put("graph", oldGraph);\r
164         }\r
165         if(TRACE) {\r
166             duration += System.nanoTime();\r
167             System.out.println("full sync in " + 1e-9*duration + "s.");\r
168         }\r
169     }\r
170     \r
171     /**\r
172      * Recursive implementation of partial and full synchronization. If {@code changeFlags}\r
173      * is null, this is full synchronization, otherwise partial synchronization.\r
174      */\r
175     private void synchronizationRec(Variable variable, SynchronizationEventHandler handler,\r
176             TObjectIntHashMap<Variable> changeFlags,\r
177             double proportionOfWork) throws DatabaseException {\r
178         String name = variable.getName(graph);\r
179         Resource type = variable.getPossibleType(graph, STR.Component);\r
180         if(type == null) {\r
181             // This same filtering is done separately in mapChildren when beginComponent has been called for the parent.\r
182             return;\r
183         }\r
184         Collection<Variable> children = variable.getChildren(graph);\r
185         if(graph.isInheritedFrom(type, STR.Composite) || graph.isInheritedFrom(type, STR.AbstractDefinedComponentType)) {\r
186             String typeId = graph.getPossibleURI(type);\r
187             if(typeId == null)\r
188                 throw new SynchronizationException("User component " + type + " does not have an URI.");\r
189             if(visitedTypes.add(typeId))\r
190                 visitType(typeId, type, handler);\r
191             boolean endComponentNeeded = false;\r
192             try {\r
193                 Collection<SerializedVariable> propertyMap = mapProperties(handler, variable);\r
194                 Collection<ChildInfo> childMap = mapChildren(handler, children);\r
195                 endComponentNeeded = true;\r
196                 handler.beginComponent(name, typeId, \r
197                         propertyMap,\r
198                         Collections.<Connection>emptyList(), \r
199                         childMap);\r
200             } catch(Exception e) {\r
201                 handler.reportProblem("Failed to synchronize " + name + ": " + e.getMessage(), e);\r
202                 if (endComponentNeeded)\r
203                     handler.endComponent();\r
204                 return;\r
205             }\r
206         } else {\r
207             String typeId = graph.getPossibleURI(type);\r
208             if(typeId == null)\r
209                 throw new SynchronizationException("User component " + type + " does not have an URI.");\r
210             if(visitedTypes.add(typeId))\r
211                 visitType(typeId, type, handler);\r
212             boolean endComponentNeeded = false;\r
213             try {\r
214                 Collection<SerializedVariable> propertyMap = mapProperties(handler, variable);\r
215                 Collection<Connection> connectionMap = mapConnections(handler, variable);\r
216                 Collection<ChildInfo> childMap = mapChildren(handler, children);\r
217                 endComponentNeeded = true;\r
218                 handler.beginComponent(name, \r
219                         typeId,\r
220                         propertyMap,\r
221                         connectionMap,\r
222                         childMap);\r
223             } catch(Exception e) {\r
224                 handler.reportProblem("Failed to synchronize " + name + ": " + e.getMessage(), e);\r
225                 if (endComponentNeeded)\r
226                     handler.endComponent();\r
227                 return;\r
228             }\r
229         }\r
230         \r
231         if(changeFlags == null) {\r
232             // Full synchronization, synchronize all children\r
233             if(children.size() > 0) {\r
234                 double proportionOfWorkForChildren = proportionOfWork / children.size();\r
235                 for(Variable child : children)\r
236                     synchronizationRec(child, handler, null, proportionOfWorkForChildren);\r
237             }\r
238             else {\r
239                 didWork(proportionOfWork);\r
240                 // Full synchronization is cancelable\r
241                 if(monitor != null && monitor.isCanceled())\r
242                     throw new CancelTransactionException();\r
243             }\r
244         }\r
245         else {\r
246             // Partial synchronization, synchronize only children with positive changeFlag\r
247             int relevantChildCount = 0;\r
248             for(final Variable child : children) {\r
249                 int changeStatus = changeFlags.get(child);\r
250                 if(changeStatus != 0)\r
251                     ++relevantChildCount;\r
252             }\r
253             if(relevantChildCount > 0) {\r
254                 double proportionOfWorkForChildren = proportionOfWork / relevantChildCount;\r
255                 for(final Variable child : children) {\r
256                     int changeStatus = changeFlags.get(child);\r
257                     if(changeStatus == 0)\r
258                         continue;\r
259                     synchronizationRec(child, handler,\r
260                             // Apply full synchronization for subtree if changeStatus > 1\r
261                             changeStatus==1 ? changeFlags : null,\r
262                             proportionOfWorkForChildren\r
263                             );\r
264                 }\r
265             }\r
266             else {\r
267                 didWork(proportionOfWork);\r
268             }\r
269         }\r
270         handler.endComponent();\r
271     }\r
272     \r
273     public void partialSynchronization(Variable variable, SynchronizationEventHandler handler, TObjectIntHashMap<Variable> changeFlags) throws DatabaseException {\r
274         long duration = 0;\r
275         if(TRACE) {\r
276             System.out.println("partialSynchronization " + variable.getURI(graph));\r
277             duration -= System.nanoTime();\r
278         }\r
279         int changeStatus = changeFlags.get(variable);\r
280         if(changeStatus == 0) return;\r
281         SCLContext context = SCLContext.getCurrent();\r
282         Object oldGraph = context.put("graph", graph);\r
283         try {\r
284             handler.beginSynchronization();\r
285             synchronizationRec(variable, handler, changeStatus == 1 ? changeFlags : null, 1.0);\r
286             handler.endSynchronization();\r
287         } finally {\r
288             context.put("graph", oldGraph);\r
289         } \r
290         if(TRACE) {\r
291             duration += System.nanoTime();\r
292             System.out.println("partial sync in " + 1e-9*duration + "s.");\r
293         }\r
294     }\r
295     \r
296     public void partialSynchronization(Variable variable, SynchronizationEventHandler handler, long fromRevision) throws DatabaseException {\r
297         TObjectIntHashMap<Variable> modifiedComponents = StructuralChangeFlattener.getModifiedComponents(graph, variable, fromRevision);\r
298         /*System.out.println("----------------");\r
299         modifiedComponents.forEachEntry(\r
300                 new TObjectIntProcedure<Variable>() {\r
301                     @Override\r
302                     public boolean execute(Variable a, int b) {\r
303                         try {\r
304                             System.out.println("Changed: " + a.getURI(graph) + " " + b);\r
305                         } catch (DatabaseException e) {\r
306                             e.printStackTrace();\r
307                         }\r
308                         return true;\r
309                     }\r
310                 });*/\r
311         partialSynchronization(variable, handler, modifiedComponents);\r
312     }\r
313     \r
314     void visitType(String typeId, Resource typeResource, SynchronizationEventHandler handler) throws DatabaseException {\r
315         Variable typeVariable = graph.syncRequest(new ResourceToPossibleVariable(typeResource), TransientCacheAsyncListener.<Variable>instance());\r
316         handler.beginType(typeId, mapProperties(handler, typeVariable));\r
317         handler.endType();\r
318     }\r
319 \r
320     public long getHeadRevisionId() throws DatabaseException {\r
321         return graph.getService(ManagementSupport.class).getHeadRevisionId()+1;\r
322     }\r
323 \r
324     \r
325 }\r