diff --git a/spring-context/src/main/kotlin/org/springframework/context/support/BeanDefinitionDsl.kt b/spring-context/src/main/kotlin/org/springframework/context/support/BeanDefinitionDsl.kt index 24006d41a6..799140a05c 100644 --- a/spring-context/src/main/kotlin/org/springframework/context/support/BeanDefinitionDsl.kt +++ b/spring-context/src/main/kotlin/org/springframework/context/support/BeanDefinitionDsl.kt @@ -20,26 +20,99 @@ import org.springframework.beans.factory.config.BeanDefinitionCustomizer import org.springframework.core.env.ConfigurableEnvironment import java.util.function.Supplier +/** + * Functional bean definition Kotlin DSL. + * + * Example: + * + * ``` + * beans { + * bean() + * bean { + * Routes(ref(), ref()) + * } + * bean("webHandler") { + * RouterFunctions.toWebHandler( + * ref().router(), + * HandlerStrategies.builder().viewResolver(ref()).build()) + * } + * bean("messageSource") { + * ReloadableResourceBundleMessageSource().apply { + * setBasename("messages") + * setDefaultEncoding("UTF-8") + * } + * } + * bean { + * val prefix = "classpath:/templates/" + * val suffix = ".mustache" + * val loader = MustacheResourceTemplateLoader(prefix, suffix) + * MustacheViewResolver(Mustache.compiler().withLoader(loader)).apply { + * setPrefix(prefix) + * setSuffix(suffix) + * } + * } + * profile("foo") { + * bean() + * } + * } + * ``` + * + * @author Sebastien Deleuze + * @see BeanDefinitionDsl + * @since 5.0 + */ +fun beans(init: BeanDefinitionDsl.() -> Unit): BeanDefinitionDsl { + val beans = BeanDefinitionDsl() + beans.init() + return beans +} + /** * Class implementing functional bean definition Kotlin DSL. * + * @constructor Create a new bean definition DSL. + * @param condition the predicate to fulfill in order to take in account the inner bean definition block * @author Sebastien Deleuze * @since 5.0 */ -open class BeanDefinitionDsl(val condition: (ConfigurableEnvironment) -> Boolean = { true }) : (GenericApplicationContext) -> Unit { +class BeanDefinitionDsl(private val condition: (ConfigurableEnvironment) -> Boolean = { true }) : (GenericApplicationContext) -> Unit { - protected val registrations = arrayListOf<(GenericApplicationContext) -> Unit>() - - protected val children = arrayListOf() - + @PublishedApi + internal val registrations = arrayListOf<(GenericApplicationContext) -> Unit>() + + @PublishedApi + internal val children = arrayListOf() + + /** + * Scope enum constants. + */ enum class Scope { + /** + * Scope constant for the standard singleton scope + * @see org.springframework.beans.factory.config.BeanDefinition.SCOPE_SINGLETON + */ SINGLETON, + /** + * Scope constant for the standard singleton scope + * @see org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE + */ PROTOTYPE } - class BeanDefinitionContext(val context: GenericApplicationContext) { + /** + * Provide read access to some application context facilities. + * @constructor Create a new bean definition context. + * @param context the `ApplicationContext` instance to use for retrieving bean references, `Environment`, etc. + */ + inner class BeanDefinitionContext(@PublishedApi internal val context: GenericApplicationContext) { - + /** + * Get a reference to the bean by type or type + name with the syntax + * `ref()` or `ref("foo")`. When leveraging Kotlin type inference + * it could be as short as `ref()` or `ref("foo")`. + * @param name the name of the bean to retrieve + * @param T type the bean must match, can be an interface or superclass + */ inline fun ref(name: String? = null) : T = when (name) { null -> context.getBean(T::class.java) else -> context.getBean(name, T::class.java) @@ -55,8 +128,14 @@ open class BeanDefinitionDsl(val condition: (ConfigurableEnvironment) -> Boolean /** * Declare a bean definition from the given bean class which can be inferred when possible. - * - * @See GenericApplicationContext.registerBean + * + * @param name the name of the bean + * @param scope Override the target scope of this bean, specifying a new scope name. + * @param isLazyInit Set whether this bean should be lazily initialized. + * @param isPrimary Set whether this bean is a primary autowire candidate. + * @param isAutowireCandidate Set whether this bean is a candidate for getting autowired into some other bean. + * @see GenericApplicationContext.registerBean + * @see org.springframework.beans.factory.config.BeanDefinition */ inline fun bean(name: String? = null, scope: Scope? = null, @@ -82,7 +161,7 @@ open class BeanDefinitionDsl(val condition: (ConfigurableEnvironment) -> Boolean /** * Declare a bean definition using the given supplier for obtaining a new instance. * - * @See GenericApplicationContext.registerBean + * @see GenericApplicationContext.registerBean */ inline fun bean(name: String? = null, scope: Scope? = null, @@ -97,7 +176,7 @@ open class BeanDefinitionDsl(val condition: (ConfigurableEnvironment) -> Boolean isPrimary?.let { bd.isPrimary = isPrimary } isAutowireCandidate?.let { bd.isAutowireCandidate = isAutowireCandidate } } - + registrations.add { val beanContext = BeanDefinitionContext(it) when (name) { @@ -121,6 +200,7 @@ open class BeanDefinitionDsl(val condition: (ConfigurableEnvironment) -> Boolean /** * Take in account bean definitions enclosed in the provided lambda only when the * specified environment-based predicate is true. + * @param condition the predicate to fulfill in order to take in account the inner bean definition block */ fun environment(condition: ConfigurableEnvironment.() -> Boolean, init: BeanDefinitionDsl.() -> Unit): BeanDefinitionDsl { val beans = BeanDefinitionDsl(condition::invoke) @@ -129,6 +209,10 @@ open class BeanDefinitionDsl(val condition: (ConfigurableEnvironment) -> Boolean return beans } + /** + * Register the bean defined via the DSL on thAh pe provided application context. + * @param context The `ApplicationContext` to use for registering the beans + */ override fun invoke(context: GenericApplicationContext) { for (registration in registrations) { if (condition.invoke(context.environment)) { @@ -140,15 +224,3 @@ open class BeanDefinitionDsl(val condition: (ConfigurableEnvironment) -> Boolean } } } - -/** - * Functional bean definition Kotlin DSL. - * - * @author Sebastien Deleuze - * @since 5.0 - */ -fun beans(init: BeanDefinitionDsl.() -> Unit): BeanDefinitionDsl { - val beans = BeanDefinitionDsl() - beans.init() - return beans -} diff --git a/spring-webflux/src/main/kotlin/org/springframework/web/reactive/function/client/WebClientExtensions.kt b/spring-webflux/src/main/kotlin/org/springframework/web/reactive/function/client/WebClientExtensions.kt index a5197bb1bb..93a24c136c 100644 --- a/spring-webflux/src/main/kotlin/org/springframework/web/reactive/function/client/WebClientExtensions.kt +++ b/spring-webflux/src/main/kotlin/org/springframework/web/reactive/function/client/WebClientExtensions.kt @@ -23,7 +23,7 @@ import reactor.core.publisher.Mono /** - * Extension for [WebClient.RequestBodySpec.body] providing a `body() variant + * Extension for [WebClient.RequestBodySpec.body] providing a `body()` variant * leveraging Kotlin reified type parameters. * * @author Sebastien Deleuze diff --git a/spring-webflux/src/main/kotlin/org/springframework/web/reactive/function/server/RouterFunctionDsl.kt b/spring-webflux/src/main/kotlin/org/springframework/web/reactive/function/server/RouterFunctionDsl.kt index 6435047f8c..68f958acd1 100644 --- a/spring-webflux/src/main/kotlin/org/springframework/web/reactive/function/server/RouterFunctionDsl.kt +++ b/spring-webflux/src/main/kotlin/org/springframework/web/reactive/function/server/RouterFunctionDsl.kt @@ -21,181 +21,408 @@ import org.springframework.http.HttpMethod import org.springframework.http.MediaType import reactor.core.publisher.Mono + /** - * Provide a [RouterFunction] Kotlin DSL in order to be able to write idiomatic Kotlin code as below: + * Allow to create easily a `RouterFunction` from a Kotlin router DSL based + * on the same building blocks than the Java one ([RouterFunction], [RequestPredicate], [HandlerFunction]). * - * ```kotlin + * Example: * + * ``` * @Configuration * class ApplicationRoutes(val userHandler: UserHandler) { * - * @Bean - * fun mainRouter() = router { - * accept(TEXT_HTML).nest { - * (GET("/user/") or GET("/users/")).invoke(userHandler::findAllView) - * GET("/users/{login}", userHandler::findViewById) - * } - * accept(APPLICATION_JSON).nest { - * (GET("/api/user/") or GET("/api/users/")).invoke(userHandler::findAll) - * POST("/api/users/", userHandler::create) - * } + * @Bean + * fun mainRouter() = router { + * accept(TEXT_HTML).nest { + * (GET("/user/") or GET("/users/")).invoke(userHandler::findAllView) + * GET("/users/{login}", userHandler::findViewById) + * } + * accept(APPLICATION_JSON).nest { + * (GET("/api/user/") or GET("/api/users/")).invoke(userHandler::findAll) + * POST("/api/users/", userHandler::create) * } - * * } - * ``` * + * } + * ``` * @author Sebastien Deleuze - * @author Yevhenii Melnyk + * @see RouterFunctionDsl * @since 5.0 - * @see Kotlin issue about supporting ::foo for member functions */ - -typealias Routes = RouterFunctionDsl.() -> Unit +fun router(routes: RouterFunctionDsl.() -> Unit) = RouterFunctionDsl().apply(routes).router() /** - * Allow to create easily a [RouterFunction] from [Routes] + * Provide a [RouterFunction] Kotlin DSL in order to be able to write idiomatic Kotlin code. + * + * @author Sebastien Deleuze + * @author Yevhenii Melnyk + * @since 5.0 + * @see Kotlin issue about supporting ::foo for member functions */ -fun router(routes: Routes) = RouterFunctionDsl().apply(routes).router() - class RouterFunctionDsl { - val routes = mutableListOf>() + private val routes = mutableListOf>() + /** + * Return a composed request predicate that tests against both this predicate AND + * the [other] predicate (String processed as a path predicate). When evaluating the + * composed predicate, if this predicate is `false`, then the [other] predicate is not + * evaluated. + * @see RequestPredicate.and + * @see RequestPredicates.path + */ infix fun RequestPredicate.and(other: String): RequestPredicate = this.and(path(other)) + /** + * Return a composed request predicate that tests against both this predicate OR + * the [other] predicate (String processed as a path predicate). When evaluating the + * composed predicate, if this predicate is `true`, then the [other] predicate is not + * evaluated. + * @see RequestPredicate.or + * @see RequestPredicates.path + */ infix fun RequestPredicate.or(other: String): RequestPredicate = this.or(path(other)) + /** + * Return a composed request predicate that tests against both this predicate (String + * processed as a path predicate) AND the [other] predicate. When evaluating the + * composed predicate, if this predicate is `false`, then the [other] predicate is not + * evaluated. + * @see RequestPredicate.and + * @see RequestPredicates.path + */ infix fun String.and(other: RequestPredicate): RequestPredicate = path(this).and(other) + /** + * Return a composed request predicate that tests against both this predicate (String + * processed as a path predicate) OR the [other] predicate. When evaluating the + * composed predicate, if this predicate is `true`, then the [other] predicate is not + * evaluated. + * @see RequestPredicate.or + * @see RequestPredicates.path + */ infix fun String.or(other: RequestPredicate): RequestPredicate = path(this).or(other) + /** + * Return a composed request predicate that tests against both this predicate AND + * the [other] predicate. When evaluating the composed predicate, if this + * predicate is `false`, then the [other] predicate is not evaluated. + * @see RequestPredicate.and + */ infix fun RequestPredicate.and(other: RequestPredicate): RequestPredicate = this.and(other) + /** + * Return a composed request predicate that tests against both this predicate OR + * the [other] predicate. When evaluating the composed predicate, if this + * predicate is `true`, then the [other] predicate is not evaluated. + * @see RequestPredicate.or + */ infix fun RequestPredicate.or(other: RequestPredicate): RequestPredicate = this.or(other) + /** + * Return a predicate that represents the logical negation of this predicate. + */ operator fun RequestPredicate.not(): RequestPredicate = this.negate() - fun RequestPredicate.nest(r: Routes) { + /** + * Route to the given router function if the given request predicate applies. This method can be + * used to create *nested routes*, where a group of routes share a common path (prefix), + * header, or other request predicate. + * @see RouterFunctions.nest + */ + fun RequestPredicate.nest(r: RouterFunctionDsl.() -> Unit) { routes += RouterFunctions.nest(this, RouterFunctionDsl().apply(r).router()) } - fun String.nest(r: Routes) { + /** + * Route to the given router function if the given request predicate (String + * processed as a path predicate) applies. This method can be used to create + * *nested routes*, where a group of routes share a common path + * (prefix), header, or other request predicate. + * @see RouterFunctions.nest + * @see RequestPredicates.path + */ + fun String.nest(r: RouterFunctionDsl.() -> Unit) { routes += RouterFunctions.nest(path(this), RouterFunctionDsl().apply(r).router()) } - operator fun RequestPredicate.invoke(f: (ServerRequest) -> Mono) { - routes += RouterFunctions.route(this, HandlerFunction { f(it) }) - } - + /** + * Route to the given handler function if the given request predicate applies. + * @see RouterFunctions.route + */ fun GET(pattern: String, f: (ServerRequest) -> Mono) { routes += RouterFunctions.route(RequestPredicates.GET(pattern), HandlerFunction { f(it) }) } + /** + * Return a {@code RequestPredicate} that matches if request's HTTP method is {@code GET} + * and the given {@code pattern} matches against the request path. + * @see RequestPredicates.GET + */ fun GET(pattern: String): RequestPredicate = RequestPredicates.GET(pattern) + /** + * Route to the given handler function if the given request predicate applies. + * @see RouterFunctions.route + */ fun HEAD(pattern: String, f: (ServerRequest) -> Mono) { routes += RouterFunctions.route(RequestPredicates.HEAD(pattern), HandlerFunction { f(it) }) } + /** + * Return a {@code RequestPredicate} that matches if request's HTTP method is {@code HEAD} + * and the given {@code pattern} matches against the request path. + * @see RequestPredicates.HEAD + */ fun HEAD(pattern: String): RequestPredicate = RequestPredicates.HEAD(pattern) + /** + * Route to the given handler function if the given POST predicate applies. + * @see RouterFunctions.route + */ fun POST(pattern: String, f: (ServerRequest) -> Mono) { routes += RouterFunctions.route(RequestPredicates.POST(pattern), HandlerFunction { f(it) }) } + /** + * Return a {@code RequestPredicate} that matches if request's HTTP method is {@code POST} + * and the given {@code pattern} matches against the request path. + * @see RequestPredicates.POST + */ fun POST(pattern: String): RequestPredicate = RequestPredicates.POST(pattern) + /** + * Route to the given handler function if the given PUT predicate applies. + * @see RouterFunctions.route + */ fun PUT(pattern: String, f: (ServerRequest) -> Mono) { routes += RouterFunctions.route(RequestPredicates.PUT(pattern), HandlerFunction { f(it) }) } + /** + * Return a {@code RequestPredicate} that matches if request's HTTP method is {@code PUT} + * and the given {@code pattern} matches against the request path. + * @see RequestPredicates.PUT + */ fun PUT(pattern: String): RequestPredicate = RequestPredicates.PUT(pattern) + /** + * Route to the given handler function if the given PATCH predicate applies. + * @see RouterFunctions.route + */ fun PATCH(pattern: String, f: (ServerRequest) -> Mono) { routes += RouterFunctions.route(RequestPredicates.PATCH(pattern), HandlerFunction { f(it) }) } + /** + * Return a {@code RequestPredicate} that matches if request's HTTP method is {@code PATCH} + * and the given {@code pattern} matches against the request path. + * @param pattern the path pattern to match against + * @return a predicate that matches if the request method is PATCH and if the given pattern + * matches against the request path + */ fun PATCH(pattern: String): RequestPredicate = RequestPredicates.PATCH(pattern) + /** + * Route to the given handler function if the given DELETE predicate applies. + * @see RouterFunctions.route + */ fun DELETE(pattern: String, f: (ServerRequest) -> Mono) { routes += RouterFunctions.route(RequestPredicates.DELETE(pattern), HandlerFunction { f(it) }) } + /** + * Return a {@code RequestPredicate} that matches if request's HTTP method is {@code DELETE} + * and the given {@code pattern} matches against the request path. + * @param pattern the path pattern to match against + * @return a predicate that matches if the request method is DELETE and if the given pattern + * matches against the request path + */ fun DELETE(pattern: String): RequestPredicate = RequestPredicates.DELETE(pattern) - + /** + * Route to the given handler function if the given OPTIONS predicate applies. + * @see RouterFunctions.route + */ fun OPTIONS(pattern: String, f: (ServerRequest) -> Mono) { routes += RouterFunctions.route(RequestPredicates.OPTIONS(pattern), HandlerFunction { f(it) }) } + /** + * Return a {@code RequestPredicate} that matches if request's HTTP method is {@code OPTIONS} + * and the given {@code pattern} matches against the request path. + * @param pattern the path pattern to match against + * @return a predicate that matches if the request method is OPTIONS and if the given pattern + * matches against the request path + */ fun OPTIONS(pattern: String): RequestPredicate = RequestPredicates.OPTIONS(pattern) + /** + * Route to the given handler function if the given accept predicate applies. + * @see RouterFunctions.route + */ fun accept(mediaType: MediaType, f: (ServerRequest) -> Mono) { routes += RouterFunctions.route(RequestPredicates.accept(mediaType), HandlerFunction { f(it) }) } + /** + * Return a {@code RequestPredicate} that tests if the request's + * {@linkplain ServerRequest.Headers#accept() accept} header is + * {@linkplain MediaType#isCompatibleWith(MediaType) compatible} with any of the given media types. + * @param mediaTypes the media types to match the request's accept header against + * @return a predicate that tests the request's accept header against the given media types + */ fun accept(mediaType: MediaType): RequestPredicate = RequestPredicates.accept(mediaType) + /** + * Route to the given handler function if the given contentType predicate applies. + * @see RouterFunctions.route + */ fun contentType(mediaType: MediaType, f: (ServerRequest) -> Mono) { routes += RouterFunctions.route(RequestPredicates.contentType(mediaType), HandlerFunction { f(it) }) } + /** + * Return a {@code RequestPredicate} that tests if the request's + * {@linkplain ServerRequest.Headers#contentType() content type} is + * {@linkplain MediaType#includes(MediaType) included} by any of the given media types. + * @param mediaTypes the media types to match the request's content type against + * @return a predicate that tests the request's content type against the given media types + */ fun contentType(mediaType: MediaType): RequestPredicate = RequestPredicates.contentType(mediaType) - fun headers(headerPredicate: (ServerRequest.Headers) -> Boolean, f: (ServerRequest) -> Mono) { - routes += RouterFunctions.route(RequestPredicates.headers(headerPredicate), HandlerFunction { f(it) }) + /** + * Route to the given handler function if the given headers predicate applies. + * @see RouterFunctions.route + */ + fun headers(headersPredicate: (ServerRequest.Headers) -> Boolean, f: (ServerRequest) -> Mono) { + routes += RouterFunctions.route(RequestPredicates.headers(headersPredicate), HandlerFunction { f(it) }) } - fun headers(headerPredicate: (ServerRequest.Headers) -> Boolean): RequestPredicate = RequestPredicates.headers(headerPredicate) - + /** + * Return a {@code RequestPredicate} that tests the request's headers against the given headers predicate. + * @param headersPredicate a predicate that tests against the request headers + * @return a predicate that tests against the given header predicate + */ + fun headers(headersPredicate: (ServerRequest.Headers) -> Boolean): RequestPredicate = RequestPredicates.headers(headersPredicate) + + /** + * Route to the given handler function if the given method predicate applies. + * @see RouterFunctions.route + */ fun method(httpMethod: HttpMethod, f: (ServerRequest) -> Mono) { routes += RouterFunctions.route(RequestPredicates.method(httpMethod), HandlerFunction { f(it) }) } + /** + * Return a {@code RequestPredicate} that tests against the given HTTP method. + * @param httpMethod the HTTP method to match to + * @return a predicate that tests against the given HTTP method + */ fun method(httpMethod: HttpMethod): RequestPredicate = RequestPredicates.method(httpMethod) + /** + * Route to the given handler function if the given path predicate applies. + * @see RouterFunctions.route + */ fun path(pattern: String, f: (ServerRequest) -> Mono) { routes += RouterFunctions.route(RequestPredicates.path(pattern), HandlerFunction { f(it) }) } + /** + * Return a {@code RequestPredicate} that tests the request path against the given path pattern. + * @see RequestPredicates.path + */ fun path(pattern: String): RequestPredicate = RequestPredicates.path(pattern) + /** + * Route to the given handler function if the given pathExtension predicate applies. + * @see RouterFunctions.route + */ fun pathExtension(extension: String, f: (ServerRequest) -> Mono) { routes += RouterFunctions.route(RequestPredicates.pathExtension(extension), HandlerFunction { f(it) }) } + /** + * Return a {@code RequestPredicate} that matches if the request's path has the given extension. + * @param extension the path extension to match against, ignoring case + * @return a predicate that matches if the request's path has the given file extension + */ fun pathExtension(extension: String): RequestPredicate = RequestPredicates.pathExtension(extension) + /** + * Route to the given handler function if the given pathExtension predicate applies. + * @see RouterFunctions.route + */ fun pathExtension(predicate: (String) -> Boolean, f: (ServerRequest) -> Mono) { routes += RouterFunctions.route(RequestPredicates.pathExtension(predicate), HandlerFunction { f(it) }) } + /** + * Return a {@code RequestPredicate} that matches if the request's path matches the given + * predicate. + * @see RequestPredicates.pathExtension + */ fun pathExtension(predicate: (String) -> Boolean): RequestPredicate = RequestPredicates.pathExtension(predicate) + /** + * Route to the given handler function if the given queryParam predicate applies. + * @see RouterFunctions.route + */ fun queryParam(name: String, predicate: (String) -> Boolean, f: (ServerRequest) -> Mono) { routes += RouterFunctions.route(RequestPredicates.queryParam(name, predicate), HandlerFunction { f(it) }) } + /** + * Return a {@code RequestPredicate} that tests the request's query parameter of the given name + * against the given predicate. + * @param name the name of the query parameter to test against + * @param predicate predicate to test against the query parameter value + * @return a predicate that matches the given predicate against the query parameter of the given name + * @see ServerRequest#queryParam(String) + */ fun queryParam(name: String, predicate: (String) -> Boolean): RequestPredicate = RequestPredicates.queryParam(name, predicate) + /** + * Route to the given handler function if the given request predicate applies. + * @see RouterFunctions.route + */ + operator fun RequestPredicate.invoke(f: (ServerRequest) -> Mono) { + routes += RouterFunctions.route(this, HandlerFunction { f(it) }) + } + + /** + * Route to the given handler function if the given predicate (String + * processed as a path predicate) applies. + * @see RouterFunctions.route + */ operator fun String.invoke(f: (ServerRequest) -> Mono) { routes += RouterFunctions.route(RequestPredicates.path(this), HandlerFunction { f(it) }) } + /** + * Route requests that match the given pattern to resources relative to the given root location. + * @see RouterFunctions.resources + */ fun resources(path: String, location: Resource) { routes += RouterFunctions.resources(path, location) } + /** + * Route to resources using the provided lookup function. If the lookup function provides a + * [Resource] for the given request, it will be it will be exposed using a + * [HandlerFunction] that handles GET, HEAD, and OPTIONS requests. + */ fun resources(lookupFunction: (ServerRequest) -> Mono) { routes += RouterFunctions.resources(lookupFunction) } - fun router(): RouterFunction { + /** + * Return a composed routing function created from all the registered routes. + */ + internal fun router(): RouterFunction { return routes.reduce(RouterFunction::and) } - operator fun invoke(request: ServerRequest): Mono> { - return router().route(request) - } - }