Spring Cloud Feign Client #RequestParam with List parameter creates a wrong request - spring-cloud

I have a Spring Clound Feign Client mapping defined as following
#RequestMapping(method = RequestMethod.GET, value = "/search/findByIdIn")
Resources<MyClass> get(#RequestParam("ids") List<Long> ids);
when I call
feignClient.get(Arrays.asList(1L,2L,3L))
according to what I can see in the debugger, the feign-core library forms the following request:
/search/findByIdIn?ids=1&ids=2&ids=3
instead of expected
/search/findByIdIn?ids=1,2,3
which would be correct for the server Spring Data REST endpoint declared in the same way as my Feign client method.
Thus, because of this issue, the request always returns empty set.
I have seen similar question, but it looks like the Feign client was working as I expect back in 2015.
I am using:
spring-cloud-starter-feign version 1.2.4.RELEASE
feign-httpclient version 9.4.0
feign-core version 9.4.0
Is there a way to correct the behaviour and "marry" the Spring Cloud Feign Client with the Spring Data REST defined endpoints?

I had the same issue with multiple occurence of the parametre instead of the expected comma separated sequence of items. The solution was really simple:
In my feign client I used arrays
feignClient.get(new Long[]{1L,2L,3L})
instead of collection/list:
feignClient.get(Arrays.asList(1L,2L,3L))

In Feign you can annotate your controller with the following
#CollectionFormat(feign.CollectionFormat.CSV) and it will process collections in
the CSV format findByIdIn?ids=1&ids=2&ids=3

Thanks #prola for your answer.
Just to add an explicit example, #CollectionFormat(feign.CollectionFormat.CSV) annotation targets a method; you can't apply globally to your Feign Client interface.
So each method will be similar to:
#RequestMapping(value = ["/objects"], method = [RequestMethod.GET])
#CollectionFormat(feign.CollectionFormat.CSV)
fun findById(
#RequestParam(value = "object.id", required = true) id: String,
#RequestParam(value = "object.fields", required = false) objectFields: List<String> = DEFAULT_FIELDS_LIST,
#RequestParam(value = "format") format: String = FORMAT,
): ResponseEntity<ObjectsDTO>
The result will be
/objects?object.fields=size,weight,location
instead of
/objects?object.fields=size&object.fields=weight&object.fields=location
You can also refer to:
1.16.Feign CollectionFormat support
OpenFeign #542: Support Multiple Collection Formats

I've just battled with this today, and the solution for me was surprisingly simple.
If you use brackets [] for denoting query array:
Resources<MyClass> get(#RequestParam("ids[]") List<Long> ids);
it will create a request that looks like this
/search/findByIdIn?ids[]=1&ids[]=2&ids[]=3
Most server side frameworks will interpret this as an array.
If your server is also in spring then you can pick this up like this
#GetMapping("/search/findByIdIn")
public ResponseEntity findByIdIn(#RequestParam("ids[]") List<Long> ids) { ... }
Just keep in mind that the query has to be encoded, [] gets encoded to %5B%5D.

Related

OpenAPI spec for reactive REST service using Quarkus

The jouney so far
I'm trying to get a reactive REST service up and running, following the official guide, using RESTEasy and JSON-B.
I also added support for OpenAPI for testing the service following this guide.
Both parts work on their own, the service properly returns the hard coded demo data. The Swagger UI shows the available routes and allows to invoke them.
However, it's not as smooth as I liked it to be...
From the simple, non-reactive routes, schemas have been correctly extracted:
Fruit:
type: object
properties:
description:
type: string
name:
type: string
But from the reactive routes, empty schemas have been extracted. For example, introducing
#GET
#Path("/{name}")
public Uni<Fruit> getOne(#PathParam(value = "name") String name) {
}
resulted in the schema:
UniFruit:
type: object
Is there a way to re-use the existing Fruit schema?
I tried annotating the route, but that didn't have any effect:
#GET
#Path("/{name}")
// #Schema(ref = "#/components/schemas/Fruit") // Nope...
// #Schema(ref = "Fruit") // Nope...
public Uni<Fruit> getOne(#PathParam(value = "name") String name) {
}
Ideally, I wouldn't want to annotate every reactive method separately anyways.
The question
Is there a way to configure project-wide to use the schema of T whenever a route returns Uni<T> or Multi<T>?
I have browsed the MicroProfile OpenAPI spec https://github.com/eclipse/microprofile-open-api/blob/master/spec/src/main/asciidoc/microprofile-openapi-spec.adoc but could not find a way to do the project wide schema change you request. You can do it for a given class, but here the class is Uni and you want different schemas depending on the parameterized type.
So the fix in Quarkus seems like the viable approach unless SmallRye OpenAPI (the implementation) has some specific ways to do this.
In the meantime, you can use the implementation attribute of the #Schema annotation:
#GET
#Path("/{name}")
#APIResponse(
content = #Content(mediaType = MediaType.APPLICATION_JSON,
schema = #Schema(implementation = Fruit.class)))
public Uni<Fruit> getOne(#PathParam(value = "name") String name) {
}

Renaming an XML/SOAP tag using Apache CXF

I've got a SOAP web-service server using Apache CXF as implementation. Due to some external technical constraint I'd like to be able to rename some XML tags naming an operation parameter (which are deprecated) in the inbound SOAP request. I was reading about using Interceptors for this, but the documentation on how to setup/configure them is not very clear.
My code to publish an endpoint is the following:
Endpoint endpoint = Endpoint.create(
"http://schemas.xmlsoap.org/soap/", new MyServer());
endpoint.publish("ws/endpoint");
Ideally I'd like to add a filter only to a given endpoint (I have several of them).
Apache's documentations about interceptors are quite clear (IMO), anyway, there is a helloworld project (based on spring boot, cxf and maven) in my github profile which you can take a look for setting up interceptors (in fact it's a baisc autentication interceptor).
For setting up an interceptor (e.g InInterceptor), your class should extend AbstractPhaseInterceptor<Message> and override handleMessage(Message message) method, then in the constructor you should declare the phase in which the interceptor is going to be applied. Finally you have to instantiate it and apply in on an Endpoint.
As you said:
rename some XML tags naming an operation parameter (which are
deprecated) in the inbound SOAP request
I think the name of the operation parameter (in WSDL file) is something different from the argument of your web method. Suppose that there is method in your endpoint named addPerson:
#WebMethod
String addPerson(Person person) {
/*method logic*/
}
and Person class:
class Person {
private String firstName;
private String lastName;
private Date birthDate;
//getters and setters
}
in order to map lastName property to a different name, you have to annotate it with
#XmlElement(name = "sureName")
private String lastName;
after applying this anotation, sureName (in wsdl file) is going to be mapped to lastName.
In addition, there is #WebParam annotation which can be used for changing the name of web method arguments:
#WebMethod
String sayHello( #WebParam(name = "sureName") String lastName);
Hope it helps.

How to pass any number of headers to Feign client without knowing all the names?

I've a use case where I need to pass all headers that start with a certain prefix to the feign client. I don't know the number or exact names of these headers. There doesn't seem to be a way to to do this easily as the Feign client expects all headers to be specified using #RequestHeader("name"). It doesn't seem to support something like #RequestHeader HttpHeaders, which would be very useful.
Any suggestions?
As of this writing, Feign doesn't support dynamic headers or query parameters using a Map. The Spring Cloud Feign client relies on the Spring annotations instead of Feign annotations, and the implementations of AnnotatedParameterProcessor have a bug such that they don't do what the documentation states they should be doing.
RequestHeader doc:
If the method parameter is Map, MultiValueMap, or HttpHeaders then the
map is populated with all header names and values.
RequestParam doc:
If the method parameter is Map or MultiValueMap and a parameter name
is not specified, then the map parameter is populated with all request
parameter names and values.
I submitted a pull request that will fix this. Until then, I'm using an extension of SpringMvcContract that uses my own AnnotatedParameterProcessor implementations. I set the custom SpringMvcContract using a Feign.Builder as follows:
#Autowired
FormattingConversionService feignConversionService;
#Bean
#Scope(SCOPE_PROTOTYPE)
public Feign.Builder feignBuilder() {
return HystrixFeign.builder()
.contract(feignContract());
}
#Bean
public Contract feignContract() {
return new EnhancedSpringMvcContract(feignConversionService);
}
From the documentation, you should be able to specify a header map for dynamic headers.
In cases where both the header field keys and values are dynamic and the range of possible keys cannot be known ahead of time and may vary between different method calls in the same api/client (e.g. custom metadata header fields such as "x-amz-meta-" or "x-goog-meta-"), a Map parameter can be annotated with HeaderMap to construct a query that uses the contents of the map as its header parameters.
#RequestLine("POST /")
void post(#HeaderMap Map<String, Object> headerMap);

With Spring Data REST, how to make custom queries use the HATEOAS output format?

I'm learning the Spring 4 stuff by converting an existing Spring 3 project. In that project I have a custom query. That query fetches data in a straightforward way, after which some heavy editing is done to the query results. Now the data is sent to the caller.
I plan on extending CrudRepository for most of my simple query needs. The data will be output in HATEOAS format.
For this custom query I think I should be adding custom behavior (spring.io, "Working with Spring Data Repositories", Section 1.3.1, "Adding custom behavior to single repositories").
As an example:
#Transactional(readOnly = true)
public List<Offer> getFiltered(List<Org> orgs, OfferSearch criteria) {
List<Offer> filteredOffers = getDateTypeFiltered(criteria);
filteredOffers = applyOrgInfo(orgs, filteredOffers);
filteredOffers = applyFilterMatches(filteredOffers, criteria);
return sortByFilterMatches(filteredOffers);
}
(The code merely illustrates that I don't have a simple value fetch going on.)
If I could use the raw results of getDateTypeFiltered(criteria) then I could put that into a CrudRepository interface and the output would be massaged into HATEOAS by the Spring libraries. But I must do my massaging in an actual Java object, and I don't know how to tell Spring to take my output and emit it in my desired output format.
Is there an easy way to get there from here? Or must I try things like do my filtering in the browser?
Thanks,
Jerome.
To properly get HAL formatted results, your query controllers must return some form of Spring HATEOAS Resource type.
#RequestMapping(method = RequestMethod.GET, value = "/documents/search/findAll")
public ResponseEntity<?> findAll() {
List<Resource<Document>> docs = new ArrayList<>();
docs.add(new Resource<Document>(new Document("doc1"), new Link("localhost")));
docs.add(new Resource<Document>(new Document("doc2"), new Link("localhost")));
Resources<Resource<Document>> resources = new Resources<Resource<Document>>(docs);
resources.add(linkTo(methodOn(ApplicationController.class).findAll()).withSelfRel());
resources.add(entityLinks.linkToCollectionResource(Document.class).withRel("documents"));
return ResponseEntity.ok(resources);
}
I have submitted a pull request to Spring Data REST to update its reference docs to specify this in http://docs.spring.io/spring-data/rest/docs/2.4.0.RELEASE/reference/html/#customizing-sdr.overriding-sdr-response-handlers
I am not sure I perfectly got your question. If I did this should be the answer: http://docs.spring.io/spring-data/jpa/docs/1.9.0.RELEASE/reference/html/#repositories.custom-implementations

Can I configure a #FeignClient url using a properties/yml file?

My goal is to create a strategy of different steps to get from a point-to-point communication between 2 components to a "full blown netflix" style of communication using eureka, ribbon, hystrix. With each iteration I want to add more while I try to limit the amount of changes to the actual code. Feign is my preferred client side framework to make this happen. First step is to create a FeignClient to communicate to the server:
#FeignClient(url = "http://localhost:9000")
interface Client {
#RequestMapping(method = RequestMethod.GET, value = "/author/{author}/addedValue/{addedValue}")
Result addToTotal(#RequestParam(value="author") String author, #RequestParam(value="addedValue") long addedValue);
}
This works but I don't want the URL to be hardcoded in the annotation. I would like to have this: #FeignClient()
and have a properties construct like: client.url: http://localhost:9000
So far I couldn't find any clues on how to configure that and I couldn't find a solution in the spring-cloud sources.
Can it be done and if yes; how?
It can be done with a "serviceId" instead of a "url". E.g.
#FeignClient("foo")
interface Client { ... }
and
foo.ribbon.listOfServers: localhost:9000
e.g. see http://projects.spring.io/spring-cloud/spring-cloud.html#spring-cloud-ribbon-without-eureka for docs.
This can be done like this:
#FeignClient(name="fd-mobileapi-service",url="${fdmobile.ribbon.listOfServers}")
Where fdmobile.ribbon.listOfServers : value is a property in application.properties.
I have tested it and it is working.
I got a way to pass the environment variables in a very simple way interface FeignClient,
#FeignClient(url = "https://"+"\${url}")
interface Client {
#RequestMapping(method = RequestMethod.GET, value = "/author/{author}/addedValue/{addedValue}")
Result addToTotal(#RequestParam(value="author") String author, #RequestParam(value="addedValue") long addedValue);
properties
#URL
url.client=${URL}
.env
URL=https:localhost:9000