Brad
Brad

Reputation: 5488

GWT RequestFactory + ServiceLocator Bug

I am using GWT 2.2 with RequestFactory. The app has an existing service layer (server side) so I am using a ServiceLocator to provide these implementations. My Proxy and RequestContexts specify the correct service and locator to use (as shown here). I am able to make basic requests for data but when I try to save, I get the following exception:

com.google.gwt.requestfactory.server.UnexpectedException: Could not instantiate Locator com.schedgy.core.service.OrganizationService. Is it default-instantiable?
at com.google.gwt.requestfactory.server.ServiceLayerDecorator.die(ServiceLayerDecorator.java:185)
at com.google.gwt.requestfactory.server.LocatorServiceLayer.newInstance(LocatorServiceLayer.java:222)
at com.google.gwt.requestfactory.server.LocatorServiceLayer.createLocator(LocatorServiceLayer.java:47)
at com.google.gwt.requestfactory.server.ServiceLayerDecorator.createLocator(ServiceLayerDecorator.java:54)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

OrganizationService is defined like:

// Parent class provides the generic Locator<Organization, String> methods
public class OrganizationService extends CompanyEntityService<Organization> {

        protected OrganizationDao organizationDao;

        protected UserDao userDao;

        protected RoleDao roleDao;

        @Inject
        public OrganizationService(
                OrganizationDao organizationDao,
                UserDao userDao,
                RoleDao roleDao) {

            super(organizationDao, Organization.class);

            this.organizationDao = organizationDao;
            this.userDao = userDao;
            this.roleDao = roleDao;
        }

... additional methods
}

My locator class looks like:

public class CompanyServiceLocator implements ServiceLocator {

    protected Injector injector;

    public CompanyServiceLocator() {        
        injector = GuiceFactory.getInjector();
    }

    @Override
    public Object getInstance(Class<?> clazz) {
        return injector.getInstance(clazz);
    }
}

The OrganizationProxy looks like:

@ProxyFor(value=Organization.class, locator=OrganizationService.class)
public interface OrganizationProxy extends CompanyEntityProxy {
... setters/getters defined here
}

The OrganizationRequest looks like:

@Service(value=OrganizationService.class, locator=CompanyServiceLocator.class)
public interface OrganizationRequest extends RequestContext {
...
}

The client side code looks something like:

OrganizationRequest req = organizationRequestFactory.request();
req.paginate(0, 10).fire(paginationReceiver); // Works!

req = organizationRequestFactory.request();
OrganizationProxy org = req.create(OrganizationProxy.class);
org.setName("test");
req.save(org).fire(receiver); // This causes the server side exception

It is obvious to me that ServiceLayerDecorator cannot instantiate OrganizationService because it does not have a default constructor, but this is why I am using Guice and have over-written the ServiceLocator to use Guice to create instances of the service. But why does the first call correctly use my ServiceLocator whereas the second does not?

Upvotes: 1

Views: 2082

Answers (2)

Brad
Brad

Reputation: 5488

I am still confused on why two classes need to provide implementations for Locators, but here is how I was able to fix this problem.

Extend the default RequestFactoryServlet so that you can inject your custom ServiceLayerDecorator by default.

public class CompanyRequestFactoryServlet extends RequestFactoryServlet {

    public CompanyRequestFactoryServlet() {
        this(new DefaultExceptionHandler(), new CompanyServiceLayerDecorator());
    }

    public SchedgyRequestFactoryServlet(ExceptionHandler exceptionHandler,
            ServiceLayerDecorator... serviceDecorators) {
        super(exceptionHandler, serviceDecorators);
    }
}

Create a ServiceLayerDecorator to provide instances of your Locator's. I use Guice, so this was quite easy. GuiceFactory in the below code is just a singleton that provides an instance of the Guice Injector.

public class CompanyServiceLayerDecorator extends ServiceLayerDecorator {

    @Override
    public <T extends Locator<?, ?>> T createLocator(Class<T> clazz) {
        return GuiceFactory.getInjector().getInstance(clazz);
    }
}

Finally, update your web.xml to use your custom servlet:

<servlet>
    <servlet-name>requestFactoryServlet</servlet-name>
    <servlet-class>com.company.core.requestfactory.CompanyRequestFactoryServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>requestFactoryServlet</servlet-name>
    <url-pattern>/gwtRequest</url-pattern>
</servlet-mapping>

Upvotes: 1

BobV
BobV

Reputation: 4173

Locators must be default-instantiable.

@ProxyFor(value=Organization.class, locator=OrganizationService.class)

is where things are going off the rails. If the OrganizationService is vending instances of Organization to fulfill the Locator interface, you'll need to make it default-instantiable or inject a ServiceLayerDecorator that implements createLocator().

The reason that the first code sample works and not the second is that the second code sample is creating and mutating an Organization based on commands from the client. In this case Locator.create() must be called by the RequestFactory server code. Without knowing what paginate() returns to the client, I suspect that no instances of Organization are being returned since it would be necessary to call the Locator.getId() and Locator.getVersion() methods.

Upvotes: 4

Related Questions