2

I have implemented Javax WS RS API MessageBodyReader for com.ca.tas.crypto.cmp.client.GeneralPKIMessageJaxRsReader and MessageBodyWriter for org.bouncycastle.asn1.cmp.PKIMessage so that I can easily use the types in CMP over HTTP REST API. Now, to register the types I have created META-INF/services/javax.ws.rs.ext.Providers file and put the class names there. Everything works fine, I can do REST calls using the API, except:

  • IntelliJ IDEA (or one of the plugins I have installed into it) complains that

    Registered extension should implement javax.ws.rs.ext.Providers

    on the two lines in the file. Based on the resources I have found somewhere on the Internet I thought adding @Provider and @Produces("application/pkixcmp") annotations should be enough.

  • I have noticed that FasterXML Jackson has META-INF/services/javax.ws.rs.ext.MessageBodyReader and META-INF/services/javax.ws.rs.ext.MessageBodyWriter files and those seem to register a class that implements the interfaces as well.

So my questions are:

  • Is the IntelliJ IDEA correct or wrong to complain about me not implementing javax.ws.rs.ext.Providers?

  • What is the correct file to register the MessageBodyReader and MessageBodyWriter implementations?

  • What is the authoritative documentation that would enlighten me about this?

Community
  • 1
  • 1
wilx
  • 17,697
  • 6
  • 59
  • 114

1 Answers1

3

Is the IntelliJ IDEA correct or wrong to complain about me not implementing javax.ws.rs.ext.Providers?

The files in the META-INF/services are part of the way we can create extensible applications by making use of the ServiceLoader. How it works is that the filename should be the name of the contract and the contents of the file should be a list of implementations of that contract. The ServiceLoader will then see the file and collect all the implementations. So using the ServiceLoader, we could do

ServiceLoader<MessageBodyReader> readersLoader
        = ServiceLoader.load(MessageBodyReader.class);

Based on the class passed to the load method, Java will search for the file

META-INF/services/javax.ws.rs.ext.MessageBodyReader

and look at the contents of that file to find all the implementations that it should load.

So based on that information, you can see that IntelliJ is correct in complaining, as your reader and writer do not correctly implement javax.ws.rs.ext.Providers.

One thing I should point out is that I don't think that the ServiceLoader class is used directly as it requires that the service implementations have no-arg constructors. But this is the exact pattern that is used in regards to the META services.

What is the correct file to register the MessageBodyReader and MessageBodyWriter implementations?

The use of META-INF/services files is not something that is a part of the JAX-RS specification. This is an implementation detail that will be specific to the JAX-RS implementation, though this pattern is used a lot. You will mostly see the files being used in reusable libraries, such as the Jackson library you mentioned1.

If the provider is going to be a part of our application, then there are more common ways in which to register it.

  • The @Provider annotation as you mentioned is a marker annotation to detect a provider class that should be registered. When scanning is enabled, the runtime scans for classes annotated with @Provider and then it will register them with the application.

    When we talk about scanning, there are a couple different ways: classpath scanning and package scanning. Classpath scanning is enabled in a JAX-RS application by having an empty Application class that is annotated with @ApplicationPath.

    @ApplicationPath("/api/*")
    public class ApplicationConfig extends Application {}
    

    This is enough to get a JAX-RS application configured2. Classpath scanning will be enabled, which will scan the entire classpath for all classes annotated with @Path and @Provider and register those classes.

    Package scanning is something that is specific to the Jersey implementation. We could configure our application as such

    @ApplicationPath("api")
    public class ApplicationConfig extends ResourceConfig {
        public ApplicationConfig() {
            package("the.package.to.scan");
        }
    }
    

    Here, we are telling Jersey to scan the the.package.to.scan package for @Path and @Provider classes to register with the application.

  • Another way to register our providers is to explicitly register them. In Application subclass, you would override getClasses() or getSingletons() to register them as a class or as an object, respectively.

    @ApplicationPath("/api/*")
    public class ApplicationConfig extends Application {
        private final Set<Class<?>> classes = new HashSet<>();
        private final Set<Object> singletons = new HashSet<>();
    
        public ApplicationConfig() {
            classes.add(MyMessageBodyReader.class);
            singletons.add(new MyMessageBodyReader());
        }
    
        @Override
        public Set<Class<?>> getClasses() {
            return this.classes;
        }
    
        @Override
        public Set<Object> getSingletons() {
            return this.singletons;
        }
    }
    

    Note that once you override either of these methods and return a non-empty set, the classpath scanning is automatically disabled, and you will need to register everything manually.

    If you are using the Jersey implementation, there are also Jersey specific ways that we can explicitly register resources and providers. For more discussion on this, see What exactly is the ResourceConfig class in Jersey 2?.

  • Another way I can think to register providers is through the use of features. We can use the vanilla Feature or we can use a DynamicFeature.

    With the Feature, we register the provider with the entire application

    // We should register the feature with our application
    public class MyFeature implements Feature {
        @Override
        public boolean configure(FeatureContext context) {
             context.register(MyMessageBodyReader.class);
        }
    }
    

    With a DynamicFeature we can selectively register a provider with specific resource methods or resource classes. See more in the Jersey docs for dynamic binding. It should be noted that dynamic binding is more used with filters and interceptors (which are also in the general sense of the term, providers) and not so much with entity providers (MessageBodyReader/Writers).

  • There may be other ways to register your providers, but the one mentioned above are the main ways that you will see it being done in an application.

What is the authoritative documentation that would enlighten me about this?

I'm not sure how much information about META-INF/service files in any documentation. But explicit registration and classpath scanning, you will probably find in the JAX-RS specification or Jersey documentation


1 - It should be noted that just because the file is there, it does not mean that it will be used. It is up to the JAX-RS implementation whether or not they care to use it. For Example, Jersey will not use it on MessageBodyReaders and writers.

2 - See How to use Jersey as JAX-RS implementation without web.xml?

Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720