0
votes

I'm trying to integrate GWT and Hibernate using RequestFactory. Everything works well, except when I try to work with an existing entity. It doesn't matter what I'm trying to do (load, update or delete), the result is always the same: NullPointerException.

After some debugging, I realized that when the request is fired, a JsonSplittable that contains only a string with the ID of my existing entity ("1", for instance) is processed as if it had a JSONObject. The method getOrReify is executed and the NPE happens.

The getOrReify seems to be looking for the operation to execute, since it's searching is "O" (propertyName). But, as I said, it won't find it because there's no JSONObject. Plus, the reifiedMap, which could provide this information, doesn't have it (but it's not null).

Exception

28/02/2012 11:10:51 com.google.web.bindery.requestfactory.server.RequestFactoryServlet doPost
GRAVE: Unexpected error
java.lang.NullPointerException
    at com.google.web.bindery.autobean.vm.impl.JsonSplittable.isNull(JsonSplittable.java:248)
    at com.google.web.bindery.autobean.shared.impl.AbstractAutoBean.getOrReify(AbstractAutoBean.java:235)
    at com.google.web.bindery.autobean.vm.impl.ProxyAutoBean.getOrReify(ProxyAutoBean.java:229)
    at com.google.web.bindery.autobean.vm.impl.BeanMethod$2.invoke(BeanMethod.java:73)
    at com.google.web.bindery.autobean.vm.impl.SimpleBeanHandler.invoke(SimpleBeanHandler.java:43)
    at $Proxy81.getOperations(Unknown Source)
    at sun.reflect.GeneratedMethodAccessor67.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at com.google.web.bindery.autobean.vm.impl.ShimHandler.invoke(ShimHandler.java:78)
    at $Proxy81.getOperations(Unknown Source)
    at com.google.web.bindery.requestfactory.server.SimpleRequestProcessor.processOperationMessages(SimpleRequestProcessor.java:496)
    at com.google.web.bindery.requestfactory.server.SimpleRequestProcessor.decodeOobMessage(SimpleRequestProcessor.java:185)
    at com.google.web.bindery.requestfactory.server.RequestState.getBeansForIds(RequestState.java:254)
    at com.google.web.bindery.requestfactory.server.RequestState.getBeansForPayload(RequestState.java:147)
    at com.google.web.bindery.requestfactory.server.RequestState.getBeanForPayload(RequestState.java:124)
    at com.google.web.bindery.requestfactory.shared.impl.EntityCodex.decode(EntityCodex.java:101)
    at com.google.web.bindery.requestfactory.server.SimpleRequestProcessor.decodeInvocationArguments(SimpleRequestProcessor.java:409)
    at com.google.web.bindery.requestfactory.server.SimpleRequestProcessor.decodeInvocationArguments(SimpleRequestProcessor.java:380)
    at com.google.web.bindery.requestfactory.server.SimpleRequestProcessor.processInvocationMessages(SimpleRequestProcessor.java:447)
    at com.google.web.bindery.requestfactory.server.SimpleRequestProcessor.process(SimpleRequestProcessor.java:225)
    at com.google.web.bindery.requestfactory.server.SimpleRequestProcessor.process(SimpleRequestProcessor.java:127)
    at com.google.web.bindery.requestfactory.server.RequestFactoryServlet.doPost(RequestFactoryServlet.java:133)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:820)
    at com.google.inject.servlet.ServletDefinition.doService(ServletDefinition.java:263)
    at com.google.inject.servlet.ServletDefinition.service(ServletDefinition.java:178)
    at com.google.inject.servlet.ManagedServletPipeline.service(ManagedServletPipeline.java:91)
    at com.google.inject.servlet.FilterChainInvocation.doFilter(FilterChainInvocation.java:62)
    at my.package.persistence.filter.HibernateSessionRequestFilter.doFilter(HibernateSessionRequestFilter.java:159)
    at com.google.inject.servlet.FilterDefinition.doFilter(FilterDefinition.java:163)
    at com.google.inject.servlet.FilterChainInvocation.doFilter(FilterChainInvocation.java:58)
    at com.google.inject.servlet.FilterDefinition.doFilter(FilterDefinition.java:168)
    at com.google.inject.servlet.FilterChainInvocation.doFilter(FilterChainInvocation.java:58)
    at my.package.persistence.filter.TenantFilter.doFilter(TenantFilter.java:171)
    at com.google.inject.servlet.FilterDefinition.doFilter(FilterDefinition.java:163)
    at com.google.inject.servlet.FilterChainInvocation.doFilter(FilterChainInvocation.java:58)
    at com.google.inject.servlet.FilterDefinition.doFilter(FilterDefinition.java:168)
    at com.google.inject.servlet.FilterChainInvocation.doFilter(FilterChainInvocation.java:58)
    at com.google.inject.servlet.ManagedFilterPipeline.dispatch(ManagedFilterPipeline.java:118)
    at com.google.inject.servlet.GuiceFilter.doFilter(GuiceFilter.java:113)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1088)
    at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:360)
    at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
    at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:181)
    at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:729)
    at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:405)
    at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
    at org.mortbay.jetty.handler.RequestLogHandler.handle(RequestLogHandler.java:49)
    at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
    at org.mortbay.jetty.Server.handle(Server.java:324)
    at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:505)
    at org.mortbay.jetty.HttpConnection$RequestHandler.content(HttpConnection.java:843)
    at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:647)
    at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:211)
    at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:380)
    at org.mortbay.io.nio.SelectChannelEndPoint.run(SelectChannelEndPoint.java:395)
    at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:488)

Entity

@Entity
public class Foro implements RequestFactoryEntity<Long> {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(length = 100)
    private String descricao;

    @Column(name = Constants.VERSION_COLUMN)
    @Version
    private Integer version;

    public Foro() {
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getDescricao() {
        return descricao;
    }

    public void setDescricao(String descricao) {
        this.descricao = descricao;
    }

    public Integer getVersion() {
        return version;
    }

    public void setVersion(Integer version) {
        this.version = version;
    }
}

Proxy

@ProxyFor(value = Foro.class, locator = Locators.Foro.class)
public interface ForoProxy extends EntityProxy {

    EntityProxyId<ForoProxy> stableId();

    String getDescricao();

    void setDescricao(String descricao);

}

Locator (simplified w/ superclass)

// Locator
public final class Locators {

    private Locators() {
    }

    public static final class Foro extends AbstractDaoLocator<ForoDao, my.package.evolution.core.entity.Foro, Long> {
    }

}

// Locator superclass
public abstract class AbstractDaoLocator<DAO extends AbstractDao<T, ID>, T extends RequestFactoryEntity<ID>, ID extends Serializable> extends AbstractLocator<T, ID> {

    @Override
    public T find(Class<? extends T> clazz, ID id) {
        try {
            return getDaoClass().newInstance().findById(id);

        } catch (Exception e) {
            throw new RuntimeException("Falha ao instanciar DAO: " + getDaoClass().getName() + ". Este DAO tem um construtor vazio?", e);
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public Class<T> getDomainType() {
        return (Class<T>) getTypes()[1];
    }

    @Override
    @SuppressWarnings("unchecked")
    public Class<ID> getIdType() {
        return (Class<ID>) getTypes()[2];
    }

    @SuppressWarnings("unchecked")
    private Class<DAO> getDaoClass() {
        return (Class<DAO>) getTypes()[0];
    }

    private Type[] getTypes() {
        return ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments();
    }

}

// AbstractDaoLocator superclass
public abstract class AbstractLocator<T extends RequestFactoryEntity<ID>, ID extends Serializable> extends Locator<T, ID> {

    @Override
    public T create(Class<? extends T> clazz) {
        try {
            return clazz.newInstance();

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public Class<T> getDomainType() {
        return (Class<T>) getTypes()[0];
    }

    @Override
    public ID getId(T domainObject) {
        return domainObject.getId();
    }

    @Override
    @SuppressWarnings("unchecked")
    public Class<ID> getIdType() {
        return (Class<ID>) getTypes()[1];
    }

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

    private Type[] getTypes() {
        return ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments();
    }

}

ServiceLocator (simplified w/ superclass)

// ServiceLocator
public final class ServiceLocators {

    private ServiceLocators() {
    }

    public static final class ForoDao extends AbstractServiceLocator<br.com.programarte.evolution.core.dao.ForoDao> {
    }

}

// ServiceLocator superclass
public abstract class AbstractServiceLocator<T> implements ServiceLocator {

    public Object getInstance(Class<?> clazz) {
        try {
            return getServiceClass().newInstance();

        } catch (Exception e) {
            throw new RuntimeException("Falha ao instanciar a classe de servi\u00e7o: " + getServiceClass().getName() + ". Ela tem um construtor vazio?", e);
        }
    }

    @SuppressWarnings("unchecked")
    private Class<T> getServiceClass() {
        return (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    }

}

DAO (superclass AbstractDao from Hibernate book)

public class ForoDao extends AbstractDao<Foro, Long>{
}

RequestFactory

public interface RequestContextFactory extends RequestFactory {

    ForoRequestContext foroRequestContext();

    @Service(value = ForoDao.class, locator = ServiceLocators.ForoDao.class)
    public interface ForoRequestContext extends RequestContext {

        Request<ForoProxy> findById(Long id);

        Request<ForoProxy> saveOrUpdate(ForoProxy proxy);

        Request<Void> delete(ForoProxy proxy);

    }

Executing

getFactory().foroRequestContext().findById(3L).fire(new SimpleReceiver<ForoProxy>() {

    @Override
    public void onSuccess(ForoProxy entity) {
        final ForoRequestContext ctx = getFactory().foroRequestContext();
        final ForoProxy entityEdit = ctx.edit(entity);
        entityEdit.setDescricao(entityEdit.getDescricao() + " (Edit)");

        ctx.saveOrUpdate(entityEdit).fire(new SimpleReceiver<ForoProxy>());
    }

});

More information (requested by Colin Alworth)

Create/Setup RequestFactory

private RequestContextFactory rcf;

private RequestContextFactory getFactory() {
    if (null == rcf) {
        rcf = new SmartContextFactory().getRequestContextFactory();
    }

    return rcf;
}

SmartContextFactory ###

public class SmartContextFactory {

    private final RequestContextFactory rcf = GWT.create(RequestContextFactory.class);
    private final EventBus eventBus = new SimpleEventBus();

    public SmartContextFactory() {
        rcf.initialize(eventBus);
    }

    public RequestContextFactory getRequestContextFactory() {
        return rcf;
    }

    public EventBus getEventBus() {
        return eventBus;
    }

}

jsonRequestString (beautified)

{
    "F": "my.package.evolution.services.requestfactory.RequestContextFactory",
    "O": [{
        "T": "cv432aQBAMWN$1T23hbccUQX5WY=",
        "V": "MS4w",
        "P": {
            "descricao": "Nowhere (1330517239646) (Edit)"
        },
        "S": "IjI3Ig==",
        "O": "UPDATE"
    }],
    "I": [{
        "P": [{
            "T": "cv432aQBAMWN$1T23hbccUQX5WY=",
            "S": "IjI3Ig=="
        }],
        "O": "r6H_7BrJ_7Y_j0lWkuJA$zEJ6mc="
    }]
}
2

2 Answers

0
votes

I'm not sure you need the stableId() method in your proxy. I think that is generated for you on the client side. Not sure that is your problem, but worth a try without that in your proxy interface.

0
votes

The "O" variable is the operations sent over the wire, declared in com.google.web.bindery.requestfactory.shared.messages.RequestMessage. An operation is described in com.google.web.bindery.requestfactory.shared.messages.OperationMessage- it represents a set or operations to perform on entities prior to persisting them (as opposed to com.google.web.bindery.requestfactory.shared.messages.InvocationMessage, which represents the service operations).

For that null pointer to occur there, the entire RequestMessage seems to need to be null - there was no object to read the "O" property from - the JsonSplittable was created somehow without underlying data.

I don't see a way that can happen in tracing the code that is called to deserialize the message from the client to a RequestMessage instance, with one exception - if a JSON string is sent instead of an object. This makes no sense, at least with any default setup.


From the updated posts, it turns out that the JsonSplittable was created wrapping a string instead of a Json object, which gives us the NPE. From this, we only know that some string is being deserialized, but the server thinks that it should be an object instead, and so is trying (and failing) to read sub-properties. Now, trying to figure out what property we are reading is proving difficult from just a stack trace, as it could be reading any sub-property to prepare it to be accessed as a bean. The id property was a likely candidate, as it should be sent as a String->Long, but if the server was expecting some other type, it would try to deserialize it as jsonobject->object.

That other operations that don't send an object to the server work correctly seems to corroborate this. I'm still suspecting that something is going wrong here (or that the stack trace has changed, or the property it gets stuck on has changed) - figuring out which property is it currently trying to read will be important to solving this.

java.lang.NullPointerException
    at com.google.web.bindery.autobean.vm.impl.JsonSplittable.isNull(JsonSplittable.java:248)
    at com.google.web.bindery.autobean.shared.impl.AbstractAutoBean.getOrReify(AbstractAutoBean.java:235)
    at com.google.web.bindery.autobean.vm.impl.ProxyAutoBean.getOrReify(ProxyAutoBean.java:229)
    at com.google.web.bindery.autobean.vm.impl.BeanMethod$2.invoke(BeanMethod.java:73)

The last frame here is where the getter is actually being invoked, try to break on the exception to read some values and learn a few things:

  • is this still happening from getOperations, and is the property still "O" that is being invoked on a string? Assuming so,
  • What is the string in the JsonSplittable that is being treated as an object? With the request string, this doesn't make sense to be anything but the only object in the O array (or the "UPDATE" string->enum).

Some other questions I asked, were answered

Can you post the message body that is being sent over the wire? This should be available as jsonRequestString in com.google.web.bindery.requestfactory.server.RequestFactoryServlet.doPost(HttpServletRequest, HttpServletResponse), which is in your stack trace, or view the text using a tool like Firebug or Chrome Inspector. Can you also share how you create/setup the RequestFactory instance? If you were using a custom transport, it is possible (though seems absurd) that the data might be being sent wrong somehow.

And finally, do any RF calls work? If they all fail, this too points to a configuration problem of some kind. If this is the only one, then maybe the message will have something that sheds a little light on the issue.