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