Package org.waveprotocol.box.server.common

Source Code of org.waveprotocol.box.server.common.CoreWaveletOperationSerializer

/**
* 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.box.server.common;

import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.protobuf.ByteString;

import org.waveprotocol.box.common.comms.WaveClientRpc.DocumentSnapshot;
import org.waveprotocol.box.common.comms.WaveClientRpc.ProtocolWaveletUpdate;
import org.waveprotocol.box.common.comms.WaveClientRpc.WaveletSnapshot;
import org.waveprotocol.box.server.util.WaveletDataUtil;
import org.waveprotocol.wave.federation.Proto.ProtocolDocumentOperation;
import org.waveprotocol.wave.federation.Proto.ProtocolHashedVersion;
import org.waveprotocol.wave.federation.Proto.ProtocolWaveletDelta;
import org.waveprotocol.wave.federation.Proto.ProtocolWaveletOperation;
import org.waveprotocol.wave.model.document.operation.AnnotationBoundaryMap;
import org.waveprotocol.wave.model.document.operation.Attributes;
import org.waveprotocol.wave.model.document.operation.AttributesUpdate;
import org.waveprotocol.wave.model.document.operation.DocOp;
import org.waveprotocol.wave.model.document.operation.DocOpCursor;
import org.waveprotocol.wave.model.document.operation.impl.AnnotationBoundaryMapImpl;
import org.waveprotocol.wave.model.document.operation.impl.AttributesImpl;
import org.waveprotocol.wave.model.document.operation.impl.AttributesUpdateImpl;
import org.waveprotocol.wave.model.document.operation.impl.DocOpBuilder;
import org.waveprotocol.wave.model.id.WaveletName;
import org.waveprotocol.wave.model.operation.OperationException;
import org.waveprotocol.wave.model.operation.core.CoreAddParticipant;
import org.waveprotocol.wave.model.operation.core.CoreWaveletDocumentOperation;
import org.waveprotocol.wave.model.operation.core.CoreWaveletOperation;
import org.waveprotocol.wave.model.operation.wave.AddParticipant;
import org.waveprotocol.wave.model.operation.wave.BlipContentOperation;
import org.waveprotocol.wave.model.operation.wave.BlipOperationVisitor;
import org.waveprotocol.wave.model.operation.wave.NoOp;
import org.waveprotocol.wave.model.operation.wave.RemoveParticipant;
import org.waveprotocol.wave.model.operation.wave.SubmitBlip;
import org.waveprotocol.wave.model.operation.wave.TransformedWaveletDelta;
import org.waveprotocol.wave.model.operation.wave.WaveletBlipOperation;
import org.waveprotocol.wave.model.operation.wave.WaveletDelta;
import org.waveprotocol.wave.model.operation.wave.WaveletOperation;
import org.waveprotocol.wave.model.operation.wave.WaveletOperationContext;
import org.waveprotocol.wave.model.schema.SchemaCollection;
import org.waveprotocol.wave.model.version.HashedVersion;
import org.waveprotocol.wave.model.wave.Constants;
import org.waveprotocol.wave.model.wave.ParticipantId;
import org.waveprotocol.wave.model.wave.data.ObservableWaveletData;
import org.waveprotocol.wave.model.wave.data.core.CoreWaveletData;
import org.waveprotocol.wave.model.wave.data.core.impl.CoreWaveletDataImpl;
import org.waveprotocol.wave.model.wave.data.impl.DataUtil;

import java.util.List;
import java.util.Map;

/**
* Utility class for serialising/deserialising wavelet operations (and their components) to/from
* their protocol buffer representations (and their components).
*
*
*/
public class CoreWaveletOperationSerializer {

  private CoreWaveletOperationSerializer() {
  }

  /**
   * Serializes an untransformed delta as a {@link ProtocolWaveletDelta}.
   *
   * @param delta to serialize
   * @return serialized protocol buffer wavelet delta
   */
  public static ProtocolWaveletDelta serialize(WaveletDelta delta) {
   ProtocolWaveletDelta.Builder protobufDelta = ProtocolWaveletDelta.newBuilder();

   for (WaveletOperation waveletOp : delta) {
     protobufDelta.addOperation(serialize(waveletOp));
   }
   protobufDelta.setAuthor(delta.getAuthor().getAddress());
   protobufDelta.setHashedVersion(serialize(delta.getTargetVersion()));

   return protobufDelta.build();
  }

  /**
   * Serializes a transformed delta as a {@link ProtocolWaveletDelta}.
   *
   * The target version of the result does not have an accompanying hash;
   * server delta hashes are redundant in the client/server protocol.
   *
   * @param delta to serialize
   * @return serialized protocol buffer wavelet delta
   */
  public static ProtocolWaveletDelta serialize(TransformedWaveletDelta delta) {
    ProtocolWaveletDelta.Builder protobufDelta = ProtocolWaveletDelta.newBuilder();

    for (WaveletOperation waveletOp : delta) {
      protobufDelta.addOperation(serialize(waveletOp));
    }
    protobufDelta.setAuthor(delta.getAuthor().getAddress());
    protobufDelta.setHashedVersion(serialize(HashedVersion.unsigned(delta.getAppliedAtVersion())));

    return protobufDelta.build();
  }

  /**
   * Serializes a wavelet operation as a {@link ProtocolWaveletOperation}.
   *
   * @param waveletOp wavelet operation to serialize
   * @return serialized protocol buffer wavelet operation
   */
  public static ProtocolWaveletOperation serialize(WaveletOperation waveletOp) {
    ProtocolWaveletOperation.Builder protobufOp = ProtocolWaveletOperation.newBuilder();

    if (waveletOp instanceof NoOp) {
      protobufOp.setNoOp(true);
    } else if (waveletOp instanceof AddParticipant) {
      protobufOp.setAddParticipant(
          ((AddParticipant) waveletOp).getParticipantId().getAddress());
    } else if (waveletOp instanceof RemoveParticipant) {
      protobufOp.setRemoveParticipant(
          ((RemoveParticipant) waveletOp).getParticipantId().getAddress());
    } else if (waveletOp instanceof WaveletBlipOperation) {
      final WaveletBlipOperation wbOp = (WaveletBlipOperation) waveletOp;
      final ProtocolWaveletOperation.MutateDocument.Builder mutation =
        ProtocolWaveletOperation.MutateDocument.newBuilder();
      mutation.setDocumentId(wbOp.getBlipId());
      wbOp.getBlipOp().acceptVisitor(new BlipOperationVisitor() {
        @Override
        public void visitBlipContentOperation(BlipContentOperation blipOp) {
          mutation.setDocumentOperation(serialize(blipOp.getContentOp()));
        }

        @Override
        public void visitSubmitBlip(SubmitBlip op) {
          throw new IllegalArgumentException("Unsupported blip operation: " + wbOp.getBlipOp());
        }
      });
      protobufOp.setMutateDocument(mutation.build());
    } else {
      throw new IllegalArgumentException("Unsupported wavelet operation: " + waveletOp);
    }

    return protobufOp.build();
  }

  /**
   * Serialize a {@link DocOp} as a {@link ProtocolDocumentOperation}.
   *
   * @param inputOp document operation to serialize
   * @return serialized protocol buffer document operation
   */
  public static ProtocolDocumentOperation serialize(DocOp inputOp) {
    final ProtocolDocumentOperation.Builder output = ProtocolDocumentOperation.newBuilder();

    inputOp.apply(new DocOpCursor() {
      private ProtocolDocumentOperation.Component.Builder newComponentBuilder() {
        return ProtocolDocumentOperation.Component.newBuilder();
      }

      @Override public void retain(int itemCount) {
        output.addComponent(newComponentBuilder().setRetainItemCount(itemCount).build());
      }

      @Override public void characters(String characters) {
        output.addComponent(newComponentBuilder().setCharacters(characters).build());
      }

      @Override public void deleteCharacters(String characters) {
        output.addComponent(newComponentBuilder().setDeleteCharacters(characters).build());
      }

      @Override public void elementStart(String type, Attributes attributes) {
        ProtocolDocumentOperation.Component.ElementStart e = makeElementStart(type, attributes);
        output.addComponent(newComponentBuilder().setElementStart(e).build());
      }

      @Override public void deleteElementStart(String type, Attributes attributes) {
        ProtocolDocumentOperation.Component.ElementStart e = makeElementStart(type, attributes);
        output.addComponent(newComponentBuilder().setDeleteElementStart(e).build());
      }

      private ProtocolDocumentOperation.Component.ElementStart makeElementStart(
          String type, Attributes attributes) {
        ProtocolDocumentOperation.Component.ElementStart.Builder e =
          ProtocolDocumentOperation.Component.ElementStart.newBuilder();

        e.setType(type);

        for (String name : attributes.keySet()) {
          e.addAttribute(ProtocolDocumentOperation.Component.KeyValuePair.newBuilder()
              .setKey(name).setValue(attributes.get(name)).build());
        }

        return e.build();
      }

      @Override public void elementEnd() {
        output.addComponent(newComponentBuilder().setElementEnd(true).build());
      }

      @Override public void deleteElementEnd() {
        output.addComponent(newComponentBuilder().setDeleteElementEnd(true).build());
      }

      @Override public void replaceAttributes(Attributes oldAttributes, Attributes newAttributes) {
        ProtocolDocumentOperation.Component.ReplaceAttributes.Builder r =
          ProtocolDocumentOperation.Component.ReplaceAttributes.newBuilder();

        if (oldAttributes.isEmpty() && newAttributes.isEmpty()) {
          r.setEmpty(true);
        } else {
          for (String name : oldAttributes.keySet()) {
            r.addOldAttribute(ProtocolDocumentOperation.Component.KeyValuePair.newBuilder()
                .setKey(name).setValue(oldAttributes.get(name)).build());
          }

          for (String name : newAttributes.keySet()) {
            r.addNewAttribute(ProtocolDocumentOperation.Component.KeyValuePair.newBuilder()
                .setKey(name).setValue(newAttributes.get(name)).build());
          }
        }

        output.addComponent(newComponentBuilder().setReplaceAttributes(r.build()).build());
      }

      @Override public void updateAttributes(AttributesUpdate attributes) {
        ProtocolDocumentOperation.Component.UpdateAttributes.Builder u =
          ProtocolDocumentOperation.Component.UpdateAttributes.newBuilder();

        if (attributes.changeSize() == 0) {
          u.setEmpty(true);
        } else {
          for (int i = 0; i < attributes.changeSize(); i++) {
            u.addAttributeUpdate(makeKeyValueUpdate(
                attributes.getChangeKey(i), attributes.getOldValue(i), attributes.getNewValue(i)));
          }
        }

        output.addComponent(newComponentBuilder().setUpdateAttributes(u.build()).build());
      }

      @Override public void annotationBoundary(AnnotationBoundaryMap map) {
        ProtocolDocumentOperation.Component.AnnotationBoundary.Builder a =
          ProtocolDocumentOperation.Component.AnnotationBoundary.newBuilder();

        if (map.endSize() == 0 && map.changeSize() == 0) {
          a.setEmpty(true);
        } else {
          for (int i = 0; i < map.endSize(); i++) {
            a.addEnd(map.getEndKey(i));
          }
          for (int i = 0; i < map.changeSize(); i++) {
            a.addChange(makeKeyValueUpdate(
                map.getChangeKey(i), map.getOldValue(i), map.getNewValue(i)));
          }
        }

        output.addComponent(newComponentBuilder().setAnnotationBoundary(a.build()).build());
      }

      private ProtocolDocumentOperation.Component.KeyValueUpdate makeKeyValueUpdate(
          String key, String oldValue, String newValue) {
        ProtocolDocumentOperation.Component.KeyValueUpdate.Builder kvu =
          ProtocolDocumentOperation.Component.KeyValueUpdate.newBuilder();
        kvu.setKey(key);
        if (oldValue != null) {
          kvu.setOldValue(oldValue);
        }
        if (newValue != null) {
          kvu.setNewValue(newValue);
        }

        return kvu.build();
      }
    });

    return output.build();
  }

  /**
   * Deserializes a {@link ProtocolWaveletDelta} as an untransformed wavelet
   * delta.
   */
  public static WaveletDelta deserialize(ProtocolWaveletDelta delta) {
    List<WaveletOperation> ops = Lists.newArrayList();
    for (ProtocolWaveletOperation op : delta.getOperationList()) {
      WaveletOperationContext context = new WaveletOperationContext(
          ParticipantId.ofUnsafe(delta.getAuthor()), Constants.NO_TIMESTAMP, 1);
      ops.add(deserialize(op, context));
    }
    HashedVersion hashedVersion = deserialize(delta.getHashedVersion());
    return new WaveletDelta(new ParticipantId(delta.getAuthor()), hashedVersion, ops);
  }

  /**
   * Deserializes a {@link ProtocolWaveletDelta} as a transformed wavelet delta.
   */
  public static TransformedWaveletDelta deserialize(ProtocolWaveletDelta delta,
      HashedVersion resultingVersion, long applicationTimestamp) {
    ParticipantId author = ParticipantId.ofUnsafe(delta.getAuthor());
    int count = delta.getOperationCount();
    Preconditions.checkArgument(count > 0, "Cannot deserialize an empty delta");
    List<WaveletOperation> ops = Lists.newArrayListWithCapacity(count);
    if (count > 1) {
      WaveletOperationContext context =
          new WaveletOperationContext(author, applicationTimestamp, 1);
      for (int i = 0; i < count - 1; i++) {
        ProtocolWaveletOperation op = delta.getOperation(i);
        ops.add(deserialize(op, context));
      }
    }
    WaveletOperationContext context =
        new WaveletOperationContext(author, applicationTimestamp, 1, resultingVersion);
    ops.add(deserialize(delta.getOperation(count - 1), context));
    return new TransformedWaveletDelta(author, resultingVersion, applicationTimestamp, ops);
  }

  /** Deserializes a protobuf to a HashedVersion POJO. */
  public static HashedVersion deserialize(ProtocolHashedVersion hashedVersion) {
    final ByteString historyHash = hashedVersion.getHistoryHash();
    return HashedVersion.of(hashedVersion.getVersion(), historyHash.toByteArray());
  }

  /** Serializes a HashedVersion POJO to a protobuf. */
  public static ProtocolHashedVersion serialize(HashedVersion hashedVersion) {
    return ProtocolHashedVersion.newBuilder().setVersion(hashedVersion.getVersion()).
      setHistoryHash(ByteString.copyFrom(hashedVersion.getHistoryHash())).build();
  }

  /**
   * Deserialize a {@link ProtocolWaveletOperation} as a {@link WaveletOperation}.
   *
   * @param protobufOp protocol buffer wavelet operation to deserialize
   * @return deserialized wavelet operation
   */
  public static WaveletOperation deserialize(ProtocolWaveletOperation protobufOp,
      WaveletOperationContext context) {
    if (protobufOp.hasNoOp()) {
      return new NoOp(context);
    } else if (protobufOp.hasAddParticipant()) {
      return new AddParticipant(context, new ParticipantId(protobufOp.getAddParticipant()));
    } else if (protobufOp.hasRemoveParticipant()) {
      return new RemoveParticipant(context, new ParticipantId(protobufOp.getRemoveParticipant()));
    } else if (protobufOp.hasMutateDocument()) {
      return new WaveletBlipOperation(protobufOp.getMutateDocument().getDocumentId(),
          new BlipContentOperation(context,
              deserialize(protobufOp.getMutateDocument().getDocumentOperation())));
    } else {
      throw new IllegalArgumentException("Unsupported operation: " + protobufOp);
    }
  }

  /**
   * Deserialize a {@link WaveletSnapshot} into a list of
   * {@link WaveletOperation}s.
   *
   * @param snapshot snapshot protocol buffer to deserialize
   * @return a list of operations
   */
  public static List<CoreWaveletOperation> deserialize(WaveletSnapshot snapshot) {
    List<CoreWaveletOperation> ops = Lists.newArrayList();
    for (String participant : snapshot.getParticipantIdList()) {
      CoreAddParticipant addOp = new CoreAddParticipant(new ParticipantId(participant));
      ops.add(addOp);
    }
    for (DocumentSnapshot document : snapshot.getDocumentList()) {
      CoreWaveletDocumentOperation docOp = new CoreWaveletDocumentOperation(
          document.getDocumentId(), deserialize(document.getDocumentOperation()));
      ops.add(docOp);
    }
    return ops;
  }

  /**
   * Deserialize a {@link ProtocolDocumentOperation} into a {@link DocOp}.
   *
   * @param op protocol buffer document operation to deserialize
   * @return deserialized DocOp
   */
  public static DocOp deserialize(ProtocolDocumentOperation op) {
    DocOpBuilder output = new DocOpBuilder();

    for (ProtocolDocumentOperation.Component c : op.getComponentList()) {
      if (c.hasAnnotationBoundary()) {
        if (c.getAnnotationBoundary().getEmpty()) {
          output.annotationBoundary(AnnotationBoundaryMapImpl.EMPTY_MAP);
        } else {
          String[] ends = new String[c.getAnnotationBoundary().getEndCount()];
          String[] changeKeys = new String[c.getAnnotationBoundary().getChangeCount()];
          String[] oldValues = new String[c.getAnnotationBoundary().getChangeCount()];
          String[] newValues = new String[c.getAnnotationBoundary().getChangeCount()];
          if (c.getAnnotationBoundary().getEndCount() > 0) {
            c.getAnnotationBoundary().getEndList().toArray(ends);
          }
          for (int i = 0; i < changeKeys.length; i++) {
            ProtocolDocumentOperation.Component.KeyValueUpdate kvu =
              c.getAnnotationBoundary().getChange(i);
            changeKeys[i] = kvu.getKey();
            oldValues[i] = kvu.hasOldValue() ? kvu.getOldValue() : null;
            newValues[i] = kvu.hasNewValue() ? kvu.getNewValue() : null;
          }
          output.annotationBoundary(
              new AnnotationBoundaryMapImpl(ends, changeKeys, oldValues, newValues));
        }
      } else if (c.hasCharacters()) {
        output.characters(c.getCharacters());
      } else if (c.hasElementStart()) {
        Map<String, String> attributesMap = Maps.newHashMap();
        for (ProtocolDocumentOperation.Component.KeyValuePair pair :
            c.getElementStart().getAttributeList()) {
          attributesMap.put(pair.getKey(), pair.getValue());
        }
        output.elementStart(c.getElementStart().getType(), new AttributesImpl(attributesMap));
      } else if (c.hasElementEnd()) {
        output.elementEnd();
      } else if (c.hasRetainItemCount()) {
        output.retain(c.getRetainItemCount());
      } else if (c.hasDeleteCharacters()) {
        output.deleteCharacters(c.getDeleteCharacters());
      } else if (c.hasDeleteElementStart()) {
        Map<String, String> attributesMap = Maps.newHashMap();
        for (ProtocolDocumentOperation.Component.KeyValuePair pair :
            c.getDeleteElementStart().getAttributeList()) {
          attributesMap.put(pair.getKey(), pair.getValue());
        }
        output.deleteElementStart(c.getDeleteElementStart().getType(),
            new AttributesImpl(attributesMap));
      } else if (c.hasDeleteElementEnd()) {
        output.deleteElementEnd();
      } else if (c.hasReplaceAttributes()) {
        if (c.getReplaceAttributes().getEmpty()) {
          output.replaceAttributes(AttributesImpl.EMPTY_MAP, AttributesImpl.EMPTY_MAP);
        } else {
          Map<String, String> oldAttributesMap = Maps.newHashMap();
          Map<String, String> newAttributesMap = Maps.newHashMap();
          for (ProtocolDocumentOperation.Component.KeyValuePair pair :
              c.getReplaceAttributes().getOldAttributeList()) {
            oldAttributesMap.put(pair.getKey(), pair.getValue());
          }
          for (ProtocolDocumentOperation.Component.KeyValuePair pair :
              c.getReplaceAttributes().getNewAttributeList()) {
            newAttributesMap.put(pair.getKey(), pair.getValue());
          }
          output.replaceAttributes(new AttributesImpl(oldAttributesMap),
              new AttributesImpl(newAttributesMap));
        }
      } else if (c.hasUpdateAttributes()) {
        if (c.getUpdateAttributes().getEmpty()) {
          output.updateAttributes(AttributesUpdateImpl.EMPTY_MAP);
        } else {
          String[] triplets = new String[c.getUpdateAttributes().getAttributeUpdateCount()*3];
          for (int i = 0, j = 0; i < c.getUpdateAttributes().getAttributeUpdateCount(); i++) {
            ProtocolDocumentOperation.Component.KeyValueUpdate kvu =
              c.getUpdateAttributes().getAttributeUpdate(i);
            triplets[j++] = kvu.getKey();
            triplets[j++] = kvu.hasOldValue() ? kvu.getOldValue() : null;
            triplets[j++] = kvu.hasNewValue() ? kvu.getNewValue() : null;
          }
          output.updateAttributes(new AttributesUpdateImpl(triplets));
        }
      } else {
        //throw new IllegalArgumentException("Unsupported operation component: " + c);
      }
    }

    return output.build();
  }

  /**
   * Deserializes the snapshot contained in the {@link ProtocolWaveletUpdate}
   * into a {@link ObservableWaveletData}.
   *
   * @param snapshot the {@link WaveletSnapshot} to deserialize.
   * @param version the version of the wavelet after all deltas have been
   *        applied.
   * @param waveletName the name of the wavelet contained in the update.
   * @throws OperationException if the ops in the snapshot can not be applied.
   */
  public static ObservableWaveletData deserializeSnapshot(
      WaveletSnapshot snapshot, ProtocolHashedVersion version, WaveletName waveletName)
      throws OperationException {
    // TODO(ljvderijk): This method does too many steps to get to the
    // ObservableWaveletData.
    // We need something simpler when operations and the protocol have been
    // edited.

    // Creating a CoreWaveletData because the current protocol lacks the
    // meta-data required to construct an ObservableWaveletData directly.
    // But this results in unnecessary object creation and copies.
    CoreWaveletData coreWavelet =
        new CoreWaveletDataImpl(waveletName.waveId, waveletName.waveletId);

    Preconditions.checkArgument(snapshot.getParticipantIdCount() > 0);
    // Have to add a single participant for the copying to complete without a
    // NPE.
    coreWavelet.addParticipant(ParticipantId.ofUnsafe(snapshot.getParticipantId(0)));

    for (DocumentSnapshot document : snapshot.getDocumentList()) {
      DocOp op =
          CoreWaveletOperationSerializer.deserialize(document.getDocumentOperation());
      coreWavelet.modifyDocument(document.getDocumentId(), op);
    }

    HashedVersion hashedVersion = CoreWaveletOperationSerializer.deserialize(version);
    ObservableWaveletData immutableWaveletData =
        DataUtil.fromCoreWaveletData(coreWavelet, hashedVersion, SchemaCollection.empty());

    ObservableWaveletData wavelet = WaveletDataUtil.copyWavelet(immutableWaveletData);

    for (String participant : snapshot.getParticipantIdList()) {
      wavelet.addParticipant(new ParticipantId(participant));
    }

    return wavelet;
  }

}
TOP

Related Classes of org.waveprotocol.box.server.common.CoreWaveletOperationSerializer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.