Package com.goodow.realtime.store.server.impl

Source Code of com.goodow.realtime.store.server.impl.MemoryDeltaStorage

/*
* Copyright 2014 Goodow.com
*
* 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 com.goodow.realtime.store.server.impl;

import com.google.inject.Inject;

import com.goodow.realtime.json.impl.JreJsonArray;
import com.goodow.realtime.json.impl.JreJsonObject;
import com.goodow.realtime.operation.Transformer;
import com.goodow.realtime.operation.impl.CollaborativeOperation;
import com.goodow.realtime.store.channel.Constants.Key;
import com.goodow.realtime.store.channel.Constants.Topic;
import com.goodow.realtime.store.impl.DocumentBridge;
import com.goodow.realtime.store.server.DeltaStorage;

import org.vertx.java.core.AsyncResult;
import org.vertx.java.core.AsyncResultHandler;
import org.vertx.java.core.Vertx;
import org.vertx.java.core.eventbus.EventBus;
import org.vertx.java.core.eventbus.ReplyException;
import org.vertx.java.core.eventbus.ReplyFailure;
import org.vertx.java.core.impl.CountingCompletionHandler;
import org.vertx.java.core.impl.DefaultFutureResult;
import org.vertx.java.core.json.JsonArray;
import org.vertx.java.core.json.JsonObject;
import org.vertx.java.platform.Container;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* This is an in-memory delta storage.
*
* Its main use is as an API example for people implementing storage adaptors.
* This storage is fully functional, except it stores all documents and operations forever in
* memory. As such, memory usage will grow without bound, it doesn't scale across multiple node
* processes and you'll lose all your data if the server restarts. Use with care.
*/
public class MemoryDeltaStorage implements DeltaStorage {
  public static String getDocIdChannel(String prefix, String docType, String docId) {
    return prefix + "/" + docType + "/" + docId + Topic.WATCH;
  }

  public static String getDocTypeChannel(String prefix, String docType) {
    return prefix + "/" + docType + Topic.WATCH;
  }

  private static final Logger log  = Logger.getLogger(MemoryDeltaStorage.class.getName());

  private final EventBus eb;
  private final String address;
  @Inject private Transformer<CollaborativeOperation> transformer;
  // Map from collection docType -> docId -> snapshotData ({v:, snapshot:[], root:{}})
  private Map<String, Map<String, JsonObject>> snapshotDatas =
      new HashMap<String, Map<String, JsonObject>>();
  // Map from collection docType -> docId -> list of opData ({v:, op:[], sid:, uid:}).
  // Operations' version is simply the index in the list.
  private Map<String, Map<String, List<JsonObject>>> opDatas =
      new HashMap<String, Map<String, List<JsonObject>>>();

  // Cache of docType/docId -> current doc version. This is needed because there's a potential race
  // condition where getOps could be missing an operation thats just been processed and as a result
  // we'll accept the same op for the same document twice. Data in here should be cleared out
  // periodically (like, 15 seconds after nobody has submitted to the document), but that logic
  // hasn't been implemented yet.
  private Map<String, Long> versions = new HashMap<String, Long>();

  @Inject
  MemoryDeltaStorage(Vertx vertx, Container container) {
    eb = vertx.eventBus();
    address = container.config().getObject("realtime_store", new JsonObject())
        .getString("address", Topic.STORE);
  }

  @Override
  public void start(CountingCompletionHandler<Void> countDownLatch) {
  }

  @Override
  public void getSnapshot(final String docType, final String docId, final Long version,
                          final AsyncResultHandler<JsonObject> callback) {
    if (version == null) {
      JsonObject snapshotData = snapshotDatas.containsKey(docType)
                                ? snapshotDatas.get(docType).get(docId) : null;
      callback.handle(new DefaultFutureResult<JsonObject>(snapshotData));
      return;
    }

    getOps(docType, docId, 0L, Math.min(version, getSnapshotVersion(docType, docId))
        , new AsyncResultHandler<JsonObject>() {
      @Override
      public void handle(AsyncResult<JsonObject> ar) {
        if (ar.failed()) {
          callback.handle(ar);
          return;
        }
        JsonObject snapshotData = new JsonObject().putNumber(Key.VERSION, 0);
        DocumentBridge snapshot = OperationProcessor.createSnapshot(docType, docId, snapshotData);
        JsonArray ops = ar.result().getArray(Key.OPS);
        Long opVersion = null;
        for (Object op : ops) {
          JsonObject opData = (JsonObject) op;
          opVersion = opData.getLong(Key.VERSION);
          try {
            snapshot.consume(transformer.createOperation(new JreJsonObject(opData.toMap())));
          } catch (Exception e) {
            log.log(Level.WARNING, "Failed to consume operation", e);
            callback.handle(new DefaultFutureResult<JsonObject>(new ReplyException(
                ReplyFailure.RECIPIENT_FAILURE, e.getMessage())));
            return;
          }
        }
        JsonObject root = new JsonObject(((JreJsonObject) snapshot.toJson()).toNative());
        snapshotData.putNumber(Key.VERSION, opVersion + 1).putObject(DeltaStorage.ROOT, root).
           putArray(Key.SNAPSHOT, new JsonArray(((JreJsonArray) snapshot.toSnapshot()).toNative()));
        callback.handle(new DefaultFutureResult<JsonObject>(snapshotData));
      }
    });
  }

  @Override
  public void writeSnapshot(String docType, String docId, JsonObject snapshotData,
                            AsyncResultHandler<Void> callback) {
    Map<String, JsonObject> type = snapshotDatas.get(docType);
    if (type == null) {
      type = new HashMap<String, JsonObject>();
      snapshotDatas.put(docType, type);
    }
    type.put(docId, snapshotData);
    callback.handle(new DefaultFutureResult<Void>().setResult(null));
  }

  @Override
  public void writeOp(String docType, String docId, JsonObject opData,
                      AsyncResultHandler<Void> callback) {
    List<JsonObject> opLog = getOpLog(docType, docId);
    // This should never actually happen unless there's bugs in delta storage. (Or you try to
    // use this memory implementation with multiple frontend servers)
    if (opData.getLong(Key.VERSION) > opLog.size()) {
      callback.handle(new DefaultFutureResult<Void>(
          new ReplyException(ReplyFailure.RECIPIENT_FAILURE,
              "Internal consistancy error - mutation storage missing parent version")));
      return;
    } else if (opData.getLong(Key.VERSION) == opLog.size()) {
      opLog.add(opData);
    }

    callback.handle(new DefaultFutureResult<Void>().setResult(null));
  }

  @Override
  public void getVersion(String docType, String docId, AsyncResultHandler<Long> callback) {
    List<JsonObject> opLog = getOpLog(docType, docId);
    callback.handle(new DefaultFutureResult<Long>(Long.valueOf(opLog.size())));
  }

  @Override
  public void getOps(String docType, String docId, Long from, Long to,
                     AsyncResultHandler<JsonObject> callback) {
    List<JsonObject> opLog = getOpLog(docType, docId);
    if (to == null) {
      to = Long.valueOf(opLog.size());
    }
    JsonArray ops = new JsonArray();
    for (int i = from.intValue(); i < to; i++) {
      ops.addObject(opLog.get(i));
    }
    JsonObject toRtn = new JsonObject().putArray(Key.OPS, ops);
    callback.handle(new DefaultFutureResult<JsonObject>(toRtn));
  }

  @Override
  public void atomicSubmit(final String docType, final String docId, final JsonObject opData,
                           final AsyncResultHandler<Void> callback) {
    // This is easy because we're the only instance in the cluster, so anything that happens
    // synchronously is safe.

    long opVersion = opData.getNumber(Key.VERSION).longValue();
    Long docVersion = getSnapshotVersion(docType, docId);
    if (docVersion != null && opVersion < docVersion) {
      callback.handle(new DefaultFutureResult<Void>(
          new ReplyException(ReplyFailure.RECIPIENT_FAILURE, "Transform needed")));
      return;
    }

    versions.put(docType + "/" + docId, opVersion + 1);
    writeOp(docType, docId, opData, new AsyncResultHandler<Void>() {
      @Override
      public void handle(AsyncResult<Void> ar) {
        if (ar.succeeded()) {
          // Post the change to anyone who's interested.
          eb.publish(MemoryDeltaStorage.getDocIdChannel(address, docType, docId), opData);
        }
        callback.handle(ar);
      }
    });
  }

  @Override
  public void postSubmit(String docType, String docId, JsonObject opData, JsonObject snapshot) {
    eb.publish(MemoryDeltaStorage.getDocTypeChannel(address, docType), opData);
  }

  private List<JsonObject> getOpLog(String docType, String docId) {
    Map<String, List<JsonObject>> type = opDatas.get(docType);
    if (type == null) {
      type = new HashMap<String, List<JsonObject>>();
      opDatas.put(docType, type);
    }
    List<JsonObject> ops = type.get(docId);
    if (ops == null) {
      ops = new ArrayList<JsonObject>();
      type.put(docId, ops);
    }
    return ops;
  }

  private Long getSnapshotVersion(String docType, String docId) {
    return versions.get(docType + "/" + docId);
  }
}
TOP

Related Classes of com.goodow.realtime.store.server.impl.MemoryDeltaStorage

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.