/** * 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.operation.core; 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.RemovedAuthorException; import org.waveprotocol.wave.model.operation.TransformException; 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' */ public class CoreTransform { /** * Transforms a pair of operations. * * @param clientOp The client's operation. * @param clientOpAuthor The author of the client's operation. * @param serverOp The server's operation. * @param serverOpAuthor The author of 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<CoreWaveletOperation> transform(CoreWaveletOperation clientOp, ParticipantId clientOpAuthor, CoreWaveletOperation serverOp, ParticipantId serverOpAuthor) throws TransformException { if (clientOp instanceof CoreWaveletDocumentOperation && serverOp instanceof CoreWaveletDocumentOperation) { CoreWaveletDocumentOperation clientWaveDocOp = (CoreWaveletDocumentOperation) clientOp; CoreWaveletDocumentOperation serverWaveDocOp = (CoreWaveletDocumentOperation) serverOp; if (clientWaveDocOp.getDocumentId().equals(serverWaveDocOp.getDocumentId())) { // Transform document operations DocOp clientMutation = clientWaveDocOp.getOperation(); DocOp serverMutation = serverWaveDocOp.getOperation(); OperationPair<DocOp> transformedDocOps = Transformer.transform(clientMutation, serverMutation); clientOp = new CoreWaveletDocumentOperation(clientWaveDocOp.getDocumentId(), transformedDocOps.clientOp()); serverOp = new CoreWaveletDocumentOperation(serverWaveDocOp.getDocumentId(), transformedDocOps.serverOp()); } else { // Different documents don't conflict; use identity transform below } } else { if (serverOp instanceof CoreRemoveParticipant) { CoreRemoveParticipant serverRemoveOp = (CoreRemoveParticipant) serverOp; if (serverRemoveOp.getParticipantId().equals(clientOpAuthor)) { // clientOpAuthor has issued a client operation that is concurrent with a server // operation to remove clientOpAuthor, hence the client operation is doomed throw new RemovedAuthorException(clientOpAuthor.getAddress()); } if (clientOp instanceof CoreRemoveParticipant) { CoreRemoveParticipant clientRemoveOp = (CoreRemoveParticipant) clientOp; if (clientRemoveOp.getParticipantId().equals(serverRemoveOp.getParticipantId())) { clientOp = CoreNoOp.INSTANCE; serverOp = CoreNoOp.INSTANCE; } } else if (clientOp instanceof CoreAddParticipant) { checkParticipantRemovalAndAddition(serverRemoveOp, (CoreAddParticipant) clientOp); } } else if (serverOp instanceof CoreAddParticipant) { CoreAddParticipant serverAddOp = (CoreAddParticipant) serverOp; if (clientOp instanceof CoreAddParticipant) { CoreAddParticipant clientAddOp = (CoreAddParticipant) clientOp; if (clientAddOp.getParticipantId().equals(serverAddOp.getParticipantId())) { clientOp = CoreNoOp.INSTANCE; serverOp = CoreNoOp.INSTANCE; } } else if (clientOp instanceof CoreRemoveParticipant) { checkParticipantRemovalAndAddition((CoreRemoveParticipant) clientOp, serverAddOp); } } } // Apply identity transform by default return new OperationPair<CoreWaveletOperation>(clientOp, serverOp); } /** * 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(CoreRemoveParticipant removeParticipant, CoreAddParticipant addParticipant) throws TransformException { ParticipantId participantId = removeParticipant.getParticipantId(); if (participantId.equals(addParticipant.getParticipantId())) { throw new TransformException("Transform error involving participant: " + participantId.getAddress()); } } }