TurboManage

David Chandler's Journal of Java Web and Mobile Development

  • David M. Chandler


    Web app developer since 1994 and former Developer Advocate with Google now residing in Colorado. Besides tech, I enjoy landscape photography and share my work at ColoradoPhoto.gallery.

  • Subscribe

  • Enter your email address to subscribe to this blog and receive notifications of new posts by email.

    Join 241 other followers

  • Sleepless Nights…

    February 2010
    S M T W T F S
    « Jan   Mar »
     123456
    78910111213
    14151617181920
    21222324252627
    28  
  • Blog Stats

    • 865,645 hits

Generic DAO for Objectify 2

Posted by David Chandler on February 9, 2010

Objectify 2 RC1 is available and I’ve updated the generic DAO I wrote about previously to use the improved Query interface. In addition, I’ve rewritten the query-by-example methods to filter on all non-null properties of the example object (but it ignores many types–see comments). Finally, note that Objectify 2 Query no longer passes through AppEngine’s TooManyResultsException if you try to do a get() which returns multiple entities. I liked this behavior, so I’ve added it back in the getByExample() method below for illustration. Enjoy.

package sandbox.server.dao;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import javax.persistence.Embedded;
import javax.persistence.Transient;

import com.google.appengine.api.datastore.EntityNotFoundException;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.Query;
import com.googlecode.objectify.helper.DAOBase;

public class ObjectifyGenericDao<T> extends DAOBase
{

	static final int BAD_MODIFIERS = Modifier.FINAL | Modifier.STATIC | Modifier.TRANSIENT;

	static
	{
		// Register all your entity classes here
		// ObjectifyService.register(MyDomain.class);
		// ...
	}

	protected Class<T> clazz;

	/**
	 * We've got to get the associated domain class somehow
	 *
	 * @param clazz
	 */
	protected ObjectifyGenericDao(Class<T> clazz)
	{
		this.clazz = clazz;
	}

	public Key<T> put(T entity)

	{
		return ofy().put(entity);
	}

	public List<Key<T>> putAll(Iterable<T> entities)
	{
		return ofy().put(entities);
	}

	public void delete(T entity)
	{
		ofy().delete(entity);
	}

	public void deleteKey(Key<T> entityKey)
	{
		ofy().delete(entityKey);
	}

	public void deleteAll(Iterable<T> entities)
	{
		ofy().delete(entities);
	}

	public void deleteKeys(Iterable<Key<T>> keys)
	{
		ofy().delete(keys);
	}

	public T get(Long id) throws EntityNotFoundException
	{
		return ofy().get(this.clazz, id);
	}

	public T get(Key<T> key) throws EntityNotFoundException
	{
		return ofy().get(key);
	}

	/**
	 * Convenience method to get all objects matching a single property
	 *
	 * @param propName
	 * @param propValue
	 * @return T matching Object
	 */
	public T getByProperty(String propName, Object propValue)
	{
		Query<T> q = ofy().query(clazz);
		q.filter(propName, propValue);
		return q.get();
	}

	public List<T> listByProperty(String propName, Object propValue)
	{
		Query<T> q = ofy().query(clazz);
		q.filter(propName, propValue);
		return asList(q.fetch());
	}

	public List<Key<T>> listKeysByProperty(String propName, Object propValue)
	{
		Query<T> q = ofy().query(clazz);
		q.filter(propName, propValue);
		return asKeyList(q.fetchKeys());
	}

	public T getByExample(T exampleObj)
	{
		Query<T> queryByExample = buildQueryByExample(exampleObj);
		Iterable<T> iterableResults = queryByExample.fetch();
		Iterator<T> i = iterableResults.iterator();
		T obj = i.next();
		if (i.hasNext())
			throw new RuntimeException("Too many results");
		return obj;
	}

	public List<T> listByExample(T exampleObj)
	{
		Query<T> queryByExample = buildQueryByExample(exampleObj);
		return asList(queryByExample.fetch());
	}

	private List<T> asList(Iterable<T> iterable)
	{
		ArrayList<T> list = new ArrayList<T>();
		for (T t : iterable)
		{
			list.add(t);
		}
		return list;
	}

	private List<Key<T>> asKeyList(Iterable<Key<T>> iterableKeys)
	{
		ArrayList<Key<T>> keys = new ArrayList<Key<T>>();
		for (Key<T> key : iterableKeys)
		{
			keys.add(key);
		}
		return keys;
	}

	private Query<T> buildQueryByExample(T exampleObj)
	{
		Query<T> q = ofy().query(clazz);

		// Add all non-null properties to query filter
		for (Field field : clazz.getDeclaredFields())
		{
			// Ignore transient, embedded, array, and collection properties
			if (field.isAnnotationPresent(Transient.class)
				|| (field.isAnnotationPresent(Embedded.class))
				|| (field.getType().isArray())
				|| (Collection.class.isAssignableFrom(field.getType()))
				|| ((field.getModifiers() & BAD_MODIFIERS) != 0))
				continue;

			field.setAccessible(true);

			Object value;
			try
			{
				value = field.get(exampleObj);
			}
			catch (IllegalArgumentException e)
			{
				throw new RuntimeException(e);
			}
			catch (IllegalAccessException e)
			{
				throw new RuntimeException(e);
			}
			if (value != null)
			{
				q.filter(field.getName(), value);
			}
		}

		return q;
	}
}
Advertisements

25 Responses to “Generic DAO for Objectify 2”

  1. Samraat said

    I think you can also add a method of the form :

    OKey getKeysByExample(T exampleObj){}

    This is useful in scenarios where you have a million fan out problem. 🙂

  2. Appreciate code for DAOBase?

    TIA

  3. Anh-Tuan said

    Thank you! Could you please publish the configuration files and one entity specific DAO class.

    Following your example, here are other methods I added in my GenericDAO. For performances and security issues, escapeFilter, escapeSortOrders, escapeProperties and escapeFetchLimit should be over-ridded in entity specific DAO class.


    /*
    * Convenience methods for updating multiple properties of an entity
    * support embedded properties update (multi-level).
    *
    **/

    public T updateProperties(T entity, Iterable properties) throws Exception{
    List eps = escapeProperties(properties);
    for(Property p : eps){
    entity = updateProperty(entity, p);
    }
    return entity;
    }

    public T updateProperty(T entity, Property property) throws Exception{
    String [] fieldNames = property.getPropertyName().split("\\.");
    int len=fieldNames.length;
    Object entityField = entity;
    for(int i=0; i< len-1; i++){
    entityField = getField(entityField, fieldNames[i]);
    }
    setField(entityField, fieldNames[len-1], property.getPropertyValue());
    return entity;
    }

    public List escapeProperties(Iterable properties) {
    /*
    * TODO: Security and performances issues should be treated here.
    * Entity classes should override this method
    */
    ArrayList eps = new ArrayList();
    for (Property p : properties) {
    eps.add(p);
    }
    return eps;
    }

    public Object getField(Object obj, String fieldName) throws Exception{
    String methName = "get"+fieldName.toUpperCase().charAt(0)+fieldName.substring(1);
    Method meth = obj.getClass().getMethod(methName);
    return meth.invoke(obj);
    }

    public void setField(Object obj, String fieldName, Object value) throws Exception{
    String methName = "set"+fieldName.toUpperCase().charAt(0)+fieldName.substring(1);
    Class parametersTypes[]= new Class[1];
    parametersTypes[0]=value.getClass();
    Method meth = obj.getClass().getMethod(methName, parametersTypes);
    Object argList[] = new Object[1];
    argList[0] = value;
    meth.invoke(obj, argList);
    }

    /**
    * Convenience method to build all possible get Queries
    *
    * @param filters
    * :query filters
    * @param sortOrders
    * : query sort orders
    * @param limit
    * : number of maximum fetched results
    * @return Query
    */

    public Query buildQuery(Iterable filters,
    Iterable sortOrders, int limit) {
    Query q = ofy().query(clazz);

    List escapedFilters = escapeFilters(filters);
    List escapedSortOrders = escapeSortOrders(sortOrders);
    int escapedLimit = escapeFetchLimit(limit);

    for (Filter f : escapedFilters) {
    q.filter(f.getFilterCondition(), f.getFilterValue());
    }
    for (String so : escapedSortOrders) {
    q.order(so);
    }
    q.limit(escapedLimit);
    return q;
    }

    public List escapeFilters(Iterable filters) {
    /*
    * TODO: Security and performances issues should be treated here Entity
    * classes should override this method
    */
    ArrayList efs = new ArrayList();
    for (Filter f : filters) {
    efs.add(f);
    }
    return efs;
    }

    public List escapeSortOrders(Iterable sortOrders) {
    /*
    * TODO: Security and performances issues should be treated here Entity
    * classes should override this method
    */
    ArrayList esos = new ArrayList();
    for (String so : sortOrders) {
    esos.add(so);
    }

    return esos;
    }

    public int escapeFetchLimit(int limit) {
    /*
    * TODO: Security and performances issues should be treated here Entity
    * classes should override this method example:
    */
    int classSpecificLimit = 51;
    if (limit >= classSpecificLimit) {
    return classSpecificLimit;
    }
    return limit;
    }

    /**
    * Convenience method to get all objects for all possible get Queries
    *
    * @param filters
    * :query filters
    * @param sortOrders
    * : query sort orders
    * @param limit
    * : number of maximum fetched objects
    * @return List
    */

    public List listByQuery(Iterable filters,
    Iterable sortOrders, int limit) {
    Query q = buildQuery(filters, sortOrders, limit);
    return asList(q.fetch());
    }

    private List asList(Iterable iterable) {
    ArrayList list = new ArrayList();
    for (T t : iterable) {
    list.add(t);
    }
    return list;
    }

    /**
    * Convenience method to get all object keys for all possible get Queries
    *
    * @param filters
    * :query filters
    * @param sortOrders
    * : query sort orders
    * @param limit
    * : number of maximum fetched keys
    * @return List<Key>
    */

    public List<Key> listKeysByQuery(Iterable filters,
    Iterable sortOrders, int limit) {
    Query q = buildQuery(filters, sortOrders, limit);
    return asKeyList(q.fetchKeys());
    }

    private List<Key> asKeyList(Iterable<Key> iterableKeys) {
    ArrayList<Key> keys = new ArrayList<Key>();
    for (Key key : iterableKeys) {
    keys.add(key);
    }
    return keys;
    }

    Here follows the code for Property end Filter


    import java.io.Serializable;

    public class Property implements Serializable{

    private static final long serialVersionUID = -7165134355494456946L;

    private String propertyName;
    private Serializable propertyValue;

    private Property(){}

    public Property(String propertyName, Serializable propertyValue){
    this.propertyName=propertyName;
    this.propertyValue=propertyValue;
    }

    public void setPropertyName(String propertyName){
    this.propertyName=propertyName;
    }

    public void setPropertyValue(Serializable propertyValue){
    this.propertyValue=propertyValue;
    }

    public String getPropertyName(){
    return propertyName;
    }

    public Serializable getPropertyValue(){
    return propertyValue;
    }
    }


    import java.io.Serializable;

    public class Filter implements Serializable{

    private static final long serialVersionUID = -7165134355494456946L;

    private String filterCondition;
    private Serializable filterValue;

    private Filter(){}

    public Filter(String filterCondition, Serializable filterValue){
    this.filterCondition=filterCondition;
    this.filterValue=filterValue;
    }

    public void setFilterCondition(String filterCondition){
    this.filterCondition=filterCondition;
    }

    public void setFilterValue(Serializable filterValue){
    this.filterValue=filterValue;
    }

    public String getFilterCondition(){
    return filterCondition;
    }

    public Serializable getFilterValue(){
    return filterValue;
    }

    }

  4. […] Generic DAO for Objectify 2 […]

  5. Hi,

    Thanks for sharing your generic dao!
    I think you can use an empty constructor and get the class using the parameterized type like this :


    public class ObjectifyGenericDao extends DAOBase
    {
    protected Class clazz;

    @SuppressWarnings("unchecked")
    public ObjectifyGenericDao(){
    clazz = ((Class) ((ParameterizedType) getClass()
    .getGenericSuperclass()).getActualTypeArguments()[0]);
    }
    ...

    This is more efficient because you don’t need to write a Constructor for each DAO that extends ObjectifyGenericDao.

  6. Viswa said

    from the original code,

    I got this Compile error
    Type mismatch: cannot convert from Map<Key,T> to List<Key>

    public List<Key> putAll(Iterable entities)
    {
    return ofy().put(entities);
    }

  7. Viswa said

    It should be
    public Map<Key, T> putAll(Iterable entities)
    {
    return ofy().put(entities);
    }

  8. Dennis said

    Hi David and Anh-Tuan,

    Thank you for sharing your code in this blog and comments.

    I’ve used it quite successfully to make a generic class that helps making performing Objectify operations easier. However, sometimes when I want to perform something a little more tricky I have to slip back into the standard Objectify way of doing it.

    Anyways, I’m planning later to write a blog entry to describe some of the things I’ve created using GAE and Objectify.

    Again, thanks for sharing David and Anh-Tuan.

  9. Keith said

    Thanks for this, David!

    I started with your GenericDao approach and took it a few steps further. The GenericDao is now the basis for a Manager layer that can be extended to override GenericDao methods as needed or add new biz logic-specific methods–and all without forcing any refactoring elsewhere in the code.

    I wrote up a pretty extensive explanation of why this approach makes sense and included tons of code samples.

    I’d love feedback, criticisms, or further suggestions.

    http://blog.essaytagger.com/2011/07/elegant-coding-with-objectify-java.html

  10. Jacob said

    Consider using this trick to obtain runtime class:

    this.clazz = (Class) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];

    It can be little unsecure though, I usually provide two versions of constructor.

  11. Andy DeWolfe said

    queryByExample() using a @Parent property didn’t work for me. I made the following change to this method and now it works as expected ( or at least how i expect it to work )

    private Query buildQueryByExample(T exampleObj)
    {
    Query q = ofy().query(clazz);

    // Add all non-null properties to query filter
    for (Field field : clazz.getDeclaredFields())
    {
    // Ignore transient, embedded, array, and collection properties
    if (field.isAnnotationPresent(Transient.class)
    || (field.isAnnotationPresent(Embedded.class))
    || (field.getType().isArray())
    || (Collection.class.isAssignableFrom(field.getType()))
    || ((field.getModifiers() & BAD_MODIFIERS) != 0))
    continue;

    field.setAccessible(true);

    Object value;
    try
    {
    value = field.get(exampleObj);
    }
    catch (IllegalArgumentException e)
    {
    throw new RuntimeException(e);
    }
    catch (IllegalAccessException e)
    {
    throw new RuntimeException(e);
    }
    if (value != null)
    {
    // If @Parent field, set the query ancestor.
    if( field.isAnnotationPresent(Parent.class) ) { // <—- ADDED THIS CHECK
    q.ancestor(value);
    } else {
    q.filter(field.getName(), value);
    }
    }
    }

    return q;
    }

    Could/should probably do this same thing for all get/list methods as well.

  12. Urahara Kisuke said

    Hello,
    First thanks for this work.
    I also have a problem with the getByExample query, when retrieving the result I have an NoSuchElementException.

    Does anyone have any idea about where it should come from ?

    java.util.NoSuchElementException
    at com.google.appengine.api.datastore.QueryResultIteratorImpl.next(QueryResultIteratorImpl.java:74)
    at com.google.appengine.api.datastore.QueryResultIteratorImpl.next(QueryResultIteratorImpl.java:27)
    at com.googlecode.objectify.util.TranslatingIterator.next(TranslatingIterator.java:35)
    at com.okiro.server.dao.ObjectifyDao.getByExample(ObjectifyDao.java:102)

    Thanks,

    • Hi, Urahara. The only way I could see this happening is if a result in the query was deleted while the query was in progress; otherwise, it might be an App Engine bug.

  13. […] Objectify, a simple-to-use framework based on the Low-Level API to access the DataStore. With the DAO implementation suggested by David Chandler, my data access layer was composed by only this […]

  14. […] I am using Chandler’s Generic DAO for Objectify 2 at https://turbomanage.wordpress.com/2010/02/09/generic-dao-for-objectify-2/ […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
%d bloggers like this: