Add annotation with a Quarkus extension - quarkus

Here is what I like to achieve.
My Quarkus app is using a homemade Quarkus extension that is securing my API endpoints with a custom annotation:
#NameBinding
#Retention(RetentionPolicy.RUNTIME)
public #interface AuthorizationSecured {
#Nonbinding String[] permissions() default{};
}
I want this custom annotation to automatically annotate my endpoints with this openapi annotation:
// org.eclipse.microprofile.openapi.annotations.security.SecurityRequirement
#SecurityRequirement(name = "jwt")
So that in swagger-ui, I can have the padlock icon displayed and specify my token when clicking on the icon.
Any idea?
EDIT:
I tried #Ladicek suggestion, here is my processor:
class AuthorizeProcessor {
#BuildStep
FeatureBuildItem feature() {
return new FeatureBuildItem("authorize");
}
#BuildStep
AdditionalBeanBuildItem registerConfigValidator() {
// some stuff here...
}
#BuildStep
AnnotationsTransformerBuildItem transform() {
return new AnnotationsTransformerBuildItem(new AnnotationsTransformer() {
public boolean appliesTo(org.jboss.jandex.AnnotationTarget.Kind kind) {
return kind == AnnotationTarget.Kind.METHOD;
}
public void transform(TransformationContext context) {
if (context.getTarget().asMethod().hasAnnotation(DotName.createSimple("com.software.company.AuthorizationSecured"))) {
context.transform().add(DotName.createSimple("org.eclipse.microprofile.openapi.annotations.security.SecurityRequirement"), AnnotationValue.createStringValue("name", "jwt")).done();
}
}
});
}
#BuildStep
void registerAuthorizeFilter(
BuildProducer<AdditionalBeanBuildItem> additionalBeanProducer,
BuildProducer<AdditionalIndexedClassesBuildItem> additionalIndexedClassesProducer,
BuildProducer<ResteasyJaxrsProviderBuildItem> resteasyJaxrsProviderProducer) {
// some stuff here...
}
}
The annotation transformer seems to do its job, I have printed some stuff in the console:
************* Found method name = secured
************* annotations before > [#GET, #Path(value = "/secured"), #Produces(value = ["text/plain"]), #AuthorizationSecured]
************* annotations after > [#GET, #Path(value = "/secured"), #Produces(value = ["text/plain"]), #AuthorizationSecured, #org.eclipse.microprofile.openapi.annotations.security.SecurityRequirement(name = "jwt")]
But actually, I don't see any effect at runtime on the app that is using the extension. What am I missing here?

Related

WebServerFactoryCustomizer is not hit in a Springboot Webflux app

Following Configure the Web Server , I add a NettyWebServerFactoryCustomizer
#Configuration
public class NettyWebServerFactoryCustomizer implements WebServerFactoryCustomizer<NettyReactiveWebServerFactory> {
#Override
public void customize(NettyReactiveWebServerFactory factory) {
factory.addServerCustomizers(httpServer -> {
return httpServer
.wiretap(true)
.metrics(true, s->s)
.doOnConnection(conn -> {
conn.addHandlerFirst(new ReadTimeoutHandler(50, TimeUnit.MILLISECONDS));
});
});
}
}
I have two questions:
When I run the app, the customize function is not hit. Where do I miss?
My purpose is to enable the Netty metrics, I can't find any documents about config the metrics in the application.yml file. so I add the NettyWebServerFactoryCustomizer.
The second parameter of .metrics(true, s->s) is a uriTagValue, Are there any example about how to pass in value? I just use s->s because I refer this, but this maybe can't avoid cardinality explosion, Are there any function like ServerWebExchange.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE) simple give us the templated URL?
I found the workaround of the question 1: define a bean instead of implementing WebServerFactoryCustomizer
#Bean
public ReactiveWebServerFactory reactiveWebServerFactory() {
NettyReactiveWebServerFactory factory = new NettyReactiveWebServerFactory();
factory.addServerCustomizers(builder -> builder
.wiretap(true)
.metrics(true,s->s)
.accessLog(true)
.doOnConnection(conn -> {
conn.addHandlerFirst(new ReadTimeoutHandler(50, TimeUnit.MILLISECONDS));
}));
return factory;
}
About your question 2 : The second parameter of .metrics(true, s->s) is a uriTagValue, Are there any example about how to pass in value?
private static final Pattern URI_TEMPLATE_PATTERN = Pattern.compile("/test/.*");
#Bean
public ReactiveWebServerFactory reactiveWebServerFactory() {
NettyReactiveWebServerFactory factory = new NettyReactiveWebServerFactory();
factory.addServerCustomizers(builder -> builder
.wiretap(true)
.metrics(true,
uriValue ->
{
Matcher matcher = URI_TEMPLATE_PATTERN .matcher(uriValue);
if (matcher.matches()) {
return "/test/";
}
return "/";
}
.accessLog(true)
.doOnConnection(conn -> {
conn.addHandlerFirst(new ReadTimeoutHandler(50, TimeUnit.MILLISECONDS));
}));
return factory;
}

Configure default Kotlin coroutine context in Spring MVC

I need to configure default coroutine context for all requests in Spring MVC. For example MDCContext (similar question as this but for MVC not WebFlux).
What I have tried
Hook into Spring - the coroutine code is here but there is no way to change the default behavior (need to change InvocableHandlerMethod.doInvoke implementation)
Use AOP - AOP and coroutines do not play well together
Any ideas?
This seems to work:
#Configuration
class ContextConfig: WebMvcRegistrations {
override fun getRequestMappingHandlerAdapter(): RequestMappingHandlerAdapter {
return object: RequestMappingHandlerAdapter() {
override fun createInvocableHandlerMethod(handlerMethod: HandlerMethod): ServletInvocableHandlerMethod {
return object : ServletInvocableHandlerMethod(handlerMethod) {
override fun doInvoke(vararg args: Any?): Any? {
val method = bridgedMethod
ReflectionUtils.makeAccessible(method)
if (KotlinDetector.isSuspendingFunction(method)) {
// Exception handling skipped for brevity, copy it from super.doInvoke()
return invokeSuspendingFunctionX(method, bean, *args)
}
return super.doInvoke(*args)
}
/**
* Copied from CoroutinesUtils in order to be able to set CoroutineContext
*/
#Suppress("UNCHECKED_CAST")
private fun invokeSuspendingFunctionX(method: Method, target: Any, vararg args: Any?): Publisher<*> {
val function = method.kotlinFunction!!
val mono = mono(YOUR_CONTEXT_HERE) {
function.callSuspend(target, *args.sliceArray(0..(args.size-2))).let { if (it == Unit) null else it }
}.onErrorMap(InvocationTargetException::class.java) { it.targetException }
return if (function.returnType.classifier == Flow::class) {
mono.flatMapMany { (it as Flow<Any>).asFlux() }
}
else {
mono
}
}
}
}
}
}
}

ByteBuddy, Modify Sleuth SpanCustomizer using javaagent bytebuddy

Have a question about modifying Sleuth SpanCustomizer, I want to customize the span name and add additional span tag.
My final goal is want to implement a class like the following code to achieve the span tag/name modification, Should I just use bytebuddy to apply this piece of class into the agent OR detect and intercept SpanCustomizer Class to do the modification?
Any advice would be helpful, Thank you!
-------------------- Final goal implement this class --------------------
#Configuration
public class TracingConfig{
#Bean(name = { HttpClientRequestParser.NAME, HttpServerRequestParser.NAME })
HttpRequestParser sleuthHttpServerRequestParser() {
return (req, context, span) -> {
HttpRequestParser.DEFAULT.parse(req, context, span);
span.tag("upstream_cluster", getUpstreamCluster(req));
span.name(getOperationName(req));
};
}
#Bean(name = { HttpClientResponseParser.NAME, HttpServerResponseParser.NAME })
HttpResponseParser sleuthHttpServerResponseParser(){
return (resp, context, span) -> {
HttpResponseParser.DEFAULT.parse(resp, context, span);
span.tag("http.status_code", Integer.toString(resp.statusCode()));
span.name(getOperationName(resp.request()));
};
}
}
My current implementation is as follows:
-------------------- Premain --------------------
public static void premain(String agentArgs, Instrumentation inst) throws Exception {
AgentBuilder.Transformer transformerHttp = new AgentBuilder.Transformer() {
#Override
public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) {
return builder
.method(ElementMatchers.<MethodDescription>any())
.intercept(MethodDelegation.to(MyHttpAdvice.class));
}
};
AgentBuilder agentBuilder = new AgentBuilder.Default();
agentBuilder = agentBuilder.type(ElementMatchers.hasSuperType(ElementMatchers.named("javax.servlet.http.HttpServletRequest"))
.or(ElementMatchers.named("org.springframework.http.client.ClientHttpResponse"))
.or(ElementMatchers.named("org.springframework.web.reactive.function.client.ClientRequest"))
.or(ElementMatchers.named("org.springframework.web.reactive.function.client.ClientResponse"))
.or(ElementMatchers.named("org.springframework.web.reactive.function.client.WebClient"))
.or(ElementMatchers.named("javax.servlet.http.HttpServletRequest")))
.transform(transformerHttp);
agentBuilder.installOn(inst);
}
-------------------- My Http Advice --------------------
public class MyHttpAdvice {
#RuntimeType
public static Object intercept(#Origin Method method,
#AllArguments Object[] allArguments,
#SuperCall Callable<?> callable) throws Exception {
long start = System.currentTimeMillis();
for (Object allArgument : allArguments) {
if (allArgument instanceof SpanCustomizer) {
SpanCustomizer req = (SpanCustomizer) allArgument;
req.tag("test1", "test2");
req.name("test3");
System.out.println("intercept http request span customizer ------- " + req.toString());
}
return callable.call();
}
}
}
}

How to remove the "_embedded" property in Spring HATEOAS

I'm using Spring Boot and HATEOAS to build a REST API and when my API returns a collection, it is wrapped inside a "_embedded" property, like so:
{
"_links":{
"self":{
"href":"http://localhost:8080/technologies"
}
},
"_embedded":{
"technologies":[
{
"id":1,
"description":"A",
"_links":{
"self":{
"href":"http://localhost:8080/technologies/1"
}
}
},
{
"id":2,
"description":"B",
"_links":{
"self":{
"href":"http://localhost:8080/technologies/2"
}
}
}
]
}
}
I want the response to be like this:
{
"_links":{
"self":{
"href":"http://localhost:8080/technologies"
}
},
"technologies":[
{
"id":1,
"description":"A",
"_links":{
"self":{
"href":"http://localhost:8080/technologies/1"
}
}
},
{
"id":2,
"description":"B",
"_links":{
"self":{
"href":"http://localhost:8080/technologies/2"
}
}
}
]
}
My TechnologiesController:
#RestController
#ExposesResourceFor(Technology.class)
#RequestMapping(value = "/technologies")
public class TechnologiesController {
...
#ResquestMapping(method = RequestMethod.GET, produces = "application/vnd.xpto-technologies.text+json")
public Resources<Resource<Technology>> getAllTechnologies() {
List<Technology> technologies = technologyGateway.getAllTechnologies();
Resources<<Resource<Technology>> resources = new Resources<Resource<Technology>>(technologyResourceAssembler.toResources(technologies));
resources.add(linkTo(methodOn(TechnologiesController.class).getAllTechnologies()).withSelfRel());
return resources;
}
The configuration class has the annotation #EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL).
What is the best way to produce the response without the "_embedded"?
As the documentation says
application/hal+json responses should be sent to requests that accept
application/json
In order to omit _embedded in you response you'll need to add
spring.hateoas.use-hal-as-default-json-media-type=false
to application.properties.
I close HAL feature, because it is hard to using Resources/Resource by restTemplate. I disable this feature by following code:
public class SpringRestConfiguration implements RepositoryRestConfigurer {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.setDefaultMediaType(MediaType.APPLICATION_JSON);
config.useHalAsDefaultJsonMediaType(false);
}
}
It work for me. HAL is good if there are more support with restTemplate.
Adding this Accept header to the request:
Accept : application/x-spring-data-verbose+json
For those who use Spring Data, and consider it as a problem - solution is to set
spring.data.rest.defaultMediaType = application/json
in application properties.
There still links will be available, but no _embedded any more.
What you're describing in the produced and expected results are semantically different things. The former thing is the HAL representation of a Collection<Technology>. The latter, which you expect is the representation of:
class Wrapper {
Resources<Technology> technologies;
}
Note how this is how we actually create the top level technologies property that you would like to see in your response. You don't create any of the latter in your controller. A top-level Resourcesinstance is basically a collection and the only way to represent a top-level collection in HAL is _embedded. Apparently you don't want that but that's what you have written in your controller method.
Assuming you have Wrapper, something like this should work (untested):
Wrapper wrapper = new Wrapper(assembler.toCollectionModel(technologies);
EntityModel<Wrapper> model = EntityModel.of(wrapper);
model.add(linkTo(…));
PS: As of Spring HATEOAS 1.0, Resources is CollectionModel and Resourceis EntityModel.
You can use this code in the service
constructor(
private httpClient: HttpClient
) { }
retrieveAllStudents(){
return this.httpClient.get<any[]>(`http://localhost:8080/students`);
}
This will deal with the _embedded part of Json and extract the desired data.
export class ListStudentsComponent implements OnInit {
// declaring variables to be used
student: Student;
students: Student[];
message: string;
// injecting student service into the constuctor
constructor(
private studentService: StudentService,
) { }
ngOnInit() {
this.refreshStudents();
}
refreshStudents(){
this.studentService.retrieveAllStudents().subscribe(
response => {
console.log(response);
this.students = response._embedded.students as Student[];
}
);
}
For latest versions in Spring RepositoryRestConfigurer doesn't include the method public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) you'd need to override the default method on RepositoryRestConfigurer which include cors parameter.
public class RestConfiguration implements RepositoryRestConfigurer {
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config, CorsRegistry cors) {
config.setDefaultMediaType(MediaType.APPLICATION_JSON);
config.useHalAsDefaultJsonMediaType(false);
}
}

customizing odata output from asp.net web api

I'm using the new ASP.NET webapi odata (version 4.0.0 last published 27/2/2013 according to Nuget)
Basically I'm doing it as described here: http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api
I'm publishing my data transfer objects and the odata atom pub feed is created but I'd like to have some more control over it. Mainly I'd like to be able to do the following:
decide what goes on the title, author and updated elements for the feed
decide whether or not to have the edit links
change what is shown in <category term="X"and in m:type in sub properties that are classes in my application. Currently they expose the c# class names with the full namespace but I don't want to expose this.
Thanks.
The OData media type formatter is more extensible now. Samples follow.
1) decide what goes on the title, author and updated elements for the feed
public class AtomMetadataFeedSerializer : ODataFeedSerializer
{
public AtomMetadataFeedSerializer(IEdmCollectionTypeReference edmType, ODataSerializerProvider serializerProvider)
: base(edmType, serializerProvider)
{
}
public override ODataFeed CreateODataFeed(IEnumerable feedInstance, ODataSerializerContext writeContext)
{
ODataFeed feed = base.CreateODataFeed(feedInstance, writeContext);
feed.Atom().Title = new AtomTextConstruct { Kind = AtomTextConstructKind.Text, Text = "My Awesome Feed" };
return feed;
}
}
public class CustomSerializerProvider : DefaultODataSerializerProvider
{
public override ODataEntrySerializer CreateEdmTypeSerializer(IEdmTypeReference edmType)
{
if (edmType.IsCollection() && edmType.AsCollection().ElementType().IsEntity())
{
// feed serializer
return new AtomMetadataFeedSerializer(edmType.AsCollection(), this);
}
return base.CreateEdmTypeSerializer(edmType);
}
}
And register the custom serializer provider using,
config.Formatters.InsertRange(0, ODataMediaTypeFormatters.Create(new CustomSerializerProvider(), new DefaultODataDeserializerProvider()));
2) customize edit links
public class CustomEntityTypeSerializer : ODataEntityTypeSerializer
{
public CustomEntityTypeSerializer(IEdmEntityTypeReference edmType, ODataSerializerProvider serializerProvider)
: base(edmType, serializerProvider)
{
}
public override ODataEntry CreateEntry(EntityInstanceContext entityInstanceContext, ODataSerializerContext writeContext)
{
ODataEntry entry = base.CreateEntry(entityInstanceContext, writeContext);
if (notProduceEditLinks)
{
entry.EditLink = null;
}
return entry;
}
}
public class CustomSerializerProvider : DefaultODataSerializerProvider
{
public override ODataEntrySerializer CreateEdmTypeSerializer(IEdmTypeReference edmType)
{
if (edmType.IsEntity())
{
// entity type serializer
return new CustomEntityTypeSerializer(edmType.AsEntity(), this);
}
return base.CreateEdmTypeSerializer(edmType);
}
}
and register the custom serializer provider as above.
We still don't support scenario 3 i.e aliasing type names and namespaces.

Resources