/** * Copyright 2008 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.operation.wave; import org.waveprotocol.wave.model.operation.OperationException; import org.waveprotocol.wave.model.operation.ReversibleOperation; import org.waveprotocol.wave.model.operation.Visitable; import org.waveprotocol.wave.model.version.HashedVersion; import org.waveprotocol.wave.model.wave.data.WaveletData; import java.util.Date; /** * Superclass of all wavelet operations. A wavelet operation occurs in a * {@link WaveletOperationContext}, and applies to a {@link WaveletData}. * * wavelet operations are also {@link Visitable} for the purpose of extracting serialization * and merge logic into separate classes. * * Operations are partitioned into wavelet operations, blip operations, and document operations, in * order to reduce the number of operation pairs that must be considered for transform and merge * (it only makes sense to transform or merge operations that apply to the same type). The boxing * relationships where document ops are boxed as a blip operation, which in turn is boxed as a * wavelet operation, are as follows: * * Operation<T> * A * ____________________________________|______________________________________ * | | | * WaveletOperation --- BlipOperation --- DocumentOperation * A | A | A * ____|__________ | ____|___________ | ______|______ * | | | | | | | | | | * ... ... WaveletBlipOperation <>-- ... ... BlipContentOperation <>-- ... (XML ops) ... * */ public abstract class WaveletOperation implements ReversibleOperation<WaveletOperation, WaveletData>, Visitable<WaveletOperationVisitor> { /** * Clones a wavelet operation, attaching a new context. */ // This might be better as a member method, but is less code this way. public static WaveletOperation cloneOp(WaveletOperation op, WaveletOperationContext newContext) { if (op instanceof NoOp) { return new NoOp(newContext); } else if (op instanceof AddParticipant) { return new AddParticipant(newContext, ((AddParticipant) op).getParticipantId()); } else if (op instanceof RemoveParticipant) { return new RemoveParticipant(newContext, ((RemoveParticipant) op).getParticipantId()); } else if (op instanceof WaveletBlipOperation) { String docId = ((WaveletBlipOperation) op).getBlipId(); // BlipContentOperation is the only expected blip op type. BlipContentOperation blipOp = (BlipContentOperation) ((WaveletBlipOperation) op).getBlipOp(); return new WaveletBlipOperation(docId, new BlipContentOperation(newContext, blipOp.getContentOp())); } else { throw new IllegalArgumentException("Un-cloneable operation: " + op); } } /** Context/metadata which does not affect the logic of an operation. */ protected final WaveletOperationContext context; /** * Constructs a wavelet operation in a given context. * * @param context context in which this operation is occuring */ protected WaveletOperation(WaveletOperationContext context) { this.context = context; } /** * Gets the operation context. * * @return the operation context. */ public WaveletOperationContext getContext() { return context; } /** * {@inheritDoc} * * This method delegates the operation logic to {@link #doApply(WaveletData)}, and then * updates the wave's timestamp and version. */ public final void apply(WaveletData wavelet) throws OperationException { // Execute subtype logic first, because if the subtype logic throws an exception, we must // leave this wrapper untouched as though the operation never happened. The subtype is // responsible for making sure if they throw an exception they must leave themselves in a // state as those the op never happened. doApply(wavelet); // Update metadata second. This means subtype subtypes should assume that the // metadata of a wavelet will be at the old state if they look at it in their // operation logic. update(wavelet); } /** * Updates the metadata of a wave, according to the operation context. * * @param wavelet wavelet to update */ public final void update(WaveletData wavelet) { if (context.hasTimestamp()) { wavelet.setLastModifiedTime(context.getTimestamp()); } if (context.getVersionIncrement() != 0L) { wavelet.setVersion(wavelet.getVersion() + context.getVersionIncrement()); } if (context.hasHashedVersion()) { wavelet.setHashedVersion(context.getHashedVersion()); } } /** * Applies this operation's logic to a given wavelet. This method can be * arbitrarily overridden by subclasses. * * @param wavelet wavelet on which this operation is to apply itself * @throws OperationException */ protected abstract void doApply(WaveletData wavelet) throws OperationException; /** * Creates a no-op operation that updates server meta data. i.e. * version numbers and distinct version. Subclasses may override this. * * @param versionIncrement the version increment for the created op * @param hashedVersion the wavelet distinct version for the created op (or null) */ public VersionUpdateOp createVersionUpdateOp(long versionIncrement, HashedVersion hashedVersion) { return new VersionUpdateOp(context.getCreator(), versionIncrement, hashedVersion); } /** * Creates the operation context for the reverse of an operation. * * @param target wavelet from which to extract state to be restored by the * reverse operation * @param versionDecrement Number of versions to decrement in reverse. * @return context for a reverse of this operation. */ protected final WaveletOperationContext createReverseContext(WaveletData target, long versionDecrement) { return new WaveletOperationContext(context.getCreator(), target.getLastModifiedTime(), -versionDecrement, target.getHashedVersion()); } /** * Creates the operation context for the reverse of an operation. * * @param target wavelet from which to extract state to be restored by the * reverse operation * @return context for a reverse of this operation. */ protected final WaveletOperationContext createReverseContext(WaveletData target) { return createReverseContext(target, context.getVersionIncrement()); } /** * Return a suffix message from the wavelet operation context; the idea is that subclasses * will override toString() to call this method and prepend some useful prefix. */ protected String suffixForToString() { return "by " + context.getCreator() + " at " + new Date(context.getTimestamp()) + " version " + context.getHashedVersion() ; } /** * Whether this operation is worthy of attribution. Subclasses may override this. * This default implementation always returns true. */ public boolean isWorthyOfAttribution() { return true; } }