Răzvan Petruescu
Răzvan Petruescu

Reputation: 725

GWT RequestFactory STRANGE behavior : No error reporting

I do have a problem, and I can't figure out what it happens... GWT beginner, working on a personal project.

Environment:

The application is built using some Spring Roo generated code as basis, later modified and extended.

The issue is that, when editing some entity fields and pressing save, nothing happens. No problem when creating a new entity instance, only on edit changing some field and pressing 'save' basically overrides the new values. So I started to thoroughly debug the client code, enabled hibernate and Spring detailed logging, but still ... nothing.

Then I made a surprising (for me) discovery. Inspecting the GWT response payload, I have seen this:

    {"S":[false],"O":        [{"T":"663_uruC_g7F5h5IXBGvTP3BBKM=","V":"MS4w","S":"IjMi","O":"UPDATE"}],"I":[{"F":true,"M":"Server Error: org.hibernate.PersistentObjectException: detached entity passed to persist: com.myvdm.server.domain.Document; nested exception is javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.myvdm.server.domain.Document"}]}  

Aha, detached entity passed to persist !!! Please note that the gwt client code uses this snippet to call the service:

requestContext.persist().using(proxy);

Arguably this could trigger the exception, and calling merge() could solve the problem, however, read on, to question 3...

Three question arise now:

Thanks a lot,

Avaiting for some opinions/suggestions.


EDITED AFTER T. BROYER's RESPONSE::

Hi Thomas, thanks for the response.

I have a custom class that implements RequestTransport and implements send(). This is how I collected the response payload. Implementation follows::

public void send(String payload, final TransportReceiver receiver) {
    TransportReceiver myReceiver = new TransportReceiver() {

        @Override
        public void onTransportSuccess(String payload) {
            try {
                receiver.onTransportSuccess(payload);
            } finally {
                eventBus.fireEvent(new RequestEvent(RequestEvent.State.RECEIVED));
            }
        }

        @Override
        public void onTransportFailure(ServerFailure failure) {
            try {
                receiver.onTransportFailure(failure);
            } finally {
                eventBus.fireEvent(new RequestEvent(RequestEvent.State.RECEIVED));
            }
        }
    };

    try {
        wrapped.send(payload, myReceiver);
    } finally {
        eventBus.fireEvent(new RequestEvent(RequestEvent.State.SENT));
    }
}

Here's the code that is executed when 'save' button is clicked in edit mode:

RequestContext requestContext = editorDriver.flush();
    if (editorDriver.hasErrors()) {
        return;
    }

    requestContext.fire(new Receiver<Void>() {
        @Override
        public void onFailure(ServerFailure error) {
            if (editorDriver != null) {
                setWaiting(false);
                super.onFailure(error);
            }
        }

        @Override
        public void onSuccess(Void ignore) {
            if (editorDriver != null) {
                editorDriver = null;
                exit(true);
            }
        }

        @Override
        public void onConstraintViolation(Set<ConstraintViolation<?>> errors) {
            if (editorDriver != null) {
                setWaiting(false);
                editorDriver.setConstraintViolations(errors);
            }
        }
    });

Based on what you said, onSuccess() should be called, and it's called

So how do I isolate exactly the code that creates the problem? I have this method that creates a fresh request context in order to persist the object

        @Override
        protected RequestContext createSaveRequestContextFor(DocumentProxy proxy) {
            DocumentRequestContext request = requests.documentRequestContext();
            request.persist().using(proxy);
            return request;
        }

and this is how it is called::

 editorDriver.edit(getProxy(), createSaveRequestContextFor(getProxy()));

As for the Spring problem, you are saying that, between two subsequent requests, the find() and persist(), the JPA entityManager should not be closed. I am still investigating this, but after I press the edit button, I see the message 'org.springframework.orm.jpa.EntityManagerFactoryUtils - Closing JPA EntityManager' and that is not right, maybe the @Transactional annotation is not applied...

Upvotes: 0

Views: 481

Answers (2)

&#220;mit
&#220;mit

Reputation: 17489

Regarding the open session in view pattern Thomas mentioned, just add this filter definitions to your web.xml to turn on the pattern in your Spring application:

<filter>
    <filter-name>
        Spring OpenEntityManagerInViewFilter
    </filter-name>
    <filter-class>
         org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter
    </filter-class>
</filter>
<filter-mapping>
    <filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Upvotes: 1

Thomas Broyer
Thomas Broyer

Reputation: 64541

Why isn't this somehow sent to the client as an error/exception?

It is. The "S": [false] indicates the first (and only) method invocation (remember, a RequestContext is a batch!) has failed. The onFailure method of the invocation's Receiver will be called.

The "F": true of the ServerFailure then says it's a fatal error, so the default implementation of Receiver#onFailure would throw a RuntimeException. However, as you do not use a Receiver at all, nothing happens and the error is silently ignored.

Note that the batch request in itself has succeeded, so the global Receiver (the one you'd pass to RequestContext#fire) would have its onSuccess method called.
Also note that Request#fire(Receiver) is a shorthand for Request#to(Receiver) followed by RequestContext#fire() (with no argument).

Why isn't this logged by Hibernate?

This I don't know, sorry.

How come the Spring Roo generated code (as I said, used as basis) works without manifesting this problem?

OK, let's explore the underlying reason of the exception: the entity is loaded by your Locator (or the entity class's findXxx static method) and then the persist method is called on the instance. If you do not use the same JPA EntityManager / Hibernate session in the find and persist methods, then you'll have the issue.
Request Factory expects you to use the open session in view pattern to overcome this. I unfortunately do not know what kind of code Spring Roo generates.

Upvotes: 2

Related Questions