/** * Queues the client operations, and sends them to the server as a delta at * the first opportunity. Will call any registered UnsavedDataListeners before * returning. * * @param operations the operations to send, all of which must specify a creator */ public void onClientOperations(WaveletOperation operations[]) throws TransformException { DeltaPair transformedPair = (new DeltaPair(Arrays.asList(operations), serverOperations)).transform(); serverOperations = transformedPair.getServer(); for (WaveletOperation o : transformedPair.getClient()) { clientOperationQueue.add(o); } triggerUnsavedDataListener(); sendDelta(); }
/** * Checks whether two operation sequences are the same. */ public static boolean areSame(List<WaveletOperation> deltaA, List<WaveletOperation> deltaB) { if (deltaA.size() != deltaB.size()) { return false; } for (int i = 0; i < deltaA.size(); ++i) { if (!matchOperations(deltaA.get(i), deltaB.get(i))) { return false; } } return true; }
/** * Transform the given client delta against the known delta history. * * @param delta The received delta * @return The transformed client operation and it starts off from the latest version. * @throws TransformException */ public WaveletDelta onClientDelta(WaveletDelta delta) throws TransformException { if (delta.getTargetVersion().getVersion() > deltaHistory.getCurrentVersion()) { throw new TransformException("Client has a newer version than server knows. client: " + delta.getTargetVersion() + ", server: " + deltaHistory.getCurrentVersion()); } WaveletDelta result = delta; while (result.getTargetVersion().getVersion() < deltaHistory.getCurrentVersion()) { TransformedWaveletDelta serverDelta = deltaHistory.getDeltaStartingAt(result.getTargetVersion().getVersion()); if (serverDelta == null) { // Note that this will trigger if the available history changes out from // under us. This should not happen as the caller of this method should // control changes to the underlying set via locks, e.g. writeLock in // the WS's WaveletContext. throw new IllegalStateException("No delta at version: " + result.getTargetVersion()); } DeltaPair pair = new DeltaPair(result, serverDelta).transform(); result = new WaveletDelta(delta.getAuthor(), serverDelta.getResultingVersion(), pair.getClient()); } return result; }
/** * Gets the client/server delta pair that will be output by an appropriately * timed and parameterised call to this expected transform. */ DeltaPair getOutput() { assertNotNull("No output client delta specified", outputClientDelta); return new DeltaPair(outputClientDelta, outputServerDelta); }
/** * Gets the delta that should be produced if all expected transforms are * performed in order on the {@link #getInputServerDelta()}. */ public List<WaveletOperation> getOutputServerDelta() { List<WaveletOperation> delta; if (expectations.isEmpty()) { delta = firstServerInputDelta; } else { delta = expectations.getLast().getOutput().getServer(); } return delta; }
/** * Detect if the whole delta is an echo back. If it is then take it as if it was an ack. * Echo back is a result of reconnection/recovery. * @return If the delta is a echo back. * @throws TransformException */ private boolean detectEchoBack(TransformedWaveletDelta serverDelta) throws TransformException { // We have got all the initial list of operations. So do nothing. if (endOfStartingDelta == null || endOfStartingDelta.getVersion() <= serverDelta.getAppliedAtVersion()) { return false; } // Check to see if we are getting a delta that was sent by us, in case the // server echos back our own delta from a recovery scenario. if (unacknowledged != null && DeltaPair.areSame(serverDelta, unacknowledged)) { // If we completely match then take it as an ack. onSuccess(serverDelta.size(), serverDelta.getResultingVersion()); return true; } // We've just got to the end of the initial list of operations // and there is no match. That means we need to merge the unacknowledged // ops and resend again. if (endOfStartingDelta.equals(serverDelta.getResultingVersion())) { mergeToClientQueue(inferredServerPath.size()); } return false; }
/** * Gets the client/server delta pair that will be output by an appropriately * timed and parameterised call to this expected transform. */ DeltaPair getOutput() { assertNotNull("No output client delta specified", outputClientDelta); return new DeltaPair(outputClientDelta, outputServerDelta); }
/** * Gets the delta that should be produced if all expected transforms are * performed in order on the {@link #getInputServerDelta()}. */ public List<WaveletOperation> getOutputServerDelta() { List<WaveletOperation> delta; if (expectations.isEmpty()) { delta = firstServerInputDelta; } else { delta = expectations.getLast().getOutput().getServer(); } return delta; }
/** * Test multiple server and client ops * @throws TransformException */ public void testMultipleClientServerOps() throws TransformException { // Client insert ".A.B" List<WaveletOperation> client = CollectionUtils.newArrayList(); client.add(CLIENT_UTIL.insert(1, "A", 1, null)); client.add(CLIENT_UTIL.insert(3, "B", 0, null)); // Server insert ".2.1" List<WaveletOperation> server = CollectionUtils.newArrayList(); server.add(SERVER_UTIL.insert(2, "1", 0, null)); server.add(SERVER_UTIL.insert(1, "2", 2, null)); DeltaPair pair = new DeltaPair(client, server); pair = pair.transform(); // Expect the transformation of the inserts are correct. If client and server // have the same insert point, client op is transformed to the left of the server op. // Expect client inserts ".A..B." assertEquals(2, pair.getClient().size()); checkInsert(pair.getClient().get(0), 1, "A", 3); checkInsert(pair.getClient().get(1), 4, "B", 1); // Expect server inserts "..2..1" assertEquals(2, pair.getServer().size()); checkInsert(pair.getServer().get(0), 4, "1", 0); checkInsert(pair.getServer().get(1), 2, "2", 3); }
/** * Test multiple server and client ops * @throws TransformException */ public void testMultipleClientServerOps() throws TransformException { // Client insert ".A.B" List<WaveletOperation> client = CollectionUtils.newArrayList(); client.add(CLIENT_UTIL.insert(1, "A", 1, null)); client.add(CLIENT_UTIL.insert(3, "B", 0, null)); // Server insert ".2.1" List<WaveletOperation> server = CollectionUtils.newArrayList(); server.add(SERVER_UTIL.insert(2, "1", 0, null)); server.add(SERVER_UTIL.insert(1, "2", 2, null)); DeltaPair pair = new DeltaPair(client, server); pair = pair.transform(); // Expect the transformation of the inserts are correct. If client and server // have the same insert point, client op is transformed to the left of the server op. // Expect client inserts ".A..B." assertEquals(2, pair.getClient().size()); checkInsert(pair.getClient().get(0), 1, "A", 3); checkInsert(pair.getClient().get(1), 4, "B", 1); // Expect server inserts "..2..1" assertEquals(2, pair.getServer().size()); checkInsert(pair.getServer().get(0), 4, "1", 0); checkInsert(pair.getServer().get(1), 2, "2", 3); }
/** * Simple test for deltas that have the same operations and the same author. * @throws TransformException */ public void testIsSame() throws TransformException { // Client insert ".A.B" List<WaveletOperation> client = CollectionUtils.newArrayList(); client.add(CLIENT_UTIL.insert(1, "A", 1, null)); client.add(CLIENT_UTIL.insert(3, "B", 0, null)); // Server insert ".A.B HashedVersion resultingVersion = HashedVersion.of(1L, new byte[] {1, 2, 3, 4}); List<WaveletOperation> server = CollectionUtils.newArrayList(); // Use CLIENT_UTIL to get the same author info. server.add(CLIENT_UTIL.insert(1, "A", 1, null)); server.add(CLIENT_UTIL.insert(3, "B", 0, resultingVersion)); // Deltas with same ops are the same, other info should be ignored assertTrue(DeltaPair.areSame(client, server)); // Transforming the ops should result in only version update server ops DeltaPair pair = new DeltaPair(client, server); pair = pair.transform(); assertEquals(0, pair.getClient().size()); assertEquals(2, pair.getServer().size()); checkVersionUpdate(pair.getServer().get(0), 1, null); checkVersionUpdate(pair.getServer().get(1), 1, resultingVersion); }
/** * Simple test for deltas that have the same operations and the same author. * @throws TransformException */ public void testIsSame() throws TransformException { // Client insert ".A.B" List<WaveletOperation> client = CollectionUtils.newArrayList(); client.add(CLIENT_UTIL.insert(1, "A", 1, null)); client.add(CLIENT_UTIL.insert(3, "B", 0, null)); // Server insert ".A.B HashedVersion resultingVersion = HashedVersion.of(1L, new byte[] {1, 2, 3, 4}); List<WaveletOperation> server = CollectionUtils.newArrayList(); // Use CLIENT_UTIL to get the same author info. server.add(CLIENT_UTIL.insert(1, "A", 1, null)); server.add(CLIENT_UTIL.insert(3, "B", 0, resultingVersion)); // Deltas with same ops are the same, other info should be ignored assertTrue(DeltaPair.areSame(client, server)); // Transforming the ops should result in only version update server ops DeltaPair pair = new DeltaPair(client, server); pair = pair.transform(); assertEquals(0, pair.getClient().size()); assertEquals(2, pair.getServer().size()); checkVersionUpdate(pair.getServer().get(0), 1, null); checkVersionUpdate(pair.getServer().get(1), 1, resultingVersion); }