Where to put #OpenAPIDefinition? - annotations

The documentation for defining general API information using the quarkus-smallrye-openapi extension is extremely sparse, and does not explain how to use all the annotations for setting up the openApi generation.
For some background, I am using a clean and largely empty project (quarkus version1.0.1.FINAL) generated from code.quarkus.io, with a single class defined as followed (With the attempted #OpenAPIDefinition annotation):
#OpenAPIDefinition(
info = #Info(
title = "Custom API title",
version = "3.14"
)
)
#Path("/hello")
public class ExampleResource {
#GET
#Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "hello";
}
}
I have eventually found that general api information (contact info, version, etc) through much digging is defined using the #OpenAPIDefinition annotation, but when used on my existing endpoint definition, no changes are made to the generated openapi specification. What am I doing wrong?

Try putting the annotation on the JAX-RS Application class. I realize you don't need one of those in a Quarkus application, but I think it doesn't hurt either. For reference in the specification TCK:
https://github.com/eclipse/microprofile-open-api/blob/master/tck/src/main/java/org/eclipse/microprofile/openapi/apps/airlines/JAXRSApp.java

Related

#Gateway(payloadExpression="..") vs #Payload("...")

Spring integration documentation explains that a payload expression must be specified when declaring a gateway from an interface method with no arguments, so that the framework knows what payload should be set on the generated message.
However, if I do the following:
<int:gateway id="myGateway"
service-interface="com.example.MyGateway"
default-request-channel="requestChannel"
default-reply-channel="replyChannel" />
for the following interface:
package com.example;
public interface MyGateway {
#Gateway(payloadExpression = "''")
String doSomething();
}
this leads to an error: "receive is not supported, because no pollable reply channel has been configured".
This works instead:
public interface MyGateway {
#Payload("''")
String doSomething();
}
Indeed, the same above documentation specifies that the payload should be specified with either #Payload or with payload-expression attribute on method elements.
However, as a user, I find it quite surprising that setting a payload expression through the #Gateway annotation does not work here, especially because the same annotation works in other contexts.
Is this on purpose or an oversight?
It is not clear why the documentation is confusing, but feel free to suggest improvements.
The #Gateway annotation is intended for configuration when using annotation-based configuration
https://docs.spring.io/spring-integration/docs/5.3.2.RELEASE/reference/html/messaging-endpoints.html#gateway-configuration-annotations
The docs clearly state to use #Payload or payload-expression when using XML configuration.

Ribbon: Unable to set default configuration using #RibbonClients(defaultConfiguration=...)

The #RibbonClients annotation allows us to customise the Ribbon configuration per client. This process is described in the documentation at http://projects.spring.io/spring-cloud/spring-cloud.html#_customizing_the_ribbon_client
This is all fine. I tried to use the same approach to override the default configuration that should be applied to all my clients. So I defined the following configuration class and made sure it is considered by the component scan:
#Configuration
#RibbonClients(defaultConfiguration = MyDefaultRibbonConfig.class)
public class MyRibbonAutoConfiguration {
}
Unfortunately, it turns out that MyDefaultRibbonConfig is not taken into account when building the ribbon client's application context. A quick look and trace at RibbonClientConfigurationRegistrar let me think my #RibbonClients(default=...) annotation is unconditionally overridden by the one provided by org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration.
However, it works if the #RibbonClients annotation is set on a inner class instead of a top-level class:
#Configuration
public class MyRibbonAutoConfiguration {
#Configuration
#RibbonClients(defaultConfiguration = MyDefaultRibbonConfig.class)
static class SubConfig {
}
}
This is a side-effect the strategy followed by RibbonClientConfigurationRegistrar to give a name to the discovered configuration beans:
registerClientConfiguration(registry,
"default." + metadata.getEnclosingClassName(),
attrs.get("defaultConfiguration"));
The configuration for annotations declared on a top-level class are then registered with a bean name set to default.null.defaultConfiguration - so the next one overrides the previous (not sure the order is predictable though).
This behaviour looks strange to me. Did I miss something? Should I proceed differently?
This was an issue in SpringCloud-Netflix 1.0.1. See https://github.com/spring-cloud/spring-cloud-netflix/issues/374 for more information.

Why is my projection interface not picked up by Spring Data REST?

I am trying to use up projections with Spring Data REST (version 2.3.0.RELEASE). I read the reference documentation, and gathered that these are the parts I need:
A JPA Entity
#Entity
public class Project implements Serializable {
#Basic(optional = false)
#Column(name = "PROJECT_NAME")
private String projectName;
// ... lots and lots of other stuff
}
A repository that works with that entity
#Repository
public interface ProjectRepository extends JpaRepository<Project, Long> { }
And a projection to retrieve just the name for that entity
#Projection(name="names", types={Project.class})
public interface ProjectProjectionNamesOnly {
String getProjectName();
}
I would like to be able to optionally retrieve just a list of names of projects, and projections seemed perfectly suited to this. So with this setup, I hit my endpoint at http://localhost:9000/projects/1?projection=names. I get back ALL of the attributes and collections links, but I expected to get back just the name and self link.
I also viewed the sample project on projections, but the example is for excerpts, which seems different from projections as it is a different section of the reference. I tried it and it didn't work anyway though.
So the question is this: How do you use spring data rest projections to retrieve just a single attribute of an entity (and its self link)?
Looks like your projection definition is not even discovered and thus it doesn't get applied if you select it for the HTTP request.
For projection interfaces to be auto-discovered they need to be placed inside the very same or a sub-package of the package of the domain type they're bound to.
If you can't put the type into that location, you can manually register a projection definition on RepositoryRestConfiguration by calling ….projectionConfiguration().addProjection(…).
The reference documentation does not really mention this at the moment but there's already a ticket to get this fixed in future versions.

Implementing Hypermedia in RESTful JAX-RS Apache CXF

I am working in a RESTful application developed in Apache CXF and I would like to introduce hypermedia functionality to it.
Most of our jaxrs:serviceBeans follow this template:
#GET
#Path("/{exampleId}")
public ExampleJSON get(#PathParam("exampleId") Integer exampleId) {
ExampleJSON example;
// Load data from repository here...
// Add link to self.
String href = javax.ws.rs.core.Link.fromResource(ExampleService.class).build().getUri().toString();
// HypermediaLink is a custom object to hold a "href" and "rel" strings
HypermediaLink linkToSelf = new HypermediaLink();
linkToSelf.setHref(href + example.getId());
linkToSelf.setRel("self");
// Inherited method, just adds a HypermediaLink to a collection in the parent class
example.addHypermediaLink(linkToSelf);
// Return JSON compatible object, JACKSON will serialize it nicely.
return example;
}
This is the basic concept. Keep in mind that I simplified this code for explanation purposes; so, it can be easily understood.
This code works fine; but I am wondering if there is a better way to do this with Apache CXF. I have some ideas for how to enhancing it; however, it will require some custom annotations.
I see some examples using Jersey, but I would like to stick with Apache CXF.
Any help would be appreciated.
Thanks
I would leverage some features of JAX-RS and / or Jackson to implement the link adding under the hood at the serialization level. So you wouldn't need to have a specific field for the link within the bean itself.
You could implement a custom MessageBodyWriter to generate a different JSON payload (for example) for your POJOs than the default. So you could dynamically add the link.
See this answer for more details: How to write an XML MessageBodyWriter provider with jersey.
If you use Jackson for the serialization, you could implement a custom serializer. Note that this is generic and will work for all supported format of Jackson.
Below is a sample code:
public class LinkBeanSerializer extends JsonSerializer<SomeBean> {
#Override
public void serialize(SomeBean bean, JsonGenerator jgen,
SerializerProvider provider) throws IOException,
JsonProcessingException {
jgen.writeStartObject();
// Fields
jgen.writeNumberField("id", bean.getId());
// Things could be generic using reflection
// Link
String href = javax.ws.rs.core.Link.fromResource(SomeBean.class).build().getUri().toString();
HypermediaLink linkToSelf = new HypermediaLink();
linkToSelf.setHref(href + bean.getId());
linkToSelf.setRel("self");
jgen.writeObjectField("hypermediaLink", linkToSelf);
jgen.writeEndObject();
}
}
Note that we could make this serializer more generic I think (something like extends JsonSerializer<Object>)
See this answer for more details: Processing JSON response using JAX-RS (how to register the custom serializer within JAX-RS, ...).
Perhaps implementing a WriterInterceptor could solve your problem but there is impact on the beans since you need to have field hypermediaLink. The interceptor could be responsible of filling the field.
See this answer for more details: Jersey Update Entity Property MessageBodyWriter.
IMO the more convenient solution is the second one. It's transparent and support all the formats supported by Jackson.
Hope it helps you,
Thierry

Servlet 3.0 annotations in conjuction with Guice

I am attempting to update a legacy Guice application, and I was wondering if there is any sort of preferred way of doing things when taking Servlet 3.0 annotations into consideration. For example, my application has a filter, FooFilter, which is defined in the Guice Module Factory method configureServlets(), as follows:
Map<String, String> fooParams = new HashMap<String, String>();
fooParams.put("someParam", "parameter information");
filter("/foo.jsp","/foo/*").through(com.example.filter.FooFilter.class, fooParams);
Is the above binding still necessary, or will it interfere with the following using the #WebFilter Servlet 3.0 annotation:
#Singleton
#WebFilter(
filterName="FooFilter",
urlPatterns={"/foo.jsp", "/foo/*"},
initParams = {
#WebInitParam(name="foo", value="Hello "),
#WebInitParam(name="bar", value=" World!")
})
public class FooFilter implements Filter {
etc....
Which method is now preferred? Will they mess with each other?
I just made quick draft how could a Servlet 3.0 support looks like. There could be a more elegant way to just call filter(Filter Class with WebFilter annotation) in configureServlet method, but that requires updated right to guice-servlet module, which is quite hard to distribute.
Well, what I did is a project at Github: https://github.com/xbaran/guice-servlet3
all you need to do is download and build. It is created on top of Guice 3.0 and works like this:
new Servlet3Module() {
#Override
protected void configureServlets3() {
scanFilters(FooFilter.class.getPackage());
}
};
The Servlet3Module extends ServletModule and contains a scanFilters method with package argument. This method will scan provided package from your classpath and try to register all classes with annotation WebFilter via filter() method.
This scan idea is based on Sitebricks (guice web framework created by Dhanji R. Prasanna) configuration system.
Honestly, I just make a draft, never try if it works. But hopefully it will. If you have any problem or question, just let me know.
PS: The support for servlets, listeners and so on could be added to, if you wish.