1
1
/*
2
- * Copyright 2002-2019 the original author or authors.
2
+ * Copyright 2002-2020 the original author or authors.
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
17
17
package org .springframework .http .codec .json ;
18
18
19
19
import java .io .IOException ;
20
- import java .io .OutputStream ;
21
20
import java .lang .annotation .Annotation ;
22
21
import java .nio .charset .Charset ;
23
22
import java .util .ArrayList ;
29
28
import com .fasterxml .jackson .core .JsonEncoding ;
30
29
import com .fasterxml .jackson .core .JsonGenerator ;
31
30
import com .fasterxml .jackson .core .JsonProcessingException ;
31
+ import com .fasterxml .jackson .core .util .ByteArrayBuilder ;
32
32
import com .fasterxml .jackson .databind .JavaType ;
33
33
import com .fasterxml .jackson .databind .ObjectMapper ;
34
34
import com .fasterxml .jackson .databind .ObjectWriter ;
35
+ import com .fasterxml .jackson .databind .SequenceWriter ;
35
36
import com .fasterxml .jackson .databind .exc .InvalidDefinitionException ;
36
37
import org .reactivestreams .Publisher ;
37
38
import reactor .core .publisher .Flux ;
44
45
import org .springframework .core .codec .Hints ;
45
46
import org .springframework .core .io .buffer .DataBuffer ;
46
47
import org .springframework .core .io .buffer .DataBufferFactory ;
47
- import org .springframework .core .io .buffer .DataBufferUtils ;
48
48
import org .springframework .core .log .LogFormatUtils ;
49
49
import org .springframework .http .MediaType ;
50
50
import org .springframework .http .codec .HttpMessageEncoder ;
@@ -115,65 +115,53 @@ public Flux<DataBuffer> encode(Publisher<?> inputStream, DataBufferFactory buffe
115
115
Assert .notNull (bufferFactory , "'bufferFactory' must not be null" );
116
116
Assert .notNull (elementType , "'elementType' must not be null" );
117
117
118
- JsonEncoding encoding = getJsonEncoding (mimeType );
119
-
120
118
if (inputStream instanceof Mono ) {
121
- return Mono .from (inputStream ).map (value ->
122
- encodeValue (value , mimeType , bufferFactory , elementType , hints , encoding )).flux ();
119
+ return Mono .from (inputStream )
120
+ .map (value -> encodeValue (value , bufferFactory , elementType , mimeType , hints ))
121
+ .flux ();
123
122
}
124
123
else {
125
- return this . streamingMediaTypes . stream ()
126
- . filter ( mediaType -> mediaType . isCompatibleWith ( mimeType ))
127
- . findFirst ()
128
- . map ( mediaType -> {
129
- byte [] separator = STREAM_SEPARATORS . getOrDefault ( mediaType , NEWLINE_SEPARATOR );
130
- return Flux . from ( inputStream ). map ( value -> {
131
- DataBuffer buffer = encodeValue (
132
- value , mimeType , bufferFactory , elementType , hints , encoding );
133
- if ( separator != null ) {
134
- buffer . write ( separator );
135
- }
136
- return buffer ;
137
- });
138
- })
139
- . orElseGet (() -> {
140
- ResolvableType listType = ResolvableType . forClassWithGenerics ( List . class , elementType );
141
- return Flux . from ( inputStream ). collectList (). map ( list ->
142
- encodeValue ( list , mimeType , bufferFactory , listType , hints , encoding )). flux ();
143
- } );
144
- }
145
- }
146
-
147
- private DataBuffer encodeValue ( Object value , @ Nullable MimeType mimeType , DataBufferFactory bufferFactory ,
148
- ResolvableType elementType , @ Nullable Map < String , Object > hints , JsonEncoding encoding ) {
124
+ byte [] separator = streamSeparator ( mimeType );
125
+ if ( separator != null ) { // streaming
126
+ try {
127
+ ObjectWriter writer = createObjectWriter ( elementType , mimeType , hints );
128
+ ByteArrayBuilder byteBuilder = new ByteArrayBuilder ( writer . getFactory (). _getBufferRecycler () );
129
+ JsonEncoding encoding = getJsonEncoding ( mimeType );
130
+ JsonGenerator generator = getObjectMapper (). getFactory (). createGenerator ( byteBuilder , encoding );
131
+ SequenceWriter sequenceWriter = writer . writeValues ( generator );
132
+
133
+ return Flux . from ( inputStream )
134
+ . map ( value -> encodeStreamingValue ( value , bufferFactory , hints , sequenceWriter , byteBuilder ,
135
+ separator )) ;
136
+ }
137
+ catch ( IOException ex ) {
138
+ return Flux . error ( ex );
139
+ }
140
+ }
141
+ else { // non-streaming
142
+ ResolvableType listType = ResolvableType . forClassWithGenerics ( List . class , elementType );
143
+ return Flux . from ( inputStream )
144
+ . collectList ()
145
+ . map ( list -> encodeValue ( list , bufferFactory , listType , mimeType , hints ))
146
+ . flux ();
147
+ }
149
148
150
- if (!Hints .isLoggingSuppressed (hints )) {
151
- LogFormatUtils .traceDebug (logger , traceOn -> {
152
- String formatted = LogFormatUtils .formatValue (value , !traceOn );
153
- return Hints .getLogPrefix (hints ) + "Encoding [" + formatted + "]" ;
154
- });
155
149
}
150
+ }
156
151
157
- JavaType javaType = getJavaType (elementType .getType (), null );
158
- Class <?> jsonView = (hints != null ? (Class <?>) hints .get (Jackson2CodecSupport .JSON_VIEW_HINT ) : null );
159
- ObjectWriter writer = (jsonView != null ?
160
- getObjectMapper ().writerWithView (jsonView ) : getObjectMapper ().writer ());
152
+ public DataBuffer encodeValue (Object value , DataBufferFactory bufferFactory ,
153
+ ResolvableType valueType , @ Nullable MimeType mimeType , @ Nullable Map <String , Object > hints ) {
161
154
162
- if (javaType .isContainerType ()) {
163
- writer = writer .forType (javaType );
164
- }
165
-
166
- writer = customizeWriter (writer , mimeType , elementType , hints );
155
+ ObjectWriter writer = createObjectWriter (valueType , mimeType , hints );
156
+ ByteArrayBuilder byteBuilder = new ByteArrayBuilder (writer .getFactory ()._getBufferRecycler ());
157
+ JsonEncoding encoding = getJsonEncoding (mimeType );
167
158
168
- DataBuffer buffer = bufferFactory .allocateBuffer ();
169
- boolean release = true ;
170
- OutputStream outputStream = buffer .asOutputStream ();
159
+ logValue (hints , value );
171
160
172
161
try {
173
- JsonGenerator generator = getObjectMapper ().getFactory ().createGenerator (outputStream , encoding );
162
+ JsonGenerator generator = getObjectMapper ().getFactory ().createGenerator (byteBuilder , encoding );
174
163
writer .writeValue (generator , value );
175
164
generator .flush ();
176
- release = false ;
177
165
}
178
166
catch (InvalidDefinitionException ex ) {
179
167
throw new CodecException ("Type definition error: " + ex .getType (), ex );
@@ -182,24 +170,97 @@ private DataBuffer encodeValue(Object value, @Nullable MimeType mimeType, DataBu
182
170
throw new EncodingException ("JSON encoding error: " + ex .getOriginalMessage (), ex );
183
171
}
184
172
catch (IOException ex ) {
185
- throw new IllegalStateException ("Unexpected I/O error while writing to data buffer " ,
173
+ throw new IllegalStateException ("Unexpected I/O error while writing to byte array builder " ,
186
174
ex );
187
175
}
188
- finally {
189
- if (release ) {
190
- DataBufferUtils .release (buffer );
191
- }
176
+
177
+ byte [] bytes = byteBuilder .toByteArray ();
178
+ DataBuffer buffer = bufferFactory .allocateBuffer (bytes .length );
179
+ buffer .write (bytes );
180
+
181
+ return buffer ;
182
+ }
183
+
184
+ private DataBuffer encodeStreamingValue (Object value , DataBufferFactory bufferFactory , @ Nullable Map <String , Object > hints ,
185
+ SequenceWriter sequenceWriter , ByteArrayBuilder byteArrayBuilder , byte [] separator ) {
186
+
187
+ logValue (hints , value );
188
+
189
+ try {
190
+ sequenceWriter .write (value );
191
+ sequenceWriter .flush ();
192
192
}
193
+ catch (InvalidDefinitionException ex ) {
194
+ throw new CodecException ("Type definition error: " + ex .getType (), ex );
195
+ }
196
+ catch (JsonProcessingException ex ) {
197
+ throw new EncodingException ("JSON encoding error: " + ex .getOriginalMessage (), ex );
198
+ }
199
+ catch (IOException ex ) {
200
+ throw new IllegalStateException ("Unexpected I/O error while writing to byte array builder" ,
201
+ ex );
202
+ }
203
+
204
+ byte [] bytes = byteArrayBuilder .toByteArray ();
205
+ byteArrayBuilder .reset ();
206
+
207
+ int offset ;
208
+ int length ;
209
+ if (bytes .length > 0 && bytes [0 ] == ' ' ) {
210
+ // SequenceWriter writes an unnecessary space in between values
211
+ offset = 1 ;
212
+ length = bytes .length - 1 ;
213
+ }
214
+ else {
215
+ offset = 0 ;
216
+ length = bytes .length ;
217
+ }
218
+ DataBuffer buffer = bufferFactory .allocateBuffer (length + separator .length );
219
+ buffer .write (bytes , offset , length );
220
+ buffer .write (separator );
193
221
194
222
return buffer ;
195
223
}
196
224
225
+ private void logValue (@ Nullable Map <String , Object > hints , Object value ) {
226
+ if (!Hints .isLoggingSuppressed (hints )) {
227
+ LogFormatUtils .traceDebug (logger , traceOn -> {
228
+ String formatted = LogFormatUtils .formatValue (value , !traceOn );
229
+ return Hints .getLogPrefix (hints ) + "Encoding [" + formatted + "]" ;
230
+ });
231
+ }
232
+ }
233
+
234
+ private ObjectWriter createObjectWriter (ResolvableType valueType , @ Nullable MimeType mimeType ,
235
+ @ Nullable Map <String , Object > hints ) {
236
+ JavaType javaType = getJavaType (valueType .getType (), null );
237
+ Class <?> jsonView = (hints != null ? (Class <?>) hints .get (Jackson2CodecSupport .JSON_VIEW_HINT ) : null );
238
+ ObjectWriter writer = (jsonView != null ?
239
+ getObjectMapper ().writerWithView (jsonView ) : getObjectMapper ().writer ());
240
+
241
+ if (javaType .isContainerType ()) {
242
+ writer = writer .forType (javaType );
243
+ }
244
+
245
+ return customizeWriter (writer , mimeType , valueType , hints );
246
+ }
247
+
197
248
protected ObjectWriter customizeWriter (ObjectWriter writer , @ Nullable MimeType mimeType ,
198
249
ResolvableType elementType , @ Nullable Map <String , Object > hints ) {
199
250
200
251
return writer ;
201
252
}
202
253
254
+ @ Nullable
255
+ private byte [] streamSeparator (@ Nullable MimeType mimeType ) {
256
+ for (MediaType streamingMediaType : this .streamingMediaTypes ) {
257
+ if (streamingMediaType .isCompatibleWith (mimeType )) {
258
+ return STREAM_SEPARATORS .getOrDefault (streamingMediaType , NEWLINE_SEPARATOR );
259
+ }
260
+ }
261
+ return null ;
262
+ }
263
+
203
264
/**
204
265
* Determine the JSON encoding to use for the given mime type.
205
266
* @param mimeType the mime type as requested by the caller
0 commit comments