/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.Preconditions;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableSet;
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 org.waveprotocol.wave.util.logging.Log;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ExecutionException;
/**
* Provides services to manage and track wavelet participants and wavelet
* subscriptions.
*
* @author yurize@apache.org (Yuri Zelikov)
* @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 {
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 LoadingCache<ParticipantId, UserManager> perUser;
private final LoadingCache<WaveId, LoadingCache<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 =
CacheBuilder.newBuilder().build(new CacheLoader<WaveId, LoadingCache<WaveletId, PerWavelet>>() {
@Override
public LoadingCache<WaveletId, PerWavelet> load(final WaveId waveId) {
return CacheBuilder.newBuilder().build(new CacheLoader<WaveletId, PerWavelet>() {
@Override
public PerWavelet load(WaveletId waveletId) {
WaveletName waveletName = WaveletName.of(waveId, waveletId);
return new PerWavelet(waveletName, hashedVersionFactory
.createVersionZero(waveletName));
}
});
}
});
perUser = CacheBuilder.newBuilder().build(new CacheLoader<ParticipantId, UserManager>() {
@Override
public UserManager load(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;
try {
entrySet = perWavelet.get(subscription.getWaveId()).asMap().entrySet();
} catch (ExecutionException ex) {
throw new RuntimeException(ex);
}
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(LOG.isFineLoggable()) {
LOG.fine("frontend initialiseWave(" + waveId +")");
}
try {
if (perWavelet.getIfPresent(waveId) == null) {
LoadingCache<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();
if(LOG.isFineLoggable()) {
LOG.fine("frontend wavelet " + waveletId + " @" + wavelet.getHashedVersion().getVersion());
}
waveletInfo.explicitParticipants.addAll(wavelet.getParticipants());
}
}
}
} catch (ExecutionException ex) {
throw new RuntimeException(ex);
}
}
/**
* 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) {
try {
return perUser.get(participantId);
} catch (ExecutionException ex) {
throw new RuntimeException(ex);
}
}
/**
* 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) {
try {
return perWavelet.get(name.waveId).get(name.waveletId);
} catch (ExecutionException ex) {
throw new RuntimeException(ex);
}
}
}