Merge branch 'trunk-staging' into 0.4-release-staging git-svn-id: https://svn.apache.org/repos/asf/incubator/wave/branches/wave-0.4-release@1517940 13f79535-47bb-0310-9956-ffa450edef68
Project: http://git-wip-us.apache.org/repos/asf/incubator-wave/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-wave/commit/7e9b6bd5 Tree: http://git-wip-us.apache.org/repos/asf/incubator-wave/tree/7e9b6bd5 Diff: http://git-wip-us.apache.org/repos/asf/incubator-wave/diff/7e9b6bd5 Branch: refs/heads/master Commit: 7e9b6bd5fadbd469481200f301b7537a1f33d6b4 Parents: 5458e2d Author: Ali Lown <[email protected]> Authored: Tue Aug 27 19:21:36 2013 +0000 Committer: Ali Lown <[email protected]> Committed: Tue Aug 27 19:21:36 2013 +0000 ---------------------------------------------------------------------- .../box/server/frontend/ClientFrontendImpl.java | 8 ++ .../box/server/frontend/WaveletInfo.java | 9 ++ .../waveserver/LocalWaveletContainer.java | 2 +- .../MemoryPerUserWaveViewHandlerImpl.java | 5 +- .../waveserver/PerUserWaveViewDistpatcher.java | 12 ++- .../waveserver/RemoteWaveletContainerImpl.java | 98 ++++++++++++++++++-- .../waveserver/SimpleSearchProviderImpl.java | 41 +++++++- .../box/server/waveserver/Wave.java | 15 +++ .../box/server/waveserver/WaveServerImpl.java | 24 +++-- .../server/waveserver/WaveletContainerImpl.java | 8 +- .../webclient/client/WindowTitleHandler.java | 8 +- .../box/webclient/search/WaveBasedDigest.java | 3 +- src/org/waveprotocol/wave/client/StageTwo.java | 2 +- .../client/wavepanel/impl/reader/Reader.java | 5 +- .../wave/model/conversation/TitleHelper.java | 48 +++++++++- .../waveprotocol/wave/model/id/IdGenerator.java | 7 ++ .../wave/model/id/IdGeneratorImpl.java | 6 ++ .../wave/model/wave/opbased/WaveViewImpl.java | 2 +- .../SimpleSearchProviderImplTest.java | 2 +- .../wave/CcBasedWaveViewTest.java | 7 +- 20 files changed, 278 insertions(+), 34 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7e9b6bd5/src/org/waveprotocol/box/server/frontend/ClientFrontendImpl.java ---------------------------------------------------------------------- diff --git a/src/org/waveprotocol/box/server/frontend/ClientFrontendImpl.java b/src/org/waveprotocol/box/server/frontend/ClientFrontendImpl.java index 68f70a8..3875b20 100644 --- a/src/org/waveprotocol/box/server/frontend/ClientFrontendImpl.java +++ b/src/org/waveprotocol/box/server/frontend/ClientFrontendImpl.java @@ -229,6 +229,9 @@ public class ClientFrontendImpl implements ClientFrontend, WaveBus.Subscriber { */ private void participantUpdate(WaveletName waveletName, ParticipantId participant, DeltaSequence newDeltas, boolean add, boolean remove) { + if(LOG.isFineLoggable()) { + LOG.fine("Notifying " + participant + " for " + waveletName); + } if (add) { waveletInfo.notifyAddedExplicitWaveletParticipant(waveletName, participant); } @@ -249,6 +252,11 @@ public class ClientFrontendImpl implements ClientFrontend, WaveBus.Subscriber { } WaveletName waveletName = WaveletName.of(wavelet.getWaveId(), wavelet.getWaveletId()); + + if(waveletInfo.getCurrentWaveletVersion(waveletName).getVersion() == 0 && LOG.isWarningLoggable()) { + LOG.warning("Wavelet does not appear to have been initialized by client. Continuing anyway."); + } + waveletInfo.syncWaveletVersion(waveletName, newDeltas); Set<ParticipantId> remainingparticipants = http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7e9b6bd5/src/org/waveprotocol/box/server/frontend/WaveletInfo.java ---------------------------------------------------------------------- diff --git a/src/org/waveprotocol/box/server/frontend/WaveletInfo.java b/src/org/waveprotocol/box/server/frontend/WaveletInfo.java index 76fda42..faf2fb3 100644 --- a/src/org/waveprotocol/box/server/frontend/WaveletInfo.java +++ b/src/org/waveprotocol/box/server/frontend/WaveletInfo.java @@ -35,6 +35,7 @@ import org.waveprotocol.wave.model.version.HashedVersion; import org.waveprotocol.wave.model.version.HashedVersionFactory; import org.waveprotocol.wave.model.wave.ParticipantId; import org.waveprotocol.wave.model.wave.data.ReadableWaveletData; +import org.waveprotocol.wave.util.logging.Log; import java.util.Map; import java.util.Map.Entry; @@ -48,6 +49,7 @@ import java.util.Set; * @see ClientFrontendImpl */ public class WaveletInfo { + private static final Log LOG = Log.get(WaveletInfo.class); /** Information we hold in memory for each wavelet. */ private static class PerWavelet { @@ -135,6 +137,10 @@ public class WaveletInfo { * Initializes front-end information from the wave store, if necessary. */ public void initialiseWave(WaveId waveId) throws WaveServerException { + if(LOG.isFineLoggable()) { + LOG.fine("frontend initialiseWave(" + waveId +")"); + } + if (!perWavelet.containsKey(waveId)) { Map<WaveletId, PerWavelet> wavelets = perWavelet.get(waveId); for (WaveletId waveletId : waveletProvider.getWaveletIds(waveId)) { @@ -144,6 +150,9 @@ public class WaveletInfo { PerWavelet waveletInfo = wavelets.get(waveletId); synchronized (waveletInfo) { waveletInfo.currentVersion = wavelet.getHashedVersion(); + if(LOG.isFineLoggable()) { + LOG.fine("frontend wavelet " + waveletId + " @" + wavelet.getHashedVersion().getVersion()); + } waveletInfo.explicitParticipants.addAll(wavelet.getParticipants()); } } http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7e9b6bd5/src/org/waveprotocol/box/server/waveserver/LocalWaveletContainer.java ---------------------------------------------------------------------- diff --git a/src/org/waveprotocol/box/server/waveserver/LocalWaveletContainer.java b/src/org/waveprotocol/box/server/waveserver/LocalWaveletContainer.java index 2d8a891..8290147 100644 --- a/src/org/waveprotocol/box/server/waveserver/LocalWaveletContainer.java +++ b/src/org/waveprotocol/box/server/waveserver/LocalWaveletContainer.java @@ -38,7 +38,7 @@ import org.waveprotocol.wave.model.version.HashedVersion; interface LocalWaveletContainer extends WaveletContainer { /** - * Manufactures remote wavelet containers. + * Manufactures local wavelet containers. */ interface Factory extends WaveletContainer.Factory<LocalWaveletContainer> { } http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7e9b6bd5/src/org/waveprotocol/box/server/waveserver/MemoryPerUserWaveViewHandlerImpl.java ---------------------------------------------------------------------- diff --git a/src/org/waveprotocol/box/server/waveserver/MemoryPerUserWaveViewHandlerImpl.java b/src/org/waveprotocol/box/server/waveserver/MemoryPerUserWaveViewHandlerImpl.java index d995d5e..7fa8408 100644 --- a/src/org/waveprotocol/box/server/waveserver/MemoryPerUserWaveViewHandlerImpl.java +++ b/src/org/waveprotocol/box/server/waveserver/MemoryPerUserWaveViewHandlerImpl.java @@ -97,7 +97,10 @@ public class MemoryPerUserWaveViewHandlerImpl implements PerUserWaveViewHandler Multimap<WaveId, WaveletId> perUserView = explicitPerUserWaveViews.get(user); if (!perUserView.containsEntry(waveletName.waveId, waveletName.waveletId)) { perUserView.put(waveletName.waveId, waveletName.waveletId); - LOG.fine("Added wavelet: " + waveletName + " to the view of user: " + user.getAddress()); + if(LOG.isFineLoggable()) { + LOG.fine("Added wavelet: " + waveletName + " to the view of user: " + user.getAddress()); + LOG.fine("View size is now: " + perUserView.size()); + } } } SettableFuture<Void> task = SettableFuture.create(); http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7e9b6bd5/src/org/waveprotocol/box/server/waveserver/PerUserWaveViewDistpatcher.java ---------------------------------------------------------------------- diff --git a/src/org/waveprotocol/box/server/waveserver/PerUserWaveViewDistpatcher.java b/src/org/waveprotocol/box/server/waveserver/PerUserWaveViewDistpatcher.java index f4db509..13c0aa2 100644 --- a/src/org/waveprotocol/box/server/waveserver/PerUserWaveViewDistpatcher.java +++ b/src/org/waveprotocol/box/server/waveserver/PerUserWaveViewDistpatcher.java @@ -30,6 +30,7 @@ import org.waveprotocol.wave.model.operation.wave.WaveletOperation; import org.waveprotocol.wave.model.version.HashedVersion; import org.waveprotocol.wave.model.wave.ParticipantId; import org.waveprotocol.wave.model.wave.data.ReadableWaveletData; +import org.waveprotocol.wave.util.logging.Log; import java.util.concurrent.CopyOnWriteArraySet; @@ -39,6 +40,7 @@ import java.util.concurrent.CopyOnWriteArraySet; * @author [email protected] (Yuri Zelikov) */ public class PerUserWaveViewDistpatcher implements WaveBus.Subscriber, PerUserWaveViewBus { + private static final Log LOG = Log.get(PerUserWaveViewDistpatcher.class); private static final CopyOnWriteArraySet<PerUserWaveViewBus.Listener> listeners = new CopyOnWriteArraySet<PerUserWaveViewBus.Listener>(); @@ -48,11 +50,19 @@ public class PerUserWaveViewDistpatcher implements WaveBus.Subscriber, PerUserWa WaveletId waveletId = wavelet.getWaveletId(); WaveId waveId = wavelet.getWaveId(); WaveletName waveletName = WaveletName.of(waveId, waveletId); + if(LOG.isInfoLoggable()) { + LOG.info("Got update for " + waveId + " " + waveletId); + } + // Find whether participants were added/removed and update the views // accordingly. for (TransformedWaveletDelta delta : deltas) { for (WaveletOperation op : delta) { if (op instanceof AddParticipant) { + if(LOG.isInfoLoggable()) { + LOG.info("Update contains AddParticipant for " + ((AddParticipant)op).getParticipantId()); + } + ParticipantId user = ((AddParticipant) op).getParticipantId(); // Check first if we need to update views for this user. for (Listener listener : listeners) { @@ -82,4 +92,4 @@ public class PerUserWaveViewDistpatcher implements WaveBus.Subscriber, PerUserWa public void removeListener(Listener listener) { listeners.remove(listener); } -} \ No newline at end of file +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7e9b6bd5/src/org/waveprotocol/box/server/waveserver/RemoteWaveletContainerImpl.java ---------------------------------------------------------------------- diff --git a/src/org/waveprotocol/box/server/waveserver/RemoteWaveletContainerImpl.java b/src/org/waveprotocol/box/server/waveserver/RemoteWaveletContainerImpl.java index a48cfc6..1cfaf46 100644 --- a/src/org/waveprotocol/box/server/waveserver/RemoteWaveletContainerImpl.java +++ b/src/org/waveprotocol/box/server/waveserver/RemoteWaveletContainerImpl.java @@ -51,7 +51,9 @@ import org.waveprotocol.wave.model.operation.wave.WaveletDelta; import org.waveprotocol.wave.model.version.HashedVersion; import org.waveprotocol.wave.util.logging.Log; +import java.util.ArrayList; import java.util.List; +import java.util.Iterator; import java.util.Map; import java.util.NavigableMap; import java.util.concurrent.Executor; @@ -73,6 +75,13 @@ class RemoteWaveletContainerImpl extends WaveletContainerImpl implements RemoteW pendingDeltas = Maps.newTreeMap(); /** + * Tracks the highest version commit notice received, which can not be performed + * due to not yet having the required deltas. This must only be access under writeLock. + */ + private boolean pendingCommit = false; + private HashedVersion pendingCommitVersion; + + /** * Create a new RemoteWaveletContainerImpl. Just pass through to the parent * constructor. */ @@ -95,14 +104,50 @@ class RemoteWaveletContainerImpl extends WaveletContainerImpl implements RemoteW @Override public void commit(HashedVersion version) { + try { + awaitLoad(); + } + catch(WaveletStateException ex) { + LOG.warning("Failed to load " + getWaveletName() + " to perform commit.", ex); + acquireWriteLock(); + markStateCorrupted(); + releaseWriteLock(); + return; + } + acquireWriteLock(); try { - persist(version, ImmutableSet.<String>of()); + attemptCommit(version); } finally { releaseWriteLock(); } } + /** + * Attempts to commit at the given version. + * This will only succeed if we are actually up to date. + * If not, then the history is assumed to be coming, and so we can just skip the whole task. + * */ + private void attemptCommit(HashedVersion version) { + HashedVersion expectedVersion = getCurrentVersion(); + if(expectedVersion == null || version.getVersion() == expectedVersion.getVersion()) { + LOG.info("Committed " + getWaveletName() + " at version " + version.getVersion()); + persist(version, ImmutableSet.<String>of()); + if(pendingCommitVersion == null || (version.getVersion() >= pendingCommitVersion.getVersion())) { + pendingCommit = false; + } + } else { + LOG.info("Ignoring commit request at " + version.getVersion() + + " since only at " + expectedVersion.getVersion()); + if(pendingCommitVersion == null || + (pendingCommitVersion != null && pendingCommitVersion.getVersion() < version.getVersion())) { + pendingCommitVersion = version; + } + LOG.info("pendingCommitVersion is now " + pendingCommitVersion.getVersion()); + pendingCommit = true; + } + } + private void internalUpdate(final List<ByteString> deltas, final String domain, final WaveletFederationProvider federationProvider, final CertificateManager certificateManager, final SettableFuture<Void> futureResult) { @@ -176,6 +221,18 @@ class RemoteWaveletContainerImpl extends WaveletContainerImpl implements RemoteW List<ByteStringMessage<ProtocolAppliedWaveletDelta>> appliedDeltas, final String domain, final WaveletFederationProvider federationProvider, final CertificateManager certificateManager, final SettableFuture<Void> futureResult) { + + try { + awaitLoad(); + } + catch(WaveletStateException ex) { + LOG.warning("Failed to load " + getWaveletName() + " to perform update.", ex); + acquireWriteLock(); + markStateCorrupted(); + releaseWriteLock(); + return; + } + LOG.info("Passed signer info check, now applying all " + appliedDeltas.size() + " deltas"); acquireWriteLock(); try { @@ -230,6 +287,11 @@ class RemoteWaveletContainerImpl extends WaveletContainerImpl implements RemoteW HashedVersion appliedAt = first.getKey(); ByteStringMessage<ProtocolAppliedWaveletDelta> appliedDelta = first.getValue(); + if(LOG.isInfoLoggable()) { + LOG.info("pendingDeltas.size(): " + Integer.toString(pendingDeltas.size())); + LOG.info("current appliedAt: " + appliedAt.getVersion() + " expected: " + expectedVersion.getVersion()); + } + // If we don't have the right version it implies there is a history we need, so set up a // callback to request it and fall out of this update if (appliedAt.getVersion() > expectedVersion.getVersion()) { @@ -310,14 +372,8 @@ class RemoteWaveletContainerImpl extends WaveletContainerImpl implements RemoteW pendingDeltas.remove(appliedAt); } - if (!haveRequestedHistory) { - notifyOfDeltas(resultingDeltas.build(), ImmutableSet.<String>of()); - futureResult.set(null); - } else if (!resultingDeltas.build().isEmpty()) { - LOG.severe("History requested but non-empty result, non-contiguous deltas?"); - } else { - LOG.info("History requested, ignoring callback"); - } + commitAndNotifyResultingDeltas(resultingDeltas, futureResult); + } catch (WaveServerException e) { LOG.warning("Update failure", e); // TODO(soren): make everyone throw FederationException instead @@ -330,6 +386,30 @@ class RemoteWaveletContainerImpl extends WaveletContainerImpl implements RemoteW } /** + * Commits the resulting deltas, notifying the server of them. + * Assumes that everything in resultingDeltas is now in-order, since + * even if the original stream was non-contiguous, we have requestedHistory. + * Even if not, it is still safe to commit up to the fragmented point. + */ + private void commitAndNotifyResultingDeltas( + ImmutableList.Builder<WaveletDeltaRecord> resultingDeltas, + final SettableFuture<Void> futureResult) { + if(!resultingDeltas.build().isEmpty()) { + notifyOfDeltas(resultingDeltas.build(), ImmutableSet.<String>of()); + futureResult.set(null); + + //Attempt to run any pending commit + if(pendingCommit) { + releaseWriteLock(); + commit(pendingCommitVersion); + acquireWriteLock(); + } + } else { + LOG.info("No deltas in list (fetching history?), ignoring callback"); + } + } + + /** * Apply a serialised applied delta to a remote wavelet. This assumes the * caller has validated that the delta is at the correct version and can be * applied to the wavelet. Must be called with writelock held. http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7e9b6bd5/src/org/waveprotocol/box/server/waveserver/SimpleSearchProviderImpl.java ---------------------------------------------------------------------- diff --git a/src/org/waveprotocol/box/server/waveserver/SimpleSearchProviderImpl.java b/src/org/waveprotocol/box/server/waveserver/SimpleSearchProviderImpl.java index 4201531..ee7093f 100644 --- a/src/org/waveprotocol/box/server/waveserver/SimpleSearchProviderImpl.java +++ b/src/org/waveprotocol/box/server/waveserver/SimpleSearchProviderImpl.java @@ -111,6 +111,13 @@ public class SimpleSearchProviderImpl implements SearchProvider { Function<ReadableWaveletData, Boolean> filterWaveletsFunction = createFilterWaveletsFunction(user, isAllQuery, withParticipantIds, creatorParticipantIds); Map<WaveId, WaveViewData> results = filterWavesViewBySearchCriteria(filterWaveletsFunction, currentUserWavesView); + + if(LOG.isFineLoggable()) { + for(Map.Entry e : results.entrySet()) { + LOG.fine("filtered results contains: " + e.getKey()); + } + } + Collection<WaveViewData> searchResult = computeSearchResult(user, startAt, numResults, queryParams, results); LOG.info("Search response to '" + query + "': " + searchResult.size() + " results, user: " @@ -128,6 +135,13 @@ public class SimpleSearchProviderImpl implements SearchProvider { // shared domain participant. currentUserWavesView.putAll(waveViewProvider.retrievePerUserWaveView(sharedDomainParticipantId)); } + + if(LOG.isFineLoggable()) { + for(Map.Entry e : currentUserWavesView.entries()) { + LOG.fine("unfiltered view contains: " + e.getKey() + " " + e.getValue()); + } + } + return currentUserWavesView; } @@ -167,16 +181,39 @@ public class SimpleSearchProviderImpl implements SearchProvider { for (WaveletId waveletId : waveletIds) { WaveletContainer waveletContainer = null; WaveletName waveletname = WaveletName.of(waveId, waveletId); + + // TODO (alown): Find some way to use isLocalWavelet to do this properly! try { - waveletContainer = waveMap.getLocalWavelet(waveletname); + if(LOG.isFineLoggable()) { + LOG.fine("Trying as a remote wavelet"); + } + waveletContainer = waveMap.getRemoteWavelet(waveletname); } catch (WaveletStateException e) { - LOG.severe(String.format("Failed to get local wavelet %s", waveletname.toString()), e); + LOG.severe(String.format("Failed to get remote wavelet %s", waveletname.toString()), e); + } catch (NullPointerException e) { + // This is a fairly normal case of it being a local-only wave. + // Yet this only seems to appear in the test suite. + // Continuing is completely harmless here. + LOG.info(String.format("%s is definitely not a remote wavelet. (Null key)", waveletname.toString()), e); } + + if(waveletContainer == null) { + try { + if(LOG.isFineLoggable()) { + LOG.fine("Trying as a local wavelet"); + } + waveletContainer = waveMap.getLocalWavelet(waveletname); + } catch (WaveletStateException e) { + LOG.severe(String.format("Failed to get local wavelet %s", waveletname.toString()), e); + } + } + // TODO (Yuri Z.) This loop collects all the wavelets that match the // query, so the view is determined by the query. Instead we should // look at the user's wave view and determine if the view matches the query. try { if (waveletContainer == null || !waveletContainer.applyFunction(matchesFunction)) { + LOG.fine("----doesn't match: " + waveletContainer); continue; } if (view == null) { http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7e9b6bd5/src/org/waveprotocol/box/server/waveserver/Wave.java ---------------------------------------------------------------------- diff --git a/src/org/waveprotocol/box/server/waveserver/Wave.java b/src/org/waveprotocol/box/server/waveserver/Wave.java index 94e7a43..b28069e 100644 --- a/src/org/waveprotocol/box/server/waveserver/Wave.java +++ b/src/org/waveprotocol/box/server/waveserver/Wave.java @@ -31,6 +31,7 @@ import org.waveprotocol.box.server.persistence.PersistenceException; import org.waveprotocol.wave.model.id.WaveId; import org.waveprotocol.wave.model.id.WaveletId; import org.waveprotocol.wave.model.id.WaveletName; +import org.waveprotocol.wave.util.logging.Log; import java.util.Iterator; import java.util.concurrent.ConcurrentMap; @@ -41,6 +42,8 @@ import java.util.concurrent.ConcurrentMap; * @author [email protected] (Soren Lassen) */ final class Wave implements Iterable<WaveletContainer> { + private static final Log LOG = Log.get(Wave.class); + private class WaveletCreator<T extends WaveletContainer> implements Function<WaveletId, T> { private final WaveletContainer.Factory<T> factory; @@ -120,6 +123,18 @@ final class Wave implements Iterable<WaveletContainer> { throw new WaveletStateException( "Interrupted looking up wavelet " + WaveletName.of(waveId, waveletId), e); } + + if(LOG.isFineLoggable()) { + if(storedWavelets != null) { + if(storedWavelets.contains(waveletId)) { + LOG.fine("Wavelet is in storedWavelets"); + } + if(waveletsMap.containsKey(waveletId)) { + LOG.fine("Wavelet is in wavletsMap"); + } + } + } + // 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 != null && !storedWavelets.contains(waveletId) http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7e9b6bd5/src/org/waveprotocol/box/server/waveserver/WaveServerImpl.java ---------------------------------------------------------------------- diff --git a/src/org/waveprotocol/box/server/waveserver/WaveServerImpl.java b/src/org/waveprotocol/box/server/waveserver/WaveServerImpl.java index 89e03d1..ecfbbb3 100644 --- a/src/org/waveprotocol/box/server/waveserver/WaveServerImpl.java +++ b/src/org/waveprotocol/box/server/waveserver/WaveServerImpl.java @@ -138,7 +138,7 @@ public class WaveServerImpl implements WaveletProvider, ReadableWaveletDataProvi if (isLocalWavelet(waveletName)) { LOG.warning("Got commit update for local wavelet " + waveletName); - callback.onFailure(FederationErrors.badRequest("Received comit update to local wavelet")); + callback.onFailure(FederationErrors.badRequest("Received commit update to local wavelet")); return; } @@ -153,17 +153,29 @@ public class WaveServerImpl implements WaveletProvider, ReadableWaveletDataProvi if (wavelet != null) { wavelet.commit(CoreWaveletOperationSerializer.deserialize(committedVersion)); } else { - // TODO(soren): This should really be changed to create the wavelet if it doesn't - // already exist and go get history up committedVersion. Moreover, when the - // protocol is enhanced to deliver commit updates reliably, we will probably need - // to only return success when we successfully retrieved history and persisted it all. - LOG.info("Got commit update for missing wavelet " + waveletName); + if(LOG.isInfoLoggable()) { + LOG.info("Got commit update for missing wavelet " + waveletName); + } + createAndCommitRemoteWavelet(waveletName, committedVersion); } callback.onSuccess(); } }; } + /** + * Creates the non-existent remote wavelet container at this server and commits it. + * Calling commit at this known version, forces the history to be fetched up to this point. + * TODO (alown): Possible race condition here with update? (Though I don't think it would result in + * anything more serious than repeated history fetches.) + */ + private void createAndCommitRemoteWavelet(WaveletName waveletName, ProtocolHashedVersion committedVersion) { + RemoteWaveletContainer wavelet = getOrCreateRemoteWavelet(waveletName); + HashedVersion v = CoreWaveletOperationSerializer.deserialize(committedVersion); + wavelet.commit(v); + LOG.info("Passed commit message for version " + v.getVersion() + " to RemoteWavelet"); + } + // // WaveletFederationProvider implementation. // http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7e9b6bd5/src/org/waveprotocol/box/server/waveserver/WaveletContainerImpl.java ---------------------------------------------------------------------- diff --git a/src/org/waveprotocol/box/server/waveserver/WaveletContainerImpl.java b/src/org/waveprotocol/box/server/waveserver/WaveletContainerImpl.java index 31fd121..fe08fe0 100644 --- a/src/org/waveprotocol/box/server/waveserver/WaveletContainerImpl.java +++ b/src/org/waveprotocol/box/server/waveserver/WaveletContainerImpl.java @@ -57,6 +57,8 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; +import javax.annotation.Nullable; + /** * Contains the history of a wavelet - applied and transformed deltas plus the * content of the wavelet. @@ -484,7 +486,7 @@ abstract class WaveletContainerImpl implements WaveletContainer { @Override public void requestHistory(HashedVersion startVersion, HashedVersion endVersion, - Receiver<ByteStringMessage<ProtocolAppliedWaveletDelta>> receiver) + Receiver<ByteStringMessage<ProtocolAppliedWaveletDelta>> receiver) throws AccessControlException, WaveletStateException { acquireReadLock(); try { @@ -548,7 +550,11 @@ abstract class WaveletContainerImpl implements WaveletContainer { } } + @Nullable protected HashedVersion getCurrentVersion() { + if(waveletState == null) + return null; + return waveletState.getCurrentVersion(); } http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7e9b6bd5/src/org/waveprotocol/box/webclient/client/WindowTitleHandler.java ---------------------------------------------------------------------- diff --git a/src/org/waveprotocol/box/webclient/client/WindowTitleHandler.java b/src/org/waveprotocol/box/webclient/client/WindowTitleHandler.java index 08987b0..ff1654f 100644 --- a/src/org/waveprotocol/box/webclient/client/WindowTitleHandler.java +++ b/src/org/waveprotocol/box/webclient/client/WindowTitleHandler.java @@ -57,10 +57,8 @@ public final class WindowTitleHandler implements WaveStore.Listener { @Override public void onOpened(WaveContext wave) { - Document document = - wave.getConversations().getRoot().getRootThread().getFirstBlip().getContent(); - String waveTitle = TitleHelper.extractTitle(document); - String windowTitle = formatTitle(waveTitle); + String waveTitle = TitleHelper.getTitle(wave); + String windowTitle = formatTitle(waveTitle); if (waveTitle == null || waveTitle.isEmpty()) { windowTitle = DEFAULT_TITLE; } @@ -76,4 +74,4 @@ public final class WindowTitleHandler implements WaveStore.Listener { private String formatTitle(String title) { return title + " - " + Session.get().getAddress() + " - " + APP_NAME; } -} \ No newline at end of file +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7e9b6bd5/src/org/waveprotocol/box/webclient/search/WaveBasedDigest.java ---------------------------------------------------------------------- diff --git a/src/org/waveprotocol/box/webclient/search/WaveBasedDigest.java b/src/org/waveprotocol/box/webclient/search/WaveBasedDigest.java index e6137ce..8c04db8 100644 --- a/src/org/waveprotocol/box/webclient/search/WaveBasedDigest.java +++ b/src/org/waveprotocol/box/webclient/search/WaveBasedDigest.java @@ -179,8 +179,7 @@ public final class WaveBasedDigest @Override public String getTitle() { - return TitleHelper.extractTitle( - wave.getConversations().getRoot().getRootThread().getFirstBlip().getContent()); + return TitleHelper.getTitle(wave); } @Override http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7e9b6bd5/src/org/waveprotocol/wave/client/StageTwo.java ---------------------------------------------------------------------- diff --git a/src/org/waveprotocol/wave/client/StageTwo.java b/src/org/waveprotocol/wave/client/StageTwo.java index 64228ab..557e838 100644 --- a/src/org/waveprotocol/wave/client/StageTwo.java +++ b/src/org/waveprotocol/wave/client/StageTwo.java @@ -463,7 +463,7 @@ public interface StageTwo { } }; WaveViewImpl<OpBasedWavelet> wave = - WaveViewImpl.create(waveletFactory, getWaveData().getWaveId(), getIdGenerator(), + WaveViewImpl.create(waveletFactory, snapshot.getWaveId(), getIdGenerator(), getSignedInUser(), WaveletConfigurator.ADD_CREATOR); // Populate the initial state. http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7e9b6bd5/src/org/waveprotocol/wave/client/wavepanel/impl/reader/Reader.java ---------------------------------------------------------------------- diff --git a/src/org/waveprotocol/wave/client/wavepanel/impl/reader/Reader.java b/src/org/waveprotocol/wave/client/wavepanel/impl/reader/Reader.java index bee1733..e97fec0 100644 --- a/src/org/waveprotocol/wave/client/wavepanel/impl/reader/Reader.java +++ b/src/org/waveprotocol/wave/client/wavepanel/impl/reader/Reader.java @@ -90,7 +90,10 @@ public final class Reader implements FocusFramePresenter.Listener, FocusOrder { } public boolean isRead(BlipView blipUi) { - return !supplement.isUnread(models.getBlip(blipUi)); + ConversationBlip blip = models.getBlip(blipUi); + if(blip != null) + return !supplement.isUnread(blip); + return false; } // http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7e9b6bd5/src/org/waveprotocol/wave/model/conversation/TitleHelper.java ---------------------------------------------------------------------- diff --git a/src/org/waveprotocol/wave/model/conversation/TitleHelper.java b/src/org/waveprotocol/wave/model/conversation/TitleHelper.java index ac2c5b3..9402d67 100644 --- a/src/org/waveprotocol/wave/model/conversation/TitleHelper.java +++ b/src/org/waveprotocol/wave/model/conversation/TitleHelper.java @@ -19,7 +19,9 @@ package org.waveprotocol.wave.model.conversation; - +//TODO (alown): should the WaveContext live under model instead? +import org.waveprotocol.box.webclient.search.WaveContext; +import org.waveprotocol.wave.model.document.Document; import org.waveprotocol.wave.model.document.MutableDocument; import org.waveprotocol.wave.model.document.ReadableWDocument; import org.waveprotocol.wave.model.document.operation.Attributes; @@ -239,6 +241,50 @@ public final class TitleHelper { } } + /** + * An error-absorbing title retrieving convenience function. + * Please use this rather than doing: + * wave.getConversations().getRoot().getRootThread().getFirstBlip().getContent() + * as that is the cause of _many_ shiny's in the client. + * In the event something went wrong, or no title exists will return the empty string. + * This is deliberate, so that it doesn't propagate an error for a rather non-critical + * code path. + */ + public static String getTitle(WaveContext waveCtx) { + ObservableConversationView conversations = waveCtx.getConversations(); + if(conversations == null) { + return ""; + } + + ObservableConversation rootConversation = conversations.getRoot(); + if(rootConversation == null) { + return ""; + } + + ObservableConversationThread rootThread = rootConversation.getRootThread(); + if(rootThread == null) { + return ""; + } + + ObservableConversationBlip firstBlip = rootThread.getFirstBlip(); + if(firstBlip == null) { + return ""; + } + + Document doc = firstBlip.getContent(); + if(doc == null) { + return ""; + } + + String title = extractTitle(doc); + if(title == null) { + return ""; + } + + return title; + } + private TitleHelper() { } } + http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7e9b6bd5/src/org/waveprotocol/wave/model/id/IdGenerator.java ---------------------------------------------------------------------- diff --git a/src/org/waveprotocol/wave/model/id/IdGenerator.java b/src/org/waveprotocol/wave/model/id/IdGenerator.java index 5c9dec8..94b9677 100644 --- a/src/org/waveprotocol/wave/model/id/IdGenerator.java +++ b/src/org/waveprotocol/wave/model/id/IdGenerator.java @@ -61,6 +61,13 @@ public interface IdGenerator { WaveletId newConversationRootWaveletId(); /** + * Creates/gets a root wavelet id for the given waveId + * (Federation happy version of newConversationRootWaveletId) + */ + + WaveletId buildConversationRootWaveletId(WaveId waveId); + + /** * Creates a user data wavelet id. * * Per-user data wavelets are specified by a leading token "user" followed by http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7e9b6bd5/src/org/waveprotocol/wave/model/id/IdGeneratorImpl.java ---------------------------------------------------------------------- diff --git a/src/org/waveprotocol/wave/model/id/IdGeneratorImpl.java b/src/org/waveprotocol/wave/model/id/IdGeneratorImpl.java index 1769f92..f762990 100644 --- a/src/org/waveprotocol/wave/model/id/IdGeneratorImpl.java +++ b/src/org/waveprotocol/wave/model/id/IdGeneratorImpl.java @@ -75,6 +75,7 @@ public class IdGeneratorImpl implements IdGenerator, IdConstants { return build(IdConstants.BLIP_PREFIX, peekUniqueToken()); } + //NOTE: These are _NOT_ federation happy. Ensure that your caller is! @Override public WaveId newWaveId() { return WaveId.of(defaultDomain, newId(WAVE_PREFIX)); @@ -91,6 +92,11 @@ public class IdGeneratorImpl implements IdGenerator, IdConstants { } @Override + public WaveletId buildConversationRootWaveletId(WaveId waveId) { + return WaveletId.of(waveId.getDomain(), CONVERSATION_ROOT_WAVELET); + } + + @Override public WaveletId newUserDataWaveletId(String address) { // TODO(anorth): Take ParticipantId as a parameter after moving it // into model.id package. http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7e9b6bd5/src/org/waveprotocol/wave/model/wave/opbased/WaveViewImpl.java ---------------------------------------------------------------------- diff --git a/src/org/waveprotocol/wave/model/wave/opbased/WaveViewImpl.java b/src/org/waveprotocol/wave/model/wave/opbased/WaveViewImpl.java index a2cff45..2ad81de 100644 --- a/src/org/waveprotocol/wave/model/wave/opbased/WaveViewImpl.java +++ b/src/org/waveprotocol/wave/model/wave/opbased/WaveViewImpl.java @@ -131,7 +131,7 @@ public final class WaveViewImpl<T extends ObservableWavelet> implements Observab this.viewer = viewer; this.idGenerator = idGenerator; this.configurator = configurator; - this.rootId = idGenerator.newConversationRootWaveletId(); + this.rootId = idGenerator.buildConversationRootWaveletId(waveId); this.userDataId = idGenerator.newUserDataWaveletId(viewer.getAddress()); } http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7e9b6bd5/test/org/waveprotocol/box/server/waveserver/SimpleSearchProviderImplTest.java ---------------------------------------------------------------------- diff --git a/test/org/waveprotocol/box/server/waveserver/SimpleSearchProviderImplTest.java b/test/org/waveprotocol/box/server/waveserver/SimpleSearchProviderImplTest.java index 30e0c2d..d966305 100644 --- a/test/org/waveprotocol/box/server/waveserver/SimpleSearchProviderImplTest.java +++ b/test/org/waveprotocol/box/server/waveserver/SimpleSearchProviderImplTest.java @@ -207,7 +207,7 @@ public class SimpleSearchProviderImplTest extends TestCase { waveMap = new WaveMap(waveletStore, notifiee, notifiee, localWaveletContainerFactory, - remoteWaveletContainerFactory, "example.com", lookupExecutor); + remoteWaveletContainerFactory, DOMAIN, lookupExecutor); searchProvider = new SimpleSearchProviderImpl(DOMAIN, digester, waveMap, waveViewProvider); } http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/7e9b6bd5/test/org/waveprotocol/wave/concurrencycontrol/wave/CcBasedWaveViewTest.java ---------------------------------------------------------------------- diff --git a/test/org/waveprotocol/wave/concurrencycontrol/wave/CcBasedWaveViewTest.java b/test/org/waveprotocol/wave/concurrencycontrol/wave/CcBasedWaveViewTest.java index 06e1cfc..863e8e3 100644 --- a/test/org/waveprotocol/wave/concurrencycontrol/wave/CcBasedWaveViewTest.java +++ b/test/org/waveprotocol/wave/concurrencycontrol/wave/CcBasedWaveViewTest.java @@ -65,7 +65,7 @@ public class CcBasedWaveViewTest extends TestCase { private static final WaveId WAVE_ID = WaveId.of("example.com", "waveId_1"); private static final WaveletId GENERATED_WAVELET_ID = WaveletId.of("example.com", "some_id"); private static final WaveletId ROOT_WAVELET_ID - = new IdGeneratorImpl("example.com", null).newConversationRootWaveletId(); + = new IdGeneratorImpl("example.com", null).buildConversationRootWaveletId(WAVE_ID); private static final String GENERATED_BLIP_ID = "some blip id"; private static final ParticipantId USER_ID = new ParticipantId("[email protected]"); private static final SchemaProvider SCHEMAS = SchemaCollection.empty(); @@ -214,6 +214,11 @@ public class CcBasedWaveViewTest extends TestCase { } @Override + public WaveletId buildConversationRootWaveletId(WaveId waveId) { + return ROOT_WAVELET_ID; + } + + @Override public String newDataDocumentId() { throw new UnsupportedOperationException("Unsupported for test"); }
