/** * Copyright 2011 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.waveprotocol.box.server.frontend; import com.google.common.base.Function; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableSet; import com.google.common.collect.MapMaker; import com.google.common.collect.Sets; import org.waveprotocol.box.common.DeltaSequence; import org.waveprotocol.box.server.waveserver.WaveServerException; import org.waveprotocol.box.server.waveserver.WaveletProvider; 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.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 java.util.Map; import java.util.Map.Entry; import java.util.Set; /** * Provides services to manage and track wavelet participants and wavelet * subscriptions. * * @author yurize@apache.org (Yuri Zelikov) * @see ClientFrontendImpl */ public class WaveletInfo { /** Information we hold in memory for each wavelet. */ private static class PerWavelet { private final HashedVersion version0; private final Set<ParticipantId> explicitParticipants; private final Set<ParticipantId> implicitParticipants; private HashedVersion currentVersion; PerWavelet(WaveletName waveletName, HashedVersion hashedVersionZero) { this.explicitParticipants = Sets.newHashSet(); this.implicitParticipants = Sets.newHashSet(); this.version0 = hashedVersionZero; this.currentVersion = version0; } synchronized HashedVersion getCurrentVersion() { return currentVersion; } synchronized void setCurrentVersion(HashedVersion version) { this.currentVersion = version; } } private final Map<ParticipantId, UserManager> perUser; private final Map<WaveId, Map<WaveletId, PerWavelet>> perWavelet; private final WaveletProvider waveletProvider; /** * Creates new instance of {@link WaveletInfo}. * * @param hashFactory the factory for hashed versions. * @param provider the {@link WaveletProvider}. * @return new {@link WaveletInfo} instance. */ public static WaveletInfo create(HashedVersionFactory hashFactory, WaveletProvider provider) { return new WaveletInfo(hashFactory, provider); } WaveletInfo(final HashedVersionFactory hashedVersionFactory, WaveletProvider waveletProvider) { this.waveletProvider = waveletProvider; perWavelet = new MapMaker().makeComputingMap(new Function<WaveId, Map<WaveletId, PerWavelet>>() { @Override public Map<WaveletId, PerWavelet> apply(final WaveId waveId) { return new MapMaker().makeComputingMap(new Function<WaveletId, PerWavelet>() { @Override public PerWavelet apply(WaveletId waveletId) { WaveletName waveletName = WaveletName.of(waveId, waveletId); return new PerWavelet(waveletName, hashedVersionFactory .createVersionZero(waveletName)); } }); } }); perUser = new MapMaker().makeComputingMap(new Function<ParticipantId, UserManager>() { @Override public UserManager apply(ParticipantId from) { return new UserManager(); } }); } /** * Returns all visible wavelets in the wave specified by subscription which * are also comply with the subscription filter. */ public Set<WaveletId> visibleWaveletsFor(WaveViewSubscription subscription, ParticipantId loggedInUser) throws WaveServerException { Set<WaveletId> visible = Sets.newHashSet(); Set<Entry<WaveletId, PerWavelet>> entrySet = perWavelet.get(subscription.getWaveId()).entrySet(); for (Entry<WaveletId, PerWavelet> entry : entrySet) { WaveletName waveletName = WaveletName.of(subscription.getWaveId(), entry.getKey()); if (subscription.includes(entry.getKey()) && waveletProvider.checkAccessPermission(waveletName, loggedInUser)) { visible.add(entry.getKey()); } } return visible; } /** * Initializes front-end information from the wave store, if necessary. */ public void initialiseWave(WaveId waveId) throws WaveServerException { if (!perWavelet.containsKey(waveId)) { Map<WaveletId, PerWavelet> wavelets = perWavelet.get(waveId); for (WaveletId waveletId : waveletProvider.getWaveletIds(waveId)) { ReadableWaveletData wavelet = waveletProvider.getSnapshot(WaveletName.of(waveId, waveletId)).snapshot; // Wavelets is a computing map, so get() initializes the entry. PerWavelet waveletInfo = wavelets.get(waveletId); synchronized (waveletInfo) { waveletInfo.currentVersion = wavelet.getHashedVersion(); waveletInfo.explicitParticipants.addAll(wavelet.getParticipants()); } } } } /** * Synchronizes the wavelet version and ensures that the deltas are * contiguous. * * @param waveletName the wavelet name. * @param newDeltas the new deltas. */ public void syncWaveletVersion(WaveletName waveletName, DeltaSequence newDeltas) { HashedVersion expectedVersion; PerWavelet waveletInfo = getWavelet(waveletName); synchronized (waveletInfo) { expectedVersion = waveletInfo.getCurrentVersion(); Preconditions.checkState(expectedVersion.getVersion() == newDeltas.getStartVersion(), "Expected deltas starting at version %s, got %s", expectedVersion, newDeltas.getStartVersion()); waveletInfo.setCurrentVersion(newDeltas.getEndVersion()); } } /** * Returns {@link UserManager} for the participant. */ public UserManager getUserManager(ParticipantId participantId) { return perUser.get(participantId); } /** * Returns the current wavelet version. */ public HashedVersion getCurrentWaveletVersion(WaveletName waveletName) { PerWavelet waveletInfo = getWavelet(waveletName); synchronized (waveletInfo) { return waveletInfo.getCurrentVersion(); } } /** * @param waveletName the waveletName. * @return the wavelet participants. */ public Set<ParticipantId> getWaveletParticipants(WaveletName waveletName) { PerWavelet waveletInfo = getWavelet(waveletName); synchronized (waveletInfo) { return ImmutableSet.copyOf(waveletInfo.explicitParticipants); } } /** * @param waveletName the waveletName. * @return the implicit wavelet participants. An implicit participant is not a * "strict" participant on the wavelet, but rather only opened the * wave and listens on updates. For example, anyone can open a shared * wave without becoming explicit participant. */ public Set<ParticipantId> getImplicitWaveletParticipants(WaveletName waveletName) { PerWavelet waveletInfo = getWavelet(waveletName); synchronized (waveletInfo) { return ImmutableSet.copyOf(waveletInfo.explicitParticipants); } } /** * Notifies that the participant was added from the wavelet. * * @param waveletName the wavelet name. * @param participant the participant. */ public void notifyAddedExplicitWaveletParticipant(WaveletName waveletName, ParticipantId participant) { PerWavelet waveletInfo = getWavelet(waveletName); synchronized (waveletInfo) { waveletInfo.explicitParticipants.add(participant); } } /** * Notifies that the participant was removed from the wavelet. * * @param waveletName the wavelet name. * @param participant the participant. */ public void notifyRemovedExplicitWaveletParticipant(WaveletName waveletName, ParticipantId participant) { PerWavelet waveletInfo = getWavelet(waveletName); synchronized (waveletInfo) { waveletInfo.explicitParticipants.remove(participant); } } /** * Notifies that an implicit participant opened the wave. * * @param waveletName the wavelet name. * @param participant the participant. */ public void notifyAddedImplcitParticipant(WaveletName waveletName, ParticipantId participant) { PerWavelet waveletInfo = getWavelet(waveletName); synchronized (waveletInfo) { if (!waveletInfo.explicitParticipants.contains(participant)) { waveletInfo.implicitParticipants.add(participant); } } } private PerWavelet getWavelet(WaveletName name) { return perWavelet.get(name.waveId).get(name.waveletId); } }