Package com.google.walkaround.wave.server

Source Code of com.google.walkaround.wave.server.WaveletDirectory$CacheEntry

/*
* Copyright 2011 Google Inc. All Rights Reserved.
*
* 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.google.walkaround.wave.server;

import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.memcache.MemcacheService;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.inject.Inject;
import com.google.walkaround.slob.shared.SlobId;
import com.google.walkaround.util.server.appengine.AbstractDirectory;
import com.google.walkaround.util.server.appengine.CheckedDatastore;
import com.google.walkaround.util.server.appengine.DatastoreUtil.InvalidPropertyException;
import com.google.walkaround.util.server.appengine.MemcacheTable;

import java.io.IOException;
import java.io.Serializable;
import java.util.logging.Logger;

import javax.annotation.Nullable;

/**
* Interface to the wavelet directory in the datastore.
*
* Directory entries are also cached in memcache.  Wavelet ids that were looked
* up but didn't exist are also cached to avoid paying a high cost if someone
* repeatedly requests the same bogus wavelet id.  The cache does not help with
* rapid requests of different bogus (or valid) wavelet ids, but at least those
* will be distributed across entity groups.
*
* @author ohler@google.com (Christian Ohler)
*/
// Like WaveletMapping, this class is rather degenerate right now: The directory
// is nothing but a set of SlobIds, and all the memcache does is avoid datastore
// lookups for wavelets that we know don't exist.
public class WaveletDirectory {

  @SuppressWarnings("unused")
  private static final Logger log = Logger.getLogger(WaveletDirectory.class.getName());

  /**
   * Thrown when an attempt is made to register a wavelet that is already known in
   * the wavelet directory.
   *
   * @author ohler@google.com (Christian Ohler)
   */
  public class ObjectIdAlreadyKnown extends Exception {
    private static final long serialVersionUID = 800996175494608917L;

    private final WaveletMapping existingMapping;

    public ObjectIdAlreadyKnown(WaveletMapping existingMapping) {
      this(existingMapping, null, null);
    }

    public ObjectIdAlreadyKnown(WaveletMapping existingMapping, String message) {
      this(existingMapping, message, null);
    }

    public ObjectIdAlreadyKnown(WaveletMapping existingMapping, Throwable cause) {
      this(existingMapping, null, cause);
    }

    public ObjectIdAlreadyKnown(WaveletMapping existingMapping,
        String message, Throwable cause) {
      super(message, cause);
      Preconditions.checkNotNull(existingMapping, "Null existingMapping");
      this.existingMapping = existingMapping;
    }

    public WaveletMapping getExistingMapping() {
      return existingMapping;
    }
  }

  @VisibleForTesting
  static class CacheEntry implements Serializable {
    private static final long serialVersionUID = 310319261711079447L;

    @Nullable private final WaveletMapping cached;

    public CacheEntry(@Nullable WaveletMapping cached) {
      this.cached = cached;
    }

    /** Null if wavelet does not exist. */
    @Nullable public WaveletMapping getCached() {
      return cached;
    }

    @Override public String toString() {
      return "CacheEntry(" + cached + ")";
    }
  }

  @VisibleForTesting
  static class Directory extends AbstractDirectory<WaveletMapping, SlobId> {
    Directory(CheckedDatastore datastore) {
      super(datastore, "WaveletDirectoryEntry");
    }

    @Override protected String serializeId(SlobId id) {
      return id.getId();
    }

    @Override protected SlobId getId(WaveletMapping mapping) {
      return mapping.getObjectId();
    }

    @Override protected void populateEntity(WaveletMapping mapping, Entity out) {
    }

    @Override protected WaveletMapping parse(Entity e) throws InvalidPropertyException {
      return new WaveletMapping(new SlobId(e.getKey().getName()));
    }
  }

  private static final String MEMCACHE_TAG = "W";

  @VisibleForTesting
  final Directory directory;
  @VisibleForTesting
  final MemcacheTable<SlobId, CacheEntry> cache;

  @Inject
  public WaveletDirectory(CheckedDatastore datastore, MemcacheTable.Factory memcacheFactory) {
    this.directory = new Directory(datastore);
    this.cache = memcacheFactory.create(MEMCACHE_TAG);
  }

  /**
   * Returns null if object id is not known.  (This doesn't mean the id is not
   * assigned; it may be assigned in the object store, which is authoritative,
   * but not in the directory.)
   * @throws IOException
   */
  @Nullable public WaveletMapping lookup(SlobId objectId) throws IOException {
    CacheEntry cached = cache.get(objectId);
    if (cached != null) {
      return cached.getCached();
    }
    WaveletMapping result = directory.getWithoutTx(objectId);
    if (result != null) {
      cache.put(objectId, new CacheEntry(result));
      return result;
    } else {
      // We have to use ADD_ONLY_IF_NOT_PRESENT since there may be a concurrent
      // register() also trying to set the value, and that has to take priority.
      cache.put(objectId, new CacheEntry(null), null,
          MemcacheService.SetPolicy.ADD_ONLY_IF_NOT_PRESENT);
      return null;
    }
  }

  public void register(WaveletMapping newMapping) throws IOException, ObjectIdAlreadyKnown {
    Preconditions.checkNotNull(newMapping, "Null newMapping");
    // We do a cache lookup here to detect inconsistencies (and perhaps to catch
    // bugs that create the same wavelet more than once cheaper than the
    // datastore could do it).  It's not strictly necessary, but the datastore
    // access that follows is much more expensive anyway, so we don't mind the
    // extra cache lookup.
    CacheEntry cached = cache.get(newMapping.getObjectId());
    if (cached != null && cached.getCached() != null) {
      throw new ObjectIdAlreadyKnown(cached.getCached(),
          "Attempt to register " + newMapping
          + ", but object already exists (cached = " + cached + ")");
    }

    log.info("About to register " + newMapping);
    WaveletMapping existingMapping = directory.getOrAdd(newMapping);
    log.info("Existing mapping: " + existingMapping);
    if (existingMapping != null) {
      throw new ObjectIdAlreadyKnown(existingMapping,
          "Attempt to register " + newMapping + ", but object already exists: " + existingMapping
          + " (cached = " + cached + ")");
    }
    cache.put(newMapping.getObjectId(), new CacheEntry(newMapping));
  }

}
TOP

Related Classes of com.google.walkaround.wave.server.WaveletDirectory$CacheEntry

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.
lement(o), m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); ga('create', 'UA-20639858-1', 'auto'); ga('send', 'pageview');