]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.db.procore/src/fi/vtt/simantics/procore/internal/TransactionToken.java
Merge branch 'master' into private/eclipse-4.7
[simantics/platform.git] / bundles / org.simantics.db.procore / src / fi / vtt / simantics / procore / internal / TransactionToken.java
index d91a39f9d7feade0a229b554f2a2d91af2506c3f..d5f5c4308903cdd6aee7d0a5c3dbed5ffe1f40a2 100644 (file)
-/*******************************************************************************\r
- * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
- * in Industry THTH ry.\r
- * All rights reserved. This program and the accompanying materials\r
- * are made available under the terms of the Eclipse Public License v1.0\r
- * which accompanies this distribution, and is available at\r
- * http://www.eclipse.org/legal/epl-v10.html\r
- *\r
- * Contributors:\r
- *     VTT Technical Research Centre of Finland - initial API and implementation\r
- *******************************************************************************/\r
-package fi.vtt.simantics.procore.internal;\r
-\r
-import java.io.IOException;\r
-import java.util.List;\r
-import java.util.TreeMap;\r
-import java.util.concurrent.atomic.AtomicLong;\r
-\r
-import org.simantics.databoard.Bindings;\r
-import org.simantics.databoard.binding.impl.TreeMapBinding;\r
-import org.simantics.databoard.serialization.SerializationException;\r
-import org.simantics.databoard.serialization.Serializer;\r
-import org.simantics.db.Operation;\r
-import org.simantics.db.Session;\r
-import org.simantics.db.common.CommentMetadata;\r
-import org.simantics.db.common.CommitMetadata;\r
-import org.simantics.db.common.MetadataUtils;\r
-import org.simantics.db.common.utils.Logger;\r
-import org.simantics.db.exception.DatabaseException;\r
-import org.simantics.db.exception.ValidationException;\r
-import org.simantics.db.impl.graph.WriteSupport;\r
-import org.simantics.db.procore.protocol.Constants;\r
-import org.simantics.db.request.WriteTraits;\r
-import org.simantics.db.service.ClusterSetsSupport;\r
-import org.simantics.db.service.ClusterUID;\r
-import org.simantics.db.service.ExternalOperation;\r
-import org.simantics.db.service.TransactionPolicySupport;\r
-\r
-public class TransactionToken implements GraphSession.Listener {\r
-    private boolean DEBUG = false;\r
-    private Session session;\r
-    private GraphSession graphSession;\r
-    private AtomicLong id;\r
-    // This is the last change set that we have send commit for.\r
-    // This does not mean that those commits have been accepted in server.\r
-    private long lastChangeSetId;\r
-    private OperationImpl lastOperation = null;\r
-    enum Type { Null, Read, Write }\r
-    private Type type;\r
-    // Count of pending transactions.\r
-    private int pending = 0;\r
-    TransactionToken(TransactionPolicySupport tps, Session session, GraphSession graphSession, TransactionToken old) {\r
-        this.session = session;\r
-        this.graphSession = graphSession;\r
-        this.type = Type.Null;\r
-        this.id = new AtomicLong();\r
-        this.id.set(Constants.NullTransactionId);\r
-        long firstChangeSetId = graphSession.getFirstChangeSetId();\r
-        if (null != old && old.lastChangeSetId > firstChangeSetId)\r
-            this.lastChangeSetId = old.lastChangeSetId;\r
-        else\r
-            this.lastChangeSetId = firstChangeSetId;\r
-        graphSession.setListener(this);\r
-    }\r
-    void startReadTransaction(int thread)\r
-    throws DatabaseException {\r
-        if (DEBUG)\r
-            System.out.println("DEBUG: Start read transaction " + graphSession);\r
-        if (Type.Null == type) {\r
-            id.set(graphSession.askReadTransaction(thread));\r
-            if (DEBUG)\r
-                if (++pending != 1)\r
-                    System.out.println("kraa: read beg pending=" + pending);\r
-            type = Type.Read;\r
-        }\r
-    }\r
-    void stopReadTransaction()\r
-    throws DatabaseException {\r
-        if (DEBUG)\r
-            System.out.println("DEBUG: Stop read transaction " + graphSession);\r
-        if (Type.Null == type)\r
-            return;\r
-        endTransaction(false);\r
-        if (DEBUG)\r
-            if (--pending != 0)\r
-                System.out.println("kraa: read end pending=" + pending);\r
-    }\r
-    void startWriteTransaction(int thread)\r
-    throws DatabaseException {\r
-        if (DEBUG)\r
-            System.out.println("DEBUG: Start write transaction " + graphSession);\r
-        if (Type.Null == type) {\r
-            id.set(graphSession.askWriteTransaction(thread, Constants.NullTransactionId));\r
-            if (DEBUG)\r
-                if (++pending != 1)\r
-                    System.out.println("kraa: write beg pending=" + pending);\r
-        } else if (Type.Read == type) {\r
-            id.set(graphSession.askWriteTransaction(thread, id.get()));\r
-            if (DEBUG)\r
-                if (++pending != 1)\r
-                    System.out.println("kraa: write beg pending=" + pending);\r
-        }\r
-        type = Type.Write;\r
-    }\r
-    void cancelBegin(WriteSupport writeSupport, SynchronizeContextI context, ClusterStream clusterStream)\r
-    throws DatabaseException {\r
-        if (DEBUG)\r
-            System.out.println("DEBUG: CancelBegin " + graphSession);\r
-        if (Type.Null == type)\r
-            return;\r
-        else if (Type.Read == type)\r
-            throw new ValidationException("Illegal token type.");\r
-        TreeMap<String, byte[]> metadata = writeSupport.getMetadata();\r
-        boolean empty = clusterStream.reallyFlush();\r
-        if (empty) {\r
-            boolean noMetadata = !hasMetadata(metadata);\r
-            if (noMetadata)\r
-                return;\r
-        }\r
-        byte[] data = makeContext(null, metadata);\r
-        graphSession.cancelCommit(id.get(), lastChangeSetId, data, context);\r
-        ++lastChangeSetId;\r
-        if (DEBUG)\r
-            System.out.println("DEBUG: CancelBegin cancelled cs=" + lastChangeSetId);\r
-        clusterStream.accept();\r
-        writeSupport.commitDone(null, lastChangeSetId);\r
-    }\r
-    void cancelEnd(WriteSupport writeSupport, WriteTraits writeTraits, ClusterStream clusterStream)\r
-    throws DatabaseException {\r
-        if (clusterStream.reallyFlush())\r
-            return;\r
-        TreeMap<String, byte[]> metadata = writeSupport.getMetadata();\r
-        byte[] data = makeContext(null, metadata);\r
-        graphSession.acceptCommit(id.get(), lastChangeSetId, data);\r
-        ++lastChangeSetId;\r
-        if (DEBUG)\r
-            System.out.println("DEBUG CancelEnd accepted commit cs=" + lastChangeSetId);\r
-        clusterStream.accept();\r
-        writeSupport.commitDone(writeTraits, lastChangeSetId);\r
-    }\r
-    void setCombine(boolean a) {\r
-        combine = a;\r
-    }\r
-    private boolean hasMetadata(TreeMap<String, byte[]> metadata) {\r
-       \r
-       if(metadata == null)\r
-               return false;\r
-       if(metadata.size() == 0)\r
-               return false;\r
-       if(metadata.size() == 1 && MetadataUtils.hasMetadata(metadata, CommentMetadata.class))\r
-               return false;\r
-       \r
-       return true;\r
-       \r
-    }\r
-    private boolean combine = false;\r
-    void commitWriteTransaction(WriteSupport writeSupport, WriteTraits writeTraits, ClusterStream clusterStream, Operation dummy)\r
-    throws DatabaseException {\r
-        if (DEBUG)\r
-            System.out.println("DEBUG: Commit write transaction " + graphSession);\r
-        if (Type.Null == type)\r
-            return;\r
-        else if (Type.Read == type)\r
-            throw new ValidationException("Illegal transaction type.");\r
-        else if (id.get() == Constants.NullTransactionId)\r
-            throw new ValidationException("Illegal transaction id.");\r
-        TreeMap<String, byte[]> metadata = writeSupport.getMetadata();\r
-        boolean empty = clusterStream.reallyFlush();\r
-        if (empty) {\r
-            boolean noMetadata = !hasMetadata(metadata);\r
-            if (noMetadata) {\r
-               List<ExternalOperation> ext = graphSession.undoContext.getPendingExternals();\r
-               if(!ext.isEmpty()) {\r
-                               CommentMetadata cm = writeSupport.getMetadata(CommentMetadata.class);\r
-                               writeSupport.addMetadata(cm.add("Automatically generated comment for empty commit with external undo operation(s)."));\r
-                               metadata = writeSupport.getMetadata();\r
-                               Logger.defaultLogError("A generated comment was added into a commit with only some external undo operation(s).");\r
-               } else {\r
-                       graphSession.undoContext.cancelCommit();\r
-                       return;\r
-               }\r
-            }\r
-        }\r
-\r
-               CommentMetadata cm = writeSupport.getMetadata(CommentMetadata.class);\r
-               if(cm.size() == 0)\r
-                       writeSupport.addMetadata(cm.add(writeTraits.toString()));\r
-\r
-               metadata = writeSupport.getMetadata();\r
-\r
-        Operation op = combine ? lastOperation : null;\r
-        combine = true;\r
-        byte[] data = makeContext(op, metadata);\r
-//        new Exception("committing cs + " + lastChangeSetId + " with metadata " + data).printStackTrace();\r
-        graphSession.acceptCommit(id.get(), lastChangeSetId, data);\r
-        ++lastChangeSetId;\r
-        if (DEBUG)\r
-            System.out.println("DEBUG Accpeted commit cs=" + lastChangeSetId);\r
-        clusterStream.accept();\r
-        writeSupport.commitDone(writeTraits, lastChangeSetId);\r
-        long opid = (null == op) ? lastChangeSetId : op.getId();\r
-        lastOperation = new OperationImpl(opid, lastChangeSetId, graphSession.undoContext.getPendingExternals());\r
-        graphSession.undoContext.commitOk(lastOperation);\r
-        if (DEBUG)\r
-            System.out.println("DEBUG: Accepted operation id=" + lastOperation.getId() + " cs=" + lastOperation.getCSId() + ".");\r
-    }\r
-    void stopWriteTransaction()\r
-    throws DatabaseException {\r
-        if (DEBUG)\r
-            System.out.println("DEBUG: Stop write transaction begin " + graphSession);\r
-        if (Type.Null == type)\r
-            return;\r
-        else if (Type.Read == type)\r
-            throw new ValidationException("Illegal transaction type.");\r
-        // Note that even if we do cancel the cluster sets are not restored.\r
-        try {\r
-            session.getService(ClusterSetsSupport.class).save();\r
-        } catch (Exception e) {\r
-            e.printStackTrace();\r
-            Logger.defaultLogError("Failed to save cluster sets.", e);\r
-        }\r
-        graphSession.undoContext.cancelCommit();\r
-        endTransaction(true);\r
-        if (DEBUG)\r
-            if (--pending != 0)\r
-                System.out.println("kraa: write end pending=" + pending);\r
-        if (DEBUG)\r
-            System.out.println("DEBUG: Stop write transaction end " + graphSession);\r
-    }\r
-    void closing()\r
-    throws DatabaseException {\r
-        if (Type.Null == type || id.get() == Constants.NullTransactionId)\r
-            return;\r
-        endTransaction(true);\r
-        if (DEBUG)\r
-            if (--pending != 0)\r
-                System.out.println("kraa: closing pending=" + pending);\r
-    }\r
-    void close() {\r
-        if (id.get() != Constants.NullTransactionId)\r
-            try {\r
-                endTransaction(true);\r
-                if (DEBUG)\r
-                    if (--pending != 0)\r
-                        System.out.println("kraa: closing pending=" + pending);\r
-            } catch (DatabaseException e) {\r
-                if (DEBUG) {\r
-                    System.out.println("Close did not finish cleanly.");\r
-                    e.printStackTrace();\r
-                }\r
-            }\r
-    }\r
-    public Operation getLastOperation() {\r
-        return lastOperation;\r
-    }\r
-    public long getHeadRevisionId() {\r
-        return lastChangeSetId;\r
-    }\r
-\r
-    static Serializer METADATA_SERIALIZER =\r
-        Bindings.getSerializerUnchecked( new TreeMapBinding(Bindings.STRING,\r
-            Bindings.BYTE_ARRAY) );\r
-\r
-    private byte[] makeContext(Operation op, TreeMap<String, byte[]> properties) {\r
-        if (properties == null)\r
-            properties = new TreeMap<String, byte[]>();\r
-        long csid = (null != op) ? op.getId() : 0;\r
-        properties.put(CommitMetadata.class.getName(), new CommitMetadata(csid).serialise(session));\r
-        properties.put("opid", Long.toString(csid).getBytes());\r
-\r
-        try {\r
-            return METADATA_SERIALIZER.serialize(properties);\r
-        } catch (SerializationException e) {\r
-            e.printStackTrace();\r
-            return new byte[0];\r
-        } catch (IOException e) {\r
-            e.printStackTrace();\r
-            return new byte[0];\r
-        }\r
-    }\r
-    private void endTransaction(boolean write)\r
-    throws DatabaseException {\r
-        if (DEBUG)\r
-            System.out.println("DEBUG: TransactionToken.end begin " + graphSession);\r
-        try {\r
-            graphSession.endTransaction(id.get(), write);\r
-        } finally {\r
-            type = Type.Null;\r
-            id.set(Constants.NullTransactionId);\r
-            synchronized (this) {\r
-                this.notify();\r
-            }\r
-        }\r
-        if (DEBUG)\r
-            System.out.println("DEBUG: TransactionToken.end end " + graphSession);\r
-    }\r
-    private void updateLastChangeSetId(long csid) {\r
-        if (DEBUG)\r
-            System.out.println("DEBUG: TransactionToken: Update last cs=" + lastChangeSetId + " new=" + csid + " "\r
-                    + graphSession + " " + this);\r
-        if (lastChangeSetId > csid)\r
-            return; // throw new\r
-// InternalError("Change set id has been corrupted. Last cs=" + lastChangeSetId\r
-// + " new=" + csid);\r
-        lastChangeSetId = csid;\r
-    }\r
-    public static void testEmpty() {\r
-        final int REPEAT_COUNT = 20;\r
-        final int REQUEST_COUNT = 500; // 1000;\r
-        double totalTime = 0;\r
-        for (int j = 0; j < REPEAT_COUNT; ++j) {\r
-            long start = System.nanoTime();\r
-            for (int i = 0; i < REQUEST_COUNT; ++i) {\r
-            }\r
-            long end = System.nanoTime();\r
-            double time = (end - start) * (1e-9);\r
-            totalTime += time;\r
-        }\r
-        double speed = REPEAT_COUNT * REQUEST_COUNT / totalTime;\r
-        String t = "Speed was " + speed + " ops per second.";\r
-        System.out.println(t);\r
-    }\r
-    @Override\r
-    public void onChangeSetId(int thread, long csid, boolean refresh) {\r
-        long ocsid = lastChangeSetId;\r
-        updateLastChangeSetId(csid);\r
-        if (refresh && csid > ocsid && id.get() == Constants.NullTransactionId)\r
-            refresh(thread, ocsid);\r
-    }\r
-    private void refresh(int thread, long csid) {\r
-        try {\r
-            if (session instanceof SessionImplSocket) {\r
-                ClusterUID[] clusterUID = graphSession.getRefresh2(csid);\r
-                ((SessionImplSocket)session).refresh(thread, clusterUID, csid);\r
-            }\r
-        } catch (DatabaseException e) {\r
-            Logger.defaultLogError(e);\r
-        }\r
-    }\r
+/*******************************************************************************
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package fi.vtt.simantics.procore.internal;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.TreeMap;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.simantics.databoard.Bindings;
+import org.simantics.databoard.binding.impl.TreeMapBinding;
+import org.simantics.databoard.serialization.SerializationException;
+import org.simantics.databoard.serialization.Serializer;
+import org.simantics.db.Operation;
+import org.simantics.db.Session;
+import org.simantics.db.common.CommentMetadata;
+import org.simantics.db.common.CommitMetadata;
+import org.simantics.db.common.MetadataUtils;
+import org.simantics.db.common.utils.Logger;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.db.exception.ValidationException;
+import org.simantics.db.impl.graph.WriteSupport;
+import org.simantics.db.procore.protocol.Constants;
+import org.simantics.db.request.WriteTraits;
+import org.simantics.db.service.ClusterSetsSupport;
+import org.simantics.db.service.ClusterUID;
+import org.simantics.db.service.ExternalOperation;
+import org.simantics.db.service.TransactionPolicySupport;
+
+public class TransactionToken implements GraphSession.Listener {
+    private boolean DEBUG = false;
+    private Session session;
+    private GraphSession graphSession;
+    private AtomicLong id;
+    // This is the last change set that we have send commit for.
+    // This does not mean that those commits have been accepted in server.
+    private long lastChangeSetId;
+    private OperationImpl lastOperation = null;
+    enum Type { Null, Read, Write }
+    private Type type;
+    // Count of pending transactions.
+    private int pending = 0;
+    TransactionToken(TransactionPolicySupport tps, Session session, GraphSession graphSession, TransactionToken old) {
+        this.session = session;
+        this.graphSession = graphSession;
+        this.type = Type.Null;
+        this.id = new AtomicLong();
+        this.id.set(Constants.NullTransactionId);
+        long firstChangeSetId = graphSession.getFirstChangeSetId();
+        if (null != old && old.lastChangeSetId > firstChangeSetId)
+            this.lastChangeSetId = old.lastChangeSetId;
+        else
+            this.lastChangeSetId = firstChangeSetId;
+        graphSession.setListener(this);
+    }
+    void startReadTransaction(int thread)
+    throws DatabaseException {
+        if (DEBUG)
+            System.out.println("DEBUG: Start read transaction " + graphSession);
+        if (Type.Null == type) {
+            id.set(graphSession.askReadTransaction(thread));
+            if (DEBUG)
+                if (++pending != 1)
+                    System.out.println("kraa: read beg pending=" + pending);
+            type = Type.Read;
+        }
+    }
+    void stopReadTransaction()
+    throws DatabaseException {
+        if (DEBUG)
+            System.out.println("DEBUG: Stop read transaction " + graphSession);
+        if (Type.Null == type)
+            return;
+        endTransaction(false);
+        if (DEBUG)
+            if (--pending != 0)
+                System.out.println("kraa: read end pending=" + pending);
+    }
+    void startWriteTransaction(int thread)
+    throws DatabaseException {
+        if (DEBUG)
+            System.out.println("DEBUG: Start write transaction " + graphSession);
+        if (Type.Null == type) {
+            id.set(graphSession.askWriteTransaction(thread, Constants.NullTransactionId));
+            if (DEBUG)
+                if (++pending != 1)
+                    System.out.println("kraa: write beg pending=" + pending);
+        } else if (Type.Read == type) {
+            id.set(graphSession.askWriteTransaction(thread, id.get()));
+            if (DEBUG)
+                if (++pending != 1)
+                    System.out.println("kraa: write beg pending=" + pending);
+        }
+        type = Type.Write;
+    }
+    void cancelBegin(WriteSupport writeSupport, SynchronizeContextI context, ClusterStream clusterStream)
+    throws DatabaseException {
+        if (DEBUG)
+            System.out.println("DEBUG: CancelBegin " + graphSession);
+        if (Type.Null == type)
+            return;
+        else if (Type.Read == type)
+            throw new ValidationException("Illegal token type.");
+        TreeMap<String, byte[]> metadata = writeSupport.getMetadata();
+        boolean empty = clusterStream.reallyFlush();
+        if (empty) {
+            boolean noMetadata = !hasMetadata(metadata);
+            if (noMetadata)
+                return;
+        }
+        byte[] data = makeContext(null, metadata);
+        graphSession.cancelCommit(id.get(), lastChangeSetId, data, context);
+        ++lastChangeSetId;
+        if (DEBUG)
+            System.out.println("DEBUG: CancelBegin cancelled cs=" + lastChangeSetId);
+        clusterStream.accept();
+        writeSupport.commitDone(null, lastChangeSetId);
+    }
+    void cancelEnd(WriteSupport writeSupport, WriteTraits writeTraits, ClusterStream clusterStream)
+    throws DatabaseException {
+        if (clusterStream.reallyFlush())
+            return;
+        TreeMap<String, byte[]> metadata = writeSupport.getMetadata();
+        byte[] data = makeContext(null, metadata);
+        graphSession.acceptCommit(id.get(), lastChangeSetId, data);
+        ++lastChangeSetId;
+        if (DEBUG)
+            System.out.println("DEBUG CancelEnd accepted commit cs=" + lastChangeSetId);
+        clusterStream.accept();
+        writeSupport.commitDone(writeTraits, lastChangeSetId);
+    }
+    void setCombine(boolean a) {
+        combine = a;
+    }
+    private boolean hasMetadata(TreeMap<String, byte[]> metadata) {
+       
+       if(metadata == null)
+               return false;
+       if(metadata.size() == 0)
+               return false;
+       if(metadata.size() == 1 && MetadataUtils.hasMetadata(metadata, CommentMetadata.class))
+               return false;
+       
+       return true;
+       
+    }
+    private boolean combine = false;
+    void commitWriteTransaction(WriteSupport writeSupport, WriteTraits writeTraits, ClusterStream clusterStream, Operation dummy)
+    throws DatabaseException {
+        if (DEBUG)
+            System.out.println("DEBUG: Commit write transaction " + graphSession);
+        if (Type.Null == type)
+            return;
+        else if (Type.Read == type)
+            throw new ValidationException("Illegal transaction type.");
+        else if (id.get() == Constants.NullTransactionId)
+            throw new ValidationException("Illegal transaction id.");
+        TreeMap<String, byte[]> metadata = writeSupport.getMetadata();
+        boolean empty = clusterStream.reallyFlush();
+        if (empty) {
+            boolean noMetadata = !hasMetadata(metadata);
+            if (noMetadata) {
+               List<ExternalOperation> ext = graphSession.undoContext.getPendingExternals();
+               if(!ext.isEmpty()) {
+                               CommentMetadata cm = writeSupport.getMetadata(CommentMetadata.class);
+                               writeSupport.addMetadata(cm.add("Automatically generated comment for empty commit with external undo operation(s)."));
+                               metadata = writeSupport.getMetadata();
+                               Logger.defaultLogError("A generated comment was added into a commit with only some external undo operation(s).");
+               } else {
+                       graphSession.undoContext.cancelCommit();
+                       return;
+               }
+            }
+        }
+
+               CommentMetadata cm = writeSupport.getMetadata(CommentMetadata.class);
+               if(cm.size() == 0)
+                       writeSupport.addMetadata(cm.add(writeTraits.toString()));
+
+               metadata = writeSupport.getMetadata();
+
+        Operation op = combine ? lastOperation : null;
+        combine = true;
+        byte[] data = makeContext(op, metadata);
+//        new Exception("committing cs + " + lastChangeSetId + " with metadata " + data).printStackTrace();
+        graphSession.acceptCommit(id.get(), lastChangeSetId, data);
+        ++lastChangeSetId;
+        if (DEBUG)
+            System.out.println("DEBUG Accpeted commit cs=" + lastChangeSetId);
+        clusterStream.accept();
+        writeSupport.commitDone(writeTraits, lastChangeSetId);
+        long opid = (null == op) ? lastChangeSetId : op.getId();
+        lastOperation = new OperationImpl(opid, lastChangeSetId, graphSession.undoContext.getPendingExternals());
+        graphSession.undoContext.commitOk(lastOperation);
+        if (DEBUG)
+            System.out.println("DEBUG: Accepted operation id=" + lastOperation.getId() + " cs=" + lastOperation.getCSId() + ".");
+    }
+    void stopWriteTransaction()
+    throws DatabaseException {
+        if (DEBUG)
+            System.out.println("DEBUG: Stop write transaction begin " + graphSession);
+        if (Type.Null == type)
+            return;
+        else if (Type.Read == type)
+            throw new ValidationException("Illegal transaction type.");
+        // Note that even if we do cancel the cluster sets are not restored.
+        graphSession.undoContext.cancelCommit();
+        endTransaction(true);
+        if (DEBUG)
+            if (--pending != 0)
+                System.out.println("kraa: write end pending=" + pending);
+        if (DEBUG)
+            System.out.println("DEBUG: Stop write transaction end " + graphSession);
+    }
+    void closing()
+    throws DatabaseException {
+        if (Type.Null == type || id.get() == Constants.NullTransactionId)
+            return;
+        endTransaction(true);
+        if (DEBUG)
+            if (--pending != 0)
+                System.out.println("kraa: closing pending=" + pending);
+    }
+    void close() {
+        if (id.get() != Constants.NullTransactionId)
+            try {
+                endTransaction(true);
+                if (DEBUG)
+                    if (--pending != 0)
+                        System.out.println("kraa: closing pending=" + pending);
+            } catch (DatabaseException e) {
+                if (DEBUG) {
+                    System.out.println("Close did not finish cleanly.");
+                    e.printStackTrace();
+                }
+            }
+    }
+    public Operation getLastOperation() {
+        return lastOperation;
+    }
+    public long getHeadRevisionId() {
+        return lastChangeSetId;
+    }
+
+    static Serializer METADATA_SERIALIZER =
+        Bindings.getSerializerUnchecked( new TreeMapBinding(Bindings.STRING,
+            Bindings.BYTE_ARRAY) );
+
+    private byte[] makeContext(Operation op, TreeMap<String, byte[]> properties) {
+        if (properties == null)
+            properties = new TreeMap<String, byte[]>();
+        long csid = (null != op) ? op.getId() : 0;
+        properties.put(CommitMetadata.class.getName(), new CommitMetadata(csid).serialise(session));
+        properties.put("opid", Long.toString(csid).getBytes());
+
+        try {
+            return METADATA_SERIALIZER.serialize(properties);
+        } catch (SerializationException e) {
+            e.printStackTrace();
+            return new byte[0];
+        } catch (IOException e) {
+            e.printStackTrace();
+            return new byte[0];
+        }
+    }
+    private void endTransaction(boolean write)
+    throws DatabaseException {
+        if (DEBUG)
+            System.out.println("DEBUG: TransactionToken.end begin " + graphSession);
+        try {
+            graphSession.endTransaction(id.get(), write);
+        } finally {
+            type = Type.Null;
+            id.set(Constants.NullTransactionId);
+            synchronized (this) {
+                this.notify();
+            }
+        }
+        if (DEBUG)
+            System.out.println("DEBUG: TransactionToken.end end " + graphSession);
+    }
+    private void updateLastChangeSetId(long csid) {
+        if (DEBUG)
+            System.out.println("DEBUG: TransactionToken: Update last cs=" + lastChangeSetId + " new=" + csid + " "
+                    + graphSession + " " + this);
+        if (lastChangeSetId > csid)
+            return; // throw new
+// InternalError("Change set id has been corrupted. Last cs=" + lastChangeSetId
+// + " new=" + csid);
+        lastChangeSetId = csid;
+    }
+    public static void testEmpty() {
+        final int REPEAT_COUNT = 20;
+        final int REQUEST_COUNT = 500; // 1000;
+        double totalTime = 0;
+        for (int j = 0; j < REPEAT_COUNT; ++j) {
+            long start = System.nanoTime();
+            for (int i = 0; i < REQUEST_COUNT; ++i) {
+            }
+            long end = System.nanoTime();
+            double time = (end - start) * (1e-9);
+            totalTime += time;
+        }
+        double speed = REPEAT_COUNT * REQUEST_COUNT / totalTime;
+        String t = "Speed was " + speed + " ops per second.";
+        System.out.println(t);
+    }
+    @Override
+    public void onChangeSetId(int thread, long csid, boolean refresh) {
+        long ocsid = lastChangeSetId;
+        updateLastChangeSetId(csid);
+        if (refresh && csid > ocsid && id.get() == Constants.NullTransactionId)
+            refresh(thread, ocsid);
+    }
+    private void refresh(int thread, long csid) {
+        try {
+            if (session instanceof SessionImplSocket) {
+                ClusterUID[] clusterUID = graphSession.getRefresh2(csid);
+                ((SessionImplSocket)session).refresh(thread, clusterUID, csid);
+            }
+        } catch (DatabaseException e) {
+            Logger.defaultLogError(e);
+        }
+    }
 }
\ No newline at end of file