/**
* 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.document.operation.DocOp;
import org.waveprotocol.wave.model.document.operation.algorithm.Transformer;
import org.waveprotocol.wave.model.operation.OperationPair;
import org.waveprotocol.wave.model.operation.TransformException;
import org.waveprotocol.wave.model.operation.RemovedAuthorException;
import org.waveprotocol.wave.model.util.Preconditions;
import org.waveprotocol.wave.model.wave.ParticipantId;
/**
* The class for transforming operations as in the Jupiter system.
*
* The Jupiter algorithm takes 2 operations S and C and produces S' and C'.
* Where the operations, S + C' = C + S'
*
* @author zdwang@google.com (David Wang)
*/
public class Transform {
//
// NOTE(user): There are many instanceof and short-lived objects in this implementation.
// If this becomes a problem in the client, then this can be rewritten as a static visitor
// tree with no instanceof or new object creation.
//
/**
* Transforms a pair of operations.
*
* @param clientOp The client's operation.
* @param serverOp The server's operation.
* @return The resulting transformed client and server operations.
* @throws TransformException if a problem was encountered during the
* transformation.
*/
public static OperationPair<WaveletOperation> transform(WaveletOperation clientOp,
WaveletOperation serverOp) throws TransformException {
// TODO(user): This is a provisional implementation. This should be
// rewritten using visitors.
Preconditions.checkNotNull(clientOp, "Null client operation");
Preconditions.checkNotNull(serverOp, "Null server operation");
if (clientOp instanceof WaveletBlipOperation && serverOp instanceof WaveletBlipOperation) {
WaveletBlipOperation clientWaveBlipOp = (WaveletBlipOperation) clientOp;
WaveletBlipOperation serverWaveBlipOp = (WaveletBlipOperation) serverOp;
if (clientWaveBlipOp.getBlipId().equals(serverWaveBlipOp.getBlipId())) {
// Transform blip operations
BlipOperation clientBlipOp = clientWaveBlipOp.getBlipOp();
BlipOperation serverBlipOp = serverWaveBlipOp.getBlipOp();
OperationPair<BlipOperation> transformedBlipOps = transform(clientBlipOp, serverBlipOp);
clientOp = new WaveletBlipOperation(clientWaveBlipOp.getBlipId(),
transformedBlipOps.clientOp());
serverOp = new WaveletBlipOperation(serverWaveBlipOp.getBlipId(),
transformedBlipOps.serverOp());
} else {
// Different blips don't conflict; use identity transform below
}
} else {
if (serverOp instanceof RemoveParticipant) {
RemoveParticipant serverRemoveOp = (RemoveParticipant) serverOp;
checkParticipantRemoval(serverRemoveOp, clientOp);
if (clientOp instanceof RemoveParticipant) {
RemoveParticipant clientRemoveOp = (RemoveParticipant) clientOp;
if (clientRemoveOp.getParticipantId().equals(serverRemoveOp.getParticipantId())) {
clientOp = new NoOp(clientRemoveOp.getContext());
serverOp = new NoOp(serverRemoveOp.getContext());
}
} else if (clientOp instanceof AddParticipant) {
checkParticipantRemovalAndAddition(serverRemoveOp, (AddParticipant) clientOp);
}
} else if (serverOp instanceof AddParticipant) {
AddParticipant serverAddOp = (AddParticipant) serverOp;
if (clientOp instanceof AddParticipant) {
AddParticipant clientAddOp = (AddParticipant) clientOp;
if (clientAddOp.getParticipantId().equals(serverAddOp.getParticipantId())) {
clientOp = new NoOp(clientAddOp.getContext());
serverOp = new NoOp(serverAddOp.getContext());
}
} else if (clientOp instanceof RemoveParticipant) {
checkParticipantRemovalAndAddition((RemoveParticipant) clientOp, serverAddOp);
}
}
}
// Apply identity transform by default
return new OperationPair<WaveletOperation>(clientOp, serverOp);
}
/**
* Transforms a pair of blip operations.
*
* @param clientOp
* @param serverOp
* @return the transformed pair.
* @throws TransformException
*/
public static OperationPair<BlipOperation> transform(BlipOperation clientOp,
BlipOperation serverOp) throws TransformException {
if (clientOp instanceof BlipContentOperation && serverOp instanceof BlipContentOperation) {
BlipContentOperation clientBlipContentOp = (BlipContentOperation) clientOp;
BlipContentOperation serverBlipContentOp = (BlipContentOperation) serverOp;
DocOp clientContentOp = clientBlipContentOp.getContentOp();
DocOp serverContentOp = serverBlipContentOp.getContentOp();
OperationPair<? extends DocOp> transformedDocOps =
Transformer.transform(clientContentOp, serverContentOp);
clientOp = new BlipContentOperation(clientBlipContentOp.getContext(),
transformedDocOps.clientOp());
serverOp = new BlipContentOperation(serverBlipContentOp.getContext(),
transformedDocOps.serverOp());
} else {
// All other blip-op pairs have identity transforms for now.
}
// Apply identity transform by default
return new OperationPair<BlipOperation>(clientOp, serverOp);
}
/**
* Checks to see if a participant has issued an operation that is concurrent
* with an operation to remove that participant, and throws an exception if
* such is the case.
*
* TODO(user): revisit this, see bug 2594800
*
* @param removeParticipant The operation to remove a participant.
* @param operation The operation to check.
* @throws RemovedAuthorException if the wavelet operation was issued by the
* participant being removed.
*/
private static void checkParticipantRemoval(RemoveParticipant removeParticipant,
WaveletOperation operation) throws RemovedAuthorException {
ParticipantId participantId = removeParticipant.getParticipantId();
if (participantId.equals(operation.getContext().getCreator())) {
throw new RemovedAuthorException(participantId.getAddress());
}
}
/**
* Checks to see if a participant is being removed by one operation and added
* by another concurrent operation. In such a situation, at least one of the
* operations is invalid.
*
* @param removeParticipant The operation to remove a participant.
* @param addParticipant The operation to add a participant.
* @throws TransformException if the same participant is being concurrently
* added and removed.
*/
private static void checkParticipantRemovalAndAddition(RemoveParticipant removeParticipant,
AddParticipant addParticipant) throws TransformException {
ParticipantId participantId = removeParticipant.getParticipantId();
if (participantId.equals(addParticipant.getParticipantId())) {
throw new TransformException("Transform error involving participant: " +
participantId.getAddress());
}
}
}