/**
* Copyright 2009 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.wave.model.wave.data.impl;
import org.waveprotocol.wave.model.document.Doc.E;
import org.waveprotocol.wave.model.document.Doc.N;
import org.waveprotocol.wave.model.document.Doc.T;
import org.waveprotocol.wave.model.document.Document;
import org.waveprotocol.wave.model.document.MutableDocument;
import org.waveprotocol.wave.model.document.indexed.DocumentHandler;
import org.waveprotocol.wave.model.document.indexed.IndexedDocument;
import org.waveprotocol.wave.model.document.operation.DocInitialization;
import org.waveprotocol.wave.model.document.operation.DocOp;
import org.waveprotocol.wave.model.document.operation.Nindo;
import org.waveprotocol.wave.model.document.operation.SuperSink;
import org.waveprotocol.wave.model.document.operation.automaton.DocumentSchema;
import org.waveprotocol.wave.model.document.raw.impl.Element;
import org.waveprotocol.wave.model.document.raw.impl.Node;
import org.waveprotocol.wave.model.document.raw.impl.Text;
import org.waveprotocol.wave.model.document.util.DocProviders;
import org.waveprotocol.wave.model.document.util.DocumentImpl;
import org.waveprotocol.wave.model.document.util.MutableDocumentProxy;
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.OperationSequencer;
import org.waveprotocol.wave.model.operation.SilentOperationSink;
import org.waveprotocol.wave.model.schema.SchemaProvider;
import org.waveprotocol.wave.model.util.Preconditions;
import org.waveprotocol.wave.model.wave.data.DocumentFactory;
import org.waveprotocol.wave.model.wave.data.DocumentOperationSink;
/**
* Mutable document that supports injection into a wave model. In order to be
* used in a wave model, a mutable document's output sink must be directed into
* the wave model. However, since there is a circular dependency between the
* document and its sink, the sink is {@link #init(SilentOperationSink)
* injected} after construction.
*
* Also supports schema validation
*
*/
public class PluggableMutableDocument extends MutableDocumentProxy.DocumentProxy
implements DocumentOperationSink {
private static class DocumentCreationContext {
final DocInitialization content;
final DocumentSchema schema;
final DocumentHandler<Node, Element, Text> handlerManager;
DocumentCreationContext(DocInitialization content, DocumentSchema schema,
DocumentHandler<Node, Element, Text> handlerManager) {
this.content = content;
this.schema = schema;
this.handlerManager = handlerManager;
}
IndexedDocument<Node, Element, Text> createDocument() throws OperationException {
return DocProviders.POJO.build(content, schema, handlerManager);
}
}
/**
* Factory.
*/
public static DocumentFactory<? extends PluggableMutableDocument> createFactory(
final SchemaProvider schemas) {
return new DocumentFactory<PluggableMutableDocument>() {
@Override
public PluggableMutableDocument create(WaveletId waveletId, String docId,
DocInitialization content) {
return new PluggableMutableDocument(content, schemas.getSchemaForId(waveletId, docId));
}
};
}
/**
* Dumb sequencer
*/
private final static class BasicSequencer implements OperationSequencer<Nindo> {
private final SuperSink sink;
private final SilentOperationSink<? super DocOp> outputSink;
private BasicSequencer(SuperSink sink,
SilentOperationSink<? super DocOp> outputSink) {
this.sink = sink;
this.outputSink = outputSink;
}
@Override
public void begin() {
}
@Override
public void end() {
}
/**
* Applies the operation to this document, and then sends it to the output
* sink.
*
* @param op mutation to apply
*/
@Override
public void consume(Nindo op) {
try {
DocOp docOp = sink.consumeAndReturnInvertible(op);
if (outputSink != null) {
outputSink.consume(docOp);
}
} catch (OperationException e) {
throw new OperationRuntimeException(
"DocumentOperationSink constructed by DocumentOperationSinkFactory "
+ "generated an OperationException when attempting to apply " + op, e);
}
}
}
/**
* Substrate underlying this document, it is only initialized when needed. Once it is created,
* the variable never changes.
*/
private IndexedDocument<Node, Element, Text> substrateDocument = null;
/**
* The contented needed to create the underlying substrate. Only used until substrateDocument is
* created and is freed immediately after.
*/
private DocumentCreationContext documentCreationContext;
private SilentOperationSink<? super DocOp> outputSink;
/**
* Creates a mutable document. This document will not be observable.
*
* @param content initialization content
*/
private PluggableMutableDocument(DocInitialization content, DocumentSchema schema) {
this(content, schema, null);
}
private IndexedDocument<Node, Element, Text> getDocument() {
if (substrateDocument == null) {
try {
createSubstrateDocument();
} catch (OperationException e) {
throw new OperationRuntimeException(
"Document initialization failed when applying operation: " +
documentCreationContext.content, e);
}
}
return substrateDocument;
}
/**
* @throws OperationException
*/
protected void createSubstrateDocument() throws OperationException {
substrateDocument = documentCreationContext.createDocument();
documentCreationContext = null;
}
/**
* Creates a mutable document, where events are sent to a handler.
*
* @param content initialization content
* @param handlerManager direct event receiver
*/
protected PluggableMutableDocument(DocInitialization content, DocumentSchema schema,
DocumentHandler<Node, Element, Text> handlerManager) {
super(null, "Impossible");
this.documentCreationContext = new DocumentCreationContext(content, schema, handlerManager);
}
@Override
public void init(final SilentOperationSink<? super DocOp> outputSink) {
Preconditions.checkState(this.outputSink == null, "Output sink may only be set once");
Preconditions.checkArgument(outputSink != null, "Output sink may not be null");
this.outputSink = outputSink;
}
@Override
protected MutableDocument<N, E, T> getDelegate() {
if (!hasDelegate()) {
SilentOperationSink<? super DocOp> delegateSink;
if (outputSink != null) {
delegateSink = outputSink;
} else {
// Output sink not set yet. That's ok, so long as ops aren't caused yet.
delegateSink = new SilentOperationSink<DocOp>() {
@Override
public void consume(DocOp op) {
Preconditions.checkState(outputSink != null, "Output sink not yet initialized");
outputSink.consume(op);
}
};
}
DocumentImpl delegate =
new DocumentImpl(new BasicSequencer(getDocument(), delegateSink), getDocument());
setDelegate(delegate);
}
return super.getDelegate();
}
@Override
public Document getMutableDocument() {
// To allow downcast to observable...
return this;
}
@Override
public DocInitialization asOperation() {
return getDocument().asOperation();
}
@Override
public void consume(DocOp op) throws OperationException {
getDocument().consume(op);
}
}