/** * 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.persistence.mongodb; import com.google.common.collect.ImmutableList; import com.google.protobuf.ByteString; import com.google.protobuf.InvalidProtocolBufferException; import com.mongodb.BasicDBList; import com.mongodb.BasicDBObject; import com.mongodb.DBObject; import org.waveprotocol.box.server.common.CoreWaveletOperationSerializer; import org.waveprotocol.box.server.persistence.PersistenceException; import org.waveprotocol.box.server.persistence.protos.ProtoDeltaStoreDataSerializer; import org.waveprotocol.box.server.waveserver.ByteStringMessage; import org.waveprotocol.box.server.waveserver.WaveletDeltaRecord; import org.waveprotocol.wave.federation.Proto.ProtocolDocumentOperation; import org.waveprotocol.wave.model.document.operation.DocOp; import org.waveprotocol.wave.model.operation.wave.AddParticipant; import org.waveprotocol.wave.model.operation.wave.BlipContentOperation; import org.waveprotocol.wave.model.operation.wave.BlipOperation; import org.waveprotocol.wave.model.operation.wave.NoOp; import org.waveprotocol.wave.model.operation.wave.RemoveParticipant; import org.waveprotocol.wave.model.operation.wave.TransformedWaveletDelta; import org.waveprotocol.wave.model.operation.wave.WaveletBlipOperation; import org.waveprotocol.wave.model.operation.wave.WaveletOperation; import org.waveprotocol.wave.model.operation.wave.WaveletOperationContext; import org.waveprotocol.wave.model.version.HashedVersion; import org.waveprotocol.wave.model.wave.ParticipantId; /** * A utility class to serialize/deserialize delta objects to/from MongoDB. The * implementation approach is analog to the provided at * {@link CoreWaveletOperationSerializer} and * {@link ProtoDeltaStoreDataSerializer} * * @author pablojan@gmail.com (Pablo Ojanguren) */ public class MongoDbDeltaStoreUtil { public static final String WAVELET_OP_WAVELET_BLIP_OPERATION = "WaveletBlipOperation"; public static final String WAVELET_OP_REMOVE_PARTICIPANT = "RemoveParticipant"; public static final String WAVELET_OP_ADD_PARTICIPANT = "AddParticipant"; public static final String WAVELET_OP_NOOP = "NoOp"; public static final String FIELD_BYTES = "bytes"; public static final String FIELD_CONTENTOP = "contentop"; public static final String FIELD_BLIPOP = "blipop"; public static final String FIELD_BLIPID = "blipid"; public static final String FIELD_PARTICIPANT = "participant"; public static final String FIELD_TYPE = "type"; public static final String FIELD_OPS = "ops"; public static final String FIELD_APPLICATIONTIMESTAMP = "applicationtimestamp"; public static final String FIELD_AUTHOR = "author"; public static final String FIELD_ADDRESS = "address"; public static final String FIELD_HISTORYHASH = "historyhash"; public static final String FIELD_VERSION = "version"; public static final String FIELD_TRANSFORMED_RESULTINGVERSION_VERSION = "transformed.resultingversion.version"; public static final String FIELD_TRANSFORMED_APPLIEDATVERSION = "transformed.appliedatversion"; public static final String FIELD_TRANSFORMED_RESULTINGVERSION = "transformed.resultingversion"; public static final String FIELD_APPLIEDATVERSION = "appliedatversion"; public static final String FIELD_RESULTINGVERSION = "resultingversion"; public static final String FIELD_TRANSFORMED = "transformed"; public static final String FIELD_APPLIED = "applied"; public static final String FIELD_WAVELET_ID = "waveletid"; public static final String FIELD_WAVE_ID = "waveid"; public static DBObject serialize(WaveletDeltaRecord waveletDelta, String waveId, String waveletId) { BasicDBObject mongoWaveletDelta = new BasicDBObject(); mongoWaveletDelta.append(FIELD_WAVE_ID, waveId); mongoWaveletDelta.append(FIELD_WAVELET_ID, waveletId); mongoWaveletDelta.append(FIELD_APPLIEDATVERSION, serialize(waveletDelta.getAppliedAtVersion())); mongoWaveletDelta.append(FIELD_APPLIED, waveletDelta.getAppliedDelta().getByteArray()); mongoWaveletDelta.append(FIELD_TRANSFORMED, serialize(waveletDelta.getTransformedDelta())); return mongoWaveletDelta; } public static DBObject serialize(HashedVersion hashedVersion) { BasicDBObject mongoHashedVersion = new BasicDBObject(); mongoHashedVersion.append(FIELD_VERSION, hashedVersion.getVersion()); mongoHashedVersion.append(FIELD_HISTORYHASH, hashedVersion.getHistoryHash()); return mongoHashedVersion; } public static DBObject serialize(ParticipantId participantId) { BasicDBObject mongoParticipantId = new BasicDBObject(); mongoParticipantId.append(FIELD_ADDRESS, participantId.getAddress()); return mongoParticipantId; } public static DBObject serialize(TransformedWaveletDelta transformedWaveletDelta) { BasicDBObject mongoTransformedWaveletDelta = new BasicDBObject(); mongoTransformedWaveletDelta.append(FIELD_AUTHOR, serialize(transformedWaveletDelta.getAuthor())); mongoTransformedWaveletDelta.append(FIELD_RESULTINGVERSION, serialize(transformedWaveletDelta.getResultingVersion())); mongoTransformedWaveletDelta.append(FIELD_APPLICATIONTIMESTAMP, transformedWaveletDelta.getApplicationTimestamp()); mongoTransformedWaveletDelta.append(FIELD_APPLIEDATVERSION, transformedWaveletDelta.getAppliedAtVersion()); BasicDBList mongoWaveletOperations = new BasicDBList(); for (WaveletOperation op : transformedWaveletDelta) { mongoWaveletOperations.add(serialize(op)); } mongoTransformedWaveletDelta.append(FIELD_OPS, mongoWaveletOperations); return mongoTransformedWaveletDelta; } public static DBObject serialize(WaveletOperation waveletOp) { final BasicDBObject mongoOp = new BasicDBObject(); if (waveletOp instanceof NoOp) { mongoOp.append(FIELD_TYPE, WAVELET_OP_NOOP); } else if (waveletOp instanceof AddParticipant) { mongoOp.append(FIELD_TYPE, WAVELET_OP_ADD_PARTICIPANT); mongoOp.append(FIELD_PARTICIPANT, serialize(((AddParticipant) waveletOp).getParticipantId())); } else if (waveletOp instanceof RemoveParticipant) { mongoOp.append(FIELD_TYPE, WAVELET_OP_REMOVE_PARTICIPANT); mongoOp.append(FIELD_PARTICIPANT, serialize(((RemoveParticipant) waveletOp).getParticipantId())); } else if (waveletOp instanceof WaveletBlipOperation) { final WaveletBlipOperation waveletBlipOp = (WaveletBlipOperation) waveletOp; mongoOp.append(FIELD_TYPE, WAVELET_OP_WAVELET_BLIP_OPERATION); mongoOp.append(FIELD_BLIPID, waveletBlipOp.getBlipId()); if (waveletBlipOp.getBlipOp() instanceof BlipContentOperation) { mongoOp.append(FIELD_BLIPOP, serialize((BlipContentOperation) waveletBlipOp.getBlipOp())); } else { throw new IllegalArgumentException("Unsupported blip operation: " + waveletBlipOp.getBlipOp()); } } else { throw new IllegalArgumentException("Unsupported wavelet operation: " + waveletOp); } return mongoOp; } public static DBObject serialize(BlipContentOperation blipContentOp) { BasicDBObject mongoBlipContentOp = new BasicDBObject(); mongoBlipContentOp.append(FIELD_CONTENTOP, serialize(blipContentOp.getContentOp())); return mongoBlipContentOp; } public static DBObject serialize(DocOp docOp) { BasicDBObject mongoDocOp = new BasicDBObject(); mongoDocOp.append(FIELD_BYTES, CoreWaveletOperationSerializer.serialize(docOp).toByteArray()); return mongoDocOp; } public static WaveletDeltaRecord deserializeWaveletDeltaRecord(DBObject dbObject) throws PersistenceException { try { return new WaveletDeltaRecord( deserializeHashedVersion((DBObject) dbObject.get(FIELD_APPLIEDATVERSION)), ByteStringMessage.parseProtocolAppliedWaveletDelta(ByteString.copyFrom((byte[]) dbObject .get(FIELD_APPLIED))), deserializeTransformedWaveletDelta((DBObject) dbObject.get(FIELD_TRANSFORMED))); } catch (InvalidProtocolBufferException e) { throw new PersistenceException(e); } } public static HashedVersion deserializeHashedVersion(DBObject dbObject) { return HashedVersion.of((Long) dbObject.get(FIELD_VERSION), (byte[]) dbObject.get(FIELD_HISTORYHASH)); } public static ParticipantId deserializeParicipantId(DBObject dbObject) { return ParticipantId.ofUnsafe((String) dbObject.get(FIELD_ADDRESS)); } public static TransformedWaveletDelta deserializeTransformedWaveletDelta(DBObject dbObject) throws PersistenceException { ParticipantId author = deserializeParicipantId((DBObject) dbObject.get(FIELD_AUTHOR)); HashedVersion resultingVersion = deserializeHashedVersion((DBObject) dbObject.get(FIELD_RESULTINGVERSION)); long applicationTimestamp = (Long) dbObject.get(FIELD_APPLICATIONTIMESTAMP); BasicDBList dbOps = (BasicDBList) dbObject.get(FIELD_OPS); ImmutableList.Builder<WaveletOperation> operations = ImmutableList.builder(); int numOperations = dbOps.size(); // Code analog to ProtoDeltaStoreDataSerializer.deserialize for (int i = 0; i < numOperations; i++) { WaveletOperationContext context; if (i == numOperations - 1) { context = new WaveletOperationContext(author, applicationTimestamp, 1, resultingVersion); } else { context = new WaveletOperationContext(author, applicationTimestamp, 1); } operations.add(deserializeWaveletOperation((DBObject) dbOps.get(i), context)); } return new TransformedWaveletDelta(author, resultingVersion, applicationTimestamp, operations.build()); } public static WaveletOperation deserializeWaveletOperation(DBObject dbObject, WaveletOperationContext context) throws PersistenceException { String type = (String) dbObject.get(FIELD_TYPE); if (type.equals(WAVELET_OP_NOOP)) { return new NoOp(context); } else if (type.equals(WAVELET_OP_ADD_PARTICIPANT)) { return new AddParticipant(context, deserializeParicipantId((DBObject) dbObject.get(FIELD_PARTICIPANT))); } else if (type.equals(WAVELET_OP_REMOVE_PARTICIPANT)) { return new RemoveParticipant(context, deserializeParicipantId((DBObject) dbObject.get(FIELD_PARTICIPANT))); } else if (type.equals(WAVELET_OP_WAVELET_BLIP_OPERATION)) { return new WaveletBlipOperation((String) dbObject.get(FIELD_BLIPID), deserializeBlipContentOperation((DBObject) dbObject.get(FIELD_BLIPOP), context)); } else { throw new IllegalArgumentException("Unsupported operation: " + type); } } public static BlipOperation deserializeBlipContentOperation(DBObject dbObject, WaveletOperationContext context) throws PersistenceException { return new BlipContentOperation(context, deserializeDocOp((DBObject) dbObject.get(FIELD_CONTENTOP))); } private static DocOp deserializeDocOp(DBObject dbObject) throws PersistenceException { try { return CoreWaveletOperationSerializer.deserialize(ProtocolDocumentOperation .parseFrom(((byte[]) dbObject.get(FIELD_BYTES)))); } catch (InvalidProtocolBufferException e) { throw new PersistenceException(e); } } }