As seen in the documentation, the standard way of declaring a route in Quarkus is with the #Path() annotation, like so :
#Path("myPath")
public class Endpoint {
#GET
public String hello() {
return "Hello, World!";
}
}
This will create the route GET /MyPath. However, #Path being an annotation, I have to give it constant expression.
I would like to be able to declare a route with a non constant expression, something like #Path(MyClass.class.getSimpleName())
I tried to implement something like this:
public class Endpoint {
public void initialize(#Observes StartupEvent ev) {
declareRoute(MyClass.class.getSimpleName(), HttpMethod.GET, this::hello);
}
public String hello() {
return "Hello, World!";
}
public void declareRoute(String path, HttpMethod method, Consumer handler) {
// TODO implement
}
}
This would create the route GET /MyClass But I have no idea how to implement declareRoute(). I tried to inject the Vertx Router since Quarkus seems to use it, but I did not find a way to add a route. Is this doable, and if so, how ?
You essentially need to do something like:
#ApplicationScoped
public class BeanRegisteringRoute {
void init(#Observes Router router) {
router.route("/my-path").handler(rc -> rc.response().end("Hello, World!"));
}
}
See this for more information
Related
I'm trying to build a simple app that calls an API with quarkus-rest-client.
I have to inject an API Key as a header which is the same for all resources of the API.
So I would like to put the value of this API Key (that depends on the environment dev/qa/prod) in the application.properties file located in src/main/resources.
I tried different ways to achieve this:
Use directly com.acme.Configuration.getKey into #ClientHeaderParam value property
Create a StoresClientHeadersFactory class which implements ClientHeadersFactory interface to inject the configuration
Finally, I found the way described below to make it work.
My question is: Is there a better way to do it?
Here's my code:
StoreService.java which is my client to reach the API
#Path("/stores")
#RegisterRestClient
#ClientHeaderParam(name = "ApiKey", value = "{com.acme.Configuration.getStoresApiKey}")
public interface StoresService {
#GET
#Produces("application/json")
Stores getStores();
}
Configuration.java
#ApplicationScoped
public class Configuration {
#ConfigProperty(name = "apiKey.stores")
private String storesApiKey;
public String getKey() {
return storesApiKey;
}
public static String getStoresApiKey() {
return CDI.current().select(Configuration.class).get().getKey();
}
}
StoresController.java which is the REST controller
#Path("/stores")
public class StoresController {
#Inject
#RestClient
StoresService storesService;
#GET
#Produces(MediaType.APPLICATION_JSON)
public Stores getStores() {
return storesService.getStores();
}
}
Late to the party, but putting this here for my own reference. There seems to be a difference in using #ClientHeaderParam and #HeaderParam, so I investigated a little further:
According to the Microprofile docs, you can put a compute method for the value in curly braces. This method can extract the property value.
See link for more examples.
EDIT: What I came up with resembles the original, but uses a default method on the interface, so you can at least discard the Configuration class. Also, using the org.eclipse.microprofile.config.Config and ConfigProvider classes to get the config value:
#RegisterRestClient
#ClientHeaderParam(name = "Authorization", value = "{getAuthorizationHeader}")
public interface StoresService {
default String getAuthorizationHeader(){
final Config config = ConfigProvider.getConfig();
return config.getValue("apiKey.stores", String.class);
}
#GET
#Produces("application/json")
Stores getStores();
I will get rid of the Configuration class and use an #HeaderParam to pass your configuration property from your rest endpoint to your rest client. The annotation will then send this property as an HTTP header to the remote service.
Somthing like this should works:
#Path("/stores")
#RegisterRestClient
public interface StoresService {
#GET
#Produces("application/json")
Stores getStores(#HeaderParam("ApiKey") storesApiKey);
}
#Path("/stores")
public class StoresController {
#ConfigProperty(name = "apiKey.stores")
private String storesApiKey;
#Inject
#RestClient
StoresService storesService;
#GET
#Produces(MediaType.APPLICATION_JSON)
public Stores getStores() {
return storesService.getStores(storesApiKey);
}
}
I am learning spring boot, and i developed the below simple example. I would like to annotate a class as Controller using #Controller. this class has constructor and I want to have access to GreetingFromDeuController as shown:
http://localhost:8080:/GreetingFromDeuController?str = "hi"
the error i am receiving is
#RequestMapping is not applicable on a constructor
please let me know how to solve.
code:
#Controller
#RequestMapping("/GreetingFromDeuController")
public class GreetingFromDeuController {
private String str;
#RequestMapping("/GreetingFrom/deu")
GreetingFromDeuController(#RequestParam(value = "str") String str) {
this.str = str;
}
#RequestMapping("/GreetingFromDeuController")
public String getGreetingFromDeu() {
return this.str;
}
}
First of all your constructor gets initialize much before you hit your URL. So you need to work on your design or tell me your business requirement and I will try to provide you a solution. My refactor code solution will help you to achieve that in two steps. First hit POST method which will do work on setting variable and then subsequent hits of GET method will return that set value.
We can refactor code like below. It will explain use of RequestMapping on method and class.
Considering we have to write two API, one for reading and one for writing.
URLS :
1. POST http://localhost:8080/example/greetings (in request body send {str:'hi'})
2. GET http://localhost:8080/example/greetings
#Controller
#RequestMapping("/example")
public class GreetingFromDeuController {
private String str;
#RequestMapping(value="/greetings" , method = RequestMethod.POST)
public void setGreetingFromDeu(#RequestBody(value = "str") String str)
{
this.str = str;
}
#RequestMapping(value="/greetings" , method = RequestMethod.GET)
public String getGreetingFromDeu()
{
return this.str;
}
}
The #RequestMapping documentation says:
Annotation for mapping web requests onto methods in request-handling
classes with flexible method signatures.
Then you can not do that, if you want to initialize your variables or whatever you can use several ways:
1.- Use #PostConstruct
#PostContruct
public void init() {
this.str = "Anything";
}
2.- Use a simple request to set anything only
#RequestMapping(value="/refresh/anythings", method = RequestMethod.PUT)
public void refresh(#RequestBody(value = "str") String str) {
this.str = str;
}
3.- Use #Value
In application.properties / application.yaml
properties.str = anything
In the Controller
#Value("${properties.str:default}") // by default str is "default"
public String str;
#RequestMapping(value="/greetings" , method = RequestMethod.GET)
public String getGreetingFromDeu() {
return this.str;
}
As far I am concerned, #RequestMapping is not meant for constructors. It should be used for annotating methods or classes. Methods that are responsible for handling requests.
#RequestMapping should be used to map request with endPoint. which can be used as class level and method level.
You can use #RestController (improved from #Controller see difference).
The ideal flow for Spring Boot is Controller -> Service -> Repository
Controller -> maps request with endPoint and return response
Service -> perform business logic
Repository -> Handle database operation
Example
#RestController
#RequestMapping("/api")
public class GreetingController {
#Autowired GreetinService greetingService;
// Request http://localhost:8080/api/GreetingFrom
#GetMapping("/GreetingFrom")
public ResponseEntity<String> GreetingRequestParam(#RequestParam(value = "name") String name) {
greetingService.performBusinessLogic(name);
return new ResponseEntity<String>("Greetings from "+name,HttpStatus.OK);
}
// Request http://localhost:8080/api/GreetingFrom/user2121
#GetMapping("/GreetingFrom/{name}")
public ResponseEntity<String> GreetingPathVariable(#PathVariable(value = "name") String name) {
return new ResponseEntity<String>("Greetings from "+name,HttpStatus.OK);
}
}
I have a simple Micronaut- based "hello world" service that has a simple security built in (for the sake of testing and illustrating the Micronaut security). The controller code in the service that implements the hello service is provided below:
#Controller("/hello")
public class HelloController
{
public HelloController()
{
// Might put some stuff in in the future
}
#Get("/")
#Produces(MediaType.TEXT_PLAIN)
public String index()
{
return("Hello to the World of Micronaut!!!");
}
}
In order to test the security mechanism, I have followed the Micronaut tutorial instructions and created a security service class:
#Singleton
public class SecurityService
{
public SecurityService()
{
// Might put in some stuff in the future
}
Flowable<Boolean> checkAuthorization(HttpRequest<?> theReq)
{
Flowable<Boolean> flow = Flowable.fromCallable(()->{
System.out.println("Security Engaged!");
return(false); <== The tutorial says return true
}).subscribeOn(Schedulers.io());
return(flow);
}
}
It should be noted that, in a departure from the tutorial, the flowable.fromCallable() lambda returns false. In the tutorial, it returns true. I had assumed that a security check would fail if a false is returned, and that a failure would cause the hello service to fail to respond.
According to the tutorials, in ordeer to begin using the Security object, it is necessary to have a filter. The filter I created is shown below:
#Filter("/**")
public class HelloFilter implements HttpServerFilter
{
private final SecurityService secService;
public HelloFilter(SecurityService aSec)
{
System.out.println("Filter Created!");
secService = aSec;
}
#Override
public Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> theReq, ServerFilterChain theChain)
{
System.out.println("Filtering!");
Publisher<MutableHttpResponse<?>> resp = secService.checkAuthorization(theReq)
.doOnNext(res->{
System.out.println("Responding!");
});
return(resp);
}
}
The problem occurs when I run the microservice and access the Helo world URL. (http://localhost:8080/hello) I cannot cause the access to the service to fail. The filter catches all requests, and the security object is engaged, but it does not seem to prevent access to the hello service. I do not know what it takes to make the access fail.
Can someone help on this matter? Thank you.
You need to change request in your filter when you no have access to resource or process request as usual. Your HelloFilter looks like this:
#Override
public Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> theReq, ServerFilterChain theChain) {
System.out.println("Filtering!");
Publisher<MutableHttpResponse<?>> resp = secService.checkAuthorization(theReq)
.switchMap((authResult) -> { // authResult - is you result from SecurityService
if (!authResult) {
return Publishers.just(HttpResponse.status(HttpStatus.FORBIDDEN)); // reject request
} else {
return theChain.proceed(theReq); // process request as usual
}
})
.doOnNext(res -> {
System.out.println("Responding!");
});
return (resp);
}
And in the last - micronaut has security module with SecurityFilter, you can use #Secured annotations or write access rules in configuration files more examples in the doc
New to Camel, so maybe I'm misunderstanding how processors and beans should interact. We have some logging to a database that we want to do throughout a camel route. The idea was to do this in a processor. However, we'd also like to do this logging from w/in the beans. Is that possible? I know I could pass it back as a return field from the bean...but it is already passing back a return.
A related question is how to pass that status, thinking it would be an exchange property or header.
Basically I want something along the lines of
processor
class EventStatusProcessor implements Processor {
#Override
void process(Exchange exchange) throws Exception {
// do some stuff, thinking this will be a header
}
}
route
from("direct:route1")
.bean(doSomething, 'doSomething')
.process(new EventStatusProcessor())
bean
#Component
#Slf4j
class DoSomething{
String doSomething()
//doing stuff
new EventStatusProcessor().process()
You can pass Exchange to method invoked with bean component too and set there headers/properties/body/whatever depending on your needs.
class DoSomething {
#SuppressWarnings("unused") //called via Camel bean invocation
public void doSomething(Exchange exchange){
exchange.setProperty("propertyFromDoSomething", "Hello, I am property");
exchange.getIn().setHeader("headerFromDoSomething", "Hi, I am header");
exchange.getIn().setBody("It's me, body!");
}
}
class EventStatusProcessor implements Processor {
#Override
public void process(Exchange exchange) throws Exception {
System.out.println(exchange.getIn().getHeader("headerFromDoSomething", String.class));
System.out.println(exchange.getProperty("propertyFromDoSomething", String.class));
System.out.println(exchange.getIn().getBody(String.class));
}
}
If you really need to call processor inside bean, as you are writing in title, extract processor to direct route and then invoke it with ProducerTemplate.
RouteBuilder
from("direct:log")
.process(new EventStatusProcessor());
DoSomething class
public class DoSomething {
#SuppressWarnings("unused") //called via Camel bean invocation
public void doSomething(Exchange exchange){
exchange.getContext().createProducerTemplate().sendBody("direct:log", "I am body and I will be passed to EventStatusProcessor");
}
}
I'm using Dropwizard 0.9.2 and I want to create a resource that requires no authentication for GET and requires basic authentication for POST.
I have tried
#Path("/protectedPing")
#Produces(MediaType.TEXT_PLAIN)
public class ProtectedPing {
#GET
public String everybody() {
return "pingpong";
}
#PermitAll
#POST
public String authenticated(){
return "secret pingpong";
}
with
CachingAuthenticator<BasicCredentials, User> ca = new CachingAuthenticator<>(environment.metrics(), ldapAuthenticator, cbSpec);
AdminAuthorizer authorizer = new AdminAuthorizer();
BasicCredentialAuthFilter<User> bcaf = new BasicCredentialAuthFilter.Builder<User>().setAuthenticator(ca).setRealm("test-oauth").setAuthorizer(authorizer).buildAuthFilter();
environment.jersey().register(bcaf);
environment.jersey().register(RolesAllowedDynamicFeature.class);
environment.jersey().register(new AuthValueFactoryProvider.Binder<>(User.class));
environment.jersey().register(new ProtectedPing());
This seems to result in all requests to "/protectedPing" requiring basic auth.
In Dropwizard 0.9.2 the documentation says to create a custom filter if I have a resource that is optionally protected. I'm assuming I need to do that, but I don't know where to start, or if that I what I actually need to do.
this is more of a jersey problem than a dropwizard problem. You can have a look here: https://jersey.java.net/documentation/latest/filters-and-interceptors.html
Essentially what you want is:
Create an annotation that indicates that you want to test for authentication (e.g. #AuthenticatePost)
Create the resource and annotate the correct method with #AuthenticatePost
Create your authentication filter (probably kind of like what you did above).
In the dynamic feature, test for the annotation to be present on the passed in resource. This will hold true for post, false for get. Then register the AuthenticationFilter directly on the resource method instead of globally on the resource.
This would be a semi-complete example of how I would solve this:
public class MyDynamicFeature implements DynamicFeature {
#Override
public void configure(ResourceInfo resourceInfo, FeatureContext context) {
if(resourceInfo.getResourceMethod().getAnnotation(AuthenticateMe.class) != null ) {
context.register(MyAuthFilter.class);
}
}
public class MyAuthFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
// do authentication here
}
}
public #interface AuthenticateMe {
}
#Path("myPath")
public class MyResource {
#GET
public String get() {
return "get-method";
}
#POST
#AuthenticateMe
public String post() {
return "post-method";
}
}
}
Note, the DynamicFeature checks that the Authenticate Annotation is present, before registering the authentication with the feature context.
I hope that helps,
let me know if you have any questions.