/** * Appends the given deltas to the deltas already stored. Updates the latest snapshot and latest * version as well. This method will make a copy of the snapshot. * * @param updatedSnapshot the snapshot after deltas have been applied * @param newDeltas the deltas that have been applied since the last call to appendDeltas. */ public void appendDeltas(ReadableWaveletData updatedSnapshot, DeltaSequence newDeltas) { HashedVersion newEndVersion = newDeltas.getEndVersion(); Preconditions.checkArgument( !newDeltas.isEmpty(), "There were no new deltas passed to appendDeltas"); Preconditions.checkArgument( updatedSnapshot.getVersion() == newEndVersion.getVersion(), String.format( "Version of snapshot %s doesn't match the HashedVersion %s", updatedSnapshot.getVersion(), newEndVersion)); Preconditions.checkArgument( areContiguousToCurrentVersion(newDeltas), String.format( "Deltas are not contiguous to the current version(%s) %s", getVersionAfterDeltas(), deltas)); WaveletName updatedWaveletName = WaveletDataUtil.waveletNameOf(updatedSnapshot); Preconditions.checkArgument( updatedWaveletName.equals(waveletName), String.format( "Updated wavelet doesn't have the same name as with which this class has been " + "instantiated. %s != %s", updatedWaveletName, waveletName)); // TODO(ljvderijk): This should actually be applying the deltas, however // they do not contain a timestamp at this time. snapshotAfterDeltas = WaveletDataUtil.copyWavelet(updatedSnapshot); deltas = DeltaSequence.join(deltas, newDeltas); }
/** @author [email protected] (Soren Lassen) */ public class WaveletDataUtilTest extends TestCase { public static final ParticipantId CREATOR = new ParticipantId("*****@*****.**"); public static final ParticipantId JOE = new ParticipantId("*****@*****.**"); public static final WaveletName WAVELET_NAME = WaveletName.of(WaveId.of("example.com", "w+wave"), WaveletId.of("example.com", "wavelet")); private WaveletOperationContext opContext(long timestamp, HashedVersion version) { return new WaveletOperationContext(CREATOR, timestamp, 1L, version); } private WaveletOperation addParticipant(ParticipantId user, long time, HashedVersion version) { return new AddParticipant(opContext(time, version), user); } private WaveletOperation addBlip(String id, long time, HashedVersion version) { return new WaveletBlipOperation( id, new BlipContentOperation(opContext(time, version), new DocOpBuilder().build())); } private TransformedWaveletDelta delta(WaveletOperation... ops) { WaveletOperation last = ops[ops.length - 1]; WaveletOperationContext ctx = last.getContext(); return new TransformedWaveletDelta( ctx.getCreator(), ctx.getHashedVersion(), ctx.getTimestamp(), Arrays.asList(ops)); } private ObservableWaveletData build(TransformedWaveletDelta... deltas) throws OperationException { return WaveletDataUtil.buildWaveletFromDeltas(WAVELET_NAME, Arrays.asList(deltas).iterator()); } public void testBuildWaveletFromOneDelta() throws Exception { WaveletData wavelet = build(delta(addParticipant(CREATOR, 1093L, HashedVersion.unsigned(1)))); assertEquals(WAVELET_NAME, WaveletDataUtil.waveletNameOf(wavelet)); assertEquals(CREATOR, wavelet.getCreator()); assertEquals(1093L, wavelet.getCreationTime()); assertEquals(1093L, wavelet.getLastModifiedTime()); assertEquals(HashedVersion.unsigned(1), wavelet.getHashedVersion()); assertEquals(ImmutableSet.of(), wavelet.getDocumentIds()); assertEquals(ImmutableSet.of(CREATOR), wavelet.getParticipants()); } public void testBuildWaveletFromThreeDeltas() throws Exception { WaveletData wavelet = build( delta(addParticipant(CREATOR, 1093L, HashedVersion.unsigned(1))), delta(addParticipant(JOE, 1492L, HashedVersion.unsigned(2))), delta(addBlip("blipid", 2010L, HashedVersion.unsigned(3)))); assertEquals(WAVELET_NAME, WaveletDataUtil.waveletNameOf(wavelet)); assertEquals(CREATOR, wavelet.getCreator()); assertEquals(1093L, wavelet.getCreationTime()); assertEquals(2010L, wavelet.getLastModifiedTime()); assertEquals(HashedVersion.unsigned(3), wavelet.getHashedVersion()); assertEquals(ImmutableSet.of("blipid"), wavelet.getDocumentIds()); assertEquals(ImmutableSet.of(CREATOR, JOE), wavelet.getParticipants()); } }
private <T extends WaveletContainer> T getWavelet( WaveletId waveletId, ConcurrentMap<WaveletId, T> waveletsMap) throws WaveletStateException { ImmutableSet<WaveletId> storedWavelets; try { storedWavelets = FutureUtil.getResultOrPropagateException(lookedupWavelets, PersistenceException.class); } catch (PersistenceException e) { throw new WaveletStateException( "Failed to lookup wavelet " + WaveletName.of(waveId, waveletId), e); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new WaveletStateException( "Interrupted looking up wavelet " + WaveletName.of(waveId, waveletId), e); } // Since waveletsMap is a computing map, we must call containsKey(waveletId) // to tell if waveletId is mapped, we cannot test if get(waveletId) returns null. if (!storedWavelets.contains(waveletId) && !waveletsMap.containsKey(waveletId)) { return null; } else { T wavelet = waveletsMap.get(waveletId); Preconditions.checkNotNull(wavelet, "computingMap returned null"); return wavelet; } }
@Override public T apply(WaveletId waveletId) { return factory.create(notifiee, WaveletName.of(waveId, waveletId), waveDomain); }
private WaveletName getFakeWaveletName(String domain) { return WaveletName.of(WaveId.of(domain, "wave"), WaveletId.of(domain, "wavelet")); }
/** * Tests for {@link WaveletState} implementations. * * @author [email protected] (Alex North) */ public abstract class WaveletStateTestBase extends TestCase { private static final WaveletName NAME = WaveletName.of(WaveId.of("example.com", "waveid"), WaveletId.of("example.com", "waveletid")); private static final ParticipantId AUTHOR = ParticipantId.ofUnsafe("*****@*****.**"); private static final DeltaTestUtil UTIL = new DeltaTestUtil(AUTHOR); private static final long TS = 1234567890L; private static final long TS2 = TS + 1; private static final long TS3 = TS2 + 1; private static final IdURIEncoderDecoder URI_CODEC = new IdURIEncoderDecoder(new JavaUrlCodec()); private static final HashedVersionFactory HASH_FACTORY = new HashedVersionFactoryImpl(URI_CODEC); private static final HashedVersion V0 = HASH_FACTORY.createVersionZero(NAME); /** Creates a new, empty wavelet state. */ protected abstract WaveletState createEmptyState(WaveletName name) throws Exception; /** * Waits for all pending persistence operations to be completed. All persistence listener * callbacks must be completed before this method returns. */ protected abstract void awaitPersistence() throws Exception; private WaveletDeltaRecord d1; private WaveletDeltaRecord d2; private WaveletDeltaRecord d3; private WaveletState target; @Override public void setUp() throws Exception { d1 = makeDelta(V0, TS, 2); d2 = makeDelta(d1.getResultingVersion(), TS2, 2); d3 = makeDelta(d2.getResultingVersion(), TS3, 1); target = createEmptyState(NAME); } public void testReportsWaveletName() { assertEquals(NAME, target.getWaveletName()); } public void testEmptyStateIsEmpty() { assertNull(target.getSnapshot()); assertEquals(V0, target.getCurrentVersion()); assertEquals(V0, target.getHashedVersion(0)); assertNull(target.getTransformedDelta(V0)); assertNull(target.getAppliedDelta(V0)); } public void testSnapshotMetadataReflectsDeltas() throws Exception { HashedVersion v2 = d1.getResultingVersion(); appendDeltas(d1); assertEquals(v2, target.getCurrentVersion()); ReadableWaveletData snapshot = target.getSnapshot(); assertEquals(AUTHOR, snapshot.getCreator()); assertEquals(v2, snapshot.getHashedVersion()); assertEquals(TS, snapshot.getCreationTime()); assertEquals(TS, snapshot.getLastModifiedTime()); assertEquals(2, snapshot.getVersion()); HashedVersion v4 = d2.getResultingVersion(); appendDeltas(d2); assertEquals(v4, target.getCurrentVersion()); snapshot = target.getSnapshot(); assertEquals(v4, snapshot.getHashedVersion()); assertEquals(4, snapshot.getVersion()); // Last-modified-time doesn't change due to unworthiness. } public void testHashedVersionAccessibleOnDeltaBoundaries() throws Exception { appendDeltas(d1, d2, d3); assertEquals(V0, target.getHashedVersion(0)); assertEquals(d1.getResultingVersion(), target.getHashedVersion(2)); assertEquals(d2.getResultingVersion(), target.getHashedVersion(4)); assertEquals(d3.getResultingVersion(), target.getHashedVersion(5)); assertNull(target.getHashedVersion(1)); assertNull(target.getHashedVersion(3)); assertNull(target.getHashedVersion(6)); } public void testDeltasAccessibleByBeginVersion() throws Exception { appendDeltas(d1, d2, d3); assertEquals(d1.transformed, target.getTransformedDelta(V0)); assertEquals(d1.applied, target.getAppliedDelta(V0)); assertEquals(d2.transformed, target.getTransformedDelta(d1.getResultingVersion())); assertEquals(d2.applied, target.getAppliedDelta(d1.getResultingVersion())); assertEquals(d3.transformed, target.getTransformedDelta(d2.getResultingVersion())); assertEquals(d3.applied, target.getAppliedDelta(d2.getResultingVersion())); // Wrong hashes return null. assertNull(target.getTransformedDelta(HashedVersion.unsigned(0))); assertNull(target.getAppliedDelta(HashedVersion.unsigned(0))); } public void testDeltasAccesssibleByEndVersion() throws Exception { appendDeltas(d1, d2, d3); for (WaveletDeltaRecord d : Arrays.asList(d1, d2, d3)) { assertEquals(d.transformed, target.getTransformedDeltaByEndVersion(d.getResultingVersion())); assertEquals(d.applied, target.getAppliedDeltaByEndVersion(d.getResultingVersion())); } // Wrong hashes return null. assertNull( target.getTransformedDeltaByEndVersion( HashedVersion.unsigned(d1.getResultingVersion().getVersion()))); assertNull( target.getAppliedDeltaByEndVersion( HashedVersion.unsigned(d1.getResultingVersion().getVersion()))); } public void testDeltaHistoryRequiresCorrectHash() throws Exception { appendDeltas(d1); // Wrong start hash. assertNull( target.getTransformedDeltaHistory(HashedVersion.unsigned(0), d1.getResultingVersion())); assertNull(target.getAppliedDeltaHistory(HashedVersion.unsigned(0), d1.getResultingVersion())); // Wrong end hash. assertNull( target.getTransformedDeltaHistory( V0, HashedVersion.unsigned(d1.getResultingVersion().getVersion()))); assertNull( target.getAppliedDeltaHistory( V0, HashedVersion.unsigned(d1.getResultingVersion().getVersion()))); } public void testSingleDeltaHistoryAccessible() throws Exception { appendDeltas(d1); DeltaSequence transformedHistory = target.getTransformedDeltaHistory(V0, d1.getResultingVersion()); assertNotNull(transformedHistory); assertEquals(1, transformedHistory.size()); assertEquals(d1.transformed, transformedHistory.get(0)); Collection<ByteStringMessage<ProtocolAppliedWaveletDelta>> appliedHistory = target.getAppliedDeltaHistory(V0, d1.getResultingVersion()); assertNotNull(appliedHistory); assertEquals(1, appliedHistory.size()); assertEquals(d1.applied, Iterables.getOnlyElement(appliedHistory)); } public void testDeltaHistoryQueriesCorrectHistory() throws Exception { appendDeltas(d1, d2, d3); checkHistoryForDeltas(d1); checkHistoryForDeltas(d1, d2); checkHistoryForDeltas(d2, d3); checkHistoryForDeltas(d1, d2, d3); } /** * Checks that a request for the deltas spanning a contiguous sequence of delta facets produces * correct results. */ private void checkHistoryForDeltas(WaveletDeltaRecord... deltas) { HashedVersion beginVersion = deltas[0].appliedAtVersion; HashedVersion endVersion = deltas[deltas.length - 1].transformed.getResultingVersion(); { List<TransformedWaveletDelta> expected = Lists.newArrayListWithExpectedSize(deltas.length); for (WaveletDeltaRecord d : deltas) { expected.add(d.transformed); } assertEquals(expected, target.getTransformedDeltaHistory(beginVersion, endVersion)); } { List<ByteStringMessage<ProtocolAppliedWaveletDelta>> expected = Lists.newArrayListWithExpectedSize(deltas.length); for (WaveletDeltaRecord d : deltas) { expected.add(d.applied); } assertTrue( Iterables.elementsEqual( expected, target.getAppliedDeltaHistory(beginVersion, endVersion))); } } public void checkSingleDeltaPersistFutureDone() throws Exception { appendDeltas(d1); Future<Void> future = target.persist(d1.getResultingVersion()); awaitPersistence(); assertTrue(future.isDone()); assertEquals(null, future.get()); assertEquals(d1.getResultingVersion(), target.getLastPersistedVersion()); } public void checkManyDeltasPersistFutureDone() throws Exception { appendDeltas(d1, d2, d3); Future<Void> future = target.persist(d3.getResultingVersion()); awaitPersistence(); assertTrue(future.isDone()); assertEquals(null, future.get()); assertEquals(d3.getResultingVersion(), target.getLastPersistedVersion()); } public void testCanPersistOnlySomeDeltas() throws Exception { appendDeltas(d1, d2, d3); Future<Void> future = target.persist(d2.getResultingVersion()); awaitPersistence(); assertTrue(future.isDone()); assertEquals(null, future.get()); assertEquals(d2.getResultingVersion(), target.getLastPersistedVersion()); future = target.persist(d3.getResultingVersion()); awaitPersistence(); assertTrue(future.isDone()); assertEquals(null, future.get()); assertEquals(d3.getResultingVersion(), target.getLastPersistedVersion()); } /** Applies a delta to the target. */ private void appendDeltas(WaveletDeltaRecord... deltas) throws InvalidProtocolBufferException, OperationException { for (WaveletDeltaRecord delta : deltas) { target.appendDelta(delta.appliedAtVersion, delta.transformed, delta.applied); } } /** * Creates a delta of no-ops and builds the corresponding applied and transformed delta objects. */ private static WaveletDeltaRecord makeDelta( HashedVersion appliedAtVersion, long timestamp, int numOps) throws InvalidProtocolBufferException { // Use no-op delta so the ops can actually apply. WaveletDelta delta = UTIL.makeNoOpDelta(appliedAtVersion, timestamp, numOps); ByteStringMessage<ProtocolAppliedWaveletDelta> appliedDelta = WaveServerTestUtil.buildAppliedDelta(delta, timestamp); TransformedWaveletDelta transformedDelta = AppliedDeltaUtil.buildTransformedDelta(appliedDelta, delta); return new WaveletDeltaRecord(appliedAtVersion, appliedDelta, transformedDelta); } }