+package fi.vtt.simantics.procore.internal;\r
+\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+import java.util.Collections;\r
+import java.util.Iterator;\r
+import java.util.List;\r
+import java.util.Vector;\r
+\r
+import org.simantics.db.Operation;\r
+import org.simantics.db.Session;\r
+import org.simantics.db.SessionVariables;\r
+import org.simantics.db.UndoContext;\r
+import org.simantics.db.WriteGraph;\r
+import org.simantics.db.common.CommentMetadata;\r
+import org.simantics.db.common.CommitMetadata;\r
+import org.simantics.db.common.UndoMetadata;\r
+import org.simantics.db.common.utils.Logger;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.db.impl.graph.WriteGraphImpl;\r
+import org.simantics.db.impl.query.QueryProcessor.SessionTask;\r
+import org.simantics.db.service.ExternalOperation;\r
+import org.simantics.db.service.ManagementSupport;\r
+import org.simantics.db.service.UndoRedoSupport;\r
+import org.simantics.scl.runtime.function.FunctionImpl1;\r
+import org.simantics.utils.DataContainer;\r
+\r
+import fi.vtt.simantics.procore.internal.SessionImplSocket.TaskHelper;\r
+\r
+public class UndoRedoSupportImpl implements UndoRedoSupport {\r
+ final private boolean DEBUG = SessionImplSocket.DEBUG;\r
+ final private SessionImplSocket session;\r
+ final ManagementSupport managementSupport;\r
+\r
+ UndoRedoSupportImpl(SessionImplSocket session) {\r
+ this.session = session;\r
+ this.managementSupport = session.getService(ManagementSupport.class);\r
+ }\r
+\r
+ @Override\r
+ public Operation undo(final Collection<Operation> ops) throws DatabaseException {\r
+ if (null == ops || ops.size() < 1)\r
+ throw new IllegalArgumentException("At least one operation must be defined.");\r
+ final Operation fop = (Operation)ops.toArray()[0];\r
+ final DataContainer<Long> id = new DataContainer<Long>(0L);\r
+ final TaskHelper th = new TaskHelper("Undo");\r
+ session.requestManager.scheduleWrite(new SessionTask(null, 0) {\r
+ @Override\r
+ public void run(int thread) {\r
+ session.flushCounter = 0;\r
+ session.clusterStream.reallyFlush();\r
+ ClientChangesImpl cs = new ClientChangesImpl(session);\r
+ if (session.clientChanges == null)\r
+ session.clientChanges = cs;\r
+ WriteGraphImpl writer = WriteGraphImpl.create(session.getQueryProvider2(), session.writeSupport, null);\r
+ session.writeState = new WriteState<Object>(writer, th.writeTraits, th.sema, th.proc);\r
+ try {\r
+ SynchronizeContext context = new SynchronizeContext(session, cs, 1);\r
+ boolean potentialConflicts = session.graphSession.undo(ops, context);\r
+ if (potentialConflicts)\r
+ th.throw_("Server thinks that there might be potential conflicts with this undo operation.");\r
+ final boolean undo = true;\r
+ if (!context.isOk(undo)) // this is a blocking operation\r
+ th.throw_("Trouble with server reply.");\r
+ } catch (Throwable e) {\r
+ if (DEBUG)\r
+ e.printStackTrace();\r
+ th.throwableSet(e);\r
+ th.sema.release();\r
+ return;\r
+ }\r
+ try {\r
+ writer.markUndoPoint(); // Undo should form it's own operation.\r
+ // Add a comment to metadata.\r
+ CommentMetadata cm = writer.getMetadata(CommentMetadata.class);\r
+ UndoMetadata um = writer.getMetadata(UndoMetadata.class);\r
+ UndoMetadata pum = getComment4Undo(fop.getId());\r
+ if (null != pum) {\r
+ writer.addMetadata(um.add(pum));\r
+ um.setTypeAndRange(pum);\r
+ }\r
+ writer.addMetadata(um.add("Undo operation " + fop.getId() + "."));\r
+ Operation ope = fop;\r
+ if (ops.size() > 1) {\r
+ writer.addMetadata(um.add("Undo " + ops.size() + " change sets."));\r
+ writer.addMetadata(um.add("First change set was " + fop.getCSId() + "."));\r
+ Operation lop = (Operation)ops.toArray()[ops.size()-1];\r
+ writer.addMetadata(um.add("Last change set was " + lop.getCSId() + "."));\r
+ ope = lop;\r
+ }\r
+ writer.addMetadata(cm.add(getComment(ope.getId())));\r
+ if (null == pum || pum.getBeginCSId() == 0) {\r
+ um.setTypeAndRange(false, ope.getId(), ope.getCSId());\r
+ writer.addMetadata(um);\r
+ }\r
+ session.getQueryProvider2().performDirtyUpdates(writer);\r
+ session.fireMetadataListeners(writer, cs);\r
+ session.getQueryProvider2().performScheduledUpdates(writer);\r
+ session.fireReactionsToSynchronize(cs);\r
+ session.fireSessionVariableChange(SessionVariables.QUEUED_WRITES);\r
+ session.printDiagnostics();\r
+ long headChangeSetId = session.state.getHeadRevisionId();\r
+ id.set(headChangeSetId+1);\r
+ } catch (Throwable e) {\r
+ if (DEBUG)\r
+ e.printStackTrace();\r
+ Logger.defaultLogError(e);\r
+ th.throwableSet(e);\r
+ }\r
+ }\r
+ });\r
+ session.acquire(th.sema, th.writeTraits);\r
+ th.throwableCheck();\r
+ long headChangeSetId = session.state.getHeadRevisionId();\r
+ if (id.get() == headChangeSetId+1)\r
+ return null; // Empty undo operation;\r
+\r
+ final ArrayList<ExternalOperation> externalRedos = new ArrayList<ExternalOperation>();\r
+ GraphSession.forExternals(ops, new FunctionImpl1<ExternalOperation, Boolean>() {\r
+\r
+ @Override\r
+ public Boolean apply(final ExternalOperation op) {\r
+ externalRedos.add(new ExternalOperation() {\r
+\r
+ @Override\r
+ public void undo() {\r
+ op.redo();\r
+ }\r
+\r
+ @Override\r
+ public void redo() {\r
+ op.undo();\r
+ }\r
+\r
+ @Override\r
+ public boolean isDisposed() {\r
+ return op.isDisposed();\r
+ }\r
+\r
+ });\r
+ return true;\r
+ }\r
+\r
+ });\r
+\r
+ return new OperationImpl(id.get(), id.get(), externalRedos);\r
+\r
+ }\r
+ private CommentMetadata getComment(long id) {\r
+ Collection<CommentMetadata> metadata;\r
+ try {\r
+ metadata = managementSupport.getMetadata(id, id, CommentMetadata.class);\r
+ if (metadata.size() > 0)\r
+ return metadata.iterator().next();\r
+ } catch (Throwable t) {\r
+ Logger.defaultLogError(t);\r
+ }\r
+ return null;\r
+ }\r
+ private UndoMetadata getComment4Undo(long id) {\r
+ Collection<UndoMetadata> metadata;\r
+ try {\r
+ metadata = managementSupport.getMetadata(id, id, UndoMetadata.class);\r
+ if (metadata.size() > 0)\r
+ return metadata.iterator().next();\r
+ } catch (Throwable t) {\r
+ Logger.defaultLogError(t);\r
+ }\r
+ return null;\r
+ }\r
+ @Override\r
+ public void undo(Operation op)\r
+ throws DatabaseException {\r
+ Collection<Operation> ops = new ArrayList<Operation>();\r
+ ops.add(op);\r
+ undo(ops);\r
+ }\r
+\r
+ @Override\r
+ public Operation getCurrent() {\r
+ return session.state.getLastOperation();\r
+ }\r
+\r
+ @Override\r
+ public int undo(Session session, int count)\r
+ throws DatabaseException {\r
+ return undoAndReturnOperations(session, count).size();\r
+ }\r
+\r
+ @Override\r
+ public List<Operation> undoAndReturnOperations(Session session, int count)\r
+ throws DatabaseException {\r
+ if ( count < 1)\r
+ return Collections.emptyList();\r
+ if (!(session instanceof SessionImplDb))\r
+ return Collections.emptyList();\r
+ SessionImplDb s = (SessionImplDb)session;\r
+ return s.graphSession.undoContext.undo(this, count);\r
+ }\r
+\r
+ @Override\r
+ public List<Operation> redo(Session session, int count)\r
+ throws DatabaseException {\r
+ if ( count < 1)\r
+ return Collections.emptyList();\r
+ if (!(session instanceof SessionImplDb))\r
+ return Collections.emptyList();\r
+ SessionImplDb s = (SessionImplDb)session;\r
+ return s.graphSession.undoContext.redo(this, count);\r
+ }\r
+\r
+ @Override\r
+ public int undoTo(Session session, long changeSet)\r
+ throws DatabaseException {\r
+ if (!(session instanceof SessionImplDb) || changeSet < 1)\r
+ return 0;\r
+ SessionImplDb s = (SessionImplDb)session;\r
+ long head = s.graphSession.getLastChangeSetId();\r
+ int SIZE = (int)(head - changeSet);\r
+ if (SIZE < 1)\r
+ return 0;\r
+ s.graphSession.undoContext.clear();\r
+ Vector<Operation> ops = new Vector<Operation>(SIZE);\r
+ ops.setSize(SIZE);\r
+ long id = changeSet;\r
+ for (int i = 0; i<SIZE; ++i) {\r
+ ++id;\r
+ Operation o = new OperationImpl(id, id);\r
+ ops.setElementAt(o, i);\r
+ }\r
+ undo(ops);\r
+ return SIZE;\r
+ }\r
+ @Override\r
+ public int initUndoListFrom(Session session, long changeSet)\r
+ throws DatabaseException {\r
+ if (!(session instanceof SessionImplDb) || changeSet < 1)\r
+ return 0;\r
+ SessionImplDb s = (SessionImplDb)session;\r
+ long head = s.graphSession.getLastChangeSetId();\r
+ int SIZE = (int)(head - changeSet + 1);\r
+ if (SIZE < 1)\r
+ return 0;\r
+ ManagementSupport ms = session.getService(ManagementSupport.class);\r
+ Collection<CommitMetadata> metadata = ms.getMetadata(changeSet, head, CommitMetadata.class);\r
+ if (metadata.size() != SIZE)\r
+ return 0;\r
+ s.graphSession.undoContext.clear();\r
+ long first = 0;\r
+ Iterator<CommitMetadata> it = metadata.iterator();\r
+ long csid = changeSet;\r
+ SIZE += csid;\r
+ for (; csid<SIZE; ++csid) {\r
+ CommitMetadata md = it.next();\r
+ if (first == 0) {\r
+ if (md.opid != 0 && md.opid != csid)\r
+ continue;\r
+ first = csid;\r
+ }\r
+ long id = md.opid != 0 ? md.opid : csid;\r
+ Operation op = new OperationImpl(id, csid);\r
+ s.graphSession.undoContext.commitOk(op);\r
+ }\r
+ return (int)(csid - first);\r
+ }\r
+ @Override\r
+ public UndoContext getUndoContext(Session session) {\r
+ if (session instanceof SessionImplSocket) {\r
+ GraphSession graphSession = ((SessionImplSocket)session).graphSession;\r
+ return graphSession != null ? graphSession.undoContext : null;\r
+ }\r
+ return null;\r
+ }\r
+ @Override\r
+ public void subscribe(ChangeListener changeListener) {\r
+ session.graphSession.undoContext.addChangeListener(changeListener);\r
+ }\r
+ @Override\r
+ public void cancel(ChangeListener changelistener) {\r
+ session.graphSession.undoContext.removeChangeListener(changelistener);\r
+ }\r
+\r
+ @Override\r
+ public void addExternalOperation(WriteGraph graph, ExternalOperation op) {\r
+ if (!(session instanceof SessionImplDb))\r
+ return;\r
+ SessionImplDb s = (SessionImplDb)session;\r
+ s.graphSession.undoContext.addExternalOperation(op);\r
+ }\r
+\r
+}\r