Clarify how GWT RequestFactory and RequestContext work
Asked Answered
G

1

5

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!

Gazebo answered 12/4, 2011 at 21:31 Comment(0)
B
18

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:

  • Don't store RequestContext instances in fields of objects whose lifetime exceeds that of the fire() method invocation.
  • Similarly, editable EntityProxy or ValueProxy instances should not be retained beyond the call to fire().
  • The 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').
  • Instances of RequestFactory should be constructed once and retained for the lifetime of the module, since they have non-trivial construction cost.
Belorussia answered 12/4, 2011 at 22:31 Comment(4)
Thank you for your clear and concise answer, however it leads to another question that may be part of my problem. I have an ObjectLocator class that extends Locator. My question is about the find(clazz, id) method that I'm overriding. I've seen an example using Objectify that uses the Google App Engine datastore api, this appears to be a caching mechanism for the server side objects. I saw another example that makes a database call to get the object. What should I return here, do I need to make a database call or implement a caching mechanism to get the server object?Gazebo
Caching the object or making a call for a fresh objects depends on the level of concurrency that you expect in your application. If the object is immutable, then caching instances makes sense. Something to remember about AppEngine is that requests are subject to bouncing between instances of your application. My suggestion is to write the simplest thing that works, and then iterate once you've determined that something is or is not a performance issue.Belorussia
Hi Bob. I seems to me that the DynaTableRf sample does not apply some of your guidelines, particularly in relation to the retaining of the RequestContext and the proxy objects; or have I misunderstood how DynaTableRf works? Do you consider the DynaTableRf to be an example of good practice, or should I go looking for another?Bushwhack
Thank you a lot. Finally someone cleared out these issues for me. Very good job, Bob!Quantitative

© 2022 - 2024 — McMap. All rights reserved.