I am trying to implement RequestFactory and the Editor framework into my app. I'm finding even after researching the forum, the Google Developer forum, and others that there is something fundamental that I don't understand about using RequestContext with RequestFactory. Here is my scenario:
I have a simple Entity that has three fields, id, version, description called CmsObjectType. I have a corresponding EntityProxy and an CmsObjectTypeServiceDAO with my CRUD operations. I have also implemented ServiceLocator and ObjectLocator classes. This code all compiles and runs.
I have also created a simple test case to test the CRUD operations, using the following:
public class RequestFactoryProvider {
public static CmsRequestFactory get() {
SimpleEventBus eventBus = new SimpleEventBus();
CmsRequestFactory requestFactory = RequestFactoryMagic.create(CmsRequestFactory.class);
ServiceLayer serviceLayer = ServiceLayer.create();
SimpleRequestProcessor processor = new SimpleRequestProcessor(
serviceLayer);
requestFactory.initialize(eventBus, new InProcessRequestTransport(
processor));
return requestFactory;
}
}
The Test:
public class TestCmsObjectTypeRequest extends Assert {
private static CmsRequestFactory requestFactory;
private static CmsObjectTypeRequestContext objectTypeRequest;
private Long newId;
@Before
public void setUp() {
requestFactory = RequestFactoryProvider.get();
objectTypeRequest = requestFactory.objectTypeRequest();
}
@Test
public void testEdit() {
final CmsObjectTypeProxy newType = objectTypeRequest
.create(CmsObjectTypeProxy.class);
newType.setDescription("NEW TYPE");
objectTypeRequest.persist(newType).to(new Receiver<Long>() {
@Override
public void onSuccess(Long response) {
if (response != null) {
newId = response;
assertTrue(true);
} else {
fail();
}
}
@Override
public void onFailure(ServerFailure error) {
fail();
}
});
// Edit the newly created object
newType.setDescription("EDITED NEW TYPE");
objectTypeRequest.update(newType).to(new Receiver<Boolean>() {
@Override
public void onSuccess(Boolean response) {
assertTrue(response);
}
@Override
public void onFailure(ServerFailure error) {
fail();
}
});
//Remove it when we're done..
objectTypeRequest.delete(newType).to(new Receiver<Boolean>() {
@Override
public void onSuccess(Boolean response) {
System.out.println("onSuccess from delete.");
assertTrue(response);
}
@Override
public void onFailure(ServerFailure error) {
fail();
}
});
objectTypeRequest.fire();
}
}
When I create a new request context and chain my method calls for create, update, and delete and then call fire(), it works with no problems in the test above. However if I try to do these calls individually by calling the method and then fire() I run into problems. I can call create() with the Receiver returning the id of the newly created entity and then I use that id to call find(id) and I get back the newly created entity. Up to this point everything works ok. However, this is where I am confused.. if I try to call edit with the current RequestContext within the Receiver's onSuccess() method from the find(id), I get an error saying the context is already in progress. If I create a local variable for the foundProxy and then try to use a new instance of RequestContext to call requestContext.edit(foundProxy) on the newly found entity and then call update() I get a server error, most commonly: Server Error: The requested entity is not available on the server. If I don't create the new instance of request context I get an IllegalStateException saying the request is already in progress. Here is the sample test that hopefully will make this clearer:
@Test
public void testEditWOChaining() {
final CmsObjectTypeProxy newType = objectTypeRequest
.create(CmsObjectTypeProxy.class);
newType.setDescription("NEW TYPE");
objectTypeRequest.persist(newType).to(new Receiver<Long>() {
@Override
public void onSuccess(Long response) {
if (response != null) {
setNewId(response);
assertTrue(true);
} else {
fail();
}
}
@Override
public void onFailure(ServerFailure error) {
fail();
}
}).fire();
if (newId != null) {
objectTypeRequest = requestFactory.objectTypeRequest();
objectTypeRequest.find(newId)
.to(new Receiver<CmsObjectTypeProxy>() {
@Override
public void onSuccess(CmsObjectTypeProxy response) {
if (response != null) {
foundProxy = response;
}
}
@Override
public void onFailure(ServerFailure error) {
fail();
}
}).fire();
}
if (foundProxy != null) {
// Edit the newly created object
objectTypeRequest = requestFactory.objectTypeRequest();
CmsObjectTypeProxy editableProxy = objectTypeRequest
.edit(foundProxy);
editableProxy.setDescription("EDITED NEW TYPE");
objectTypeRequest.update(editableProxy).to(new Receiver<Boolean>() {
@Override
public void onSuccess(Boolean response) {
assertTrue(response);
}
@Override
public void onFailure(ServerFailure error) {
fail();
}
}).fire();
}
// Remove it when we're done..
objectTypeRequest.delete(foundProxy).to(new Receiver<Boolean>() {
@Override
public void onSuccess(Boolean response) {
System.out.println("onSuccess from delete.");
assertTrue(response);
}
@Override
public void onFailure(ServerFailure error) {
fail();
}
});
objectTypeRequest.fire();
}
Here are my questions.. What is the best way to handle an edit if it is not associated with a create() but with a find()? If I try to chain the find with an update my foundProxy is null and things don't update. Do proxies have to stay bound to the context in which they are created in order to be able to perform updates on them? If someone could explain how this works or point me to some documentation that points out what I am missing I would be grateful. Is it possible this is something to do with the way the testing framework processes the requests? I have read the following so if I missed something in them please let me know: Great description by tbroyer
Google docs Any help would be greatly appreciated. Thank you!
Take a look at the RequestFactoryTest
in the GWT source code for examples. The testChangedEdit()
method is similar to what you're trying to write. It calls a find()
method and then operates on the returned proxy in the onSuccess()
method.
A RequestContext
isn't a long-lived object. It is only valid from the time that it is called to when you call fire()
on it. It can be re-used only if the onFailure()
or onViolation()
method is called in your Receiver
.
An EntityProxy
or ValueProxy
returned via Receiver.onSuccess()
represents a snapshot of server data. Thus, the proxy is immutable unless it is associated with a RequestContext
by calling edit()
. The proxies returned by RequestContext.create()
are mutable. A mutable proxy is always associated with exactly one RequestContext
and it is an error to "cross the streams." It is not an error to re-edit()
a mutable proxy.
The reason it works this way is to allow the RequestFactory client to only send deltas to the server. The deltas are applied to the long-lived entities on the server by calling the domain object's find()
method (or using a Locator
). The RequestContext is essentially an accumulator for proxy.setFoo()
calls and one or more Request
/ InstanceRequest
invocations.
General guidelines:
fire()
method invocation.EntityProxy
or ValueProxy
instances should not be retained beyond the call to fire()
.EntityProxyId
returned from EntityProxy.stableId()
can be retained indefinitely, even from a newly-created proxy. The stableId
object is suitable for use as the key in Map
objects and has stable object-identity semantics (i.e. two snapshots of the same server domain object with different versions would return the same `EntityProxyId').RequestFactory
should be constructed once and retained for the lifetime of the module, since they have non-trivial construction cost.