TurboManage

David Chandler's Journal of Java Web and Mobile Development

  • David M. Chandler

    Google Cloud Platform Data Engineering Instructor with ROI Training now residing in Colorado with the wife of my youth (31 years). Besides tech, I enjoy aviation and landscape photography.

  • Subscribe

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

    Join 1,120 other subscribers
  • Sleepless Nights…

    March 2011
    S M T W T F S
     12345
    6789101112
    13141516171819
    20212223242526
    2728293031  
  • Blog Stats

    • 1,045,964 hits

Using GWT RequestFactory with Objectify

Posted by David Chandler on March 25, 2011

I’ve put together a little sample project using GWT’s RequestFactory with Objectify. It’s not very sophisticated, but should get you started.

RequestFactory is a relatively new GWT feature that makes it especially easy to create data-oriented services. In this example, we’ll create a RequestFactory service backed by Objectify to manage arbitrary lists of items such as a ToDo list, grocery list, etc.

Domain Model

First, let’s look at our domain classes (ItemList, ListItem, and AppUser):

ItemList.java

package com.turbomanage.listwidget.domain;

import java.util.List;

import javax.persistence.Embedded;

import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity;
import com.turbomanage.listwidget.shared.proxy.ItemListProxy.ListType;

/**
 * POJO that represents a list of items such as a ToDo list.
 * The items are stored as an embedded object.
 */
@Entity
public class ItemList extends DatastoreObject
{
	private String name;
	private Key<AppUser> owner;
	private ListType listType;
	@Embedded
	private List<ListItem> items;
        // Getters and setters omitted
}

DatastoreObject.java:

package com.turbomanage.listwidget.domain;

import javax.persistence.Id;
import javax.persistence.PrePersist;

public class DatastoreObject
{
	@Id
	private Long id;
	private Integer version = 0;

	/**
	 * Auto-increment version # whenever persisted
	 */
	@PrePersist
	void onPersist()
	{
		this.version++;
	}
	// Getters and setters omitted
}

ItemList is an Objectify @Entity. In order for it to work as an entity with RequestFactory, it must have ID and version properties, which are inherited from the DatastoreObject convenience class. Items in the list are stored as an embedded object using the @Embedded JPA annotation supported by Objectify. I could store the items in a separate entity in order to minimize the cost of the query to retrieve just the list names; however, most of the lists will contain only a few items so this serves our example better. Because it is embedded, ListItem is not an entity for purposes of Objectify or RequestFactory, just a simple value type.

ListItem.java:

package com.turbomanage.listwidget.domain;

import java.util.Date;

/**
 * POJO that represents an item in a list.
 */
public class ListItem // POJO
{
	private String itemText;
	private Date dateCreated;
        // Getters and setters omitted
}

AppUser represents a user of our application. It’s an Objectify @Entity and is referenced by the ItemList entity.

package com.turbomanage.listwidget.domain;

import javax.persistence.Entity;

/**
 * An application user, named with a prefix to avoid confusion with GAE User type
 */
@Entity
public class AppUser extends DatastoreObject
{
	private String email;

	public AppUser()
	{
		// No-arg constructor required by Objectify
	}
	// Getters and setters omitted
}

Persistence Layer

The persistence layer on the server side is just a standard ObjectifyDAO that handles all the CRUD operations. It has a static initializer that registers the entity types (ItemList and AppUser–for a later post). ItemListDao extends the generic ObjectifyDao and provides a bit of logic to restrict listAll() to those lists owned by the current user.

ItemListDao.java:

package com.turbomanage.listwidget.server.service;

import java.util.List;

import com.googlecode.objectify.Key;
import com.turbomanage.listwidget.domain.AppUser;
import com.turbomanage.listwidget.domain.ItemList;

public class ItemListDao extends ObjectifyDao<ItemList>
{
	@Override
	public List<ItemList> listAll()
	{
		return this.listAllForUser();
	}

	/**
	 * Wraps put() so as not to return a Key, which RF can't handle
	 *
	 * @param obj
	 */
	public void save(ItemList list)
	{
		AppUser loggedInUser = LoginService.getLoggedInUser();
		list.setOwner(loggedInUser);
		this.put(list);
	}

	public ItemList saveAndReturn(ItemList list)
	{
		AppUser loggedInUser = LoginService.getLoggedInUser();
		list.setOwner(loggedInUser);
		Key<ItemList> key = this.put(list);
		try
		{
			return this.get(key);
		}
		catch (Exception e)
		{
			throw new RuntimeException(e);
		}
	}

	/**
	 * Remove a list. Since items are embedded, they are removed automatically.
	 *
	 * @param list
	 */
	public void removeList(ItemList list)
	{
		this.delete(list);
	}
}

ItemListDao wraps the put() method from ObjectifyDao with a save() method that returns void. This is because put() returns a Key to the newly persisted object, but RequestFactory on the client doesn’t know about about types such as com.googlecode.objectify.Key that exist only on the server (remember that the GWT compiler requires Java source for all types used on the client).

Through the magic of RequestFactory, we can expose ItemListDao directly as a service to be called from the GWT client. This is simpler than GWT-RPC, which requires each service impl to extend RemoteServiceServlet, and much less code than the Command pattern, which typically utilizes Action, Result, and handler classes for each service method (albeit the Command pattern still gives you the most application control over caching, batching, and the like). All that’s needed is a trivial ServiceLocator class which is used by RequestFactory to request an instance of any RequestFactory service.

DaoServiceLocator.java:

package com.turbomanage.listwidget.server.locator;

import com.google.gwt.requestfactory.shared.ServiceLocator;

/**
 * Generic locator service that can be referenced in the @Service annotation
 * for any RequestFactory service stub
 *
 * @author turbomanage
 */
public class DaoServiceLocator implements ServiceLocator {
	public Object getInstance(Class<?> clazz) {
		try {
			return clazz.newInstance();
		} catch (InstantiationException e) {
			throw new RuntimeException(e);
		} catch (IllegalAccessException e) {
			throw new RuntimeException(e);
		}
	}
}

Unlike GWT-RPC, RequestFactory services do not directly implement the service interface. As we will see shortly, this is because RequestFactory service interfaces are intended only for the client side and do not use server-side entity types directly. Services do have to provide all the methods in the client-side interface; however, this is enforced via run-time validation, not at compile time as with GWT-RPC. The loss of some compile-time safety is one disadvantage of the RequestFactory approach.

Proxy Interfaces

When using an object-relational mapping framework like Objectify, entities typically contain annotations and other code from the ORM framework for which the Java source is not available to the GWT compiler. Therefore, the same entity representation cannot be used on both client and server. This problem can be partially solved by using the GWT super-src capability to supply a GWT-safe implementation of server-side classes. Objectify does this for its Key type and annotations so the same annotated domain class can be used on both client and server with GWT-RPC, and Gilead offers something similar for Hibernate.

Another approach to the problem is to use the DTO pattern. However, this has two main disadvantages. First, you now have two different representations of each entity class and must manually keep the code in sync. Second, every service call must copy data from the real entity to its client-safe DTO and vice versa. Again, there are frameworks to help with this, but it’s a pain.

The RequestFactory approach solves the entity / DTO problem in a unique way that overcomes the weaknesses of the classic DTO pattern. With RequestFactory, you declare a proxy interface for each entity. Unlike a classic DTO, the proxy interface exposes only method signatures so there are no implementation details to keep in sync. Furthermore, RequestFactory automatically populates client objects from the corresponding entities on the server and vice versa. Here are our proxy interfaces:

ItemListProxy.java:

package com.turbomanage.listwidget.shared.proxy;

import java.util.List;

import com.google.gwt.requestfactory.shared.ProxyFor;
import com.turbomanage.listwidget.domain.ItemList;
import com.turbomanage.listwidget.server.locator.ObjectifyLocator;

@ProxyFor(value = ItemList.class, locator = ObjectifyLocator.class)
public interface ItemListProxy extends DatastoreObjectProxy
{
	// Note: enums work!
	public enum ListType {NOTES, TODO}

	String getName();
	void setName(String name);
	List<ListItemProxy> getItems();
	ListType getListType();
	AppUserProxy getOwner();
	void setListType(ListType type);
	void setItems(List<ListItemProxy> asList);
}

Because ItemList is a Datastore entity with ID and version properties, ListItemProxy extends the EntityProxy interface. The @ProxyFor annotation specifies the domain type on the server, and the locator attribute specifies an entity locator class (see next section). Note that entities can define their own enum types, and they must be declared in the proxy interface, not the entity itself, in order for the enum type to be available on the client.

ListItemProxy.java:

package com.turbomanage.listwidget.shared.proxy;

import java.util.Date;

import com.google.gwt.requestfactory.shared.ProxyFor;
import com.google.gwt.requestfactory.shared.ValueProxy;
import com.turbomanage.listwidget.domain.ListItem;

@ProxyFor(value = ListItem.class)
public interface ListItemProxy extends ValueProxy
{
	String getItemText();
	void setItemText(String itemText);
	Date getDateCreated();
}

ListItem is not an entity. It doesn’t have ID and version properties because it is embedded within the ItemList entity. Therefore, ListItem extends the ValueProxy interface, which can be used for any “value type” (non-entity).

AppUserProxy.java:

package com.turbomanage.listwidget.shared.proxy;

import com.google.gwt.requestfactory.shared.ProxyFor;
import com.turbomanage.listwidget.domain.AppUser;
import com.turbomanage.listwidget.server.locator.ObjectifyLocator;

@ProxyFor(value=AppUser.class, locator=ObjectifyLocator.class)
public interface AppUserProxy extends DatastoreObjectProxy
{
	String getEmail();
}

Finally, AppUserProxy is an entity so it extends EntityProxy.

Generic Entity Locator

RequestFactory requires each entity to provide four Locator methods. These are used by RequestFactoryServlet to obtain an instance of an entity in order to populate it with property values from the client request before invoking the service method. Fortunately, the required methods can be implemented in a separate class which can be shared by all entities. Here is the generic ObjectifyLocator used with all the entities and specified in the @ProxyFor annotation for each entity proxy above.

package com.turbomanage.listwidget.server.locator;

import com.google.gwt.requestfactory.shared.Locator;
import com.googlecode.objectify.helper.DAOBase;
import com.turbomanage.listwidget.domain.DatastoreObject;

/**
 * Generic @Locator for objects that extend DatastoreObject
 */
public class ObjectifyLocator extends Locator<DatastoreObject, Long>
{
	@Override
	public DatastoreObject create(Class<? extends DatastoreObject> clazz)
	{
		try
		{
			return clazz.newInstance();
		} catch (InstantiationException e)
		{
		  throw new RuntimeException(e);
		} catch (IllegalAccessException e)
		{
			throw new RuntimeException(e);
		}
	}

	@Override
	public DatastoreObject find(Class<? extends DatastoreObject> clazz, Long id)
	{
		DAOBase daoBase = new DAOBase();
		return daoBase.ofy().find(clazz, id);
	}

	@Override
	public Class<DatastoreObject> getDomainType()
	{
		// Never called
		return null;
	}

	@Override
	public Long getId(DatastoreObject domainObject)
	{
		return domainObject.getId();
	}

	@Override
	public Class<Long> getIdType()
	{
		return Long.class;
	}

	@Override
	public Object getVersion(DatastoreObject domainObject)
	{
		return domainObject.getVersion();
	}
}

ObjectifyLocator makes use of the fact that all the entities extend DatastoreObject, which defines an ID property of type Long. If your entities don’t extend a common base class or interface, you can still use a generic entity locator by using reflection to call the getId() and getVersion() methods on the underlying entity.

RequestFactory Interface

The heart of RequestFactory is the interface in which you define your services. Each service stub is an interface that extends RequestContext having one or more service methods, and for each service you define an accessor method in the main RequestFactory interface. If you have a small number of services, you can define everything in one class as below.

ListWidgetRequestFactory.java:

package com.turbomanage.listwidget.shared.service;

import java.util.List;

import com.google.gwt.requestfactory.shared.Request;
import com.google.gwt.requestfactory.shared.RequestContext;
import com.google.gwt.requestfactory.shared.RequestFactory;
import com.google.gwt.requestfactory.shared.Service;
import com.turbomanage.listwidget.server.locator.DaoServiceLocator;
import com.turbomanage.listwidget.server.service.ItemListDao;
import com.turbomanage.listwidget.shared.proxy.ItemListProxy;

public interface ListwidgetRequestFactory extends RequestFactory
{
	/**
	 * Service stub for methods in ItemListDao
	 * 
	 * TODO Enhance RequestFactory to enable service stubs to extend a base interface
	 * so we don't have to repeat methods from the base ObjectifyDao in each stub
	 */
	@Service(value = ItemListDao.class, locator = DaoServiceLocator.class)
	interface ItemListRequestContext extends RequestContext
	{
		Request<List<ItemListProxy>> listAll();
		Request<Void> save(ItemListProxy list);
		Request<ItemListProxy> saveAndReturn(ItemListProxy newList);
		Request<Void> removeList(ItemListProxy list);
	}
	
	ItemListRequestContext itemListRequest();
}

ItemListRequestContext is a service stub and itemListRequest() is its accessor method. The @Service annotation specifies the implementation class on the server, and the locator attribute specifies a ServiceLocator class from which an instance of the service may be obtained–in this case, our generic DaoServiceLocator shown above.

Service method argument and return types use the proxy interfaces, not the real entity types. Server implementations use the real types and RequestFactory automatically maps between the two. In addition, all service methods return a Request object parameterized with the real return type (or rather, its proxy type). Server implementations return the real entity type directly. Services that have no return type should return Request in the client interface. The disparity between entity types on client and server prevents services from directly implementing the service interface as with GWT-RPC.

Again, note that we’re able to use a DAO directly as our service implementation. Furthermore, as of GWT 2.3, a service can be inherited from a base class (in this case, ObjectifyDao) as per this issue. This is a really powerful capability, as it allows you to implement the common CRUD operations in a single class on the server with no boilerplate code for each service impl. However, on the client side, service stubs cannot yet extend a base interface other than RequestContext, so each service stub must declare methods inherited from a base class as well as those implemented in the service class.

Wiring and Startup

Starting up your RequestFactory service layer in the client is as simple as

private final ListwidgetRequestFactory rf = GWT.create(ListwidgetRequestFactory.class);

In the sample app, I’ve done this in a factory class so it will be available to all presenter classes.

Using RequestFactory

First, let’s look at creating a new ItemList and persisting it to the server. Here is an excerpt from ListsActivity which creates a new instance of ItemListProxy and sends it to the server:

	public void persistList(String listName)
	{
		final ListwidgetRequestFactory rf = this.clientFactory
				.getRequestFactory();
		ItemListRequestContext reqCtx = rf.itemListRequest();
		final ItemListProxy newList = reqCtx.create(ItemListProxy.class);
		newList.setName(listName);
		newList.setListType(ListType.TODO);
		reqCtx.saveAndReturn(newList).fire(new Receiver<ItemListProxy>()
		{
			@Override
			public void onSuccess(final ItemListProxy savedList)
			{
				// Refresh table
				listDataProvider.getData();
				...
			}
		});
	}

Since ItemListProxy is an interface, how can we get an instance of it? By calling the create() method inherited from RequestContext. RequestFactory uses the new AutoBeans framework in GWT, which can be used to create an instance of any interface. Using AutoBeans, RequestFactory registers a visitor object for each setter in order to track all changes to the instance. This is what allows RequestFactory to send only changes to the server with each request.

Once we have an instance of our proxy, we can populate it using its setter methods and call the service method, which returns a Request but doesn’t actually do anything yet. In order to invoke the method on the server, we must fire() the Request and pass it a new Receiver object to handle the callback. Receiver is analogous to GWT-RPC’s AsyncCallback, but is hooked in via the return type (Request) instead of an additional service method argument as with GWT-RPC.

Now let’s look at a query to retrieve all ItemLists. Here is the getData() method from the AsyncDataProvider in ListsActivity that updates the CellTable showing all lists.

		private void getData()
		{
			logger.warning("getting data");
			// To retrieve relations and value types, use .with()
			Request<List<ItemListProxy>> findAllReq = rf.itemListRequest()
					.listAll().with("owner");
			// Receiver specifies return type
			findAllReq.fire(new Receiver<List<ItemListProxy>>()
			{
				@Override
				public void onSuccess(List<ItemListProxy> response)
				{
					updateRowCount(response.size(), true);
					updateRowData(0, response);
				}
			});
		}

The listAll() service call is similar to saveAndReturn() above with one important addition: the with(“owner”) method. This retrieves the related AppUser entity (using its proxy type, of course). The with() method specifies the bean-style name of a property to be populated with the request and is required to retrieve entity properties that are themselves proxy types (EntityType or ValueType), as RequestFactory does not retrieve related entities automatically. You can pass a comma-separated list of property names (“owner,creator,…”) and use dot notation to specify nested objects (“owner.address.city”).

Note: this post isn’t about CellTables, but observe that both updateRowData() and updateRowCount() are required to properly update the table when using an AsyncDataProvider.

Finally, let’s look at an example of updating an object previously returned from the server. Here is an excerpt from EditListActivity:

		public void update(int index, ListItemProxy item, final String newText)
		{
			ItemListRequestContext reqCtx = clientFactory.getRequestFactory()
					.itemListRequest();
			ListItemProxy editItem = reqCtx.edit(item);
			editItem.setItemText(newText);
			editList = reqCtx.edit(editList);
			editList.getItems().set(index, editItem);
			// Save the list since items are embedded
			reqCtx.save(editList).fire(new Receiver<Void>()
			{
				@Override
				public void onSuccess(Void response)
				{
					eventBus.fireEvent(new MessageEvent(newText + " updated",
							MessageType.INFO));
				}
			});
		}

This update() method changes the text of an item in an ItemList and takes an ItemListProxy argument that represents an ItemList previously returned from the server. Before any changes can be made to it, we must call RequestContext.edit() in order to make it mutable and allow RequestFactory to track changes. Then we can getItems(), set the new item text, and save the modified ItemList.

Summary

The combination of RequestFactory and Objectify makes it possible to write a service layer for GWT and App Engine with very little boilerplate code. Hopefully this will help you get started. Comments welcome, as well as contributions to the sample project referenced throughout. Enjoy!

73 Responses to “Using GWT RequestFactory with Objectify”

  1. @HandstandSam said

    Thanks for sharing this. Just read “Furthermore, the EntityProxy interface enables RequestFactory to compute and send only changes (“deltas”) to the server. Here’s the EntityProxy corresponding to the Employee shown above.” – Will definitely have to check that out.

  2. Peter Knego said

    I tried RequestFactory for our own project but decided to abandon it for the following reasons:

    1. RequestFactory can not be used to transfer Maps. Old plain GWT-RPC can do it.

    2. You have to manually write proxy interface for every entity. This is like writing DTOs – there is a lot of duplicate code.

    3. When writing proxy interfaces, there is no type-safety: for every property in entity there is corresponding method in proxy interface. This has to be managed manually. When project evolves there is bound to be changes to entities. Developers have to keep this in mind and sync code manually, which is error prone.

    In GWT there is now a new option to do custom serialization without DTOs or super-sourcing: CustomFieldSerializer.

    I used this to serialize datastore’s Entity (used by objectigfy’s @PrePersist/@PostLoad)

    • Thanks, Peter, very helpful summary. I am likewise concerned about lack of type safety and hopeful that it will be addressed through tooling soon.

      • Andy said

        This is a bit leftfield but I use a hybrid approach, JSON over gwt-rpc utilising AutoBeanCodex etc.

        I have interface DTO’s but ensure my entities implement them to at least go some way to protecting code state.

        Due to the use of AutoBeanFactory I can serialize and deserialize on both sides with little effort. The upside is that it is even less boiler plate code than RequestFactory. I simply have generic service objects that take my json object graph and then deserialize. If I need to map for JPA i use Dozer.

      • Interesting, Andy. Would love to see a blog post on this.

    • You could minimize the type safety problems by writing your own generator. I use xText to generate both the entity classes and the proxy interfaces (as well as other boilerplate stuff for gwt/RF). This helps a lot with type safety and such, though it has its own problems (more tools to learn and update among others). But at least never have to worry about type safety.

      BTW, great post, David! I am just beginning to re-factor my code from using JDO to Objectify and this article really helps a lot.

      Best, Stefan

      • Thanks, Stefan. FYI, Google Plugin for Eclipse 2.4 beta has some tooling around RequestFactory to improve type safety, and GWT trunk (tools, actually) has a Java annotation processor (requestfactory-apt.jar) that you can add to Eclipse to provide a great deal of type safety. I’ll write up something on this once it’s released and generally available.

  3. Chuck L said

    I recently watched the Google I/O presentation you delivered on this exact topic. Thank you for the talk and the examples. I’ve looked through the examples and a few others provided by the GWT team and while some of them handle Authentication, none of them handle ACLs. I’m still trying to wrap my head around all the background magic of RF, but I’m not seeing a point where I can intercept or reject operations based on the Authenticated user. I can user Filters to force authentication, but I can’t limit, once a person is authenticated which fields or operations they can perform.

    Any guidance?

    • The most straightforward way is for each of your RF service impls to check permissions / enforce ACLs. If you wanted to check ACLs before invoking any service method, you could add a ServiceLayerDecorator and implement the invoke method to check ACLs first. For an example of a custom ServiceLayerDecorator, see the listwidget sample at r25, shortlink here: http://goo.gl/61HZ. Note that you have to extend RequestFactoryServlet (also shown in the sample) to wire in your own SLD.

      Also, there was a thread on the GWT group regarding the use of ServiceLayerDecorator to wire in Spring, which you could perhaps use to incorporate Spring Security.

  4. Chuck L said

    Thanks for the guidance! The provided Shortlink is showing as disabled by Google.

  5. Sam Edge said

    Excellent guide on the glue between RF and Objectify. So far it has served me well wiring my project 🙂
    You might like to update some of the source codes. It wasnt till I checked the SVN I realized DatastoreObjectProxy was a wrapper that you’ve removed. Thanks for the article much appreciated

    As displayed of 14th may 2010:

    package com.turbomanage.listwidget.shared.proxy;

    import java.util.List;

    import com.google.gwt.requestfactory.shared.ProxyFor;
    import com.turbomanage.listwidget.domain.ItemList;
    import com.turbomanage.listwidget.server.locator.ObjectifyLocator;

    @ProxyFor(value = ItemList.class, locator = ObjectifyLocator.class)
    public interface ItemListProxy extends DatastoreObjectProxy
    {
    // Note: enums work!
    public enum ListType {NOTES, TODO}

    String getName();
    void setName(String name);
    List getItems();
    ListType getListType();
    AppUserProxy getOwner();
    void setListType(ListType type);
    void setItems(List asList);
    }

    revision 26:

    package com.turbomanage.listwidget.shared.proxy;

    import java.util.Date;

    import com.google.gwt.requestfactory.shared.ProxyFor;
    import com.google.gwt.requestfactory.shared.ValueProxy;
    import com.turbomanage.listwidget.domain.ListItem;

    /**
    * Represents an item in a list
    *
    * @author turbomanage
    */
    @ProxyFor(ListItem.class)
    public interface ListItemProxy extends ValueProxy
    {
    String getItemText();
    void setItemText(String itemText);
    Date getDateCreated();
    }

  6. Cain Wong said

    David,
    Thank you for this posting. It has been very helpful in getting me ramped up with RequestFactory.

    I’ve found though that I cannot use as many of the BaseObjectifyDao methods directly as I was expecting without getting “did not pass validation” errors.

    For example, I have a dao for a class called PO, PODao that extends BaseObjectifyDao. In the corresponding service (context) I declared a method like this:
    Request get(Long id);

    Since the “get” method is implemented in the Base, I wouldn’t expect to need to implement it in PODao. But without implementing it, I get the following error:
    [ERROR] Method overloads found in type com.cloudsherpas.server.dao.PODao named get:
    java.lang.Object get(com.googlecode.objectify.Key )
    java.lang.Object get(java.lang.Long )

    Since it seemed to have a problem with overloading, I next tried to just rename one of the methods:
    public T getById(Long id)
    … and I updated the service interface to reflect the change:
    Request getById(Long id);

    This resulted in a different error:
    [ERROR] Could not find matching method in com.cloudsherpas.server.dao.PODao.
    Possible matches:
    java.lang.Object getById(java.lang.Long)

    So, it seems that the framework is not smart enough to determine the return type.

    I finally got it working by creating a wrapper to my “get” function:
    public PO getPO(Long id){
    PO po = null;
    try {
    po = this.get(id);
    } catch (EntityNotFoundException e) {
    //do nothing return null
    }
    return po;
    }
    … and updating the service interface:
    Request getPO(Long id);

    Does this seem right to you? Is there a way to get the framework to pick-up on the return types?

    • Cain Wong said

      Doh! Apparently my object typing has been interpreted as bad HTML and isn’t displaying. All the types in angled brackets were stripped out of my code samples. In my actual code the return “Request” objects are typed with the appropriate Proxy classes.

      • This is the expected behavior. If your DAO returns a PO object, the service stub must return Request<PO>. Return types should be specialized in a concrete class like PODao as in your working example.

      • Manu said

        I am interested in this topic too. Your article is very helpful. In your example you wrote a lot of methods in the generic ObjectifyDao service. I did the same in my test project. But then if I override some methods of the ObjectifyDao in the specific PersonDao service I get the overload error:

        [ERROR] Method overloads found in type com.testapp.server.service.PersonDao named saveAndReturn:
        com.testapp.domain.Person saveAndReturn(com.testapp.domain.Person )
        java.lang.Object saveAndReturn(java.lang.Object )

        What’s wrong with my code?

        This is the service stub:

        @Service(value = PersonDao.class, locator = DaoServiceLocator.class)
        interface PersonRequestContext extends RequestContext {
        Request saveAndReturn(PersonProxy p);
        }

        This is the method:

        @Override
        public Person saveAndReturn(Person p) {
        return super.saveAndReturn(p);
        }

        So I can’t reuse base methods because the service stub needs to call the specific object methods. At this point I can’t understand why you wrote all these base methods if they can’t be used directly and also they can’t be called from the specific dao class because of the overload error.

        Could you please explain me this problem? Thanks a lot!

        Btw I’m using this package for the RequestFactory: com.google.web.bindery.requestfactory.

      • @Manu, what’s the signature of the saveAndReturn method in your ObjectifyDao? Are you using the generic T in the return type?

      • Manu said

        @David Yes, I’m using the generic T type. Anyway I solved by changing the name of the specific method for the Person object, which calls the super one. Is it the right way?

      • That works, but you shouldn’t have to rename the method. I can reproduce this with the saveAndReturn method. Looks like it might be a bug in GWT’s RequestFactoryInterfaceValidator, researching…

      • Manu said

        @David Thanks for the feedback, please just keep me informed if you find something more about this issue.

  7. littlewing said

    Hello
    Thank you very much for this article and the code hosted on coe.google.com
    Both helped me !

    Regards
    Alexandre Touret

  8. Cain Wong said

    I had the same experience. I wasn’t able to overload any method, but instead needed to rename the methods.

  9. Maxime Mularz said

    Hi David,

    Thanks for your post, it was for me so helpful

    Kind Regards
    Maxime Mularz

  10. David,

    I really enjoyed your post, it made a few things more clear for me. I currently have an issue with ValueProxies, and was wondering if you could point me in the right direction…

    We use Hibernate & Spring, so we are using Locators to bridge the gap. But I have a piece of UI that needs to be able to get and send a number of ValueProxies. We need to use ValueProxies in this case as initializing the potentially large list of EntityProxies would be prohibitive, however, we can get the list of ValueProxies cheaply and just update those items that are needed…

    To make a long story short, is there a way to get & send ValueProxies?? I can’t find a single example, however a few people mentioned being able to use ValueProxies and RF in place of GWT-RPC, which I would much rather do as we use EntityProxies everywhere else… If I have to use GWT-RPC, I’ll have to make 2 calls that could not be contained inside a transaction.

    At the moment, as soon as I try to make the request ( request.getLinkedEntitiesFor(entityKey).fire(new Receiver().fire() {…} ) it throws an UnbrellaException.. (I’m beginning to hate that exception) at: at com.google.web.bindery.requestfactory.shared.impl.AbstractRequestContext.fail(AbstractRequestContext.java:588)

    I can send code if needed… or if you have any idea where I should look, your help would be greatly appreciated.

    /christien

    • Thanks, Christien,

      To nest fire() calls, you need to fire the second one within the onSuccess() method of the Receiver for the first.

      Have a look at RequestFactoryTest in the GWT src for examples of nested fire() as well as using ValueProxys.

      • Hi David,

        I don’t believe I’m calling the fire in a nested way.. what happens is this:

        * I have an editor that opens in a new DialogBox,
        * When the editor loads (it already has a proxy at this point), it loads a few sub editors, most of whom are tied to the proxy passed in.
        * One sub editor, however needs to load data from a different source & asks for a set of ValueProxies via {{ request.getLinkedEntitiesFor(entityKey).fire(new Receiver().fire() {…} }}. We could load them as entities, however they would be overkill and would load far too much data (thanks to how Hibernate is mapped — changing the mapping is not an option at this point).
        * Everything except the request for the ValueProxies loads fine. The request fails with a ServerError.

        I looked in the RequestFactoryTest.. all its ValueProxies are loaded or modified via an EntityProxy (unless I missed one…).

        I’m truly mind-boggled at how to proceed forward without completely rewriting these proxies…

      • Use .with() to retrieve related ValueProxies. By nesting, I was referring to fire(new Receiver().fire()). I’m surprised that even compiles, as you fire() a RequestContext, not a Receiver.

      • I don’t think I’m explaining things right here…

        * I don’t want to get a ValueProxy via an Entity proxy.. I want to make a request for a ValueProxy alone.. I them want to modify it and send it back via another request.
        * I do not want to get an EntityProxy as part of this request… We are using EntityProxies (with embeded ValueProxies (and other EntityProxies) already in numerous parts of the application with no issues.

      • See RequestFactoryTest.testValueObjectReturnedFromRequestIsImmutable(). It makes a request for a ValueProxy and edits it. Doesn’t send it back, but I don’t know why that would be a problem.

  11. Bademus said

    it would be nice to see example of editing List with EditorFamework. I’m stuck with this problem.
    Thanks.

  12. Micah said

    It seems this article glazes over the code found in ObjectifyDao as if it’s expected the reader already understands it’s purpose. Unfortunately, I have no idea what is going on in that class and it seems to be a core component of this entire example yet specific enough to the example that I can’t just copy/paste it into my project. Can you write either expand this article or write up a new article that details the workings of that class?

  13. Maxime Mularz said

    Hi David,

    I’ve got questions about RequestFactory. How can I do with a generic Class ?

    public class Person

    public interface PersonProxy or

    Server Side :

    public class PersonDao extends ObjectifyDao<Person>

    I don’t know how to start ? I’ve this error message : Invalid Request parameterization T extends java.lang.Object

    Thanks for you reply,
    Regards
    Maxime

    • Maxime,

      The entity class itself (Person) cannot be generic, only the service (ObjectifyDao). On the client, you still need a RequestContext interface for each concrete service. This is changing in 2.4, which will allow service interfaces on the client to extend a generic base class also. Is that what you’re asking?

  14. Asaf Shakarchi said

    Nice job,

    But current example shows a list of items which is embedded,
    Is there any chance for getting some info / example with Objectify,RF,Editors and relationships via keys ?

    I still don’t understand whether Objectify Key should be exposed as KeyProxy in RF proxies,

    Assuming I have a very simple relationships such as:

    public class Client {
    @Id Long id;
    String name;
    }

    public class Person {
    @Id Long id;
    Key client;
    }

    Just for the sake of the example, assuming I want to have an Editor based view for Person with a ValueListBox which contains the selection of one of the Clients,

    In the approach of exposing ofy’s Key as KeyProxy, when fetching the ‘Client’ list to fill the ValueListBox, the ClientProxy instances doesn’t have the key (as models only contains their ID and Keys of relationships),

    So Client key should be generated on client side somehow ? if so how?

    Another issue is that ValueListBox for example can only process one type of entity for its ProxyRenderer,SimpleKeyProvider methods, so a ValueListBox is a problem because the renderer needs to be based on the ClientProxy for having full details.

    Thanks !

  15. Ben said

    Hi david !

    You did a great job with this example. Very helpfull !

    I encountered a problem while deploying to GAE, and would like to get your feedback.

    Here is the stackTrace that would happen after a long upload request to Blobstore (nothing requestFactory related) e.g all GAE Instances would have been shut down prior to this call.

    com.google.web.bindery.requestfactory.server.RequestFactoryServlet doPost: Unexpected error
    java.lang.IllegalArgumentException: Class ‘myapp.server.domain.Item’ was not registered
    at com.googlecode.objectify.ObjectifyFactory.getMetadataForClassName(ObjectifyFactory.java:241)
    at com.googlecode.objectify.ObjectifyFactory.getMetadata(ObjectifyFactory.java:211)
    at com.googlecode.objectify.impl.ObjectifyImpl.find(ObjectifyImpl.java:160)
    at com.googlecode.objectify.impl.ObjectifyImpl.find(ObjectifyImpl.java:174)
    at myapp.server.locator.ObjectifyLocator.find(ObjectifyLocator.java:26)
    at myapp.server.locator.ObjectifyLocator.find(ObjectifyLocator.java:1)
    at com.google.web.bindery.requestfactory.server.LocatorServiceLayer.doLoadDomainObject(LocatorServiceLayer.java:210)
    at com.google.web.bindery.requestfactory.server.LocatorServiceLayer.loadDomainObject(LocatorServiceLayer.java:95)
    at com.google.web.bindery.requestfactory.server.ServiceLayerDecorator.loadDomainObject(ServiceLayerDecorator.java:120)
    at com.google.web.bindery.requestfactory.server.ReflectiveServiceLayer.loadDomainObjects(ReflectiveServiceLayer.java:225)
    at com.google.web.bindery.requestfactory.server.ServiceLayerDecorator.loadDomainObjects(ServiceLayerDecorator.java:126)
    at com.google.web.bindery.requestfactory.server.ServiceLayerDecorator.loadDomainObjects(ServiceLayerDecorator.java:126)
    at com.google.web.bindery.requestfactory.server.RequestState.getBeansForIds(RequestState.java:272)
    at com.google.web.bindery.requestfactory.server.RequestState.getBeansForPayload(RequestState.java:148)
    at com.google.web.bindery.requestfactory.server.SimpleRequestProcessor.processOperationMessages(SimpleRequestProcessor.java:471)
    at com.google.web.bindery.requestfactory.server.SimpleRequestProcessor.process(SimpleRequestProcessor.java:202)
    at com.google.web.bindery.requestfactory.server.SimpleRequestProcessor.process(SimpleRequestProcessor.java:125)
    at com.google.web.bindery.requestfactory.server.RequestFactoryServlet.doPost(RequestFactoryServlet.java:118)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:637)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
    at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:511)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1166)
    at com.google.apphosting.utils.servlet.ParseBlobUploadFilter.doFilter(ParseBlobUploadFilter.java:97)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
    at com.google.apphosting.runtime.jetty.SaveSessionFilter.doFilter(SaveSessionFilter.java:35)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
    at com.google.apphosting.utils.servlet.TransactionCleanupFilter.doFilter(TransactionCleanupFilter.java:43)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
    at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:388)
    at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
    at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:182)
    at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:765)
    at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:418)
    at com.google.apphosting.runtime.jetty.AppVersionHandlerMap.handle(AppVersionHandlerMap.java:249)
    at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
    at org.mortbay.jetty.Server.handle(Server.java:326)
    at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:542)
    at org.mortbay.jetty.HttpConnection$RequestHandler.headerComplete(HttpConnection.java:923)
    at com.google.apphosting.runtime.jetty.RpcRequestParser.parseAvailable(RpcRequestParser.java:76)
    at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:404)
    at com.google.apphosting.runtime.jetty.JettyServletEngineAdapter.serviceRequest(JettyServletEngineAdapter.java:135)
    at com.google.apphosting.runtime.JavaRuntime.handleRequest(JavaRuntime.java:262)
    at com.google.apphosting.base.RuntimePb$EvaluationRuntime$2.handleRequest(RuntimePb.java:9819)
    at com.google.net.rpc.impl.RpcUtil.handleRequest(RpcUtil.java:445)
    at com.google.net.rpc.impl.RpcUtil.runRpcInApplication(RpcUtil.java:414)
    at com.google.net.rpc.impl.Server$RpcTask.runInContext(Server.java:579)
    at com.google.tracing.TraceContext$TraceContextRunnable$1.run(TraceContext.java:454)
    at com.google.tracing.TraceContext.runInContext(TraceContext.java:694)
    at com.google.tracing.TraceContext$AbstractTraceContextCallback.runInInheritedContextNoUnref(TraceContext.java:332)
    at com.google.tracing.TraceContext$AbstractTraceContextCallback.runInInheritedContext(TraceContext.java:324)
    at com.google.tracing.TraceContext$TraceContextRunnable.run(TraceContext.java:452)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
    at java.lang.Thread.run(Thread.java:636)

    It seems that the ObjectifDao class is not called prior to the Locator being called.
    I found out, as a workaround, that adding the following to the ObjectifyLocator solves my problem:

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

    Doesn’t seem totaly satisfying to me…And double the registering done in ObjectifyDao. Have I missed something somewhere ?

    Thanks for the feedback and again for the post.

  16. Micah said

    The Objectify documentation actually talks a bit about the registering in statics issue and I recommend checking it out. This is of particular importance if you are using a generic locator because it will be a common case where you query for a datastore object before that object has been referenced in your code. If you do this, your class will not have loaded and your statics won’t be initialized yet.

    Personally, I recommend finding some central entry point into your server side and registering all of your objects in a static there. For request factory, the easiest way to have a guaranteed entry point is to override the RequestFactory servlet (I don’t remember the exact name at the moment) and reference that instead of the built-in RequestFactory servlet in your webapp.xml. Then anytime a RequestFactory request hits the server that servlet class will be called and you can be sure that by the time your objectify code is hit all of your Objectify classes will have been registered.

  17. […] generic entity locator you can use with JPA. But David Chandler has written a sample project “RequestFactory with Objectify“. Perhaps this is also interesting for […]

  18. Hi David,

    This is Vinay (from Bell South days). Was very happy to see your picture and the article.

    Thanks,

    Vinay

  19. matt kesinger said

    New to gwt, just downloaded list-widget sample app. Attempting to run it I get the message: Class com.turbomanage.listwidget.domain.AppUser has application-identity and no objectid-class specified yet has 0 primary key fields.

    I see the ID is being inherited from what you call a convenience class. Any idea why this is giving an error?

  20. Andrey said

    David,

    Thanks for this very helpful tutorial!

    I was wondering what would be the right way to map polymorphism support in Objectify to RequestFactory framework (if it is possible at all). I.e. how to write proxies/request services for the following set of polymorphic domain objects:

    @Entity class Shape extends DatastoreObject {…}
    @Subclass class Circle extends Shape { … }
    @Subclass class Rectangle extends Shape { … }

    Objectify DAO can return a list of Shape objects that would be a mix of Circles and Rectangles. I’m a bit stuck on how to create the proxy interface and the request service class for that.

    Thanks!
    Andrey

  21. Eugene said

    I use RF as main CRUD client-server interface, but I also use Atmosphere framework as server-push mechanism.
    My aim is multi-client application. All clients must receive notification about changes made by anyone. The communication looks like this:

    @ProxyFor(TestEntity.class)
    TestProxy extends EntityProxy

    | |
    TestProxy obj |——–RF update entity——>|— RequestFactory Servlet
    | | |
    | | |
    | | |
    | | |
    AutoBean obj |<–Atmosphere notification—|<–| Atmosphere Servlet creates AutoBean
    | with entity to all client |
    | |
    | |
    | |
    | |
    | |
    | |
    client server

    But RF adds to AutoBean some kind of additional information. Therefore I can’t use autobaen received from Atmosphere in next communication by RF channel.
    How I can create autobean as RF does it?

  22. Jai said

    Hi
    I am new to GWT, Request factory & Objectify. I need to work on a project with this technologies now. I tried some samples which i could not succeed. I am facing problem with the database connection. I am trying to connect SQL Server 2005 database could not succeed. Can anyone please help me and sample code for connection to sqlserver 2005.

    Thanks in advance
    Jai

  23. mo said

    Hi David,

    Thank you very much for your work on GWT and all the help you provide.

    I was wondering if it is possible to update the listwidget project so that it works with objectify 3.1 and gwt 4.2?
    I’m having many issues to try and make it work.

    Cheers,
    Mo

  24. George said

    Trying with latest GWT/RequestFactory and I’m unable to supply an @Id in a superclass, the datanucleus enhancer does not find it. Is there a way around this or do I just need to abandon the superclass?

    Error:
    SEVERE: DataNucleus Enhancer completed with an error. Please review the enhancer log for full details. Some classes may have been enhanced but some caused errors
    Class has application-identity and no objectid-class specified yet has 0 primary key fields. Unable to use SingleFieldIdentity.

    • Hi George,

      Objectify doesn’t use Datanucleus at all. You can turn off the Datanucleus enhancer for your project (project properties | Builders). Having said that, I can’t remember which versions of Objectify support this (if any), but the docs should have the answer.

  25. Chris said

    Hi David,
    This is a very useful example, thank you!

    I have a question regarding the implementation of a pattern which allows for paged results. I would like to return a generic wrapper that includes cursor and number found. However, creating a result wrapper which includes these properties and a generic list will not work without creating a specific wrapper proxy for each list type I want to return. Is this an appropriate design? If not, what is the suggested pattern for using RequestFactory with paging?

    Thanks very much,
    Chris

    • Hi Chris,

      You won’t be able to pass a Cursor directly to GWT, but you could pass a reference to the cursor using App Engine’s Cursor.toWebSafeString(). In that case, you could use 2 different service methods like

      CursorResult runQuery(queryParams…); List getNext(cursorId, numRows);

      This may help: http://stackoverflow.com/questions/10988490/using-searching-asyncdataprovider-with-objectify-google-app-engine

      /dmc

      • Chris said

        Thanks very much for the quick response, David.

        Given the design of RequestFactory, your recommendation seems like one of the few ways to do what I want. The double-query feels awkward but I guess there isn’t a better solution right now.

        On this subject, can you explain the reason for not supporting a request wrapper? Is my desire to use one discordant with the best practices for querying this type of dataset? Or is the double-query not a big deal?

      • Hi Chris,

        Result wrappers are supported, just not result wrappers with stateful, non-serializable objects like cursors. A database cursor exists only on the server, and RequestFactory doesn’t know about cursor internals so it can’t provide automatic paging (GWT needs the Java sources for any server-side stuff to compile to JS, which is one of the reasons for proxy objects in RF). In this case, two service methods are necessary because you’re requesting different things. The first call runs the query and returns a cursor ID. Subsequent calls using the second service method pass the cursor ID and retrieve the next n rows from the cursor. As long as the cursor is still in memory on the server, you’re not double querying.

        HTH, /dmc

      • Chris said

        Hi David,
        Thanks for clarifying the difference between the non-serializable form of the cursor and the webSafeString method which provides a cursor ID. Where I am unclear is how one can retrieve a cursor without iterating through the results first.

        If I understand your recommendation, you are saying that one should:

        * make one request that contains whatever parameters one wants to use on the actual query.
        – parameter: Presumably this includes the page size or limit
        – parameter: The previous cursor also seems necessary otherwise we are doing an offset instead of a limit
        – returns: on the first call it would be null but have value on each subsequent call
        * receive the result (which is the cursor ID) and make another query that retrieves the actual data.
        – parameter: This query includes the previously retrieved cursor ID. The presumption is that the cursor ID marks the first record from which the query will continue.
        – parameter: limit or end cursor
        – returns: the list of values

        The issue that I am having is with the first query. That query will always return null unless the query’s iterator is traversed via it’s next() method. If a DataStore read is incurred on each call to next, then I think that the only thing that differs between my two calls is that I am getting the cursor from one but the list from the other.

        It seems that the most efficient way to use a cursor is to pass it back with its relevant results list. Since I can’t do that, the only other way that I can think to hold onto that cursor is to stick it in memcache or a session var. I have been looking at this for a couple of weeks and waiting for the Aha! moment…it is yet to arrive…

        By the way, thanks again for taking the time to work through this with me.

        Regards,
        Chris

      • Chris, I think you will have to hold on to the cursor in session. As far as Objectify particulars of next / offset / limit / cursor, you might try the Objectify discussion group.

        Cheers! /dmc

      • Chris said

        Thanks again, David. I am pretty clear on Objectify. It’s RequestFactory that is causing my confusion.

        My previous GWT-RPC based solution used a wrapper that contained a parameterized list with a websafe cursor and the number-found. This was in an AsyncDataProvider that was attached to a cell list and provided all that was needed to implement paging on a large dataset.

        At any rate, I think that most of my confusion lies with incorrect assumptions that I have made on RequestFactory which I am slowly unraveling. Thanks for this great post!

        All the best,
        Chris

      • Hi Chris,

        Out of curiosity, what motivates you to switch from GWT-RPC to RF? Although I like it on the server as I’ve demonstrated in this article, I’ve found the RF API to be rather intrusive on the client, as any code that wants to create a new instance of a proxy object has to obtain a reference to the RequestFactory to do so. This means RF shows up in presenters (in MVP arch), not just the service layer. In addition, the whole mechanism around history & ids I found to be rather disjoint. Despite this article, I’ve not yet migrated my GWT-RPC production code to RF and probably won’t.

      • Chris said

        Hi David,
        My biggest motivator is so that I may understand how it works. I have not committed to it but have spend considerable time implementing a subset of my desired functionality. It was my hope that I could reduce some of my custom-built helper functions in favor of something that is maintained by Google engineers. That said, there are some functions that are very compelling. I have started working with the ServiceLayerDecorator and which I have been combining with annotations to provide method level security. While going through this, it occurred to me that this is not prevented by my current framework…I just hadn’t thought of it. I also like the ability to extend the RequestTransport. I think there are some interesting things that can be done with that.

        At the end, the thing that will keep me from advancing in RequestFactory is the paging issue that started this thread. I cannot replicate the generic paging wrapper that I developed using GWT-RPC. This means that I cannot use a lot of the implementations that I have already developed that rely on them. I also agree with your note on history and ids. This doesn’t easily plug into my solution. As someone who does not give up easily, I will probably keep trying but for now, I will probably shelve it in favor of my current solution and look for opportunities to simplify my current solution.

        Best,
        Chris

  26. Simon Shaw said

    Great article, however after getting the listwidget project from svn and attempting to build mvn -X gwt:run I get the following error:

    \Dev\listwidget\web\target\listwidget-1.0-SNAPSHOT\WEB-INF\classes\com\turbomanage\listwidget\shared\service\ListwidgetRequestFactoryDeobfuscatorBuilder.java:[7,13] duplicate class: com.turbomanage.listwidget.shared.service.ListwidgetRequestFactoryDeobfuscatorBuilder

    I am using:
    Apache Maven 3.0.2 (r1056850; 2011-01-09 02:58:10+0200)
    Java version: 1.6.0_32, vendor: Sun Microsystems Inc.
    GWT: 2.5.0
    GAE: 1.7.3

  27. Abel Barbosa said

    Hi David,

    Thank you for the post. Is probably clear enough for most of people. Unfortunately I wasn’t able to replicate it. I keep getting a NullPointerException when I try to run the following code:

    //Validate name
    //TODO warn user if invalid
    if (txtName==null||txtName.getText().equals(“”)){return;}
    String n = txtName.getText();

    //Validate fiscal number
    //TODO warn user if invalid
    if (txtFiscalNumber==null||txtFiscalNumber.getText().equals(“”)){return;}
    String fn = txtFiscalNumber.getText();

    BeingRequestContext reqCtx = rf.beingRequestContext();
    final BeingProxy beingProxy = reqCtx.create(BeingProxy.class);
    beingProxy.setName(n);
    beingProxy.setFiscalNumber(fn);

    try{
    reqCtx.saveAndReturn(beingProxy).fire(new Receiver(){

    @Override
    public void onSuccess(BeingProxy being) {
    addToList(being);
    }
    });
    } catch(Exception ex){
    ex.printStackTrace();
    }

    I can post any additional code in order to help finding the problem. I tried everything but I don’t seem to get around it.

    I would really appreciate your help.

    Best regards.

  28. Manu said

    Hi David,

    Based on your 2011 IO talk I decided to start my first *ever* web application using GWT (that was already decided) with the library combo Objectify+RF+GWTP (that is after listening to your talk). So far everything is going OK and although I had a lot to learn at the beginning I am now rolling well. So I thank you for that.

    My first question is… do you still think (2 years and a half later) that it is a highly productive combination of libraries? (reading your comments above it seems like after a while you became frustrated with it)

    Then, there is a technical question….

    In your example, you propose using a convenience DatastoreObject class that has a locator for RF. This is really convenient.

    However, all your entities are root-entities (or non-parented entities). I don’t know if the @Parent concept existed for Objectify3 but it seems for Objectify4, using parents is mandatory when you need to do transactions.

    The problem with parented-entities is that your locator’s find() method is no longer valid. Because for parented entities you need to provide the parent key in order to fetch the entity from datastore.

    Do you have any preferred solution on how to implement the locator for parented entities? How would you pass the parent key through RF?

    If you know anywhere were I can find some sample code about this, it’d be great. Thanks-

    • Thanks for your comment, Manu. WOW, RF + GWTP is a pretty big pill to swallow for your first Web app! Congrats on getting that working.
      I gave up on RF about a year after the talk, mostly because I got tired of the obtuse error messages and found it harder to do simple things like create new entities and cache responses than with GWT-RPC. Also the original authors left Google and it never seemed to gain traction within the community (Thomas Broyer notwithstanding).
      Regarding Objectify 4, I’m sadly a little behind so can’t point you to any code but I suspect that the problem you describe would require a find() method with an additional argument and thus a change to the RF implementation itself.

Leave a reply to David Chandler Cancel reply