Skip to content
New issue

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

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

Already on GitHub? Sign in 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.

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

No branches or pull requests

3 participants