/**
* 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.wave.model.testing;
import org.waveprotocol.wave.model.id.IdGenerator;
import org.waveprotocol.wave.model.id.WaveId;
import org.waveprotocol.wave.model.id.WaveletId;
import org.waveprotocol.wave.model.operation.OperationException;
import org.waveprotocol.wave.model.operation.OperationRuntimeException;
import org.waveprotocol.wave.model.operation.SilentOperationSink;
import org.waveprotocol.wave.model.operation.SilentOperationSink.Executor;
import org.waveprotocol.wave.model.operation.wave.WaveletOperation;
import org.waveprotocol.wave.model.schema.SchemaProvider;
import org.waveprotocol.wave.model.version.HashedVersion;
import org.waveprotocol.wave.model.wave.ParticipantId;
import org.waveprotocol.wave.model.wave.data.DocumentFactory;
import org.waveprotocol.wave.model.wave.data.ObservableWaveletData;
import org.waveprotocol.wave.model.wave.data.WaveletData;
import org.waveprotocol.wave.model.wave.data.impl.EmptyWaveletSnapshot;
import org.waveprotocol.wave.model.wave.data.impl.ObservablePluggableMutableDocument;
import org.waveprotocol.wave.model.wave.data.impl.WaveletDataImpl;
import org.waveprotocol.wave.model.wave.opbased.OpBasedWavelet;
import org.waveprotocol.wave.model.wave.opbased.WaveViewImpl;
/**
* Factory for creating {@link OpBasedWavelet} instances suitable for testing.
*
*/
public final class OpBasedWaveletFactory implements WaveViewImpl.WaveletFactory<OpBasedWavelet>,
Factory<OpBasedWavelet> {
/**
* An operation sink that, on every operation it consumes, fires a version
* update operation back to the wavelet, then passes the operation along to
* the next sink. Wavelet versioning is specifically designed to be
* server-controlled. In a test context, this sink is used to simulate the
* behaviour of a wavelet server firing back acknowledgements with version
* updates, in order that tests that mutate wavelets also see version number
* increase.
*/
private final static class VersionIncrementingSink implements
SilentOperationSink<WaveletOperation> {
private final WaveletData data;
private final SilentOperationSink<? super WaveletOperation> output;
public VersionIncrementingSink(WaveletData data,
SilentOperationSink<? super WaveletOperation> output) {
this.data = data;
this.output = output;
}
@Override
public void consume(WaveletOperation op) {
// Update local version, simulating server response.
try {
op.createVersionUpdateOp(1, null).apply(data);
} catch (OperationException e) {
throw new OperationRuntimeException("test sink verison update failed", e);
}
// Pass to output sink.
output.consume(op);
}
}
/**
* Builder, through which a factory can be conveniently configured.
*/
public final static class Builder {
private final SchemaProvider schemas;
private ObservableWaveletData.Factory<?> holderFactory;
private SilentOperationSink<? super WaveletOperation> sink;
private ParticipantId author;
public Builder(SchemaProvider schemas) {
this.schemas = schemas;
}
public Builder with(SilentOperationSink<? super WaveletOperation> sink) {
this.sink = sink;
return this;
}
public Builder with(ObservableWaveletData.Factory<?> holderFactory) {
this.holderFactory = holderFactory;
return this;
}
public Builder with(ParticipantId author) {
this.author = author;
return this;
}
public OpBasedWaveletFactory build() {
if (holderFactory == null) {
DocumentFactory<?> docFactory = ObservablePluggableMutableDocument.createFactory(schemas);
holderFactory = WaveletDataImpl.Factory.create(docFactory);
}
if (sink == null) {
sink = SilentOperationSink.VOID;
}
if (author == null) {
// Old tests expect this.
author = FAKE_PARTICIPANT;
}
return new OpBasedWaveletFactory(holderFactory, sink, author);
}
}
private static final ParticipantId FAKE_PARTICIPANT = new ParticipantId("fake@example.com");
// Parameters with which to create the OpBasedWavelets.
private final ObservableWaveletData.Factory<?> holderFactory;
private final SilentOperationSink<? super WaveletOperation> sink;
private final ParticipantId author;
// Testing hacks.
private MockWaveletOperationContextFactory lastContextFactory;
private MockParticipationHelper lastAuthoriser;
/**
* Creates a factory, which creates op-based waves that adapt wave data
* holders provided by another factory, sending produced operations to a given
* sink.
*
* @param holderFactory factory for providing wave data holders
* @param sink sink to which produced operations are sent
* @param author id to which edits are to be attributed
*/
private OpBasedWaveletFactory(ObservableWaveletData.Factory<?> holderFactory,
SilentOperationSink<? super WaveletOperation> sink,
ParticipantId author) {
this.holderFactory = holderFactory;
this.sink = sink;
this.author = author;
}
public static Builder builder(SchemaProvider schemas) {
return new Builder(schemas);
}
@Override
public OpBasedWavelet create() {
IdGenerator gen = FakeIdGenerator.create();
return create(gen.newWaveId(), gen.newConversationWaveletId(), FAKE_PARTICIPANT);
}
@Override
public OpBasedWavelet create(WaveId waveId, WaveletId waveletId, ParticipantId creator) {
long now = System.currentTimeMillis();
HashedVersion v0 = HashedVersion.unsigned(0);
ObservableWaveletData waveData = holderFactory
.create(new EmptyWaveletSnapshot(waveId, waveletId, creator, v0, now));
lastContextFactory = new MockWaveletOperationContextFactory().setParticipantId(author);
lastAuthoriser = new MockParticipationHelper();
SilentOperationSink<WaveletOperation> executor =
Executor.<WaveletOperation, WaveletData>build(waveData);
SilentOperationSink<WaveletOperation> out = new VersionIncrementingSink(waveData, sink);
return new OpBasedWavelet(waveId, waveData, lastContextFactory, lastAuthoriser, executor, out);
}
/**
* Gets the authoriser provided to help the last {@link OpBasedWavelet} that
* was created. The result is undefined if no wavelets have been created.
*/
public MockParticipationHelper getLastAuthoriser() {
return lastAuthoriser;
}
/**
* Gets the helper provided to the last {@link OpBasedWavelet} that was
* created. The result is undefined if no wavelets have been created.
*/
public MockWaveletOperationContextFactory getLastContextFactory() {
return lastContextFactory;
}
}