exciting life of Entity Proxies in contexts of RequestFactory
April 17, 2011 Leave a comment
Introduction
Since GWt 2.1 we can use special Request Factory interface to implement data access layer to server side objects. There are several pages which describe how to use it properly. For me the most resourceful are :
- official explanation from GWT site http://code.google.com/webtoolkit/doc/latest/DevGuideRequestFactory.html
- dynatablerf project, which shows request factory in action: http://code.google.com/p/google-web-toolkit/source/browse/trunk/samples/dynatablerf/?r=8464
- google groups where a lots of issues have been solved and questions have been answered : http://groups.google.com/group/google-web-toolkit
- blogs and tutorials, e.g. Thomas Broyer blog: http://tbroyer.posterous.com/
I am using those pages really often, but still, sometimes I am running into problems which are not described anywhere.. E.g. I was having some issues while using RequestContext’s and EntityProxies in some typical user workflows. After learning about how it works and how it should be used correctly I have decided to share this knowledge, because I was not able to find anywhere before. I will not explain how the request factory works, if you need to make first steps on that, follow the link http://code.google.com/webtoolkit/doc/latest/DevGuideRequestFactory.html.
I) I assume you already know
- GWT basics + GWT Request Factory basics
- For looking on code examples :
- Basic knowledge about ORM ( best will be JDO, but if you know JPA you will understand it as well)
- usage of GEP plugin to configure and manage your project in Eclipse
II) Technologies used
- GWT 2.2.0
- App Engine 1.4.3
- Java Data Objects (JDO)
III) Tools Used
- Eclipse Helios
- Google Eclipse Plugin
- Google App Engine
IV) How it should be done: typical READ UPDATE workflow
Student s = dao.getById(studentId);
s.setName("ted");
dao.update(s);
1) Get entity from the server.
You need to initialize RequestContext instance, then call method which retrieves entity, and afterwords fire the request
req0 = requestFactory.studentRequest();
req0.findStudent(id).fire(new Receiver<StudentProxy>() {
@Override
public void onSuccess(StudentProxy response) {
s1 = response;
}
});
2) Mark response for changes
The response you have from the server is a special object used by request factory to store properties of your entity along with other informations needed by RF to work. This object is of type which extends AutoBean<T extends StudentProxy> and if you look at it in debug mode you can see its properties:
some of those properties will be discussed in this article, those are
- frozen : flag which informs if entity(bean) has been locked for changes
- tags[requestContext] : informs which request context is responsible for managing this entity ( e.g. holding its changes)
- you can see also values table – it keeps all properties which you can set and get through the methods you have declared while implementing your Entity Proxy
So as you can see in our example the bean is : FROZEN and its requestContext is set to null. In this state you cannot perform any changes on this bean, all you can do is only call its getters. In order to UNLOCK the bean for changes you need to
a) create new requestContext
b) mark the bean for changes by calling edit() method.
Pay attention: the edit(arg) method will RETURN unlocked proxy (instead of unlocking the “arg”) – don’t perform changes on the one you have used as an argument. It is a common mistake to do req1.edit(s1) and try to do operations with s1.. this may end up with some red messages comming from the console..
StudentRequest req1 = requestFactory.studentRequest(); StudentProxy s2 = req1.edit(s1);
3) Apply changes
Now the s2 bean is ready for any changes. So you can set the properties without running into any exceptions
s2.setName("Stefan");
4) Save changes
In order to save the changes into database you must call some update method. Here you must use the same request which you used for editing the entity. If you use another one you will recieve exception, because only req1 has informations about this entity and its changes.
req1.update(s2).fire(new Receiver<StudentProxy>() {
@Override
public void onSuccess(StudentProxy response) {
s3 = response;
}
});
5) After server response
Server has successfully updated entity and returned back new copy of entity. Since now s2 entity is useless and you should not use it anymore.
6) More changes?
Here we are in the same situation as in point 2) so if you want to make more changes, you have to repeat the process: create new RequestContext, edit entity with it, send changes to the server using RequestContext.
7) Create Read Update workflow
CRU workflow will be quite similar. After creating and saving the entity with usage of some request ( lets say req0) we will end up in point 1) in which we got newly created entity from the server.
V) Possible traps along the way
As you can see life of entities in your application cannot be called boring:) they are edited, changed, freezed and so on..:) Also while you are working with them you may get into some troubles if you not follow the described workflow correctly. I will give some most probable trouble scenarios – along with exceptions which are thrown, so if you see one of those while running your app you will know were to look for the solution.
Lets go back to the picture and see the last column : “What could happen if” – there I have put some operations which are not allowed for current states of request contexts and proxies.
1) Trying to edit locked entity.
If an entity is frozen ( locked for changes) you cannot:
- change its properties
- use it in requestContext method calls.
If you will try to do that, you will recieve exception : java.lang.IllegalStateException: The AutoBean has been frozen.
When entity may be frozen?
a) every entity returned as a response is frozen
b) every entity which has been used in requestContext call will get frozen.
In first situation solution is easy – you just have to unlock given entity. In order to do that you must use instance of your RequestContext class and call edit() method.
StudentRequest req1 = requestFactory.studentRequest(); StudentProxy s2 = req1.edit(s1);
In second situation you should not use given entity any more, It cannot be edited because it has already a requestContext assigned. If you want to change it you must retrieve instance of this entity from server again and follow instructions for point a).
2) Trying to call requestContext.edit() on entity which has already requestContext assigned.
If you have retrieved entity from the server or created a new one, and afterwords you are trying to use ANOTHER RequestContext to edit it e.g. in this way:
StudentRequest req = requestFactory.studentRequest();
s1 = req.create(StudentProxy.class);
// s1 is connected with "req" and one context is just enough for it
StudentRequest reqZZZ = requestFactory.studentRequest();
reqZZZ.edit(s1); // you cannot do it - here exception will be thrown
you will surely recieve an exception:
java.lang.IllegalArgumentException: Attempting to edit an EntityProxy previously edited by another RequestContext
You may run into this problem in situation where you have a bean, but you have no track of request context which has created or edited the bean in some previous method call. In this situation you must save the previous requestContext somewhere, or send it along with the entity to the point of intrest… The best solution may be to create some special layer which holds currently used request.
3) Trying to reuse Request Context which has already been fired
You can use request context to create and edit many different entities ( also of different type). You can also accumulate the methods which should be fired. But what you cannot do is to try to use it twice to fire a request. If you have created reqest and call fire() method on it, you cannot repeat that.. If you do you will get: java.lang.IllegalStateException:A request is already in progress exception.
Solution is simply create new requestContext.
VI) More complicated traps
Those 3 situations I have mentioned are quite easy to solve. But the more complicated your data model is, and the more unusual widgets and user work-flows are you using the more problems may occur. At first time I was really unhappy with the fact that the RequestContext.edit() method is not working “in place”. E.g my problem was
I have an Student object which has a list of Subjects he is studying.
- GUI : I have widget which displays info about student and his subjects, you can edit info about several subjects – change its descriptions etc. After you are done you click save button which save ALL the changes.
save button triggers method dao.update(Student student) which makes cascade update on student and list of his subjects.
and now how to do it properly? Before I am going to edit any property of subject I need to edit it.. so
List<SubjectProxy> subjects = student.getSubjects();
showSubjectsInEditableList(subjects);
// method called after user will provide new name of subject
public void changeNameOfSubject(String name, Subject selectedSubject)
{
SubjectProxy subject = req.edit(selectedSubject);
subject.setName(name);
// but subject is not on the list student.getSubjects()..
}
public void save()
{
// here changes which have been made will not be propagated...
req.update(student);
}
What we may do in this situation is e.g. replace old value from the list with new value
public void changeNameOfSubject(String name, Subject selectedSubject)
{
SubjectProxy subject = req.edit(selectedSubject);
subject.setName(name);
student.getSubjects().remove(selectedSubject); // here nothing will happen!
student.getSubjects().add(subject);
}
I am not fan of this solution and am still trying to find something more elegant.. Anyway working with request factory sometimes can be a little but surprising. But still taking into account all the pros and cons it is great framework which if you learn it makes wiring server and client data objects really easy and save.

