/** * Finds range of server deltas needed to transform against, then transforms all client ops * against the server ops. */ private VersionedWaveletDelta transformSubmittedDelta( WaveletDelta submittedDelta, HashedVersion appliedVersion) throws OperationException, InvalidHashException { NavigableSet<VersionedWaveletDelta> serverDeltas = deserializedTransformedDeltas.tailSet( deserializedTransformedDeltas.floor( emptyDeserializedDeltaAtVersion(appliedVersion.getVersion())), true); if (serverDeltas.size() == 0) { LOG.warning("Got empty server set, but not sumbitting to head! " + submittedDelta); // Not strictly an invalid hash, but it's a related issue throw new InvalidHashException("Cannot submit to head"); } // Confirm that the target version/hash of this delta is valid. if (!serverDeltas.first().version.equals(appliedVersion)) { LOG.warning( "Mismatched hashes: expected: " + serverDeltas.first().version + " got: " + appliedVersion); // Don't leak the hash to the client in the error message. throw new InvalidHashException("Mismatched hashes at version " + appliedVersion.getVersion()); } ParticipantId clientAuthor = submittedDelta.getAuthor(); List<WaveletOperation> clientOps = submittedDelta.getOperations(); for (VersionedWaveletDelta d : serverDeltas) { // If the client delta transforms to nothing before we've traversed all the server // deltas, return the version at which the delta was obliterated (rather than the // current version) to ensure that delta submission is idempotent. if (clientOps.isEmpty()) { return new VersionedWaveletDelta(new WaveletDelta(clientAuthor, clientOps), d.version); } ParticipantId serverAuthor = d.delta.getAuthor(); List<WaveletOperation> serverOps = d.delta.getOperations(); if (clientAuthor.equals(serverAuthor) && clientOps.equals(serverOps)) { return d; } clientOps = transformOps(clientOps, clientAuthor, serverOps, serverAuthor); } return new VersionedWaveletDelta(new WaveletDelta(clientAuthor, clientOps), currentVersion); }
/** * Commit an applied delta to this wavelet container. * * @param appliedDelta to commit * @param transformedDelta of the applied delta * @return result of the application */ protected DeltaApplicationResult commitAppliedDelta( ByteStringMessage<ProtocolAppliedWaveletDelta> appliedDelta, WaveletDelta transformedDelta) { ProtocolWaveletDelta transformedProtocolDelta = WaveletOperationSerializer.serialize(transformedDelta, currentVersion); transformedDeltas.add(transformedProtocolDelta); deserializedTransformedDeltas.add(new VersionedWaveletDelta(transformedDelta, currentVersion)); appliedDeltas.add(appliedDelta); HashedVersion newVersion = HASHED_HISTORY_VERSION_FACTORY.create( appliedDelta.getByteArray(), currentVersion, transformedDelta.getOperations().size()); currentVersion = newVersion; return new DeltaApplicationResult( appliedDelta, transformedProtocolDelta, WaveletOperationSerializer.serialize(newVersion)); }