Using #RequestLine with Feign - java

I have a working Feign interface defined as:
#FeignClient("content-link-service")
public interface ContentLinkServiceClient {
#RequestMapping(method = RequestMethod.GET, value = "/{trackid}/links")
List<Link> getLinksForTrack(#PathVariable("trackid") Long trackId);
}
If I change this to use #RequestLine
#FeignClient("content-link-service")
public interface ContentLinkServiceClient {
#RequestLine("GET /{trackid}/links")
List<Link> getLinksForTrack(#Param("trackid") Long trackId);
}
I get the exception
Caused by: java.lang.IllegalStateException: Method getLinksForTrack not annotated with HTTP method type (ex. GET, POST)
Any ideas why?

I wouldn't expect this to work.
#RequestLine is a core Feign annotation, but you are using the Spring Cloud #FeignClient which uses Spring MVC annotations.

Spring has created their own Feign Contract to allow you to use Spring's #RequestMapping annotations instead of Feigns. You can disable this behavior by including a bean of type feign.Contract.Default in your application context.
If you're using spring-boot (or anything using Java config), including this in an #Configuration class should re-enable Feign's annotations:
#Bean
public Contract useFeignAnnotations() {
return new Contract.Default();
}

The reason I got this error is that I used both #FeignClient and #RequestLine annotations in my FooClient interface.
Before a fix.
import org.springframework.cloud.openfeign.FeignClient; // #FeignClient
import feign.RequestLine; // #RequestLine
import org.springframework.web.bind.annotation.PathVariable;
#FeignClient("foo")
public interface FooClient {
#RequestLine("GET /api/v1/foos/{fooId}")
#Headers("Content-Type: application/json")
ResponseEntity getFooById(#PathVariable("fooId") Long fooId); // I mistakenly used #PathVariable annotation here, but this should be #Param
}
Then, I got this error.
Caused by: java.lang.IllegalStateException: Method FooClient#getFooById(Long) not annotated with HTTP method type (ex. GET, POST)
After a fix
// removed #FeignClient
// removed #PathVariable
import feign.Param; // Added
import feign.RequestLine; // #RequestLine
// removed #FeignClient("foo")
public interface FooClient {
#RequestLine("GET /api/v1/foos/{fooId}")
#Headers("Content-Type: application/json")
Foo getFooById(#Param("fooId") Long fooId); // used #Param
}
If you are interested in the configuration classes.
Please note that I tried to create Feign Clients Manually.
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScans;
import org.springframework.context.annotation.Configuration;
#Configuration
#ComponentScans(value = {
#ComponentScan(basePackages = {
"com.example.app.service.web.client",
})
})
public class FeignConfig {
#Value(value = "${app.foo.service.client.url}")
protected String url; // http://localhost:8081/app
#Bean
public FooClient fooClient() {
FooClient fooClient = Feign.builder()
// .client(RibbonClient.create())
.client(new OkHttpClient())
.encoder(new GsonEncoder())
.decoder(new GsonDecoder())
.logger(new Slf4jLogger(FooClient.class))
.logLevel(Logger.Level.FULL)
.target(FooClient.class, url);
return fooClient;
}
}
References
https://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-feign.html
https://www.baeldung.com/intro-to-feign
https://www.baeldung.com/feign-requestline
https://stackoverflow.com/a/32488372/12898581

Your #RequestMapping value looks ok, but you're likely should consider slightly rewriting it:
#GetMapping(value = "/{trackid}/links")
List<Link> getLinksForTrack(#PathVariable(name = "trackid") Long trackId);
Btw I did not succeeded with getting #RequestLine to work due to same error as yours.
Also for #ReactiveFeignClients Contract.Default() yields to following errors:
java.lang.IllegalStateException: Method MyClient#doStuff(String,String) not annotated with HTTP method type (ex. GET, POST)
Warnings:
- Class MyClient has annotations [Component, ReactiveFeignClient, Metadata] that are not used by contract Default
- Method doStuff has an annotation GetMapping that is not used by contract Default
and should be fixed like:
var MyClient = WebReactiveFeign.builder()
.contract(new ReactiveContract(new SpringMvcContract()))
.target(MyClient, "http://example.com")

Related

Optional #PathVariable in Spring based rest call

I am using Spring boot 2.2.2 with java 1.8. I have a rest end point /getViewAnalyticsByDimension of post type. Now to accommodate a feature I need to change the api to /getViewAnalyticsByDimension/{priceAgreementId}. I want to declare path variable priceAgreementId as optional. But when I post the request to /getViewAnalyticsByDimension when there is no price agreement id from Postman it is not able to find the controller method. I can ask my frontend counter-part to send "blank" when there is no priceAgreementId and the actual priceAgreementId when it is available. But that is not elegant.
You can have multiple request mappings for a given method. So in your case you can do something as follows to make it work with and without path variable:
#RequestMapping(value = {"/getViewAnalyticsByDimension", "/getViewAnalyticsByDimension/{priceAgreementId}"}")
public Object getObject(#PathVariable Optional<Object> priceAgreementId) {
//...
}
If you are using a legacy version of spring you can have two different methods:
#RequestMapping(value = "/getViewAnalyticsByDimension/{priceAgreementId}")
public Object getObject(#PathVariable(name = "priceAgreementId") Object priceAgreementId) {
//...
}
#RequestMapping(value = "/getViewAnalyticsByDimension")
public Object getObject() {
//...
}
You can checkout https://www.baeldung.com/spring-optional-path-variables more info
You have to modify your method following way,
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Optional;
#RestController
public class Test {
#RequestMapping(value = {"/getViewAnalyticsByDimension", "/getViewAnalyticsByDimension/{priceAgreementId}"})
public Object getObject(#PathVariable(required = false) Optional<String> priceAgreementId) {
if (priceAgreementId.isPresent()) {
// do something
} else {
// do something else
}
return something;
}
}
I checked both URLs accessible, (It's just an example)
http://localhost:8888/getViewAnalyticsByDimension
http://localhost:8888/getViewAnalyticsByDimension/5

Custom AOP spring boot annotation with ribbon client blocking api call with return "1"

I have really poor experience with ribbon/eureka so forgive me if this is a stupid question:
I have two different microservice both connected to a discovery server, the first one calls the second using a custom annotation that sends a request using rest template.
Custom annotation name is PreHasAuthority
Controller :
#PreHasAuthority(value="[0].getProject()+'.requirements.update'")
#PostMapping(CREATE_UPDATE_REQUIREMENT)
public ResponseEntity<?> createUpdateRequirement(#Valid #RequestBody RequirementDTO requirementDTO
, HttpServletRequest request, HttpServletResponse response) {
return requirementService.createUpdateRequirement(requirementDTO, request, response);
}
Annotation interface :
import java.lang.annotation.*;
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface PreHasAuthority {
String value();
}
Annotation implementation:
import java.lang.reflect.Method;
import javax.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;
import netcomgroup.eu.service.AuthenticationService;
#Aspect
#Component
public class PreHasAuthorityServiceAspect {
#Autowired
private AuthenticationService authenticationService;
#Around(value = "#annotation(PreHasAuthority)")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
PreHasAuthority preHasAuthority = method.getAnnotation(PreHasAuthority.class);
Object[] args = joinPoint.getArgs();
String permission = preHasAuthority.value();
ExpressionParser elParser = new SpelExpressionParser();
Expression expression = elParser.parseExpression(permission);
String per = (String) expression.getValue(args);
String token =null;
for(Object o : args) {
if(o instanceof HttpServletRequest) {
HttpServletRequest request = (HttpServletRequest)o;
token=request.getHeader("X-Auth");
break;
}
}
if(token==null) {
throw new IllegalArgumentException("Token not found");
}
boolean hasPerm = authenticationService.checkPermission(per,token);
if(!hasPerm)
throw new Exception("Not Authorized");
}
}
My Ribbon configuration
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RoundRobinRule;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
public class RibbonConfiguration {
#Autowired
IClientConfig config;
#Bean
public IRule ribbonRule(IClientConfig config) {
return new RoundRobinRule();
}
}
Eureka config in application properties
#Eureka config
eureka.client.serviceUrl.defaultZone= http://${registry.host:localhost}:${registry.port:8761}/eureka/
eureka.client.healthcheck.enabled= true
eureka.instance.leaseRenewalIntervalInSeconds= 10
eureka.instance.leaseExpirationDurationInSeconds= 10
by calling the api from postman request is sendend correctly to the second microservice and i'm certain the return is "true".
After that the request stops before entering the createUpdateRequirement method and returns '1' as postman body response. No error of sort is provided.
My guess is that the problem resides within the custom annotation, cause when i remove the annotation the api call works perfectly, but i cannot understand the problem as it seems all setted up correctly to me.
Your #Around advice never calls joinPoint.proceed(). Hence, the intercepted target method will never be executed.
The second problem is that your advice method returns void, i.e. it will never match any method returning another type such as the ResponseEntity<?> createUpdateRequirement(..) method.
Besides, around is a reserved keyword in native AspectJ syntax. Even though it might work in annotation-driven syntax, you ought to rename your advice method to something else like aroundAdvice or interceptPreHasAuthority - whatever.
Please do read an AspectJ or Spring AOP tutorial, especially the Spring manual's AOP chapter. 😉

micronaut #RequestScope - not creating bean per incoming http-request

I have a class the following class as RequestScope bean:
#RequestScope
class RequestContext {
private String requestId;
private String traceId;
private String authorisedId;
private String routeName;
// few more fields
#Inject RequestContext(SecurityContext securityContext) {
this.requestId = UUID.randomUUID().toString();
if(securityService.getAuthentication().isPresent()){
this.authorisedId = (securityService
.getAuthentication().get()).getUserId().toString();
}
}
/* to be updated in controller method interceptors */
public void updateRouteName(String name){
this.routeName = name;
}
The idea is to have an object containing the REST request level custom data accessible across the application, the scope of the this obviously should be within the current request. This can be used for say.. logging - whenever devs log anything from the application, some of the request meta data goes with it.
I am not clear what the #RequestScope bean really is:
From its definition - my assumption is it is created for every new http-request and same instance is shared for the life of that request.
when is it constructed by Micronaut ? Is it immutable ?
Across multiple requests I can see the same requestId ( expecting new UUID for every request)
Is it the right use-case for #RequestScope bean?
I was running into an issue regarding #RequestScope so I'll post an answer here for others.
I was trying to inject a #RequestScope bean into an HTTP filter, set a value in the bean, and then read it later from another bean. For example
#RequestScope
class RequestScopeBean() {
var id: Int? = null
}
#Filter
class SetRequestScopeBeanHere(
private val requestScopeBean: Provider<RequestScopeBean>
) {
override fun doFilterOnce(request: HttpRequest<*>, chain: ServerFilterChain): Publisher<MutableHttpResponse<*>> {
requestScopeBean.get().id = // id from Http Request
}
}
#Singleton
class GetRequestScopeBeanHere(
private val requestScopeBean: Provider<RequestScopeBean>
) {
fun getIdFromRequestScopeBean() {
println(requestScopeBean.get().id)
}
}
In this example before any controller is executed my filter (SetRequestScope) is called, this will set requestScopeBean.id but the key is that the request scope bean must be wrapped in a javax.inject.Provider, otherwise setting the field won't work.
Down the line, when GetRequestScopeBeanHere::getIdFromRequestScopeBean is called it'll have access to the requestScopeBean.id set earlier
This is intentional by Micronaut:
https://github.com/micronaut-projects/micronaut-core/issues/1615
when is it constructed by Micronaut ?
A #RequestScope bean is created during request processing, the first time the bean is needed.
Is it immutable ?
It could be. You get to decide if the bean is mutable or not when you write the class. As written in your example, RequestContext is mutable. If you remove the updateRouteName method, that bean would be immutable.
Is it the right use-case for #RequestScope bean?
I don't think so, but that is really an opinion based question.
EDIT: Based On Comments Added Below
See the project at https://github.com/jeffbrown/rscope.
https://github.com/jeffbrown/rscope/blob/2935a4c1fc60f350198d7d3c1dbf9a7eedd333b3/src/main/java/rscope/DemoController.java
package rscope;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
#Controller("/")
public class DemoController {
private final DemoBean demoBean;
public DemoController(DemoBean demoBean) {
this.demoBean = demoBean;
}
#Get("/doit")
public String doit() {
return String.format("Bean identity: %d", demoBean.getBeanIdentity());
}
}
https://github.com/jeffbrown/rscope/blob/2935a4c1fc60f350198d7d3c1dbf9a7eedd333b3/src/main/java/rscope/DemoBean.java
package rscope;
import io.micronaut.runtime.http.scope.RequestScope;
#RequestScope
public class DemoBean {
public DemoBean() {
}
public int getBeanIdentity() {
return System.identityHashCode(this);
}
}
https://github.com/jeffbrown/rscope/blob/2935a4c1fc60f350198d7d3c1dbf9a7eedd333b3/src/test/java/rscope/DemoControllerTest.java
package rscope;
import io.micronaut.http.client.RxHttpClient;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.test.annotation.MicronautTest;
import org.junit.jupiter.api.Test;
import javax.inject.Inject;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
#MicronautTest
public class DemoControllerTest {
#Inject
#Client("/")
RxHttpClient client;
#Test
public void testIndex() throws Exception {
// these will contain the identity of the the DemoBean used to handle these requests
String firstResponse = client.toBlocking().retrieve("/doit");
String secondResponse = client.toBlocking().retrieve("/doit");
assertTrue(firstResponse.matches("^Bean identity: \\d*$"));
assertTrue(secondResponse.matches("^Bean identity: \\d*$"));
// if you modify DemoBean to be #Singleton instead of
// #RequestScope, this will fail because the same instance
// will be used for both requests
assertNotEquals(firstResponse, secondResponse);
}
}

How to pass an argument to a Spring Boot REST Controller?

I am trying to pass an argument to my #RESTController Spring Boot class.
In the #POSTMapping method I want to use a method of a self defined Java class for processing the received body and returning a response.
The Spring application is launched in Application.java. The Controller-Object seems to get created implicitly.
I already tried adding a constructor to my RESTController class. But I couldn't find a way to call that constructor with an argument.
// Application.java
public static void main (String[] args) {
SpringApplication.run(Application.class, args);
}
//ConnectorService.java
#RestController
public class ConnectorService {
private Solveable solver;
public ConnectorService() {}
public ConnectorService (Solveable solveable) {
this.solver = solveable;
}
#CrossOrigin(origins = "http://localhost:3000")
#PostMapping(path = "/maze")
public Solution Test(#RequestBody Test test) {
return solver.solve(test);
}
}
Even though i could define a second constructor, i didn't find any way to call it with my Object.
Use #RequestParam annotation to pass an argument
You can pass parameter with #RequestParam annotation like this:
#CrossOrigin(origins = "http://localhost:3000")
#PostMapping(path = "/maze")
public Solution Test(#RequestParam("paramName") String param, #RequestBody Test test) {
return solver.solve(test);
}
And you can put it with http request:
http://localhost:3000/maze?paramName=someValue
Assuming that you have POST request, there may be different ways to build this request, depending on the API testing tools you use.
#RestController follows the same rules for dependency injection as any other #Component in Spring framework.
If you have a single constructor, Spring will try to „inject” the parameters while instantiating the controller.
You need to register your dependency as a Spring bean.
It seems that you are new to Spring and you are starting with advanced topics like Spring Boot and rest controllers. Please find some time to read about the basics.
Yo can create a Bean configuration file to initialize your objects like:
#Configuration
#ComponentScan("com.xxx.xxx") // the base package you want to scan
public class Config {
#Bean
//where Solveable is a class and is annotated with an Spring's annotation
public Solveable solveable() {
return new Solveable();
}
}
And use the #Autowired annotation to inject the object in:
#Autowired
public ConnectorService (Solveable solveable) {
this.solver = solveable;
}
This last block will initialize or pass(what you want) the object to the ConnectorService class.

Jersey: Error when a class has both JAX-RS and JAX-WS annotations

Using Jersey 1.7, JAX-WS 2.2.3, Tomcat 6.0.30 and the following method declaration prevents Jersey servlet to start:
#POST
#Produces("text/plain")
public void postIt(#WebParam(name = "paramOne") final String paramOne,
final String paramTwo) {
// ...
}
The generated exception is:
SEVERE: Missing dependency for method public
java.lang.String com.sun.jersey.issue.MyResource.postIt(
java.lang.String,java.lang.String) at parameter at index 0
SEVERE: Method, public void
com.sun.jersey.issue.MyResource.postIt(
java.lang.String,java.lang.String),
annotated with POST of resource,
class com.sun.jersey.issue.MyResource,
is not recognized as valid resource method.
If the #WebParam annotation is removed, it all works fine.
Now, please have in mind that I am not trying to work with mere strings, rather, I am migrating complicated Objects that got marshalled/unmarshalled using SOAP to RESTful services, but I must provide both interfaces for a while, without breaking the previous WASDs. The method is just a minimalistic scenario.
Has any of you any idea of the status of this? Has it been fixed? Suggestions?
The specification is clear on this. Section 3.3.2.1 tells us that:
Resource methods MUST NOT have more
than one parameter that is not
annotated with one of the above listed
annotations.
The above listed annotations are the JAX-RS parameter annotations: #QueryParam, #MatrixParam, etc.
There is, however, a Jersey specific way to solve this problem. Using InjectableProvider. So, a method that defines two non-JAX-RS parameters:
#POST
public void postIt(#CustomInjectable final Customer customer,
final Transaction transaction) {
// ...
}
Of course, we have to code the annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.PARAMETER)
public #interface CustomInjectable {
}
An implementation of InjectableProvider that knows how to provide Customers:
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.InjectableProvider;
import com.sun.jersey.api.model.Parameter;
#Provider
public class CustomerInjectableProvider implements
InjectableProvider<CustomInjectable, Parameter> {
// you can use #Context variables, as in any Provider/Resource
#Context
private Request request;
public ComponentScope getScope() {
// ComponentScope.Singleton, Request or Undefined
}
public Injectable getInjectable(ComponentContext i,
CustomInjectable annotation,
Parameter param) {
Injectable injectable = null;
if (Customer.getClass().isAssignableFrom(param.getParameterClass()) {
injectable = getInjectable();
}
return injectable;
}
private Injectable getInjectable() {
return new Injectable<Customer>() {
public Customer getValue() {
// parse the customer from request... or session... or whatever...
}
};
}
}
But, Jersey considers only the last annotation (see JERSEY-ISSUE-731), so be careful.
And, a more portable way (if you do care about that, anyway):
// simple bean
public class CustomerWithTransaction {
private Customer customer;
private Transaction transaction;
// getters and setters
}
Then change the method to:
#POST
public void postIt(CustomerWithTransaction customerWithTransaction) {
// ...
}
Then create your own MessageBodyReader for CustomerWithTransaction, where you can also access any context variables (request, headers, etc.).

Categories

Resources