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 Peru. I am currently offering public and private developer training courses in the US and Latin America as well as working on Android, GWT, and App Engine projects.

  • Subscribe

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

    Join 227 other followers

  • Sleepless Nights…

    January 2010
    S M T W T F S
    « Dec   Feb »
     12
    3456789
    10111213141516
    17181920212223
    24252627282930
    31  
  • Blog Stats

    • 671,088 hits

Archive for January, 2010

Simplify with Objectify: a lightweight persistence framework for AppEngine

Posted by David Chandler on January 28, 2010

It is wonderful that Google provides JDO and JPA interfaces to the AppEngine Datastore. However, the learning curve is somewhat steep due to AppEngine’s non-relational nature and unique concepts such as entity groups, owned and un-owned relationships, etc. In addition, the PersistenceManager lifecycle (and particularly the necessity of detaching objects) often gets in the way. I’ve written a lot of code on this blog just to handle injecting the PM where it’s needed in services, unit tests, etc.

In stark contrast to all this stands the DataStore low-level API.  You can call the DatastoreService from anywhere in your code via the DatastoreServiceFactory, and there are only four methods to understand: get, put, delete, and prepare (query). It makes no pretense of being relational, but unfortunately, no pretense of being an object store, either, as the low-level API stores only objects of type com.google.appengine.api.datastore.Entity.

Enter Objectify, a recently-announced open source persistence framework for AppEngine. Objectify preserves the simplicity and transparency of the low-level API and does all the work of converting your domain objects to and from Datastore Entities. It’s a tiny jar (36k) with no external dependencies and zero startup time, which helps mitigate the AppEngine cold start problem.

In the process of Objectify-ing my old JDO code, I’m building a base DAO class that I can simply extend for each domain class and get all the standard CRUD operations for free. Because most Objectify methods use parameterized types, my generic DAO is largely redundant, but nevertheless provides a consistent layer in which to add type-specific methods like findUserByEmailOrPhone. Let’s look at some code.

First, a simple domain class. Note there are fewer and simpler annotations required than for JPA / JDO.

package com.roa.common.domain;

import java.io.Serializable;

import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class User implements Serializable
{
	private static final long serialVersionUID = -1126191336687818754L;

	@Id
	// Objectify auto-generates Long IDs just like JDO / JPA
	private Long id;
	private String firstName;
	private String lastName;
	private String emailAddress;
	private String zipCode;
	private String googleAccountId;

	public User()
	{
		// Empty constructor needed for GWT serialization and Objectify
	}

	public Long getId()
	{
		return id;
	}

	public void setId(Long id)
	{
		this.id = id;
	}

	...
}

We can do persistence operations by calling the UserDao:

		// Create new user
		User u = new User();
		u.setEmailAddress("test@example.com");
		u.setFirstName("Test");
		u.setLastName("User");
		u.setGoogleAccountId("testAccountId");
		UserDao userDao = new UserDao();
		OKey<User> key = userDao.add(u);

		// Retrieve user by ID
		dao.get(u.getId());

		// Find users matching an email address
		users = dao.listByProperty("emailAddress", "test@example.com");

All the DAO methods above are provided in my base DAO, so the UserDao itself is quite simple:

package com.roa.server.dao;

import java.util.logging.Logger;

import com.googlecode.objectify.ObjectifyService;
import com.roa.common.domain.User;

public class UserDao extends ObjectifyDao<User>
{
	private static final Logger LOG = Logger.getLogger(UserDao.class.getName());

	static
	{
		ObjectifyService.register(User.class);
	}

	public UserDao()
	{
		super(User.class);
	}

}

And finally, my generic base DAO (a work in progress, caveat emptor). Note that I’ve created a couple methods to allow query by example, which I generally prefer to enumerating methods for every use case (getByEmail, getByLastName, getByFirstAndLastName, etc.). The getByProperty and listByProperty methods take a single property name and value on which to filter, and getByExample / listByExample allow you to specify multiple property names on which to filter.

package com.roa.server.dao;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;

import com.google.appengine.api.datastore.EntityNotFoundException;
import com.googlecode.objectify.OKey;
import com.googlecode.objectify.OQuery;
import com.googlecode.objectify.ObjectifyService;
import com.googlecode.objectify.helper.DAOBase;

public class ObjectifyDao<T> extends DAOBase
{
	private Class<T> clazz;

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

	public OKey<T> add(T entity)

	{
		OKey<T> key = ofy().put(entity);
		return key;
	}

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

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

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

	/**
	 * Convenience method to get an object matching a single property
	 *
	 * @param propName
	 * @param propValue
	 * @return T matching Object
	 */
	public T getByProperty(String propName, Object propValue)
	{
		OQuery<T> q = ObjectifyService.createQuery(clazz);
		q.filter(propName, propValue);
		T obj = ofy().prepare(q).asSingle();
		return obj;
	}

	public List<T> listByProperty(String propName, Object propValue)
	{
		OQuery<T> q = ObjectifyService.createQuery(clazz);
		q.filter(propName, propValue);
		List<T> list = ofy().prepare(q).asList();
		return list;
	}

	public T getByExample(T u, String... matchProperties)
	{
		OQuery<T> q = ObjectifyService.createQuery(clazz);
		// Find non-null properties and add to query
		for (String propName : matchProperties)
		{
			Object propValue = getPropertyValue(u, propName);
			q.filter(propName, propValue);
		}
		T obj = ofy().prepare(q).asSingle();
		return obj;
	}

	public List<T> listByExample(T u, String... matchProperties)
	{
		OQuery<T> q = ObjectifyService.createQuery(clazz);
		// Find non-null properties and add to query
		for (String propName : matchProperties)
		{
			Object propValue = getPropertyValue(u, propName);
			q.filter(propName, propValue);
		}
		List<T> list = ofy().prepare(q).asList();
		return list;
	}

	private Object getPropertyValue(Object obj, String propertyName)
	{
		BeanInfo beanInfo;
		try
		{
			beanInfo = Introspector.getBeanInfo(obj.getClass());
		}
		catch (IntrospectionException e)
		{
			throw new RuntimeException(e);
		}
		PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
		for (PropertyDescriptor propertyDescriptor : propertyDescriptors)
		{
			String propName = propertyDescriptor.getName();
			if (propName.equals(propertyName))
			{
				Method readMethod = propertyDescriptor.getReadMethod();
				try
				{
					Object value = readMethod.invoke(obj, new Object[] {});
					return value;
				}
				catch (IllegalArgumentException e)
				{
					throw new RuntimeException(e);
				}
				catch (IllegalAccessException e)
				{
					throw new RuntimeException(e);
				}
				catch (InvocationTargetException e)
				{
					throw new RuntimeException(e);
				}
			}
		}
		return null;
	}

}

Have fun!

Posted in AppEngine | 23 Comments »

How to do joins in AppEngine

Posted by David Chandler on January 25, 2010

So the AppEngine DataStore DOES do joins, after all… using the mysterious list properties. This is highly recommended viewing for anyone building a large social application or otherwise looking for a simple alternative to a SQL inner join. You cannot organize your data model quite the same as you would in a relational database, but you can easily simulate querying an intersection table.

Building Scalable, Complex Apps on AppEngine

Posted in AppEngine | 3 Comments »

GWT 2 upgrade almost painless

Posted by David Chandler on January 13, 2010

I took the plunge and upgraded my Google Eclipse plugin and SDKs to GWT 2 using the normal Eclipse update mechanism. The new development mode threw me for a bit of a loop, but that’s the only problem so far. The GWT 2 docs say that the browser will automatically prompt you to download the Google Web Toolkit Developer plugin, but that wasn’t happening for my existing GWT project, only new ones.

The solution was simply to delete the generated app directories under the war folder, and clean and rebuild the project. Then I was prompted to download the plugin as expected, and the browser makes the connection to Eclipse so I can view the GWT logs.

So far so good. I’m anxious to try out the new UIBinder templating and hopefully simplify page layout issues…

Posted in Google Web Toolkit, Headsmack | Leave a Comment »

GWT layout gotcha

Posted by David Chandler on January 12, 2010

I previously wrote about GWT layout with HTML and CSS. One gotcha I’ve found is that you must be careful not to attach a widget to a DIV nested inside a DIV that is attached to a widget. By way of negative example, consider this HTML:

<div id="main">
	<div id="app">
		<div id="detail_header"></div>
	</div>
</div>

Because the detail_header DIV is nested inside the main DIV, this will fail:

		RootPanel.get("main").add(contentContainerPresenter.getDisplay().asWidget());
		RootPanel.get("detail_header").add(userPresenter.getDisplay().asWidget());

The error message is not particularly obvious, which is why I wrote this post:

[ERROR] Uncaught exception escaped
java.lang.AssertionError: A widget that has an existing parent widget may not be added to the detach list
at com.google.gwt.user.client.ui.RootPanel.detachOnWindowClose(RootPanel.java:122)

The solution is simply to untangle the DIVs by making them siblings instead of nested:

<div id="main">
	<div id="detail_header"></div>
	<div id="app"></div>
</div>

Posted in Google Web Toolkit, Headsmack | 2 Comments »

Notes on GWT layout with HTML and CSS

Posted by David Chandler on January 4, 2010

For the GWT app I’m currently working on, I’ve decided to lay out as much of the page as possible using static HTML / CSS and attach GWT widgets to their appropriate DIVs. This approach enables more productive design iteration for the main components of the page, as you can use standard design tools (Dreamweaver, etc., but in my case, just the Eclipse editor and Ctrl+R in the browser). Example:

<div id="main">
	<div id="app">
		<div id="roa-message-panel">
		<table>
			<tr>
				<td id="roa-message-text"></td>
			</tr>
		</table>
		</div>
	</div>
</div>
<div id="right">
	<div id="quick_links"></div>
	<div id="admin_links"></div>
</div>

In my main presenter (see GWT for my overall approach), I attach each view to the corresponding DIV as follows:

RootPanel.get(RoaStyles.BODY_PANEL_CONTENT_ID).add(contentContainerPresenter.getDisplay().asWidget());
RootPanel.get(RoaStyles.BODY_PANEL_NAV_ID).add(navPresenter.getDisplay().asWidget());

In the process of working out the kinks in my CSS, I’ve learned much more than I ever wanted to about the differences between IE and every other browser, but the one that really stinks is the way IE calculates width (not that Microsoft didn’t get it right–just that it’s different from other browsers). Fortunately, there’s a great Wikipedia article on IE’s border box model.

First, I had to fix my HTML to get IE 8 out of quirks mode. I ended up going with HTML 4.01 transitional. Here’s a handy reference for the different DOCTYPES: http://www.w3.org/QA/2002/04/valid-dtd-list.html. The Web Developer Toolbar for both IE and Firefox can do HTML validation, which may help elucidate the reason IE is in quirks mode in the first place.

Then, I was able to remove from my HTML the conditional comment referencing an IE-specific stylesheet. Now I have a single HTML and CSS that works on Firefox, Safari, and IE (except 6). I’m contemplating using the CSS3 border-box and various browser-specific properties described in the above Wikipedia article to make them all work like IE 6. The main reason for this is design convenience, as the older IE border-box model includes padding in the width, which keeps things nice and even for fixed-width layouts and lets you specify padding in relative terms (em or percentage) without having to know the corresponding pixel widths. This way, when users change the font size, the whole layout scales up and down evenly.

An excellent book on CSS for design-challenged folks like me is The CSS Anthology by Rachel Andrew.

Browser joy…

Posted in Google Web Toolkit | 5 Comments »

 
Follow

Get every new post delivered to your Inbox.

Join 227 other followers

%d bloggers like this: