1 package org.simantics.structural.synchronization.client;
3 import java.util.ArrayList;
4 import java.util.Collection;
5 import java.util.Collections;
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;
31 import gnu.trove.map.hash.TObjectIntHashMap;
32 import gnu.trove.set.hash.THashSet;
34 public class Synchronizer {
36 private static final Logger LOGGER = LoggerFactory.getLogger(Synchronizer.class);
40 StructuralResource2 STR;
41 THashSet<String> visitedTypes = new THashSet<String>();
42 IProgressMonitor monitor;
44 double workDone = 0.0;
48 public Synchronizer(ReadGraph graph) {
50 L0 = Layer0.getInstance(graph);
51 STR = StructuralResource2.getInstance(graph);
54 public void setMonitor(IProgressMonitor monitor, int maxWork) {
55 this.monitor = monitor;
56 this.maxWork = maxWork;
57 this.workDoneInteger = 0;
60 private void didWork(double amount) {
62 //System.out.println(workDone);
64 int t = (int)(workDone * maxWork);
65 if(t > workDoneInteger) {
66 monitor.worked(t-workDoneInteger);
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());
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)
83 result.add(mapChild(child));
84 } catch(Exception e) {
85 handler.reportProblem("Failed to get ChildInfo for " + child + ".", e);
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);
105 result.add(new Connection(name, cps));
113 private SerializedVariable serialize(SynchronizationEventHandler handler, Variable var, String name) throws DatabaseException {
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);
122 } catch (MissingVariableValueException e) {
123 handler.reportProblem("Failed to read " + name + ": " + e.getMessage());
126 while((cur = cur.getCause()) != null) {
127 if(!(cur instanceof MissingVariableValueException)) {
128 handler.reportProblem(" " + getSafeDescription(cur));
131 } catch (Exception e) {
132 handler.reportProblem("Failed to serialize " + name + ": " + getSafeDescription(e), e);
139 private String getSafeDescription(Throwable cur) {
140 return cur.getClass().getName() + (cur == null || cur.getMessage() != null ? ": " + cur.getMessage() : "");
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);
153 * Assumes that the variable points to a composite.
155 public void fullSynchronization(Variable variable, SynchronizationEventHandler handler) throws DatabaseException {
157 if(LOGGER.isTraceEnabled()) {
158 LOGGER.trace("fullSynchronization {}", variable.getURI(graph));
159 duration -= System.nanoTime();
161 SCLContext context = SCLContext.getCurrent();
162 Object oldGraph = context.put("graph", graph);
164 handler.beginSynchronization();
165 synchronizationRec(variable, handler, null, 1.0);
166 handler.endSynchronization();
168 context.put("graph", oldGraph);
170 if(LOGGER.isTraceEnabled()) {
171 duration += System.nanoTime();
172 LOGGER.trace("full sync done in {} s", 1e-9*duration);
177 * Recursive implementation of partial and full synchronization. If {@code changeFlags}
178 * is null, this is full synchronization, otherwise partial synchronization.
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);
186 // This same filtering is done separately in mapChildren when beginComponent has been called for the parent.
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);
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;
198 Collection<SerializedVariable> propertyMap = mapProperties(handler, variable);
199 Collection<ChildInfo> childMap = mapChildren(handler, children);
200 endComponentNeeded = true;
201 handler.beginComponent(name, typeId,
203 Collections.<Connection>emptyList(),
205 } catch(Exception e) {
206 handler.reportProblem("Failed to synchronize " + name + ": " + e.getMessage(), e);
207 if (endComponentNeeded)
208 handler.endComponent();
212 String typeId = graph.getPossibleURI(type);
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;
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,
228 } catch(Exception e) {
229 handler.reportProblem("Failed to synchronize " + name + ": " + e.getMessage(), e);
230 if (endComponentNeeded)
231 handler.endComponent();
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);
244 didWork(proportionOfWork);
245 // Full synchronization is cancelable
246 if(monitor != null && monitor.isCanceled())
247 throw new CancelTransactionException();
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;
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)
264 synchronizationRec(child, handler,
265 // Apply full synchronization for subtree if changeStatus > 1
266 changeStatus==1 ? changeFlags : null,
267 proportionOfWorkForChildren
272 didWork(proportionOfWork);
275 handler.endComponent();
278 public void partialSynchronization(Variable variable, SynchronizationEventHandler handler, TObjectIntHashMap<Variable> changeFlags) throws DatabaseException {
280 if(LOGGER.isTraceEnabled()) {
281 LOGGER.trace("partialSynchronization {}", variable.getURI(graph));
282 duration -= System.nanoTime();
284 int changeStatus = changeFlags.get(variable);
285 if(changeStatus == 0) return;
286 SCLContext context = SCLContext.getCurrent();
287 Object oldGraph = context.put("graph", graph);
289 handler.beginSynchronization();
290 synchronizationRec(variable, handler, changeStatus == 1 ? changeFlags : null, 1.0);
291 handler.endSynchronization();
293 context.put("graph", oldGraph);
295 if(LOGGER.isTraceEnabled()) {
296 duration += System.nanoTime();
297 LOGGER.trace("partial sync in {} s", 1e-9*duration);
301 public void partialSynchronization(Variable variable, SynchronizationEventHandler handler, long fromRevision) throws DatabaseException {
302 boolean trace = LOGGER.isTraceEnabled();
304 LOGGER.trace("Partial synchronization for {} from rev {}", variable.getURI(graph), fromRevision);
306 TObjectIntHashMap<Variable> modifiedComponents = StructuralChangeFlattener.getModifiedComponents(graph, variable, fromRevision);
309 LOGGER.trace("----------------");
310 modifiedComponents.forEachEntry((Variable a, int b) -> {
312 LOGGER.trace("Changed: " + a.getURI(graph) + " " + b);
313 } catch (DatabaseException e) {
314 LOGGER.error("Variable.getURI failed", e);
320 partialSynchronization(variable, handler, modifiedComponents);
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));
329 public long getHeadRevisionId() throws DatabaseException {
330 return graph.getService(ManagementSupport.class).getHeadRevisionId()+1;