springdoc-openapi: How can I use #RouterOperation in coRouter DSL - kotlin

I'm using springdoc-openapi with Kotlin and WebFlux.fn.
I wanted to use #RouterOperation annotation at every path in CoRouterFunctionDsl but I couldn't.
#Configuration
class UserRouter(private val userHandler: UserHandler) {
// #RouterOperations annotation works here.
#Bean
fun userRouter = coRouter {
("/v1").nest {
// I want to use #RouterOperation annotation here.
GET("/users", userHandler::getUsers)
// I want to use #RouterOperation annotation here.
GET("/users/{userId}", userHandler::getUserById)
// I want to use #RouterOperation annotation here.
POST("/users", userHandler::postUser)
}
}
}
There doesn't seem to be any relevant documentation about this.
How can I use #RouterOperation in coRouter DSL?

The same principles applies to Kotlin DSL (coRouter): CoRouterFunctionDsl Beans are instances of RouterFunction.
Here is a sample syntax:
#FlowPreview
#Bean
#RouterOperations(
RouterOperation(path = "/test", method = arrayOf(RequestMethod.GET), beanClass = ProductRepositoryCoroutines::class, beanMethod = "getAllProducts"),
RouterOperation(path = "/test/{id}", method = arrayOf(RequestMethod.GET), beanClass = ProductRepositoryCoroutines::class, beanMethod = "getProductById"))
fun productRoutes(productsHandler: ProductsHandler) = coRouter {
GET("/test", productsHandler::findAll)
GET("/test/{id}", productsHandler::findOne)
}

Related

Micronaut-Core: How to create dynamic endpoints

Simple question. Is it possible to create endpoints without #Endpoint?
I want to create rather dynamic endpoints by a file and depending on the content to its context.
Thanks!
Update about my idea. I would to create something like a plugin system, to make my application more extensible for maintenance and future features.
It is worth to be mentioned I am using Micronaut with Kotlin. Right now I've got fixed defined Endpoints, which matches my command scripts.
My description files will be under /src/main/resources
I've got following example description file how it might look like.
ENDPOINT: GET /myapi/customendpoint/version
COMMAND: """
#!/usr/bin/env bash
# This will be executed via SSH and streamed to stdout for further handling
echo "1.0.0"
"""
# This is a template JSON which will generate a JSON as production on the endpoint
OUTPUT: """
{
"version": "Server version: $RESULT"
}
"""
How I would like to make it work with the application.
import io.micronaut.docs.context.events.SampleEvent
import io.micronaut.context.event.StartupEvent
import io.micronaut.context.event.ShutdownEvent
import io.micronaut.runtime.event.annotation.EventListener
#Singleton
class SampleEventListener {
/*var invocationCounter = 0
#EventListener
internal fun onSampleEvent(event: SampleEvent) {
invocationCounter++
}*/
#EventListener
internal fun onStartupEvent(event: StartupEvent) {
// 1. I read all my description files
// 2. Parse them (for what I created a parser)
// 3. Now the tricky part, how to add those information to Micronaut Runtime
val do = MyDescription() // After I parsed
// Would be awesome if it is that simple! :)
Micronaut.addEndpoint(
do.getEndpoint(), do.getHttpOption(),
MyCustomRequestHandler(do.getCommand()) // Maybe there is a base class for inheritance?
)
}
#EventListener
internal fun onShutdownEvent(event: ShutdownEvent) {
// shutdown logic here
}
}
You can create a custom RouteBuilder that will register your custom endpoints at runtime:
#Singleton
class CustomRouteBuilder extends DefaultRouteBuilder {
#PostConstruct
fun initRoutes() {
val do = MyDescription();
val method = do.getMethod();
val routeUri = do.getEndpoint();
val routeHandle = MethodExecutionHandle<Object, Object>() {
// implement the 'MethodExecutionHandle' in a suitable manner to invoke the 'do.getCommand()'
};
buildRoute(HttpMethod.parse(method), routeUri, routeHandle);
}
}
Note that while this would still feasible, it would be better to consider another extension path as the solution defeats the whole Micronaut philosophy of being an AOT compilation framework.
It was actually pretty easy. The solution for me was to implement a HttpServerFilter.
#Filter("/api/sws/custom/**")
class SwsRouteFilter(
private val swsService: SwsService
): HttpServerFilter {
override fun doFilter(request: HttpRequest<*>?, chain: ServerFilterChain?): Publisher<MutableHttpResponse<*>> {
return Flux.from(Mono.fromCallable {
runBlocking {
swsService.execute(request)
}
}.subscribeOn(Schedulers.boundedElastic()).flux())
}
}
And the service can process with the HttpRequest object:
suspend fun execute(request: HttpRequest<*>?): MutableHttpResponse<Feedback> {
val path = request!!.path.split("/api/sws/custom")[1]
val httpMethod = request.method
val parameters: Map<String, List<String>> = request.parameters.asMap()
// TODO: Handle request body
// and do your stuff ...
}

Custom Lint annotation detector is not triggered

I am trying to write my first Lint rule. For now I just want to detect the
use of the annotation #AnyThread.
I have created a module to implement my custom rule. The gradle file for this module is (I use the gradle plugin version 3.6.1):
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_1_8
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
compileOnly "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
compileOnly 'com.android.tools.lint:lint-api:26.6.1'
compileOnly 'com.android.tools.lint:lint-checks:26.6.1'
testImplementation "com.android.tools.lint:lint:26.6.1"
testImplementation "com.android.tools.lint:lint-tests:26.6.1"
testImplementation "com.android.tools:testutils:26.6.1"
testImplementation "junit:junit:4.12"
}
jar {
manifest {
attributes("Lint-Registry-v2": "com.test.lint.MyIssueRegistry")
}
}
My detector is:
package com.test.lint
//...
class AnyThreadAnnotationDetector: AbstractAnnotationDetector(), Detector.UastScanner {
companion object {
private const val AnyThreadId = "AnyThreadId"
const val AnyThreadDescription = "This is an attempt to find AnyThread annotation in code"
const val AnyThreadExplanation = "AnyThread annotation found!"
val ANYTHREAD_ANNOTATION_ISSUE = Issue.create(
id = AnyThreadId,
briefDescription = AnyThreadDescription,
explanation = AnyThreadExplanation,
category = Category.CORRECTNESS,
priority = 4,
severity = Severity.INFORMATIONAL,
implementation = Implementation(
AnyThreadAnnotationDetector::class.java,
Scope.JAVA_FILE_SCOPE
)
)
}
override fun applicableAnnotations(): List<String>? = listOf("androidx.annotation.AnyThread")
override fun visitAnnotationUsage(
context: JavaContext,
usage: UElement,
type: AnnotationUsageType,
annotation: UAnnotation,
qualifiedName: String,
method: PsiMethod?,
annotations: List<UAnnotation>,
allMemberAnnotations: List<UAnnotation>,
allClassAnnotations: List<UAnnotation>,
allPackageAnnotations: List<UAnnotation>
) {
context.report(
issue = ANYTHREAD_ANNOTATION_ISSUE,
scope = usage,
location = context.getNameLocation(usage),
message = "A message"
)
}
}
My IssueRegistry is:
class MyIssueRegistry : IssueRegistry() {
override val issues: List<Issue>
get() = listOf(
AnyThreadAnnotationDetector.ANYTHREAD_ANNOTATION_ISSUE)
override val api: Int = CURRENT_API
}
I wrote some tests:
class AnyThreadAnnotationDetectorTest {
#Test
fun noAnnotatedFileKotlin() {
TestLintTask.lint()
.files(
LintDetectorTest.kotlin(
"""
|package foo;
|
|class XmlHttpRequest {
|}""".trimMargin()
)
)
.allowMissingSdk()
.issues(AnyThreadAnnotationDetector.ANYTHREAD_ANNOTATION_ISSUE)
.run()
.expectClean()
}
#Test
fun annotatedKotlinMethod() {
TestLintTask.lint()
.files(
LintDetectorTest.kotlin(
"""
|package foo;
|
|import androidx.annotation.AnyThread
|
|class XmlHttpRequest {
|#AnyThread
|fun test(){}
|}""".trimMargin()
)
)
.allowMissingSdk()
.issues(AnyThreadAnnotationDetector.ANYTHREAD_ANNOTATION_ISSUE)
.run()
.expect(
"""
Just a test to find annotations
0 errors, 0 warnings
""".trimIndent()
)
}
#Test
fun testNoisyDetector() {
TestLintTask.lint().files(Stubs.ANYTHREAD_EXPERIMENT)
.allowMissingSdk()
.issues(AnyThreadAnnotationDetector.ANYTHREAD_ANNOTATION_ISSUE)
.run()
.expect(
"""
Just a test to find annotations
0 errors, 0 warnings
""".trimIndent()
)
}
}
Where the Stubs.ANYTHREAD_EXPERIMENT is:
object Stubs {
val ANYTHREAD_EXPERIMENT = kotlin(
"com/test/applicationlintdemoapp/AnythreadAnnotationStubs.kt",
"""
package com.test.applicationlintdemoapp
import androidx.annotation.AnyThread
class AnythreadClassExperiment {
#AnyThread
fun setTimeToNow() {
TimeTravelProvider().setTime(System.currentTimeMillis())
}
#AnyThread
fun setTimeToEpoch() {
TimeTravelProvider().setTime(0)
}
fun violateTimeTravelAccords() {
TimeTravelProvider().setTime(-1)
}
}
"""
).indented().within("src")
}
All my test fail (except noAnnotatedFileKotlin), actually if I put a breakpoint on
the call to context.report the test made in debug mode is never paused, meaning
that the annotation androidx.annotation.AnyThread is never detected.
What could go wrong ? what did I miss?
I have seen and read a some docs:
Writing custom lint rules
KotlinConf 2017 - Kotlin Static Analysis with Android Lint
Writing your first Lint check
Making Custom Lint for Kotlin Code
Getting the Most Out of Android Lint
Coding in Style: Static Analysis with Custom Lint Rules
And I controlled the configuration by implementing the NoisyDetector given
in the talk Coding in Style: Static Analysis with Custom Lint Rules, the result
of the test are fine.
I might be a little late to answer this, but it might be useful for other people who run into this question
I'm having the same problem, I need to find usages of an Annotation and report them. But for some reason the Kotlin UAST (Java works fine) doesn't record/report annotations. I'm using a sort of workaround to get through this
Workaround
Instead of visiting annotations, I'm visiting UMethod or UClass depending on what you need. Then I'm doing a manual String.contains() check on the node.sourcePsi.text to see if the annotation is there
override fun getApplicableUastTypes() = listOf(UMethod::class.java)
override fun createUastHandler(context: JavaContext): UElementHandler {
return object : UElementHandler() {
override fun visitMethod(node: UMethod) {
if (!shouldSkip(node.sourcePsi) && node.isAnnotatedWith("AnyThread")) {
context.report(
issue = ANYTHREAD_ANNOTATION_ISSUE,
scope = usage,
location = context.getNameLocation(usage),
message = "A message"
)
}
}
}
// Skip KtClass, because it calls the `visitMethod` method since the class has the constructor method in it
private fun shouldSkip(node: PsiElement?): Boolean = node is KtClass
}
fun UAnnotated.isAnnotatedWith(annotation: String) = sourcePsi?.text?.contains("#$annotation") == true
Drawbacks
The problem I see with this is that it will be called for every method instead of only when the annotation is found, and the shouldSkip() method seems like a hack to me. But other than that it works correctly and should report problems
Note: Calling node.hasAnnotation(), node.findAnnotation() or context.evaluator.hasAnnotation() will not find annotations in Kotlin
You can add stubs for the #AnyThread annotation by adding SUPPORT_ANNOTATIONS_JAR to the lint().files(...) call or manually declaring the #AnyThread annotation class in a separate test source file.
An example of using SUPPORT_ANNOTATIONS_JAR inside of CheckResultDetectorTest can be found here.

Implement #slf4j annotation from Lombok in Kotlin

Is there a way to implement something similar to Lombok's annotation #slf4j with annotation class in Kotlin?
Right now I have an extension function that instantiates a Logger Factory for me, and I must create these variables in each of my classes just like the example below:
#RestController
#RequestMapping("/api/v1/sample")
class SampleController() {
private val log = logger()
#GetMapping
fun show(): String {
log.info("SOME LOGGING MESSAGE")
return "OK"
}
}
inline fun <reified T> T.logger(): Logger {
if (T::class.isCompanion) {
return LoggerFactory.getLogger(T::class.java.enclosingClass)
}
return LoggerFactory.getLogger(T::class.java)
}
What I want to achieve is something like:
#Logger
#RestController
#RequestMapping("/api/v1/sample")
class SampleController() {
#GetMapping
fun show(): String {
log.info("SOME LOGGING MESSAGE")
return "OK"
}
}
made this pretty thing yesterday
#Target(AnnotationTarget.CLASS)
#Retention(AnnotationRetention.RUNTIME)
annotation class Log {
companion object {
inline var <reified T> T.log: Logger
get() = LoggerFactory.getLogger(T::class.java)
set(value) {}
}
}
edit:
Don't use this mess above. use
companion object {
private val log: Logger = LoggerFactory.getLogger(this::class.java)
}
see Idiomatic way of logging in Kotlin
Turning Danny Lagrouw's comment into an answer:
You can get a similar to #Slf4j low-boilerplate solution that does not require you to specify the class manually by using Micro Utils' kotlin-logging
import mu.KotlinLogging
private val log = KotlinLogging.logger {}
class LoggingDemoClass() {
fun logSometing() {
log.info("Logging like a pro!")
}
}
Caveat: I've only just started using it but and haven't investigated any of the kotlin sugar it puts on top of Slf4j but so far it seems to handle well enough as a drop-in replacement for #Slf4j in kotlin code.

How to retrieve all instances that matches a given type using Koin Dependency Injection (Kotlin)

How do you achieve the following using Koin DI:
single { AValidator() } bind IValidator::class
single { BValidator() } bind IValidator::class
single { CValidator() } bind IValidator::class
single { DValidator() } bind IValidator::class
In the class where I want to have all validators injected I use the following:
val validators: List<IValidator> by inject()
Expecting that all different implementations of interface IValidator are injected automatically.
I know that is actually supported in Kodein, where you would simply do:
val validators: List<IValidator> by kodein.allInstances()
Would love to know whether this is possible within Koin.
Thanks!
With Koin 2+, you can now declare your instances individually
single { AValidator() } bind IValidator::class
single { BValidator() } bind IValidator::class
single { CValidator() } bind IValidator::class
single { DValidator() } bind IValidator::class
Then use getAll<TInterface> to request all of them
val validators: List<IValidator> = getKoin().getAll()
// with lazy
val validators: List<IValidator> by lazy { getKoin().getAll<IValidator>() }
And use bind<TInterface, TImplementation> to request a single instance
val validator: IValidator = getKoin().bind<IValidator, AValidator>()
Source
https://start.insert-koin.io/#/getting-started/modules-definitions?id=additional-types
According to the documentation, I can do something like the following:
single(name = "validators") {
listOf(AValidator(), BValidator(), CValidator(), DValidator())
}
And retrieve it with:
val validators: List<IValidator> by inject(name = "validators")
It works for now, but injecting a single validator of the list above would for example not work.
For more details: https://insert-koin.io/docs/1.0/documentation/reference/index.html
Feel free to add another solution!
Built in Koin getAll() is also not working for me, regardless of version. So its either a bug that persisted for years or poor documentation. However this is my custom solution, koin version 3.3.0.
#OptIn(KoinInternalApi::class)
inline fun <reified T : Any> getAllCustom(): List<T> =
KoinJavaComponent.getKoin().let { koin ->
koin.instanceRegistry.instances.map { it.value.beanDefinition }
.filter { it.kind == Kind.Singleton }
.filter { it.primaryType.isSubclassOf(T::class) }
.map { koin.get(clazz = it.primaryType, qualifier = null, parameters = null) }
}
And then provide it like this
single {
SomeClass(getAllCustom())
}

Create an annotation instance in Kotlin

I have a framework written in Java that, using reflection, get the fields on an annotation and make some decisions based on them. At some point I am also able to create an ad-hoc instance of the annotation and set the fields myself. This part looks something like this:
public #interface ThirdPartyAnnotation{
String foo();
}
class MyApp{
ThirdPartyAnnotation getInstanceOfAnnotation(final String foo)
{
ThirdPartyAnnotation annotation = new ThirdPartyAnnotation()
{
#Override
public String foo()
{
return foo;
}
};
return annotation;
}
}
Now I am trying to do the exact thing in Kotlin. Bear in mind that the annotation is in a third party jar.
Anyway, here is how I tried it in Kotlin:
class MyApp{
fun getAnnotationInstance(fooString:String):ThirdPartyAnnotation{
return ThirdPartyAnnotation(){
override fun foo=fooString
}
}
But the compiler complains about: Annotation class cannot be instantiated
So the question is: how should I do this in Kotlin?
You can do this with Kotlin reflection:
val annotation = ThirdPartyAnnotation::class.constructors.first().call("fooValue")
In the case of annotation having no-arg constructor (e.g. each annotation field has a default value), you can use following approach:
annotation class SomeAnnotation(
val someField: Boolean = false,
)
val annotation = SomeAnnotation::class.createInstance()
This is the solution I might have found but feels like a hack to me and I would prefer to be able to solve it within the language.
Anyway, for what is worth,it goes like this:
class MyApp {
fun getInstanceOfAnnotation(foo: String): ThirdPartyAnnotation {
val annotationListener = object : InvocationHandler {
override fun invoke(proxy: Any?, method: Method?, args: Array<out Any>?): Any? {
return when (method?.name) {
"foo" -> foo
else -> FindBy::class.java
}
}
}
return Proxy.newProxyInstance(ThirdPartyAnnotation::class.java.classLoader, arrayOf(ThirdPartyAnnotation::class.java), annotationListener) as ThirdPartyAnnotation
}
}