All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.tectonica.gae.GaeKeyValueStore Maven / Gradle / Ivy

Go to download

Set of Java utility classes, all completely independent, to provide lightweight solutions for common situations

There is a newer version: 0.6.1
Show newest version
package com.tectonica.gae;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.locks.Lock;

import com.google.appengine.api.datastore.Blob;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Query.Filter;
import com.google.appengine.api.datastore.Query.FilterOperator;
import com.google.appengine.api.datastore.Query.FilterPredicate;
import com.tectonica.collections.KeyValueStore;
import com.tectonica.util.SerializeUtil;

public class GaeKeyValueStore extends KeyValueStore
{
	private static DatastoreService ds = DatastoreServiceFactory.getDatastoreService();

	private final Class valueClass;
	private final String kind;
	private final Key ancestor; // dummy parent for all entities to guarantee Datastore consistency
	private final List> indexes;

	public GaeKeyValueStore(Class valueClass, KeyMapper keyMapper)
	{
		super(keyMapper);
		if (valueClass == null)
			throw new NullPointerException();
		this.valueClass = valueClass;
		this.kind = valueClass.getSimpleName();
		this.ancestor = KeyFactory.createKey(kind, BOGUS_ANCESTOR_KEY_NAME);
		this.indexes = new ArrayList<>();
	}

	/***********************************************************************************
	 * 
	 * GETTERS
	 *
	 ***********************************************************************************/

	@Override
	public V get(String key)
	{
		try
		{
			return entityToValue(ds.get(KeyFactory.createKey(ancestor, kind, key)));
		}
		catch (EntityNotFoundException e)
		{
			return null;
		}
	}

	@Override
	public Iterator> iterator()
	{
		return entryIteratorOfQuery(newQuery()); // query without filters = all
	}

	@Override
	public Iterator keyIterator()
	{
		return keyIteratorOfQuery(newQuery().setKeysOnly());
	}

	@Override
	public Iterator valueIterator()
	{
		return valueIteratorOfQuery(newQuery());
	}

	@Override
	public Iterator> iteratorFor(Set keySet)
	{
		Filter filter = new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.IN, keySet);
		return entryIteratorOfQuery(newQuery().setFilter(filter));
	}

	/***********************************************************************************
	 * 
	 * SETTERS (UTILS)
	 *
	 ***********************************************************************************/

	@Override
	protected Modifier getModifier(final String key, ModificationType purpose)
	{
		// insert, replace and update are all treated the same in GAE: all simply do save()
		return new Modifier()
		{
			@Override
			public V getModifiableValue()
			{
				return get(key); // same implementation as read-only get, as in both cases we deserialize a new instance
			}

			@Override
			public void commit(V value)
			{
				save(key, value);
			}
		};
	}

	@Override
	public Lock getModificationLock(String key)
	{
		return GaeMemcacheLock.getLock(kind + ":" + key, true);
	}

	/***********************************************************************************
	 * 
	 * SETTERS
	 *
	 ***********************************************************************************/

	@Override
	public void insert(String key, V value)
	{
		save(key, value);
	}

	/***********************************************************************************
	 * 
	 * DELETERS
	 *
	 ***********************************************************************************/

	@Override
	public void delete(String key)
	{
		ds.delete(KeyFactory.createKey(ancestor, kind, key));
	}

	@Override
	public void truncate()
	{
		for (Entity entity : ds.prepare(newQuery().setKeysOnly()).asIterable())
			ds.delete(entity.getKey());
	}

	/***********************************************************************************
	 * 
	 * INDEXES
	 *
	 ***********************************************************************************/

	@Override
	public  Index createIndex(String indexName, IndexMapper mapFunc)
	{
		GaeIndexImpl index = new GaeIndexImpl<>(mapFunc, indexName);
		indexes.add(index);
		return index;
	}

	/**
	 * GAE implementation of an index - simply exposes the Datastore property filters
	 * 
	 * @author Zach Melamed
	 */
	public class GaeIndexImpl extends Index
	{
		public GaeIndexImpl(IndexMapper mapFunc, String name)
		{
			super(mapFunc, name);

			if (name == null || name.isEmpty())
				throw new RuntimeException("index name is mandatory in " + GaeIndexImpl.class.getSimpleName());
		}

		public Iterator keyIteratorOf(F f)
		{
			return keyIteratorOfQuery(newIndexQuery(f).setKeysOnly());
		}

		@Override
		public Iterator valueIteratorOf(F f)
		{
			return valueIteratorOfQuery(newIndexQuery(f));
		}

		private Query newIndexQuery(F f)
		{
			Filter filter = new FilterPredicate(propertyName(), FilterOperator.EQUAL, f);
			return newQuery().setFilter(filter);
		}

		private String propertyName()
		{
			return COL_NAME_INDEX_PREFIX + name;
		}

		private F getIndexedFieldOf(V value)
		{
			return mapper.getIndexedFieldOf(value);
		}
	}

	/***********************************************************************************
	 * 
	 * DATASTORE UTILS
	 *
	 ***********************************************************************************/

	private static final String COL_NAME_ENTRY_VALUE = "value";
	private static final String COL_NAME_INDEX_PREFIX = "_i_";
	private static final String BOGUS_ANCESTOR_KEY_NAME = " ";

	private V entityToValue(Entity entity)
	{
		Blob blob = (Blob) entity.getProperty(COL_NAME_ENTRY_VALUE);
		return SerializeUtil.bytesToObj(blob.getBytes(), valueClass);
	}

	private Entity entryToEntity(String key, V value)
	{
		Entity entity = new Entity(kind, key, ancestor);
		entity.setProperty(COL_NAME_ENTRY_VALUE, new Blob(SerializeUtil.objToBytes(value)));
		for (int i = 0; i < indexes.size(); i++)
		{
			GaeIndexImpl index = indexes.get(i);
			Object field = (value == null) ? null : index.getIndexedFieldOf(value);
			entity.setProperty(index.propertyName(), field);
		}
		return entity;
	}

	private void save(String key, V value)
	{
		ds.put(entryToEntity(key, value));
	}

	private Query newQuery()
	{
		return new Query(kind).setAncestor(ancestor);
	}

	private Iterator> entryIteratorOfQuery(Query q)
	{
		final Iterator iter = ds.prepare(q).asIterator();
		return new Iterator>()
		{
			@Override
			public boolean hasNext()
			{
				return iter.hasNext();
			}

			@Override
			public KeyValue next()
			{
				final Entity entity = iter.next();
				return new KeyValue()
				{
					@Override
					public String getKey()
					{
						return entity.getKey().getName();
					}

					@Override
					public V getValue()
					{
						return entityToValue(entity);
					}
				};
			}

			@Override
			public void remove()
			{
				throw new UnsupportedOperationException();
			}
		};
	}

	private Iterator keyIteratorOfQuery(Query q)
	{
		final Iterator iter = ds.prepare(q).asIterator();
		return new Iterator()
		{
			@Override
			public boolean hasNext()
			{
				return iter.hasNext();
			}

			@Override
			public String next()
			{
				return iter.next().getKey().getName();
			}

			@Override
			public void remove()
			{
				throw new UnsupportedOperationException();
			}
		};
	}

	private Iterator valueIteratorOfQuery(Query q)
	{
		final Iterator iter = ds.prepare(q).asIterator();
		return new Iterator()
		{
			@Override
			public boolean hasNext()
			{
				return iter.hasNext();
			}

			@Override
			public V next()
			{
				return entityToValue(iter.next());
			}

			@Override
			public void remove()
			{
				throw new UnsupportedOperationException();
			}
		};
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy