/** * 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.waveserver; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; 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 com.typesafe.config.Config; import org.waveprotocol.box.common.ExceptionalIterator; import org.waveprotocol.box.server.CoreSettingsNames; import org.waveprotocol.box.server.executor.ExecutorAnnotations.LookupExecutor; 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.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; /** * 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 = ListenableFutureTask.create( new Callable<ImmutableSet<WaveletId>>() { @Override public ImmutableSet<WaveletId> call() throws PersistenceException { return waveletStore.lookup(waveId); } }); lookupExecutor.execute(task); return task; } private final LoadingCache<WaveId, Wave> waves; private final WaveletStore<?> store; @Inject public WaveMap(final DeltaAndSnapshotStore waveletStore, final WaveletNotificationSubscriber notifiee, final LocalWaveletContainer.Factory localFactory, final RemoteWaveletContainer.Factory remoteFactory, @Named(CoreSettingsNames.WAVE_SERVER_DOMAIN) final String waveDomain, Config config, @LookupExecutor final Executor lookupExecutor) { this.store = waveletStore; waves = CacheBuilder.newBuilder() .maximumSize(config.getInt("core.wave_cache_size")) .expireAfterAccess(config.getDuration("core.wave_cache_expire", TimeUnit.MINUTES), TimeUnit.MINUTES) .build(new CacheLoader<WaveId, Wave>() { @Override public Wave load(WaveId waveId) throws Exception { 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.asMap().clear(); } /** * Returns defensive copy of the map that holds waves. */ Map<WaveId, Wave> getWaves() { return ImmutableMap.copyOf(waves.asMap()); } public ExceptionalIterator<WaveId, WaveServerException> getWaveIds() { Iterator<WaveId> inner = waves.asMap().keySet().iterator(); return ExceptionalIterator.FromIterator.create(inner); } public ImmutableSet<WaveletId> lookupWavelets(WaveId waveId) throws WaveletStateException { try { ListenableFuture<ImmutableSet<WaveletId>> future = waves.get(waveId).getLookedupWavelets(); 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); } catch (ExecutionException ex) { throw new RuntimeException(ex); } } /** * Tries to fetch the wavelet as remote, if not found - fetches as local * wavelet. * * @param waveletName the wavelet name * @return the local or remote wavelet. * @throws WaveletStateException if something goes wrong */ public WaveletContainer getWavelet(WaveletName waveletName) throws WaveletStateException { WaveletContainer waveletContainer = null; try { waveletContainer = getRemoteWavelet(waveletName); } catch (WaveletStateException | NullPointerException e) { // Ignored. } if (waveletContainer == null) { waveletContainer = getLocalWavelet(waveletName); } return waveletContainer; } public LocalWaveletContainer getLocalWavelet(WaveletName waveletName) throws WaveletStateException { try { return waves.get(waveletName.waveId).getLocalWavelet(waveletName.waveletId); } catch (ExecutionException ex) { throw new RuntimeException(ex); } } public RemoteWaveletContainer getRemoteWavelet(WaveletName waveletName) throws WaveletStateException { try { return waves.get(waveletName.waveId).getRemoteWavelet(waveletName.waveletId); } catch (ExecutionException ex) { throw new RuntimeException(ex); } } public LocalWaveletContainer getOrCreateLocalWavelet(WaveletName waveletName) { try { return waves.get(waveletName.waveId).getOrCreateLocalWavelet(waveletName.waveletId); } catch (ExecutionException ex) { throw new RuntimeException(ex); } } public RemoteWaveletContainer getOrCreateRemoteWavelet(WaveletName waveletName) { try { return waves.get(waveletName.waveId).getOrCreateRemoteWavelet(waveletName.waveletId); } catch (ExecutionException ex) { throw new RuntimeException(ex); } } }