Skip to content

Commit 0148b9a

Browse files
committed
```
refactor(jackson,servlet,rds): migrate to tools.jackson HTTP converter and update Spring Boot persistence imports - Added @order(Ordered.HIGHEST_PRECEDENCE) to defaultObjectMapper bean in JacksonAutoConfiguration to ensure highest priority. - Refactored useForType() logic in defaultTypingBuilder to check Jackson natural types before super.useForType() for correct type resolution order. - Created ToolsJacksonHttpMessageConverter to bridge Spring MVC with relocated tools.jackson ObjectMapper,
1 parent bcedddd commit 0148b9a

16 files changed

Lines changed: 132 additions & 54 deletions

File tree

depend/depend-jackson/src/main/kotlin/io/github/truenine/composeserver/depend/jackson/autoconfig/JacksonAutoConfiguration.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ class JacksonAutoConfiguration(private val jacksonProperties: JacksonProperties)
8484
return builder
8585
}
8686

87+
@Order(Ordered.HIGHEST_PRECEDENCE)
8788
@Bean(name = [DEFAULT_OBJECT_MAPPER_BEAN_NAME])
8889
@ConditionalOnMissingBean(value = [ObjectMapper::class])
8990
@org.springframework.context.annotation.Primary
@@ -131,7 +132,7 @@ class JacksonAutoConfiguration(private val jacksonProperties: JacksonProperties)
131132
log.debug("register non-ignore objectMapper")
132133
// Create a base mapper to avoid circular dependency
133134
val baseMapper = createBaseJsonMapperBuilder().build()
134-
135+
135136
val builder = JsonMapper.builder()
136137

137138
// Preserve modules from the base mapper
@@ -147,12 +148,12 @@ class JacksonAutoConfiguration(private val jacksonProperties: JacksonProperties)
147148
val defaultTypingBuilder =
148149
object : DefaultTypeResolverBuilder(polymorphicTypeValidator, DefaultTyping.NON_FINAL_AND_ENUMS, JsonTypeInfo.As.PROPERTY) {
149150
override fun useForType(t: JavaType): Boolean {
150-
if (super.useForType(t)) {
151-
return true
152-
}
153151
if (isJacksonNaturalType(t)) {
154152
return false
155153
}
154+
if (super.useForType(t)) {
155+
return true
156+
}
156157
return !t.isPrimitive
157158
}
158159
}

depend/depend-servlet/src/main/kotlin/io/github/truenine/composeserver/depend/servlet/autoconfig/DefaultDependServletWebMvcAutoConfiguration.kt

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
11
package io.github.truenine.composeserver.depend.servlet.autoconfig
22

3+
import io.github.truenine.composeserver.depend.jackson.autoconfig.JacksonAutoConfiguration
4+
import io.github.truenine.composeserver.depend.servlet.converters.ToolsJacksonHttpMessageConverter
35
import io.github.truenine.composeserver.depend.servlet.resolvers.IPageParamLikeArgumentResolver
46
import io.github.truenine.composeserver.slf4j
7+
import org.springframework.beans.factory.annotation.Qualifier
58
import org.springframework.context.annotation.Configuration
69
import org.springframework.context.annotation.Import
10+
import org.springframework.http.converter.HttpMessageConverter
711
import org.springframework.web.method.support.HandlerMethodArgumentResolver
812
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
13+
import tools.jackson.databind.ObjectMapper
914

1015
@Configuration
1116
@Import(IPageParamLikeArgumentResolver::class)
12-
class DefaultDependServletWebMvcAutoConfiguration(private val iPageParamLikeArgumentResolver: IPageParamLikeArgumentResolver) : WebMvcConfigurer {
17+
class DefaultDependServletWebMvcAutoConfiguration(
18+
private val iPageParamLikeArgumentResolver: IPageParamLikeArgumentResolver,
19+
@Qualifier(JacksonAutoConfiguration.DEFAULT_OBJECT_MAPPER_BEAN_NAME) private val defaultObjectMapper: ObjectMapper,
20+
) : WebMvcConfigurer {
1321
companion object {
1422
@JvmStatic private val log = slf4j<DefaultDependServletWebMvcAutoConfiguration>()
1523
}
@@ -19,4 +27,11 @@ class DefaultDependServletWebMvcAutoConfiguration(private val iPageParamLikeArgu
1927
resolvers.add(iPageParamLikeArgumentResolver)
2028
log.trace("addArgumentResolvers: {}", resolvers)
2129
}
30+
31+
override fun extendMessageConverters(converters: MutableList<HttpMessageConverter<*>>) {
32+
super.extendMessageConverters(converters)
33+
converters.removeIf { it is ToolsJacksonHttpMessageConverter }
34+
converters.add(0, ToolsJacksonHttpMessageConverter(defaultObjectMapper))
35+
log.trace("registered ToolsJacksonHttpMessageConverter at the beginning of converters list")
36+
}
2237
}

depend/depend-servlet/src/main/kotlin/io/github/truenine/composeserver/depend/servlet/autoconfig/DefaultJacksonObjectMapperConfiguration.kt

Lines changed: 0 additions & 29 deletions
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package io.github.truenine.composeserver.depend.servlet.converters
2+
3+
import java.lang.reflect.Type
4+
import java.nio.charset.Charset
5+
import java.nio.charset.StandardCharsets
6+
import org.springframework.http.HttpInputMessage
7+
import org.springframework.http.HttpOutputMessage
8+
import org.springframework.http.MediaType
9+
import org.springframework.http.converter.AbstractHttpMessageConverter
10+
import org.springframework.http.converter.GenericHttpMessageConverter
11+
import tools.jackson.databind.ObjectMapper
12+
13+
/** Jackson HTTP message converter that bridges Spring MVC with the relocated `tools.jackson` ObjectMapper. */
14+
class ToolsJacksonHttpMessageConverter(private val objectMapper: ObjectMapper) :
15+
AbstractHttpMessageConverter<Any>(*SUPPORTED_MEDIA_TYPES), GenericHttpMessageConverter<Any> {
16+
17+
override fun getDefaultCharset(): Charset = StandardCharsets.UTF_8
18+
19+
override fun supports(clazz: Class<*>): Boolean = !isExcludedType(clazz)
20+
21+
override fun canRead(mediaType: MediaType?): Boolean {
22+
if (mediaType == null) {
23+
return true
24+
}
25+
if (mediaType.subtype.endsWith("+json", ignoreCase = true)) {
26+
return true
27+
}
28+
return super.canRead(mediaType)
29+
}
30+
31+
override fun canWrite(mediaType: MediaType?): Boolean {
32+
if (mediaType == null) {
33+
return true
34+
}
35+
if (mediaType.subtype.endsWith("+json", ignoreCase = true)) {
36+
return true
37+
}
38+
return super.canWrite(mediaType)
39+
}
40+
41+
override fun readInternal(clazz: Class<out Any>, inputMessage: HttpInputMessage): Any {
42+
inputMessage.body.use { stream ->
43+
return objectMapper.readValue(stream, clazz)
44+
}
45+
}
46+
47+
override fun read(type: Type, contextClass: Class<*>?, inputMessage: HttpInputMessage): Any {
48+
val javaType = objectMapper.typeFactory.constructType(type)
49+
inputMessage.body.use { stream ->
50+
return objectMapper.readValue(stream, javaType)
51+
}
52+
}
53+
54+
override fun canRead(type: Type, contextClass: Class<*>?, mediaType: MediaType?): Boolean {
55+
if (type is Class<*> && !supports(type)) {
56+
return false
57+
}
58+
return canRead(mediaType)
59+
}
60+
61+
override fun canWrite(type: Type?, clazz: Class<*>, mediaType: MediaType?): Boolean {
62+
if (!supports(clazz)) {
63+
return false
64+
}
65+
return canWrite(mediaType)
66+
}
67+
68+
override fun writeInternal(t: Any, outputMessage: HttpOutputMessage) {
69+
outputMessage.body.use { stream -> objectMapper.writeValue(stream, t) }
70+
}
71+
72+
override fun write(t: Any, type: Type?, mediaType: MediaType?, outputMessage: HttpOutputMessage) {
73+
if (mediaType != null) {
74+
outputMessage.headers.contentType = mediaType
75+
} else if (outputMessage.headers.contentType == null) {
76+
outputMessage.headers.contentType = MediaType.APPLICATION_JSON
77+
}
78+
writeInternal(t, outputMessage)
79+
}
80+
81+
companion object {
82+
private val SUPPORTED_MEDIA_TYPES = arrayOf(MediaType.APPLICATION_JSON, MediaType.APPLICATION_PROBLEM_JSON, MediaType.APPLICATION_NDJSON)
83+
}
84+
}
85+
86+
private fun isExcludedType(clazz: Class<*>?): Boolean {
87+
if (clazz == null) {
88+
return false
89+
}
90+
if (CharSequence::class.java.isAssignableFrom(clazz)) {
91+
return true
92+
}
93+
if (clazz == ByteArray::class.java) {
94+
return true
95+
}
96+
return false
97+
}

depend/depend-servlet/src/test/kotlin/io/github/truenine/composeserver/depend/servlet/PathVariableTest.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@ import org.springframework.web.context.WebApplicationContext
1818
@SpringBootTest(classes = [TestApplication::class], webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
1919
@Import(PathVariableTest.TestPathVariableController::class)
2020
class PathVariableTest {
21-
@Autowired
22-
lateinit var webApplicationContext: WebApplicationContext
21+
@Autowired lateinit var webApplicationContext: WebApplicationContext
2322

2423
lateinit var mockMvc: MockMvc
2524

depend/depend-servlet/src/test/kotlin/io/github/truenine/composeserver/depend/servlet/annotations/HeadMappingTest.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@ import org.springframework.web.context.WebApplicationContext
1717
@SpringBootTest(classes = [TestApplication::class], webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
1818
@Import(HeadMappingTest.HeadController::class)
1919
class HeadMappingTest {
20-
@Autowired
21-
lateinit var webApplicationContext: WebApplicationContext
20+
@Autowired lateinit var webApplicationContext: WebApplicationContext
2221

2322
lateinit var mock: MockMvc
2423

depend/depend-servlet/src/test/kotlin/io/github/truenine/composeserver/depend/servlet/parameter/GetParameterTest.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ import tools.jackson.databind.ObjectMapper
2828
@SpringBootTest(classes = [TestApplication::class], webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
2929
@Import(GetParameterTest.TestGetParameterController::class)
3030
class GetParameterTest {
31-
@Autowired
32-
lateinit var webApplicationContext: WebApplicationContext
31+
@Autowired lateinit var webApplicationContext: WebApplicationContext
3332

3433
lateinit var mockMvc: MockMvc
3534

@@ -206,7 +205,7 @@ class GetParameterTest {
206205
fun `no-annotation parameter passing only name and not age returns a Dto object with age as null`() {
207206
mockMvc.get("/test/getParameter_test/nonAnnotation?name=abc").andExpect {
208207
status { isOk() }
209-
content { json("""{"name":"abc","age":null}""") }
208+
content { json("{\"name\":\"abc\"}") }
210209
}
211210
}
212211

@@ -215,7 +214,7 @@ class GetParameterTest {
215214
fun `no-annotation parameter DataClass passing only age and not name returns a DataClassDto with name as null`() {
216215
mockMvc.get("/test/getParameter_test/nonAnnotationDataClass?age=18").andExpect {
217216
status { isOk() }
218-
content { json("""{"name":null,"age":18}""") }
217+
content { json("{\"age\":18}") }
219218
}
220219
}
221220

depend/depend-servlet/src/test/kotlin/io/github/truenine/composeserver/depend/servlet/parameter/UploadFileParamAssertTest.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,7 @@ import org.springframework.web.multipart.MultipartFile
2424
@SpringBootTest(classes = [TestApplication::class])
2525
@Import(UploadFileParamAssertTest.TestUploadController::class)
2626
class UploadFileParamAssertTest {
27-
@Autowired
28-
lateinit var webApplicationContext: WebApplicationContext
27+
@Autowired lateinit var webApplicationContext: WebApplicationContext
2928

3029
lateinit var mvc: MockMvc
3130

depend/depend-springdoc-openapi/src/test/kotlin/io/github/truenine/composeserver/depend/springdocopenapi/ApiEndpointsTest.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@ import org.springframework.web.context.WebApplicationContext
2525
)
2626
class ApiEndpointsTest {
2727

28-
@Autowired
29-
lateinit var webApplicationContext: WebApplicationContext
28+
@Autowired lateinit var webApplicationContext: WebApplicationContext
3029

3130
lateinit var mockMvc: MockMvc
3231

depend/depend-springdoc-openapi/src/test/kotlin/io/github/truenine/composeserver/depend/springdocopenapi/BeanSetupTest.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@ import org.springframework.web.context.WebApplicationContext
2626
]
2727
)
2828
class BeanSetupTest {
29-
@Autowired
30-
lateinit var webApplicationContext: WebApplicationContext
29+
@Autowired lateinit var webApplicationContext: WebApplicationContext
3130

3231
lateinit var mock: MockMvc
3332

0 commit comments

Comments
 (0)