16
16
17
17
package org .springframework .messaging .rsocket ;
18
18
19
- import java .nio . charset . StandardCharsets ;
19
+ import java .util . Arrays ;
20
20
import java .util .Collections ;
21
+ import java .util .LinkedHashMap ;
22
+ import java .util .List ;
21
23
import java .util .Map ;
22
24
25
+ import io .netty .buffer .ByteBuf ;
26
+ import io .netty .buffer .ByteBufAllocator ;
27
+ import io .netty .buffer .CompositeByteBuf ;
28
+ import io .netty .buffer .Unpooled ;
23
29
import io .rsocket .Payload ;
24
30
import io .rsocket .RSocket ;
31
+ import io .rsocket .metadata .CompositeMetadataFlyweight ;
25
32
import org .reactivestreams .Publisher ;
26
33
import reactor .core .publisher .Flux ;
27
34
import reactor .core .publisher .Mono ;
32
39
import org .springframework .core .codec .Decoder ;
33
40
import org .springframework .core .codec .Encoder ;
34
41
import org .springframework .core .io .buffer .DataBuffer ;
42
+ import org .springframework .core .io .buffer .DataBufferFactory ;
43
+ import org .springframework .core .io .buffer .DataBufferUtils ;
44
+ import org .springframework .core .io .buffer .NettyDataBuffer ;
45
+ import org .springframework .core .io .buffer .NettyDataBufferFactory ;
35
46
import org .springframework .lang .Nullable ;
36
47
import org .springframework .util .Assert ;
37
48
import org .springframework .util .MimeType ;
44
55
*/
45
56
final class DefaultRSocketRequester implements RSocketRequester {
46
57
58
+ static final MimeType COMPOSITE_METADATA = new MimeType ("message" , "x.rsocket.composite-metadata.v0" );
59
+
60
+ static final MimeType ROUTING = new MimeType ("message" , "x.rsocket.routing.v0" );
61
+
62
+ static final List <MimeType > METADATA_MIME_TYPES = Arrays .asList (COMPOSITE_METADATA , ROUTING );
63
+
64
+
47
65
private static final Map <String , Object > EMPTY_HINTS = Collections .emptyMap ();
48
66
49
67
50
68
private final RSocket rsocket ;
51
69
52
- @ Nullable
53
70
private final MimeType dataMimeType ;
54
71
72
+ private final MimeType metadataMimeType ;
73
+
55
74
private final RSocketStrategies strategies ;
56
75
57
- private DataBuffer emptyDataBuffer ;
76
+ private final DataBuffer emptyDataBuffer ;
58
77
59
78
60
- DefaultRSocketRequester (RSocket rsocket , @ Nullable MimeType dataMimeType , RSocketStrategies strategies ) {
79
+ DefaultRSocketRequester (
80
+ RSocket rsocket , MimeType dataMimeType , MimeType metadataMimeType ,
81
+ RSocketStrategies strategies ) {
82
+
61
83
Assert .notNull (rsocket , "RSocket is required" );
84
+ Assert .notNull (dataMimeType , "'dataMimeType' is required" );
85
+ Assert .notNull (metadataMimeType , "'metadataMimeType' is required" );
62
86
Assert .notNull (strategies , "RSocketStrategies is required" );
87
+
88
+ Assert .isTrue (METADATA_MIME_TYPES .contains (metadataMimeType ),
89
+ () -> "Unexpected metadatata mime type: '" + metadataMimeType + "'" );
90
+
63
91
this .rsocket = rsocket ;
64
92
this .dataMimeType = dataMimeType ;
93
+ this .metadataMimeType = metadataMimeType ;
65
94
this .strategies = strategies ;
66
95
this .emptyDataBuffer = this .strategies .dataBufferFactory ().wrap (new byte [0 ]);
67
96
}
@@ -72,6 +101,16 @@ public RSocket rsocket() {
72
101
return this .rsocket ;
73
102
}
74
103
104
+ @ Override
105
+ public MimeType dataMimeType () {
106
+ return this .dataMimeType ;
107
+ }
108
+
109
+ @ Override
110
+ public MimeType metadataMimeType () {
111
+ return this .metadataMimeType ;
112
+ }
113
+
75
114
@ Override
76
115
public RequestSpec route (String route ) {
77
116
return new DefaultRequestSpec (route );
@@ -82,13 +121,28 @@ private static boolean isVoid(ResolvableType elementType) {
82
121
return (Void .class .equals (elementType .resolve ()) || void .class .equals (elementType .resolve ()));
83
122
}
84
123
124
+ private DataBufferFactory bufferFactory () {
125
+ return this .strategies .dataBufferFactory ();
126
+ }
127
+
85
128
86
129
private class DefaultRequestSpec implements RequestSpec {
87
130
88
- private final String route ;
131
+ private final Map <Object , MimeType > metadata = new LinkedHashMap <>(4 );
132
+
133
+
134
+ public DefaultRequestSpec (String route ) {
135
+ Assert .notNull (route , "'route' is required" );
136
+ metadata (route , ROUTING );
137
+ }
138
+
89
139
90
- DefaultRequestSpec (String route ) {
91
- this .route = route ;
140
+ @ Override
141
+ public RequestSpec metadata (Object metadata , MimeType mimeType ) {
142
+ Assert .isTrue (this .metadata .isEmpty () || metadataMimeType ().equals (COMPOSITE_METADATA ),
143
+ "Additional metadata entries supported only with composite metadata" );
144
+ this .metadata .put (metadata , mimeType );
145
+ return this ;
92
146
}
93
147
94
148
@ Override
@@ -122,7 +176,7 @@ else if (adapter != null) {
122
176
}
123
177
else {
124
178
Mono <Payload > payloadMono = Mono
125
- .fromCallable (() -> encodeValue (input , ResolvableType .forInstance (input ), null ))
179
+ .fromCallable (() -> encodeData (input , ResolvableType .forInstance (input ), null ))
126
180
.map (this ::firstPayload )
127
181
.doOnDiscard (Payload .class , Payload ::release )
128
182
.switchIfEmpty (emptyPayload ());
@@ -139,14 +193,14 @@ else if (adapter != null) {
139
193
140
194
if (adapter != null && !adapter .isMultiValue ()) {
141
195
Mono <Payload > payloadMono = Mono .from (publisher )
142
- .map (value -> encodeValue (value , dataType , encoder ))
196
+ .map (value -> encodeData (value , dataType , encoder ))
143
197
.map (this ::firstPayload )
144
198
.switchIfEmpty (emptyPayload ());
145
199
return new DefaultResponseSpec (payloadMono );
146
200
}
147
201
148
202
Flux <Payload > payloadFlux = Flux .from (publisher )
149
- .map (value -> encodeValue (value , dataType , encoder ))
203
+ .map (value -> encodeData (value , dataType , encoder ))
150
204
.switchOnFirst ((signal , inner ) -> {
151
205
DataBuffer data = signal .get ();
152
206
if (data != null ) {
@@ -163,24 +217,80 @@ else if (adapter != null) {
163
217
}
164
218
165
219
@ SuppressWarnings ("unchecked" )
166
- private <T > DataBuffer encodeValue (T value , ResolvableType valueType , @ Nullable Encoder <?> encoder ) {
220
+ private <T > DataBuffer encodeData (T value , ResolvableType valueType , @ Nullable Encoder <?> encoder ) {
221
+ if (value instanceof DataBuffer ) {
222
+ return (DataBuffer ) value ;
223
+ }
167
224
if (encoder == null ) {
168
- encoder = strategies .encoder (ResolvableType .forInstance (value ), dataMimeType );
225
+ valueType = ResolvableType .forInstance (value );
226
+ encoder = strategies .encoder (valueType , dataMimeType );
169
227
}
170
228
return ((Encoder <T >) encoder ).encodeValue (
171
- value , strategies . dataBufferFactory (), valueType , dataMimeType , EMPTY_HINTS );
229
+ value , bufferFactory (), valueType , dataMimeType , EMPTY_HINTS );
172
230
}
173
231
174
232
private Payload firstPayload (DataBuffer data ) {
175
- return PayloadUtils .createPayload (getMetadata (), data );
233
+ DataBuffer metadata ;
234
+ try {
235
+ metadata = getMetadata ();
236
+ return PayloadUtils .createPayload (metadata , data );
237
+ }
238
+ catch (Throwable ex ) {
239
+ DataBufferUtils .release (data );
240
+ throw ex ;
241
+ }
176
242
}
177
243
178
244
private Mono <Payload > emptyPayload () {
179
245
return Mono .fromCallable (() -> firstPayload (emptyDataBuffer ));
180
246
}
181
247
182
248
private DataBuffer getMetadata () {
183
- return strategies .dataBufferFactory ().wrap (this .route .getBytes (StandardCharsets .UTF_8 ));
249
+ if (metadataMimeType ().equals (COMPOSITE_METADATA )) {
250
+ CompositeByteBuf metadata = getAllocator ().compositeBuffer ();
251
+ this .metadata .forEach ((key , value ) -> {
252
+ DataBuffer dataBuffer = encodeMetadata (key , value );
253
+ CompositeMetadataFlyweight .encodeAndAddMetadata (metadata , getAllocator (), value .toString (),
254
+ dataBuffer instanceof NettyDataBuffer ?
255
+ ((NettyDataBuffer ) dataBuffer ).getNativeBuffer () :
256
+ Unpooled .wrappedBuffer (dataBuffer .asByteBuffer ()));
257
+
258
+ });
259
+ return asDataBuffer (metadata );
260
+ }
261
+ Assert .isTrue (this .metadata .size () < 2 , "Composite metadata required for multiple entries" );
262
+ Map .Entry <Object , MimeType > entry = this .metadata .entrySet ().iterator ().next ();
263
+ Assert .isTrue (metadataMimeType ().equals (entry .getValue ()),
264
+ () -> "Expected metadata MimeType '" + metadataMimeType () + "', actual " + this .metadata );
265
+ return encodeMetadata (entry .getKey (), entry .getValue ());
266
+ }
267
+
268
+ @ SuppressWarnings ("unchecked" )
269
+ private <T > DataBuffer encodeMetadata (Object metadata , MimeType mimeType ) {
270
+ if (metadata instanceof DataBuffer ) {
271
+ return (DataBuffer ) metadata ;
272
+ }
273
+ ResolvableType type = ResolvableType .forInstance (metadata );
274
+ Encoder <T > encoder = strategies .encoder (type , mimeType );
275
+ Assert .notNull (encoder , () -> "No encoder for metadata " + metadata + ", mimeType '" + mimeType + "'" );
276
+ return encoder .encodeValue ((T ) metadata , bufferFactory (), type , mimeType , EMPTY_HINTS );
277
+ }
278
+
279
+ private ByteBufAllocator getAllocator () {
280
+ return bufferFactory () instanceof NettyDataBufferFactory ?
281
+ ((NettyDataBufferFactory ) bufferFactory ()).getByteBufAllocator () :
282
+ ByteBufAllocator .DEFAULT ;
283
+ }
284
+
285
+ private DataBuffer asDataBuffer (ByteBuf byteBuf ) {
286
+ if (bufferFactory () instanceof NettyDataBufferFactory ) {
287
+ return ((NettyDataBufferFactory ) bufferFactory ()).wrap (byteBuf );
288
+ }
289
+ else {
290
+ DataBuffer dataBuffer = bufferFactory ().wrap (byteBuf .nioBuffer ());
291
+ byteBuf .release ();
292
+ return dataBuffer ;
293
+ }
184
294
}
185
295
}
186
296
@@ -259,7 +369,7 @@ private <T> Flux<T> retrieveFlux(ResolvableType elementType) {
259
369
}
260
370
261
371
private DataBuffer retainDataAndReleasePayload (Payload payload ) {
262
- return PayloadUtils .retainDataAndReleasePayload (payload , strategies . dataBufferFactory ());
372
+ return PayloadUtils .retainDataAndReleasePayload (payload , bufferFactory ());
263
373
}
264
374
}
265
375
0 commit comments