/**
* Copyright 2010 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.waveserver;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.MapMaker;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListenableFutureTask;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import org.waveprotocol.box.common.ExceptionalIterator;
import org.waveprotocol.box.server.CoreSettings;
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 java.util.Iterator;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executor;
/**
* A collection of wavelets, local and remote, held in memory.
*
* @author soren@google.com (Soren Lassen)
*/
public class WaveMap {
/**
* Returns a future whose result is the ids of stored wavelets in the given wave.
* Any failure is reported as a {@link PersistenceException}.
*/
private static ListenableFuture<ImmutableSet<WaveletId>> lookupWavelets(
final WaveId waveId, final WaveletStore<?> waveletStore, Executor lookupExecutor) {
ListenableFutureTask<ImmutableSet<WaveletId>> task =
new ListenableFutureTask<ImmutableSet<WaveletId>>(
new Callable<ImmutableSet<WaveletId>>() {
@Override
public ImmutableSet<WaveletId> call() throws PersistenceException {
return waveletStore.lookup(waveId);
}
});
lookupExecutor.execute(task);
return task;
}
private final ConcurrentMap<WaveId, Wave> waves;
private final WaveletStore<?> store;
@Inject
public WaveMap(final DeltaAndSnapshotStore waveletStore,
final WaveletNotificationSubscriber notifiee,
WaveBus dispatcher,
final LocalWaveletContainer.Factory localFactory,
final RemoteWaveletContainer.Factory remoteFactory,
@Named(CoreSettings.WAVE_SERVER_DOMAIN) final String waveDomain,
@LookupExecutor final Executor lookupExecutor) {
// NOTE(anorth): DeltaAndSnapshotStore is more specific than necessary, but
// helps Guice out.
this.store = waveletStore;
waves = new MapMaker().makeComputingMap(new Function<WaveId, Wave>() {
@Override
public Wave apply(WaveId waveId) {
ListenableFuture<ImmutableSet<WaveletId>> lookedupWavelets =
lookupWavelets(waveId, waveletStore, lookupExecutor);
return new Wave(waveId, lookedupWavelets, notifiee, localFactory, remoteFactory,
waveDomain);
}
});
}
/**
* Loads all wavelets from storage.
*
* @throws WaveletStateException if storage access fails.
*/
public void loadAllWavelets() throws WaveletStateException {
try {
ExceptionalIterator<WaveId, PersistenceException> itr = store.getWaveIdIterator();
while (itr.hasNext()) {
WaveId waveId = itr.next();
lookupWavelets(waveId);
}
} catch (PersistenceException e) {
throw new WaveletStateException("Failed to scan waves", e);
}
}
/**
* Unloads all wavelets from memory.
*
* @throws WaveletStateException if storage access fails.
*/
public void unloadAllWavelets() throws WaveletStateException {
waves.clear();
}
/**
* Returns defensive copy of the map that holds waves.
*/
Map<WaveId, Wave> getWaves() {
return ImmutableMap.copyOf(waves);
}
public ExceptionalIterator<WaveId, WaveServerException> getWaveIds() {
Iterator<WaveId> inner = waves.keySet().iterator();
return ExceptionalIterator.FromIterator.create(inner);
}
public ImmutableSet<WaveletId> lookupWavelets(WaveId waveId) throws WaveletStateException {
ListenableFuture<ImmutableSet<WaveletId>> future = waves.get(waveId).getLookedupWavelets();
try {
return FutureUtil.getResultOrPropagateException(future, PersistenceException.class);
} catch (PersistenceException e) {
throw new WaveletStateException("Failed to look up wave " + waveId, e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new WaveletStateException("Interrupted while looking up wave " + waveId, e);
}
}
public LocalWaveletContainer getLocalWavelet(WaveletName waveletName)
throws WaveletStateException {
return waves.get(waveletName.waveId).getLocalWavelet(waveletName.waveletId);
}
public RemoteWaveletContainer getRemoteWavelet(WaveletName waveletName)
throws WaveletStateException {
return waves.get(waveletName.waveId).getRemoteWavelet(waveletName.waveletId);
}
public LocalWaveletContainer getOrCreateLocalWavelet(WaveletName waveletName) {
return waves.get(waveletName.waveId).getOrCreateLocalWavelet(waveletName.waveletId);
}
public RemoteWaveletContainer getOrCreateRemoteWavelet(WaveletName waveletName) {
return waves.get(waveletName.waveId).getOrCreateRemoteWavelet(waveletName.waveletId);
}
}