Skip to content

Commit 1959997

Browse files
committed
Refactor Subject and related APIs to use List<String> for path parameters, SortedMap<String, String> for query parameters, update factories, and improve path handling consistency.
1 parent 4fe9935 commit 1959997

13 files changed

Lines changed: 116 additions & 49 deletions

File tree

buildSrc/src/main/kotlin/Channel.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ object Channel : FunctionProvider {
102102
.addParameter(channelRequestSupplierParameter)
103103
.addCode(
104104
"""
105-
%N.%N(%N, %N,%L) { request -> %N.asFlow(with(%N) { %T(request.path, request.pathVariables, request.queryParameters, %N.asPublisher(request.receive)).invoke() }) }
105+
%N.%N(%N, %N,%L) { request -> %N.asFlow(with(%N) { %T(request.pathParameters, request.pathVariables, request.queryParameters, %N.asPublisher(request.receive)).invoke() }) }
106106
"""
107107
.trimIndent(),
108108
binderProperty,

buildSrc/src/main/kotlin/Constants.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ val pathSupplierClassName = ClassName(apiPackageName, "PathSupplier")
4848
val requestClassName = ClassName(apiPackageName, "Request")
4949
val requestSupplierClassName = ClassName(apiPackageName, "RequestSupplier")
5050
val subjectClassName = ClassName("com.caplin.integration.datasourcex.util", "Subject")
51+
val pathMemberName = MemberName(subjectClassName.nestedClass("Companion"), "path")
5152
val channelRequestClassName = ClassName(apiPackageName, "ChannelRequest")
5253
val channelRequestSupplierClassName = ClassName(apiPackageName, "ChannelRequestSupplier")
5354
val channelSupplierClassName = ClassName(apiPackageName, "ChannelSupplier")

buildSrc/src/main/kotlin/GenerateApi.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ abstract class GenerateApi : DefaultTask() {
164164
val packageName = "$rootPackageName.${publisherType.packageName}"
165165
val fileBuilder =
166166
FileSpec.builder(packageName, bindTypeName)
167+
.apply { if (publishTypeName != "Broadcast") addImport(pathMemberName) }
167168
.addAnnotation(
168169
AnnotationSpec.builder(Suppress::class)
169170
.useSiteTarget(AnnotationSpec.UseSiteTarget.FILE)

reactive/api/api/datasourcex-reactive-api.api

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,10 @@ public final class com/caplin/integration/datasourcex/reactive/api/ChannelConfig
104104
}
105105

106106
public final class com/caplin/integration/datasourcex/reactive/api/ChannelRequest : com/caplin/integration/datasourcex/util/Subject {
107-
public fun <init> (Ljava/lang/String;Ljava/util/LinkedHashMap;Ljava/util/Map;Ljava/lang/Object;)V
108-
public fun getPath ()Ljava/lang/String;
107+
public fun <init> (Ljava/util/List;Ljava/util/LinkedHashMap;Ljava/util/SortedMap;Ljava/lang/Object;)V
109108
public fun getPathParameters ()Ljava/util/List;
110109
public final fun getPathVariables ()Ljava/util/LinkedHashMap;
111-
public fun getQueryParameters ()Ljava/util/Map;
110+
public fun getQueryParameters ()Ljava/util/SortedMap;
112111
public final fun getReceive ()Ljava/lang/Object;
113112
}
114113

@@ -209,11 +208,10 @@ public final class com/caplin/integration/datasourcex/reactive/api/RecordType :
209208
}
210209

211210
public final class com/caplin/integration/datasourcex/reactive/api/Request : com/caplin/integration/datasourcex/util/Subject {
212-
public fun <init> (Ljava/lang/String;Ljava/util/LinkedHashMap;Ljava/util/Map;)V
213-
public fun getPath ()Ljava/lang/String;
211+
public fun <init> (Ljava/util/List;Ljava/util/LinkedHashMap;Ljava/util/SortedMap;)V
214212
public fun getPathParameters ()Ljava/util/List;
215213
public final fun getPathVariables ()Ljava/util/LinkedHashMap;
216-
public fun getQueryParameters ()Ljava/util/Map;
214+
public fun getQueryParameters ()Ljava/util/SortedMap;
217215
}
218216

219217
public abstract interface class com/caplin/integration/datasourcex/reactive/api/RequestSupplier {
Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,21 @@
11
package com.caplin.integration.datasourcex.reactive.api
22

33
import com.caplin.integration.datasourcex.util.Subject
4+
import java.util.SortedMap
45

56
/**
67
* A decomposed subject request passed to a supplier for a channel bind.
78
*
8-
* @property path The full requested subject.
9+
* @property pathParameters The subject's path parts, in order.
910
* @property pathVariables The path variables extracted from the subject, keyed by name, in pattern
10-
* order (which backs [pathParameters]).
11+
* order.
1112
* @property queryParameters The query parameters parsed from the subject's optional trailing
1213
* `?a=b&c=d`, or empty when there is no query.
1314
* @property receive The flow of messages received from the client on this channel.
1415
*/
1516
class ChannelRequest<R>(
16-
override val path: String,
17+
override val pathParameters: List<String>,
1718
val pathVariables: LinkedHashMap<String, String>,
18-
override val queryParameters: Map<String, String>,
19+
override val queryParameters: SortedMap<String, String>,
1920
val receive: R,
20-
) : Subject {
21-
override val pathParameters: List<String>
22-
get() = pathVariables.values.toList()
23-
}
21+
) : Subject
Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
11
package com.caplin.integration.datasourcex.reactive.api
22

33
import com.caplin.integration.datasourcex.util.Subject
4+
import java.util.SortedMap
45

56
/**
67
* A decomposed subject request passed to a supplier for an active or container bind.
78
*
8-
* @property path The full requested subject.
9+
* @property pathParameters The subject's path parts, in order.
910
* @property pathVariables The path variables extracted from the subject, keyed by name, in pattern
10-
* order (which backs [pathParameters]).
11+
* order.
1112
* @property queryParameters The query parameters parsed from the subject's optional trailing
1213
* `?a=b&c=d`, or empty when there is no query.
1314
*/
1415
class Request(
15-
override val path: String,
16+
override val pathParameters: List<String>,
1617
val pathVariables: LinkedHashMap<String, String>,
17-
override val queryParameters: Map<String, String>,
18-
) : Subject {
19-
override val pathParameters: List<String>
20-
get() = pathVariables.values.toList()
21-
}
18+
override val queryParameters: SortedMap<String, String>,
19+
) : Subject

reactive/core/src/main/kotlin/com/caplin/integration/datasourcex/reactive/core/Binder.kt

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import com.caplin.integration.datasourcex.reactive.api.ServiceConfig
4141
import com.caplin.integration.datasourcex.util.AntPatternNamespace
4242
import com.caplin.integration.datasourcex.util.AntPatternNamespace.Companion.addIncludeNamespace
4343
import com.caplin.integration.datasourcex.util.Subject
44+
import com.caplin.integration.datasourcex.util.Subject.Companion.path
4445
import com.caplin.integration.datasourcex.util.getLogger
4546
import java.util.concurrent.ConcurrentHashMap
4647
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -100,7 +101,11 @@ private constructor(val dataSource: ScopedDataSource, private val serviceInfo: S
100101

101102
/** Decomposes [subject] into a [Request] using this namespace's extraction rules. */
102103
private fun AntPatternNamespace.request(subject: String): Request =
103-
Request(subject, extractPathVariables(subject), extractQueryParameters(subject))
104+
Request(
105+
extractPathParameters(subject),
106+
extractPathVariables(subject),
107+
extractQueryParameters(subject),
108+
)
104109

105110
private data class ServiceInfo(
106111
val service: Service,
@@ -133,6 +138,7 @@ private constructor(val dataSource: ScopedDataSource, private val serviceInfo: S
133138
) {
134139
val config = with(configure) { ActiveConfig.Mapping().apply { invoke() } }
135140
val namespace = namespace.withUsernameDecoding(config.objectMappings)
141+
logBind("active mapping", namespace, config.objectMappings)
136142
serviceInfo?.registerNamespace(namespace, config.objectMappings)
137143

138144
dataSource.createActivePublisher(
@@ -193,6 +199,7 @@ private constructor(val dataSource: ScopedDataSource, private val serviceInfo: S
193199
) {
194200
val config = with(configure) { ActiveConfig.Json().apply { invoke() } }
195201
val namespace = namespace.withUsernameDecoding(config.objectMappings)
202+
logBind("active JSON", namespace, config.objectMappings)
196203
serviceInfo?.registerNamespace(namespace, config.objectMappings)
197204

198205
with(JsonContext()) {
@@ -209,6 +216,7 @@ private constructor(val dataSource: ScopedDataSource, private val serviceInfo: S
209216
) {
210217
val config = with(configure) { ActiveConfig.Record().apply { invoke() } }
211218
val namespace = namespace.withUsernameDecoding(config.objectMappings)
219+
logBind("active record", namespace, config.objectMappings)
212220
serviceInfo?.registerNamespace(namespace, config.objectMappings)
213221

214222
with(RecordContext(config.images, config.recordType)) {
@@ -261,6 +269,7 @@ private constructor(val dataSource: ScopedDataSource, private val serviceInfo: S
261269
) {
262270
val config = with(configure) { ChannelConfig.Record().apply { invoke() } }
263271
val namespace = namespace.withUsernameDecoding(config.objectMappings)
272+
logBind("record channel", namespace, config.objectMappings)
264273
serviceInfo?.registerNamespace(namespace, config.objectMappings)
265274

266275
val type = config.channelType
@@ -321,7 +330,7 @@ private constructor(val dataSource: ScopedDataSource, private val serviceInfo: S
321330
val sendToClientJob =
322331
supplier(
323332
ChannelRequest(
324-
channel.subject,
333+
namespace.extractPathParameters(channel.subject),
325334
namespace.extractPathVariables(channel.subject),
326335
namespace.extractQueryParameters(channel.subject),
327336
fromClientChannel.consumeAsFlow(),
@@ -358,6 +367,7 @@ private constructor(val dataSource: ScopedDataSource, private val serviceInfo: S
358367
) {
359368
val config = with(configure) { ChannelConfig.Json().apply { invoke() } }
360369
val namespace = namespace.withUsernameDecoding(config.objectMappings)
370+
logBind("JSON channel", namespace, config.objectMappings)
361371
serviceInfo?.registerNamespace(namespace, config.objectMappings)
362372

363373
val type = config.channelType
@@ -406,7 +416,7 @@ private constructor(val dataSource: ScopedDataSource, private val serviceInfo: S
406416
val sendToClientJob =
407417
supplier(
408418
ChannelRequest(
409-
channel.subject,
419+
namespace.extractPathParameters(channel.subject),
410420
namespace.extractPathVariables(channel.subject),
411421
namespace.extractQueryParameters(channel.subject),
412422
fromClientChannel.consumeAsFlow(),
@@ -441,6 +451,7 @@ private constructor(val dataSource: ScopedDataSource, private val serviceInfo: S
441451
flow: Flow<BroadcastEvent<Subject>>,
442452
) {
443453
val config = with(configure) { BroadcastConfig.Mapping().apply { invoke() } }
454+
logBind("broadcast mapping", namespace)
444455
serviceInfo?.registerNamespace(namespace, emptyMap())
445456

446457
dataSource.launch {
@@ -496,6 +507,7 @@ private constructor(val dataSource: ScopedDataSource, private val serviceInfo: S
496507
flow: Flow<BroadcastEvent<Map<String, String>>>,
497508
) {
498509
val config = with(configure) { BroadcastConfig.Record().apply { invoke() } }
510+
logBind("broadcast record", namespace)
499511
serviceInfo?.registerNamespace(namespace, emptyMap())
500512

501513
with(RecordContext(true, config.recordType)) {
@@ -556,7 +568,8 @@ private constructor(val dataSource: ScopedDataSource, private val serviceInfo: S
556568
) {
557569
val rowPattern =
558570
AntPatternNamespace("${containerSubjectPattern.pattern}${config.rowPathSuffix}/{itemId}")
559-
logger.info { "Using item pattern $rowPattern for container pattern $containerSubjectPattern" }
571+
logBind("active container", containerSubjectPattern, config.objectMappings)
572+
logBind("container row", rowPattern)
560573

561574
serviceInfo?.registerNamespace(containerSubjectPattern, config.objectMappings)
562575
serviceInfo?.registerNamespace(rowPattern, emptyMap())
@@ -751,6 +764,28 @@ private constructor(val dataSource: ScopedDataSource, private val serviceInfo: S
751764
publishSubjectErrorEvent(messageFactory.createSubjectErrorEvent(subject, SubjectError.NotFound))
752765
}
753766

767+
private fun logBind(
768+
what: String,
769+
namespace: AntPatternNamespace,
770+
objectMappings: Map<String, String>? = null,
771+
) {
772+
logger.info {
773+
buildString {
774+
append(
775+
"Binding $what to namespace '${namespace.pattern}' " +
776+
"(posix '${namespace.posixExtendedPattern}')",
777+
)
778+
serviceInfo?.let { info ->
779+
append(" on service '${info.serviceConfig.name}'")
780+
objectMappings
781+
?.takeIf(Map<String, String>::isNotEmpty)
782+
?.let(namespace::getObjectMap)
783+
?.let { append(", object map '${it.fromPattern}' -> '${it.toPattern}'") }
784+
}
785+
}
786+
}
787+
}
788+
754789
private fun ServiceInfo.registerNamespace(
755790
antPatternNamespace: AntPatternNamespace,
756791
mappings: Map<String, String>?,
@@ -774,5 +809,13 @@ private constructor(val dataSource: ScopedDataSource, private val serviceInfo: S
774809
service.setType(CONTRIB)
775810

776811
dataSource.createService(service)
812+
logger.info {
813+
"Created service '${serviceConfig.name}' " +
814+
"(remoteLabelPattern=${serviceConfig.remoteLabelPattern}, " +
815+
"discardTimeout=${serviceConfig.discardTimeout}, " +
816+
"throttleTime=${serviceConfig.throttleTime}, " +
817+
"requiredState=${serviceConfig.requiredState}, " +
818+
"ifLabelPatterns=${serviceConfig.ifLabelPatterns}, type=$CONTRIB)"
819+
}
777820
}
778821
}

reactive/kotlin/src/test/kotlin/com/caplin/integration/datasourcex/reactive/kotlin/BindTest.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import com.caplin.integration.datasourcex.reactive.api.InsertAt.HEAD
1919
import com.caplin.integration.datasourcex.reactive.api.Request
2020
import com.caplin.integration.datasourcex.reactive.api.RequestSupplier
2121
import com.caplin.integration.datasourcex.util.AntPatternNamespace
22+
import com.caplin.integration.datasourcex.util.Subject.Companion.path
2223
import com.caplin.integration.datasourcex.util.flow.ValueOrCompletion
2324
import com.caplin.integration.datasourcex.util.flow.ValueOrCompletion.Completion
2425
import com.caplin.integration.datasourcex.util.flow.ValueOrCompletion.Value
@@ -684,7 +685,7 @@ class BindTest :
684685
captured!!.also { request ->
685686
request.path shouldBeEqual "/QUERY/EUR%2FUSD?location=ONSHORE&tenor=SPOT"
686687
request.pathVariables shouldBeEqual mapOf("productPair" to "EUR/USD")
687-
request.pathParameters shouldBeEqual listOf("EUR/USD")
688+
request.pathParameters shouldBeEqual listOf("QUERY", "EUR/USD")
688689
request.queryParameters shouldBeEqual mapOf("location" to "ONSHORE", "tenor" to "SPOT")
689690
}
690691
}
@@ -729,8 +730,8 @@ class BindTest :
729730
// the ordinary path variable still is.
730731
captured!!.pathVariables shouldBeEqual
731732
mapOf("username" to "john%2Fdoe", "productPair" to "EUR/USD")
732-
// pathParameters mirrors pathVariables' values in pattern order.
733-
captured!!.pathParameters shouldBeEqual listOf("john%2Fdoe", "EUR/USD")
733+
// pathParameters holds every path segment, URL-decoded, regardless of the pattern.
734+
captured!!.pathParameters shouldBeEqual listOf("PRIVATE", "john/doe", "EUR/USD")
734735
}
735736
}
736737

spring/src/main/kotlin/com/caplin/integration/datasourcex/spring/internal/DataSourceServerBootstrap.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import com.caplin.integration.datasourcex.spring.annotations.IngressDestinationV
1111
import com.caplin.integration.datasourcex.spring.internal.DataSourceRequestTypeMessageCondition.RequestType
1212
import com.caplin.integration.datasourcex.util.AntPatternNamespace
1313
import com.caplin.integration.datasourcex.util.AntPatternNamespace.Companion.addIncludeNamespace
14+
import com.caplin.integration.datasourcex.util.Subject.Companion.path
1415
import com.caplin.integration.datasourcex.util.getLogger
1516
import java.time.Duration
1617
import java.util.concurrent.atomic.AtomicReference

util/api/datasourcex-util.api

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ public final class com/caplin/integration/datasourcex/util/AntPatternNamespace :
88
public static final fun addIncludeNamespace (Lcom/caplin/datasource/Service;Lcom/caplin/integration/datasourcex/util/AntPatternNamespace;)V
99
public final fun copy (Ljava/util/Set;)Lcom/caplin/integration/datasourcex/util/AntPatternNamespace;
1010
public static synthetic fun copy$default (Lcom/caplin/integration/datasourcex/util/AntPatternNamespace;Ljava/util/Set;ILjava/lang/Object;)Lcom/caplin/integration/datasourcex/util/AntPatternNamespace;
11+
public final fun extractPathParameters (Ljava/lang/String;)Ljava/util/List;
1112
public final fun extractPathVariables (Ljava/lang/String;)Ljava/util/LinkedHashMap;
12-
public final fun extractQueryParameters (Ljava/lang/String;)Ljava/util/Map;
13+
public final fun extractQueryParameters (Ljava/lang/String;)Ljava/util/SortedMap;
1314
public final fun getObjectMap (Ljava/util/Map;)Lcom/caplin/integration/datasourcex/util/AntPatternNamespace$ObjectMap;
1415
public final fun getPathVariables ()Ljava/util/Set;
1516
public final fun getPattern ()Ljava/lang/String;
@@ -156,15 +157,17 @@ public final class com/caplin/integration/datasourcex/util/SimpleDataSourceFacto
156157

157158
public abstract interface class com/caplin/integration/datasourcex/util/Subject {
158159
public static final field Companion Lcom/caplin/integration/datasourcex/util/Subject$Companion;
159-
public abstract fun getPath ()Ljava/lang/String;
160+
public static fun getPath (Lcom/caplin/integration/datasourcex/util/Subject;)Ljava/lang/String;
160161
public abstract fun getPathParameters ()Ljava/util/List;
161-
public abstract fun getQueryParameters ()Ljava/util/Map;
162+
public abstract fun getQueryParameters ()Ljava/util/SortedMap;
162163
public static fun invoke (Ljava/util/List;)Lcom/caplin/integration/datasourcex/util/Subject;
163164
public static fun invoke (Ljava/util/List;Ljava/util/Map;)Lcom/caplin/integration/datasourcex/util/Subject;
164165
public static fun invoke ([Ljava/lang/String;)Lcom/caplin/integration/datasourcex/util/Subject;
165166
}
166167

167168
public final class com/caplin/integration/datasourcex/util/Subject$Companion {
169+
public final fun buildPath (Ljava/util/List;Ljava/util/SortedMap;)Ljava/lang/String;
170+
public final fun getPath (Lcom/caplin/integration/datasourcex/util/Subject;)Ljava/lang/String;
168171
public final fun invoke (Ljava/util/List;)Lcom/caplin/integration/datasourcex/util/Subject;
169172
public final fun invoke (Ljava/util/List;Ljava/util/Map;)Lcom/caplin/integration/datasourcex/util/Subject;
170173
public final fun invoke ([Ljava/lang/String;)Lcom/caplin/integration/datasourcex/util/Subject;

0 commit comments

Comments
 (0)