/**
* 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();
}
}