Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Incompatible with agrest #19

Open
timcu opened this issue Aug 7, 2021 · 3 comments
Open

Incompatible with agrest #19

timcu opened this issue Aug 7, 2021 · 3 comments

Comments

@timcu
Copy link

timcu commented Aug 7, 2021

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.

  • add tapestry-resteasy to project
<dependency>
    <groupId>org.tynamo</groupId>
    <artifactId>tapestry-resteasy</artifactId>
    <version>0.7.0</version>
</dependency>
  • write a service to setup the AgRuntime
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;
  }
}
  • write a simple resource with a single @get method using Agrest
@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 the AgRuntime and resource like in my previous mail.
    @Contribute(javax.ws.rs.core.Application.class)
    public static void configureRestProviders(Configuration<Object> singletons, AgrestService svcAgrest) {
        singletons.add(svcAgrest.agRuntime());
        singletons.addInstance(CategoryResource.class);
    }
  • test it with your browser or curl

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
Copy link
Member

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
Copy link
Author

timcu commented Aug 16, 2021

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

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

@benweidig
Copy link

As I wrote on the mailing list, the line

} else if (clazz.isAnnotationPresent(Provider.class)) {
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.

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants