WebTestClient gets 404 on Spring Boot 2.4.0-M3 while works fine on 2.4.0-M2 - spring-boot

I have test that works properly with Spring 2.4.0-M2 but after upgrading to 2.4.0-M3 it breaks - returns 404 for a route that is registered.
My app:
#SpringBootApplication(proxyBeanMethods = false)
class ExampleApp
fun main(args: Array<String>) {
runApplication<ExampleApp>(
init = {
addInitializers(BeansInitializer())
},
args = args
)
}
beans:
class BeansInitializer : ApplicationContextInitializer<GenericApplicationContext> {
#Suppress("LongMethod")
override fun initialize(applicationContext: GenericApplicationContext) {
beans {
bean {
router {
"/routes".nest {
GET("/{id}") { ServerResponse.ok().bodyValue(Foo("ok")) }
POST("/") { ServerResponse.ok().bodyValue(Foo("ok")) }
}
}
}
}
.initialize(applicationContext)
}
}
data class Foo(val status: String)
My test:
#SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = [
ExampleApp::class
]
)
class FailingTest #Autowired constructor(
context: ApplicationContext,
) {
val webTestClient: WebTestClient = WebTestClient.bindToApplicationContext(context)
.configureClient()
.build()
#Test
fun `should interact with routes`() {
webTestClient
.post()
.uri("/routes")
.bodyValue(SampleBody("123"))
.exchange()
.expectStatus()
.isOk // returns 404 on 2.4.0-M3 / passes on 2.4.0-M2
}
data class SampleBody(val id: String)
}
test application.yml
context:
initializer:
classes: com.example.BeansInitializer
On 2.4.0-M3 tests fail with following message:
java.lang.AssertionError: Status expected:<200 OK> but was:<404 NOT_FOUND>
On 2.4.0-M2 they pass.
Is there something that changed through the versions? Or this is a bug?

The change in behaviour that you are seeing is due to an improvement in Spring Framework during the development of 5.3.
By default, Spring Framework will match an optional trailing path separator (/). This optional / should be in addition to the path specified in your routes.
You have two routes:
GET /routes/{id}
POST /routes/
The support for an optional trailing path separator means that you could make a get request to /routes/56/ (an additional trailing /), but it should not mean that you can make a request to POST /routes (removal of a trailing /).
If you want to be able to make POST requests to both /routes and /routes/, you should define the route as /routes:
beans {
bean {
router {
"/routes".nest {
GET("/{id}") { ServerResponse.ok().bodyValue(Foo("ok")) }
POST("") { ServerResponse.ok().bodyValue(Foo("ok")) }
}
}
}
}

Related

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

Spring-fu-Kofu: Unable to wire `NamedParameterJdbcTemplate`

I am playing around with Kofu functional Bean DSL. I am using Spring-Data-JDBC with Spring-MVC and trying to autowire NamedParameterJdbcTemplate. However, I am have been receiving this error that no beans found for it while running tests. In a annotation based approach, we don’t have to supply an explicit NamedParameterJdbcTemplate. My sample app here: https://github.com/overfullstack/kofu-mvc-jdbc. And PFB some code snippets from it:
val app = application(WebApplicationType.SERVLET) {
beans {
bean<SampleService>()
bean<UserHandler>()
}
enable(dataConfig)
enable(webConfig)
}
val dataConfig = configuration {
beans {
bean<UserRepository>()
}
listener<ApplicationReadyEvent> {
ref<UserRepository>().init()
}
}
val webConfig = configuration {
webMvc {
port = if (profiles.contains("test")) 8181 else 8080
router {
val handler = ref<UserHandler>()
GET("/", handler::hello)
GET("/api", handler::json)
}
converters {
string()
jackson()
}
}
}
class UserRepository(private val client: NamedParameterJdbcTemplate) {
fun count() =
client.queryForObject("SELECT COUNT(*) FROM users", emptyMap<String, String>(), Int::class.java)
}
open class UserRepositoryTests {
private val dataApp = application(WebApplicationType.NONE) {
enable(dataConfig)
}
private lateinit var context: ConfigurableApplicationContext
#BeforeAll
fun beforeAll() {
context = dataApp.run(profiles = "test")
}
#Test
fun count() {
val repository = context.getBean<UserRepository>()
assertEquals(3, repository.count())
}
#AfterAll
fun afterAll() {
context.close()
}
}
This is the error:
Parameter 0 of constructor in com.sample.UserRepository required a bean of type 'org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate' that could not be found.
Action:
Consider defining a bean of type 'org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate' in your configuration.
Please help, thanks
Apparently Kofu doesn't pick datasource from application.properties file. Everything is meant to be declarative and no implicit derivations. (Basically no Spring magic 🙂). This worked for me:
val dataConfig = configuration {
beans {
bean {
val dataSourceBuilder = DataSourceBuilder.create()
dataSourceBuilder.driverClassName(“org.h2.Driver”)
dataSourceBuilder.url(“jdbc:h2:mem:test”)
dataSourceBuilder.username(“SA”)
dataSourceBuilder.password(“”)
dataSourceBuilder.build()
}
bean<NamedParameterJdbcTemplate>()
bean<UserRepository>()
}
listener<ApplicationReadyEvent> {
ref<UserRepository>().init()
}
}

#PostConstruct method runs before flyway

I know this kind of question has been asked before.
I have a method which is annotated with #PostConstruct.
The methods assumes that all Flyway scripts have been executed before invocation.
It seems that Flyway also uses #PostConstruct annotated methods and that these methods are called after my method.
I tried to annotate my method with #DependOn and different flyway beennames.
Unfortunately without success. Can anybody help me.
Solution:
I would set a dependency on the FlywayMigrationInitializer in the constructor. When the Initializer is created and set up, the migrations are run.
Or you can depend on the flywayInitializer bean (#DependsOn("flywayInitializer")). The bean is named flywayInitializer, of the class FlywayMigrationInitializer and it is created in FlywayAutoConfiguration.java.
FlywayMigrationInitializer implements InitializingBean and calls the migrate method in the afterPropertiesSet method.
Example:
#Component
// #DependsOn("flywayInitializer")
#Slf4j
public class TestPostConstruct {
public TestPostConstruct(FlywayMigrationInitializer flywayForceInitialization) {
}
#PostConstruct
public void testPostConstruct() {
log.info("----> in testPostConstruct");
}
}
The Spring Boot log:
INFO 4760 --- [main] o.f.core.internal.command.DbMigrate : Successfully applied 1 migration to schema "PUBLIC" (execution time 00:00.130s)
INFO 4760 --- [main] c.example.flywayinit.TestPostConstruct : ----> in testPostConstruct
For new Flyway this work (use Flyway callbacks)
#Configuration
class FlywayConfig(env: Environment) {
private val env: Environment
init {
this.env = env
}
#Bean(initMethod = "migrate")
fun flyway(dbLoadService: DbLoadService): Flyway {
return Flyway(
Flyway.configure()
.baselineOnMigrate(true)
.dataSource(
env.getRequiredProperty("spring.datasource.url"),
env.getRequiredProperty("spring.datasource.username"),
env.getRequiredProperty("spring.datasource.password")
)
//запуск загрузки из базы после окончания миграции
.callbacks(FlywayMigrationsCompleteCallback {
dbLoadService.loadAllCertificateInformation()
})
)
}
class FlywayMigrationsCompleteCallback(private val callback: () -> Unit) : Callback {
override fun supports(event: Event?, context: Context?): Boolean {
return event == Event.AFTER_MIGRATE
}
override fun canHandleInTransaction(event: Event?, context: Context?): Boolean {
return true
}
override fun handle(event: Event?, context: Context?) {
callback()
}
override fun getCallbackName(): String {
return FlywayMigrationsCompleteCallback::class.simpleName!!
}
}
#Component
class DbLoadService(private val certificateRepository:CertificateRepository) {
#Volatile var certificate: List<Certificate>?=null
fun loadAllCertificateInformation(){
val findAll = certificateRepository.findAll()
runBlocking {
certificate = findAll.toList()
}
}
}

Async Spring Boot using Kotlin not working

I'm trying to create a Spring Service that performs an operation asynchronously and returns a ListenableFuture. I want the failure callback to be triggered when the operation fails - my attempt to do this is to use AsyncResult.forExecutionException as seen below:
#Service
open class UserClientService {
#Async
fun fetchUser(email: String): ListenableFuture<User> {
val uri = buildUri(email)
val headers = buildHeaders()
try {
val result = restTemplate.exchange(uri, HttpMethod.GET, HttpEntity<Any>(headers), User::class.java)
return AsyncResult.forValue(result.body)
} catch (e: RestClientException) {
return AsyncResult.forExecutionException(e)
}
}
}
The entry-point:
#SpringBootApplication
#EnableAsync
open class UserProxyApplication
fun main(args: Array<String>) {
SpringApplication.run(UserProxyApplication::class.java, *args)
}
The Spring RestController implementation is as follows:
#RestController
#RequestMapping("/users")
class UserController #Autowired constructor(
val client: UserClientService
) {
#RequestMapping(method = arrayOf(RequestMethod.GET))
fun getUser(#RequestParam(value = "email") email: String): DeferredResult<ResponseEntity<User>> {
val result = DeferredResult<ResponseEntity<User>>(TimeUnit.SECONDS.toMillis(10))
client.fetchUser(email).addCallback(
{ success -> result.setResult(ResponseEntity.ok(success)) },
{ failure -> result.setResult(ResponseEntity(HttpStatus.NOT_FOUND)) }
)
return result;
}
}
Problem is that the failure callback in the UserController is never triggered when an exception is thrown in the UserClientService REST call. Instead, the success callback is triggered with success argument being null.
In Kotlin, I can check if success is null by using success!! - this throws an exception that then does trigger the failure callback with failure argument being the NPE.
Question is how can I trigger the failure callback in the UserController when an exception has occurred in the UserClientService?
Update A it seems that everything is executed on the same thread "http-nio-8080-exec-XXX" regardless of whether I use #Async or not -- see comments.
This all works if:
A) the method fetchUser is declared open, i.e. not final so that Spring can proxy the call
...or...
B) you create an interface IUserClientService and use that in the constructor of the UserController:
interface IUserClientService {
fun fetchUser(email: String): ListenableFuture<User>
}
Now the UserClientService implements the interface:
#Service
open class UserClientService : IUserClientService {
#Async
override fun fetchUser(email: String): ListenableFuture<User> {
// ... rest as shown in question ...
And finally the UserController:
#RestController
#RequestMapping("/users")
class UserController #Autowired constructor(
val client: IUserClientService
) {
#RequestMapping(method = arrayOf(RequestMethod.GET))
fun getUser(#RequestParam(value = "email") email: String): DeferredResult<ResponseEntity<User>> {
// ... rest as shown in question ...
Not sure if this is because I'm using Kotlin. The examples that I've seen don't require implementing an interface.

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

Resources