Package com.almende.eve.state.google

Source Code of com.almende.eve.state.google.DatastoreState

package com.almende.eve.state.google;

import java.io.IOException;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import com.almende.eve.state.AbstractState;
import com.google.appengine.api.memcache.MemcacheService;
import com.google.appengine.api.memcache.MemcacheService.IdentifiableValue;
import com.google.appengine.api.memcache.MemcacheService.SetPolicy;
import com.google.appengine.api.memcache.MemcacheServiceFactory;
import com.google.code.twig.ObjectDatastore;
import com.google.code.twig.annotation.AnnotationObjectDatastore;

/**
* @class DatastoreState
*
* A state for an Eve Agent, which stores the data in the Google Datastore.
* This state is only available when the application is running in Google
* App Engine.
*
* The state provides general information for the agent (about itself,
* the environment, and the system configuration), and the agent can store its
* state in the state.
* The state extends a standard Java Map.
*
* During the lifetime of a DatastoreState, the state synchronized over all
* running instances of the same DatastoreState using MemCache.
*
* Usage:<br>
*     AgentFactory factory = new AgentFactory(config);<br>
*     DatastoreState state =
*          new DatastoreState(factory, "agentType", "agentId");<br>
*     state.put("key", "value");<br>
*     System.out.println(state.get("key")); // "value"<br>
*
* @author jos
*/
public class DatastoreState extends AbstractState<Serializable> {
  private Map<String, Serializable> properties = new ConcurrentHashMap<String, Serializable>();
  private MemcacheService cache = MemcacheServiceFactory.getMemcacheService();
  private IdentifiableValue cacheValue = null;
  private boolean isChanged = false;
 
  public DatastoreState() {}

  public DatastoreState(String agentId) {
    super(agentId);
  }

  /**
   * Retrieve the url of the agents app from the system environment
   * eve.properties, for example "http://myapp.appspot.com"
   *
   * @return appUrl
   */
  /* TODO: cleanup
  // TODO: replace this with usage of environment
  private String getAppUrl() {
    String appUrl = null;
 
    // TODO: retrieve the servlet path from the servlet parameters itself
    // http://www.jguru.com/faq/view.jsp?EID=14839
    // search for "get servlet path without request"
    // System.out.println(req.getServletPath());

    String environment = SystemProperty.environment.get();
    String id = SystemProperty.applicationId.get();
    // String version = SystemProperty.applicationVersion.get();
   
    if (environment.equals("Development")) {
      // TODO: check the port
      appUrl = "http://localhost:8888";
    } else {
      // production
      // TODO: reckon with the version of the application?
      appUrl = "http://" + id + ".appspot.com";
      // TODO: use https by default
      //appUrl = "https://" + id + ".appspot.com";
    }
   
    return appUrl;
  }
  */

  /**
   * Load the state from cache. If the state is not available in cache,
   * it will be loaded from the Datastore and the cache will be created.
   * If there is no state stored in both cache and Datastore, an empty
   * map with properties is initialized.
   */
  private void load() {
    // load from cache
    boolean success = loadFromCache();
    if (!success) {
      // if not in cache, load from datastore
      success = loadFromDatastore();
      if (success) {
        // create memcache entry
        saveToCache();
      }
    }
  }
 
  /**
   * Store changes in the state into memcache, and mark the state as
   * changed. The state will be stored in the datastore after a fixed delay
   * or when the method .destroy() is executed.
   * @return success    returns true if the change is saved in memcache.
   */
  private boolean save() {
    return save(false);
  }
 
  /**
   * Store changes in the state into memcache, and mark the state as
   * changed. The state will be stored in the datastore after a fixed delay
   * or when the method .destroy() is executed.
   * @param force       If true, the save method will immediately write
   *                    The state to the datastore instead of after a delay.
   * @return success    returns true if the change is saved in memcache.
   */
  private boolean save(boolean force) {
    // save to memcache
    isChanged = true;
    boolean success = saveToCache();

    if (force) {
      // store immediately in the datastore
      isChanged = false;
      saveToDatastore();
    }
    else {
      // TODO: (re)implement a mechanism to write to the datastore less often
      isChanged = false;
      saveToDatastore();
    }
   
    return success;
  }

  /**
   * Load the state from cache
   * @return success
   */
  @SuppressWarnings("unchecked")
  private boolean loadFromCache() {
    cacheValue = cache.getIdentifiable(getAgentId());
    if (cacheValue != null && cacheValue.getValue() != null) {
      properties = (Map<String, Serializable>) cacheValue.getValue();
      return true;
    }
   
    return false;
  }
 
  /**
   * Save the state to cache
   * If the cache is changed since the last retrieval of the cache, saving
   * will fail and false will be returned.
   * @return success
   */
  private boolean saveToCache() {
    boolean success = false;
    if (cacheValue != null) {
      success = cache.putIfUntouched(getAgentId(), cacheValue, properties);
    }
    else {
      success = cache.put(getAgentId(), properties, null,
          SetPolicy.ADD_ONLY_IF_NOT_PRESENT);
      if (success) {
        // reload from cache to get the IdentifiableValue from cache,
        // for later reference
        success = loadFromCache();
      }
    }
   
    return success;
  }
 
  /**
   * load the properties from the datastore
   * @return success    True if successfully loaded
   * @throws IOException
   * @throws ClassNotFoundException
   * @return
   */
  @SuppressWarnings("unchecked")
  private boolean loadFromDatastore () {
    try {
      ObjectDatastore datastore = new AnnotationObjectDatastore();
      KeyValue entity = datastore.load(KeyValue.class, getAgentId());
     
      @SuppressWarnings("rawtypes")
      Class<? extends HashMap> MAP_OBJECT_CLASS =
        (new HashMap<String, Serializable>()).getClass();
     
      if (entity != null) {
        // TODO: can this be simplified with the following?:
        //       Map<String, Object> newProperties = entity.getValue(Map.class);
        Map<String, Serializable> newProperties = entity.getValue(MAP_OBJECT_CLASS);
        if (newProperties != null) {
          properties = newProperties;
        }
      }
     
      return true;
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    }
   
    return false;
  }
 
  /**
   * Write the properties to the datastore when they are changed
   * @param entity
   * @return success    True if successfully saved
   * @throws IOException
   */
  private boolean saveToDatastore () {
    try {
      ObjectDatastore datastore = new AnnotationObjectDatastore();
      KeyValue entity = new KeyValue(getAgentId(), properties);
      datastore.store(entity);
      return true;
    } catch (IOException e) {
      e.printStackTrace();
    }

    return false;
  }

  /**
   * Delete the entity from the Datastore
   * @param entity
   */
  private void deleteFromDatastore () {
    ObjectDatastore datastore = new AnnotationObjectDatastore();
    KeyValue entity = datastore.load(KeyValue.class, getAgentId());
    if (entity != null) {
      datastore.delete(entity);
    }
  }
 
  /**
   * Delete the entity from the Cache
   * @param entity
   */
  private void deleteFromCache () {
    cache.delete(getAgentId());
    // TODO: check if deletion was successful?
  }
 
  /**
   * init is executed once before the agent method is invoked
   */
  @Override
  public void init() {}

  /**
   * If the state is changed, it will be stored in the datastore on destroy.
   */
  @Override
  public void destroy() {
    if (isChanged) {
      isChanged = false;
      saveToDatastore();
    }
  }

  /**
   * Permanently delete this state
   */
  protected void delete() {
    clear();
    deleteFromCache();
    deleteFromDatastore();

    isChanged = false;
  }

  @Override
  public Serializable get(String key) {
    load();
    return properties.get(key);
  }

  @Override
  public Serializable locPut(String key, Serializable value) {
    load();
    Serializable ret = properties.put(key, value);
    boolean success = save();
    if (!success) {
      ret = null;
    }
    return ret;
  }

  @Override
  public boolean containsKey(String key) {
    load();
    return properties.containsKey(key);
  }

  @Override
  public Serializable remove(String key) {
    load();
    Serializable value = properties.remove(key);
    save();
    return value;
  }

  @Override
  public void clear() {
    load();
    properties.clear();
    save();
  }

  public boolean isEmpty() {
    load();
    return properties.isEmpty();
  }

  @Override
  public Set<String> keySet() {
    load();
    return properties.keySet();
  }

  @Override
  public boolean locPutIfUnchanged(String key, Serializable newVal, Serializable oldVal) {
    boolean result=false;
    load();
    if ((oldVal == null && properties.containsKey(key)) || properties.get(key).equals(oldVal)){
      properties.put(key,newVal);
      save();
      result=true;
    }
    return result;
  }
 
  @Override
  public int size() {
    load();
    return properties.size();
  }
}
TOP

Related Classes of com.almende.eve.state.google.DatastoreState

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.