/** * 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.Preconditions; import com.google.common.collect.ImmutableSet; import com.google.inject.Inject; import org.waveprotocol.box.common.ExceptionalIterator; import org.waveprotocol.box.server.persistence.FileNotFoundPersistenceException; import org.waveprotocol.box.server.persistence.PersistenceException; import org.waveprotocol.box.server.util.WaveletDataUtil; 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.operation.OperationException; import org.waveprotocol.wave.model.operation.wave.TransformedWaveletDelta; import org.waveprotocol.wave.model.version.HashedVersion; import org.waveprotocol.wave.model.wave.data.ReadableWaveletData; import java.io.IOException; import java.util.Collection; import java.util.Iterator; /** * Wave store backed by a {@link DeltaStore}. * * @author soren@google.com (Soren Lassen) */ class DeltaStoreBasedSnapshotStore implements DeltaAndSnapshotStore { /** * Wraps an {@link IOException} in a {@link RuntimeException}. */ private static class RuntimeIOException extends RuntimeException { private final IOException cause; public RuntimeIOException(IOException cause) { super(cause); this.cause = cause; } public IOException getIOException() { return cause; } } /** * Reads the transformed deltas from a {@link WaveletDeltaRecordReader}. */ private static class TransformedWaveletDeltaIterator implements Iterator<TransformedWaveletDelta> { private final WaveletDeltaRecordReader reader; private long nextVersion = 0; public TransformedWaveletDeltaIterator(WaveletDeltaRecordReader reader) { this.reader = reader; } @Override public boolean hasNext() { return !reader.isEmpty() && nextVersion < reader.getEndVersion().getVersion(); } /** * {@inheritDoc} * * @throws RuntimeIOException if the underlying reader throws * {@link IOException} * @throws IllegalStateException if there are gaps between deltas or * the next delta is empty */ @Override public TransformedWaveletDelta next() { try { TransformedWaveletDelta delta = reader.getTransformedDelta(nextVersion); Preconditions.checkState(delta != null, "no delta at version %s", nextVersion); Preconditions.checkState( delta.getAppliedAtVersion() < delta.getResultingVersion().getVersion(), "delta [%s, %s) is empty", delta.getAppliedAtVersion(), delta.getResultingVersion()); nextVersion = delta.getResultingVersion().getVersion(); return delta; } catch (IOException e) { throw new RuntimeIOException(e); } } @Override public void remove() { throw new UnsupportedOperationException(); } } /** * Reads all deltas and applies them all to construct the end wavelet state. */ private static ReadableWaveletData buildWaveletFromDeltaReader(WaveletDeltaRecordReader reader) throws PersistenceException { try { // TODO(soren): better error handling of IllegalStateExceptions and // OperationExceptions thrown from here ReadableWaveletData wavelet = WaveletDataUtil.buildWaveletFromDeltas(reader.getWaveletName(), new TransformedWaveletDeltaIterator(reader)); Preconditions.checkState(wavelet.getHashedVersion().equals(reader.getEndVersion())); return wavelet; } catch (OperationException e) { throw new PersistenceException(e); } catch (RuntimeIOException e) { throw new PersistenceException(e.getIOException()); } } /** * Creates a {@link DeltaAndSnapshotStore.WaveletAccess} instance which wraps * {@code deltasAccess}. * * @throws IllegalStateException if the delta history is bad */ private static WaveletAccess createWaveletAccess(DeltaStore.DeltasAccess deltasAccess) throws PersistenceException { ReadableWaveletData wavelet; wavelet = deltasAccess.isEmpty() ? null : buildWaveletFromDeltaReader(deltasAccess); return new DeltasAccessBasedWaveletAccess(deltasAccess, wavelet); } /** * Wraps {@link DeltaStore.DeltasAccess}. */ static class DeltasAccessBasedWaveletAccess extends ForwardingWaveletDeltaRecordReader implements WaveletAccess { private final DeltaStore.DeltasAccess deltasAccess; // TODO(soren): figure out thread safe access // (synchronize access to snapshot, isClosed? or make them atomic types?) private ReadableWaveletData snapshot; // is null when there are no deltas private boolean isClosed = false; private DeltasAccessBasedWaveletAccess(DeltaStore.DeltasAccess deltasAccess, ReadableWaveletData snapshot) { this.deltasAccess = deltasAccess; this.snapshot = snapshot; } @Override protected WaveletDeltaRecordReader delegate() { Preconditions.checkState(!isClosed, "Illegal access after closure"); return deltasAccess; } @Override public boolean isEmpty() { // Don't use the underlying deltasAccess method, rather let this // be controlled by our own state. return getSnapshot() == null; } @Override public HashedVersion getEndVersion() { // Don't use the underlying deltasAccess method, rather let this // be controlled by our own state. return getSnapshot().getHashedVersion(); } @Override public ReadableWaveletData getSnapshot() { Preconditions.checkState(!isClosed, "Illegal access after closure"); return snapshot; } @Override public void appendDeltas(Collection<WaveletDeltaRecord> deltas, ReadableWaveletData resultingSnapshot) throws PersistenceException { Preconditions.checkState(!isClosed, "Illegal access after closure"); // First append the deltas. deltasAccess.append(deltas); // Once the deltas have been stored, we update the wavelet data, which // affects the result of any calls to getEndVersion() or getSnapshot(). snapshot = resultingSnapshot; } @Override public void close() throws IOException { // We don't check if we're already closed, it's ok to call close() twice. isClosed = true; deltasAccess.close(); } } private final DeltaStore deltaStore; /** * Constructs a {@link DeltaAndSnapshotStore} instance which wraps {@code deltaStore}. * * @param deltaStore The underlying {@link DeltaStore}. */ @Inject public DeltaStoreBasedSnapshotStore(DeltaStore deltaStore) { this.deltaStore = deltaStore; } @Override public WaveletAccess open(WaveletName waveletName) throws PersistenceException { return createWaveletAccess(deltaStore.open(waveletName)); } @Override public void delete(WaveletName waveletName) throws PersistenceException, FileNotFoundPersistenceException { deltaStore.delete(waveletName); } @Override public ImmutableSet<WaveletId> lookup(WaveId waveId) throws PersistenceException { return deltaStore.lookup(waveId); } @Override public ExceptionalIterator<WaveId, PersistenceException> getWaveIdIterator() throws PersistenceException { return deltaStore.getWaveIdIterator(); } }