tynamo / tapestry-resteasy

RESTEasy & Tapestry5 Integration
http://www.tynamo.org/tapestry-resteasy+guide/
Apache License 2.0
17 stars 10 forks source link

Incompatible with agrest #19

Open timcu opened 3 years ago

timcu commented 3 years ago

Trying to use agrest with tapestry-resteasy produces an error in org.tynamo.resteasy.ResteasyRequestFilter

Application.getSingletons() returned unknown class type: io.agrest.runtime.AgRuntime

ResteasyRequestFilter doesn't support all the available types RESTEasy has to offer, so it throws the exception. Only Resources and Providers are supported, but AgRuntime is a "Feature" type.

Example code using a simple data model from https://github.com/agrestio/agrest-bookstore-example . I can provide full source code if that helps.

<dependency>
    <groupId>org.tynamo</groupId>
    <artifactId>tapestry-resteasy</artifactId>
    <version>0.7.0</version>
</dependency>
public class AgrestServiceImpl implements AgrestService {
  public AgrestServiceImpl() {
    ServerRuntime runtime = ServerRuntime.builder().addConfig("cayenne-project.xml").build();
    AgCayenneModule cayenneExt = AgCayenneBuilder.build(runtime);
    agRuntime = new AgBuilder().module(cayenneExt).build();
  }
  private AgRuntime agRuntime;
  public AgRuntime agRuntime() {
    return agRuntime;
  }
}
@Path("/category")
@Produces(MediaType.APPLICATION_JSON)
public class CategoryResource {
  @Context
  private Configuration config;
  @GET
  public DataResponse<Category> getAll(@Context UriInfo uriInfo) {
      return Ag.select(Category.class, config).uri(uriInfo).get();
  }
}
    @Contribute(javax.ws.rs.core.Application.class)
    public static void configureRestProviders(Configuration<Object> singletons, AgrestService svcAgrest) {
        singletons.add(svcAgrest.agRuntime());
        singletons.addInstance(CategoryResource.class);
    }

http://localhost:8080/tapestry-agrest/rest/category

java.lang.RuntimeException: Exception constructing service 'ResteasyRequestFilter': Error invoking constructor public org.tynamo.resteasy.ResteasyRequestFilter(java.lang.String,org.slf4j.Logger,org.apache.tapestry5.http.services.ApplicationGlobals,javax.ws.rs.core.Application,org.apache.tapestry5.ioc.services.SymbolSource,boolean,org.apache.tapestry5.ioc.services.UpdateListenerHub,long,long,boolean) throws javax.servlet.ServletException: Application.getSingletons() returned unknown class type: io.agrest.runtime.AgRuntime

ascandroli commented 3 years ago

Hi Tim

I was looking for litle code challenge for my vacation so I may be able to help you with this :) If you can provide a working project that would be a great time saver for me so I can focus on the actual issue.

posible workaround

For a quick workaround, and following some of the points Ben's was making in the thread in the tapestry's list, I'd try to wrap the AgRuntime in a @Resource. Try it and let me know, or share the code with me and I could give it a try.

support for @Feature

As Ben correctly pointed out in the thread:

Only Resources and Providers are supported, but AgRuntime is a "Feature" type.

The only option I see (without tapestry-resteasy adding it) is overriding the ResteasyRequestFilter and extending the conditions for provider detection to allow instances of Feature, too. Features are internally registered like providers if I saw it correctly in the RESTEasy code.

For registering Resources and Providers I basically copied ServletContainerDispatcher.java. Looks like they haven't added Features yet so I don't have a good reference on how to do it automatically.

But, again as Ben correctly pointed out, Resteasy is registering it internally as a Provider using providerFactory.registerProvider, this is why I think the proposed workaround could work. If not I could some code to allow the explicit contribution of Features

timcu commented 3 years ago

Thanks for looking into this. Here is my proof of concept project

https://github.com/timcu/tapestry-agrest-example

benweidig commented 3 years ago

As I wrote on the mailing list, the line https://github.com/tynamo/tapestry-resteasy/blob/15362b94cbcdc24454f209d3163de8a290c6b7fb/src/main/java/org/tynamo/resteasy/ResteasyRequestFilter.java#L148 seems to be the problem (and the instance variant a little lower).

I tried to work on this on our fork, but didn't found the time to actually test it... It shouldn't be the task of tapestry-resteasy to decide what's a provider and what's not. The extra-handling for resources is fine, but the rest of the contributions could be assumed to be provider-compatible. I'm pretty sure that RestEasy itself is checking for eligibility.

Our ResteasyRequestFilter#processApplication(Application) (we're still using T5.6.x) looks like this:

    private void processApplication(Application config) {
        log.info("Deploying {}: {}", Application.class.getName(), config.getClass());

        List<Runnable> registerResources = new ArrayList<>();
        List<Runnable> registerProviders = new ArrayList<>();

        if (config.getClasses() != null) {
            for (Class<?> clazz : config.getClasses()) {
                if (GetRestful.isRootResource(clazz)) {
                    registerResources.add(() -> this.dispatcher.getRegistry().addPerRequestResource(clazz));
                }
                else {
                    registerProviders.add(() -> this.providerFactory.registerProvider(clazz));
                }
            }
        }

        if (config.getSingletons() != null) {
            for (Object obj : config.getSingletons()) {
                if (GetRestful.isRootResource(obj.getClass())) {
                    registerResources.add(() -> this.dispatcher.getRegistry().addSingletonResource(obj));
                }
                else {
                    registerProviders.add(() -> this.providerFactory.registerProviderInstance(obj));

                }
            }
        }

        registerProviders.forEach(Runnable::run);
        registerResources.forEach(Runnable::run);
    }

We register everything that's contributed as a provider, except resources. But just like @ascandroli we only use Providers so far, and the code isn't tested with Features yet.