Error when trying to make a REST Call in Quarkus - java

I'm trying to execute a request at another service that I own.
The guides I'm using to create the app are:
QUARKUS - Using the REST Client
QUARKUS - CDI Reference
QUARKUS - Workshop
I'm getting an error like:
org.jboss.resteasy.spi.UnhandledException: java.lang.RuntimeException: Error injecting com.easy.ecomm.core.product.ProductClient com.easy.ecomm.core.cart.CartService.productClient
org.eclipse.microprofile.rest.client.RestClientDefinitionException: Parameters and variables don't match on interface com.easy.ecomm.core.product.ProductClient::findProductById
Here's the class ProductClient
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
#Path("products")
#RegisterRestClient(configKey = "products-api")
public interface ProductClient {
#GET
#Path("{id}")
Product findProductById(String id);
}
Here's the Service Layer:
import org.eclipse.microprofile.rest.client.inject.RestClient;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
#ApplicationScoped
public class CartService {
#Inject
#RestClient
ProductClient productClient;
public void addItem(String cartId, String productId, Integer amount){
// Code to find the cart on a queue.
Product product = findProduct(productId);
cart.getItems().add(new CartItem(amount, product));
}
private Product findProduct(String productId) {
return productClient.findProductById(productId);
}
}
and the application.properties:
products-api/mp-rest/url=http://localhost:8060
products-api/mp-rest/scope=javax.inject.Singleton
The dependencies are the same as we have on the guides quarkus-rest-client and quarkus-rest-client-jackson
Things that I've already tried:
Remove the ConfigKey from the #RegisterRestClient and use the full path on the application.properties, add the Jandex Plugin on my POM.xml as described here.
But still no success. Each change gives me the same error message.

You forgot to annotate your path variable with #PathParam, that's why it can't instantiate the client:
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import javax.ws.rs.PathParam;
#Path("products")
#RegisterRestClient(configKey = "products-api")
public interface ProductClient {
#GET
#Path("{id}")
Product findProductById(#PathParam("id") String id);
}

Related

Keep getting error "Unable to implement Repository method: RepoClass.updateAll(Iterable arg0). No possible implementations found."

Using Micronaut data v3, I created the following simple classes:
MyEntity.java
package test;
import io.micronaut.data.annotation.GeneratedValue;
import io.micronaut.data.annotation.Id;
import io.micronaut.data.annotation.MappedEntity;
#MappedEntity
public class MyEntity {
#id
#GeneratedValue(GeneratedValue.Type.AUTO)
Long id;
String name;
}
MyRepo.java:
package test;
import io.micronaut.data.jdbc.annotation.JdbcRepository;
import io.micronaut.data.model.query.builder.sql.Dialect;
import io.micronaut.data.repository.CrudRepository;
#JdbcRepository(dialect = Dialect.MYSQL)
public interface MyRepo extends CrudRepository<MyEntity, Long> {
}
The project can build without above 2 classes. Once I add these 2 classes, I got the error
java: Unable to implement Repository method: MyRepo.updateAll(Iterable arg0). No possible implementations found.

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);
}
}

Using #RequestLine with Feign

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")

Play Framework Does Not Create Models

I just downloaded the play framework from their site and am working through this tutorial.
I've noticed the framework creates the folders app/controllers and app/views, but not a models folder. I created it manually and added Task.java to it. When I get to the section entitled "Rendering the first page" and open localhost:9000/tasks I get a compilation error that says package play.models does not exist. Here is what my Task.java looks like:
package models;
import java.util.*;
public class Task {
public Long id;
#Required
public String label;
public static List<Task> all() {
return new ArrayList<Task>();
}
public static void create(Task task) {
}
public static void delete(Long id) {
}
}
Here is application.java, the file generating the compilation error:
package controllers;
import play.*;
import play.mvc.*;
import views.html.*;
import play.data.*;
import play.models.*; // COMPILATION ERROR: "package play.models does not exist"!
public class Application extends Controller {
static Form<Task> taskForm = Form.form(Task.class);
public static Result index() {
//return ok(index.render("Your new application is ready."));
return redirect(routes.Application.tasks());
}
public static Result tasks() {
return ok(views.html.index.render(Task.all(), taskForm));
}
public static Result newTask() {
return TODO;
}
public static Result deleteTask(Long id) {
return TODO;
}
}
I believe it's supposed to be import models.Task; as opposed to import play.models.*;
That's quite confusing (IMHO) step in this tutorial, instead scroll down to Persist the tasks in a database section which describes preparing a model to cooperate with DB :) (it extends Model class, uses proper annotations, etc)
As you recognized it yet, you need to create a models package yourself.
Also as cYn wrote: you should import models like models.SomeModel into your controller
You are correct HukeLau_DABA , the Play will not create the models package for you. you have to create it.
I got these imports in my Application controller class. I got this sample play application running.
import play.api._
import play.api.mvc._
import play.api.data.Form
import play.api.data.Forms._
import models.Task
and another thing in Eclipse is it will not import the necessary imports automatically.
it is bit pain now, once the IDE support get better I hope this will change.

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