@ -1,5 +1,5 @@
/ *
/ *
* Copyright 2002 - 2018 the original author or authors .
* Copyright 2002 - 2019 the original author or authors .
*
*
* Licensed under the Apache License , Version 2 . 0 ( the "License" ) ;
* Licensed under the Apache License , Version 2 . 0 ( the "License" ) ;
* you may not use this file except in compliance with the License .
* you may not use this file except in compliance with the License .
@ -23,6 +23,8 @@ import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono ;
import reactor.core.publisher.Mono ;
import org.springframework.core.ParameterizedTypeReference ;
import org.springframework.core.ParameterizedTypeReference ;
import org.springframework.core.ReactiveAdapter ;
import org.springframework.core.ReactiveAdapterRegistry ;
import org.springframework.core.ResolvableType ;
import org.springframework.core.ResolvableType ;
import org.springframework.core.io.Resource ;
import org.springframework.core.io.Resource ;
import org.springframework.core.io.buffer.DataBuffer ;
import org.springframework.core.io.buffer.DataBuffer ;
@ -44,6 +46,7 @@ import org.springframework.util.MultiValueMap;
*
*
* @author Arjen Poutsma
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @author Rossen Stoyanchev
* @author Sebastien Deleuze
* @since 5 . 0
* @since 5 . 0
* /
* /
public abstract class BodyInserters {
public abstract class BodyInserters {
@ -61,6 +64,8 @@ public abstract class BodyInserters {
private static final BodyInserter < Void , ReactiveHttpOutputMessage > EMPTY_INSERTER =
private static final BodyInserter < Void , ReactiveHttpOutputMessage > EMPTY_INSERTER =
( response , context ) - > response . setComplete ( ) ;
( response , context ) - > response . setComplete ( ) ;
private static final ReactiveAdapterRegistry registry = ReactiveAdapterRegistry . getSharedInstance ( ) ;
/ * *
/ * *
* Inserter that does not write .
* Inserter that does not write .
@ -73,16 +78,68 @@ public abstract class BodyInserters {
/ * *
/ * *
* Inserter to write the given object .
* Inserter to write the given object .
* < p > Alternatively , consider using the { @code syncB ody( Object ) } shortcuts on
* < p > Alternatively , consider using the { @code b ody( Object ) } shortcuts on
* { @link org . springframework . web . reactive . function . client . WebClient WebClient } and
* { @link org . springframework . web . reactive . function . client . WebClient WebClient } and
* { @link org . springframework . web . reactive . function . server . ServerResponse ServerResponse } .
* { @link org . springframework . web . reactive . function . server . ServerResponse ServerResponse } .
* @param body the body to write to the response
* @param body the body to write to the response
* @param < T > the type of the body
* @param < T > the type of the body
* @return the inserter to write a single object
* @return the inserter to write a single object
* @throws IllegalArgumentException if { @code body } is a { @link Publisher } or an
* instance of a type supported by { @link ReactiveAdapterRegistry # getSharedInstance ( ) } ,
* for which { @link # fromPublisher ( Publisher , Class ) } or
* { @link # fromProducer ( Object , Class ) } should be used .
* @see # fromPublisher ( Publisher , Class )
* @see # fromProducer ( Object , Class )
* /
* /
public static < T > BodyInserter < T , ReactiveHttpOutputMessage > fromObject ( T body ) {
public static < T > BodyInserter < T , ReactiveHttpOutputMessage > fromObject ( T body ) {
Assert . notNull ( body , "Body must not be null" ) ;
Assert . isNull ( registry . getAdapter ( body . getClass ( ) ) , "'body' should be an object, for reactive types use a variant specifying a publisher/producer and its related element type" ) ;
return ( message , context ) - >
writeWithMessageWriters ( message , context , Mono . just ( body ) , ResolvableType . forInstance ( body ) , null ) ;
}
/ * *
* Inserter to write the given producer of value ( s ) which must be a { @link Publisher }
* or another producer adaptable to a { @code Publisher } via
* { @link ReactiveAdapterRegistry } .
* < p > Alternatively , consider using the { @code body } shortcuts on
* { @link org . springframework . web . reactive . function . client . WebClient WebClient } and
* { @link org . springframework . web . reactive . function . server . ServerResponse ServerResponse } .
* @param < T > the type of the body
* @param producer the source of body value ( s ) .
* @param elementClass the type of values to be produced
* @return the inserter to write a producer
* @since 5 . 2
* /
public static < T > BodyInserter < T , ReactiveHttpOutputMessage > fromProducer ( T producer , Class < ? > elementClass ) {
Assert . notNull ( producer , "'producer' must not be null" ) ;
Assert . notNull ( elementClass , "'elementClass' must not be null" ) ;
ReactiveAdapter adapter = ReactiveAdapterRegistry . getSharedInstance ( ) . getAdapter ( producer . getClass ( ) ) ;
Assert . notNull ( adapter , "'producer' type is unknown to ReactiveAdapterRegistry" ) ;
return ( message , context ) - >
writeWithMessageWriters ( message , context , producer , ResolvableType . forClass ( elementClass ) , adapter ) ;
}
/ * *
* Inserter to write the given producer of value ( s ) which must be a { @link Publisher }
* or another producer adaptable to a { @code Publisher } via
* { @link ReactiveAdapterRegistry } .
* < p > Alternatively , consider using the { @code body } shortcuts on
* { @link org . springframework . web . reactive . function . client . WebClient WebClient } and
* { @link org . springframework . web . reactive . function . server . ServerResponse ServerResponse } .
* @param < T > the type of the body
* @param producer the source of body value ( s ) .
* @param elementType the type of values to be produced
* @return the inserter to write a producer
* @since 5 . 2
* /
public static < T > BodyInserter < T , ReactiveHttpOutputMessage > fromProducer ( T producer , ParameterizedTypeReference < ? > elementType ) {
Assert . notNull ( producer , "'producer' must not be null" ) ;
Assert . notNull ( elementType , "'elementType' must not be null" ) ;
ReactiveAdapter adapter = ReactiveAdapterRegistry . getSharedInstance ( ) . getAdapter ( producer . getClass ( ) ) ;
Assert . notNull ( adapter , "'producer' type is unknown to ReactiveAdapterRegistry" ) ;
return ( message , context ) - >
return ( message , context ) - >
writeWithMessageWriters ( message , context , Mono . just ( body ) , ResolvableType . forInstance ( body ) ) ;
writeWithMessageWriters ( message , context , producer , ResolvableType . forType ( elementType ) , adapter ) ;
}
}
/ * *
/ * *
@ -102,7 +159,7 @@ public abstract class BodyInserters {
Assert . notNull ( publisher , "Publisher must not be null" ) ;
Assert . notNull ( publisher , "Publisher must not be null" ) ;
Assert . notNull ( elementClass , "Element Class must not be null" ) ;
Assert . notNull ( elementClass , "Element Class must not be null" ) ;
return ( message , context ) - >
return ( message , context ) - >
writeWithMessageWriters ( message , context , publisher , ResolvableType . forClass ( elementClass ) ) ;
writeWithMessageWriters ( message , context , publisher , ResolvableType . forClass ( elementClass ) , null ) ;
}
}
/ * *
/ * *
@ -122,7 +179,7 @@ public abstract class BodyInserters {
Assert . notNull ( publisher , "Publisher must not be null" ) ;
Assert . notNull ( publisher , "Publisher must not be null" ) ;
Assert . notNull ( typeReference , "ParameterizedTypeReference must not be null" ) ;
Assert . notNull ( typeReference , "ParameterizedTypeReference must not be null" ) ;
return ( message , context ) - >
return ( message , context ) - >
writeWithMessageWriters ( message , context , publisher , ResolvableType . forType ( typeReference . getType ( ) ) ) ;
writeWithMessageWriters ( message , context , publisher , ResolvableType . forType ( typeReference . getType ( ) ) , null ) ;
}
}
/ * *
/ * *
@ -145,8 +202,8 @@ public abstract class BodyInserters {
/ * *
/ * *
* Inserter to write the given { @code ServerSentEvent } publisher .
* Inserter to write the given { @code ServerSentEvent } publisher .
* < p > Alternatively , you can provide event data objects via
* < p > Alternatively , you can provide event data objects via
* { @link # fromPublisher ( Publisher , Class ) } , and set the "Content-Type" to
* { @link # fromPublisher ( Publisher , Class ) } or { @link # fromProducer ( Object , Class ) } ,
* { @link MediaType # TEXT_EVENT_STREAM text / event - stream } .
* and set the "Content-Type" to { @link MediaType # TEXT_EVENT_STREAM text / event - stream } .
* @param eventsPublisher the { @code ServerSentEvent } publisher to write to the response body
* @param eventsPublisher the { @code ServerSentEvent } publisher to write to the response body
* @param < T > the type of the data elements in the { @link ServerSentEvent }
* @param < T > the type of the data elements in the { @link ServerSentEvent }
* @return the inserter to write a { @code ServerSentEvent } publisher
* @return the inserter to write a { @code ServerSentEvent } publisher
@ -169,7 +226,7 @@ public abstract class BodyInserters {
* Return a { @link FormInserter } to write the given { @code MultiValueMap }
* Return a { @link FormInserter } to write the given { @code MultiValueMap }
* as URL - encoded form data . The returned inserter allows for additional
* as URL - encoded form data . The returned inserter allows for additional
* entries to be added via { @link FormInserter # with ( String , Object ) } .
* entries to be added via { @link FormInserter # with ( String , Object ) } .
* < p > Note that you can also use the { @code syncB ody( Object ) } method in the
* < p > Note that you can also use the { @code b ody( Object ) } method in the
* request builders of both the { @code WebClient } and { @code WebTestClient } .
* request builders of both the { @code WebClient } and { @code WebTestClient } .
* In that case the setting of the request content type is also not required ,
* In that case the setting of the request content type is also not required ,
* just be sure the map contains String values only or otherwise it would be
* just be sure the map contains String values only or otherwise it would be
@ -201,7 +258,7 @@ public abstract class BodyInserters {
* Object or an { @link HttpEntity } .
* Object or an { @link HttpEntity } .
* < p > Note that you can also build the multipart data externally with
* < p > Note that you can also build the multipart data externally with
* { @link MultipartBodyBuilder } , and pass the resulting map directly to the
* { @link MultipartBodyBuilder } , and pass the resulting map directly to the
* { @code syncB ody( Object ) } shortcut method in { @code WebClient } .
* { @code b ody( Object ) } shortcut method in { @code WebClient } .
* @param multipartData the form data to write to the output message
* @param multipartData the form data to write to the output message
* @return the inserter that allows adding more parts
* @return the inserter that allows adding more parts
* @see MultipartBodyBuilder
* @see MultipartBodyBuilder
@ -217,7 +274,7 @@ public abstract class BodyInserters {
* { @link HttpEntity } .
* { @link HttpEntity } .
* < p > Note that you can also build the multipart data externally with
* < p > Note that you can also build the multipart data externally with
* { @link MultipartBodyBuilder } , and pass the resulting map directly to the
* { @link MultipartBodyBuilder } , and pass the resulting map directly to the
* { @code syncB ody( Object ) } shortcut method in { @code WebClient } .
* { @code b ody( Object ) } shortcut method in { @code WebClient } .
* @param name the part name
* @param name the part name
* @param value the part value , an Object or { @code HttpEntity }
* @param value the part value , an Object or { @code HttpEntity }
* @return the inserter that allows adding more parts
* @return the inserter that allows adding more parts
@ -233,7 +290,7 @@ public abstract class BodyInserters {
* as multipart data .
* as multipart data .
* < p > Note that you can also build the multipart data externally with
* < p > Note that you can also build the multipart data externally with
* { @link MultipartBodyBuilder } , and pass the resulting map directly to the
* { @link MultipartBodyBuilder } , and pass the resulting map directly to the
* { @code syncB ody( Object ) } shortcut method in { @code WebClient } .
* { @code b ody( Object ) } shortcut method in { @code WebClient } .
* @param name the part name
* @param name the part name
* @param publisher the publisher that forms the part value
* @param publisher the publisher that forms the part value
* @param elementClass the class contained in the { @code publisher }
* @param elementClass the class contained in the { @code publisher }
@ -251,7 +308,7 @@ public abstract class BodyInserters {
* allows specifying generic type information .
* allows specifying generic type information .
* < p > Note that you can also build the multipart data externally with
* < p > Note that you can also build the multipart data externally with
* { @link MultipartBodyBuilder } , and pass the resulting map directly to the
* { @link MultipartBodyBuilder } , and pass the resulting map directly to the
* { @code syncB ody( Object ) } shortcut method in { @code WebClient } .
* { @code b ody( Object ) } shortcut method in { @code WebClient } .
* @param name the part name
* @param name the part name
* @param publisher the publisher that forms the part value
* @param publisher the publisher that forms the part value
* @param typeReference the type contained in the { @code publisher }
* @param typeReference the type contained in the { @code publisher }
@ -278,15 +335,25 @@ public abstract class BodyInserters {
}
}
private static < P extends Publisher < ? > , M extends ReactiveHttpOutputMessage > Mono < Void > writeWithMessageWriters (
private static < M extends ReactiveHttpOutputMessage > Mono < Void > writeWithMessageWriters (
M outputMessage , BodyInserter . Context context , P body , ResolvableType bodyType ) {
M outputMessage , BodyInserter . Context context , Object body , ResolvableType bodyType , @Nullable ReactiveAdapter adapter ) {
Publisher < ? > publisher ;
if ( body instanceof Publisher ) {
publisher = ( Publisher < ? > ) body ;
}
else if ( adapter ! = null ) {
publisher = adapter . toPublisher ( body ) ;
}
else {
publisher = Mono . just ( body ) ;
}
MediaType mediaType = outputMessage . getHeaders ( ) . getContentType ( ) ;
MediaType mediaType = outputMessage . getHeaders ( ) . getContentType ( ) ;
return context . messageWriters ( ) . stream ( )
return context . messageWriters ( ) . stream ( )
. filter ( messageWriter - > messageWriter . canWrite ( bodyType , mediaType ) )
. filter ( messageWriter - > messageWriter . canWrite ( bodyType , mediaType ) )
. findFirst ( )
. findFirst ( )
. map ( BodyInserters : : cast )
. map ( BodyInserters : : cast )
. map ( writer - > write ( body , bodyType , mediaType , outputMessage , context , writer ) )
. map ( writer - > write ( publisher , bodyType , mediaType , outputMessage , context , writer ) )
. orElseGet ( ( ) - > Mono . error ( unsupportedError ( bodyType , context , mediaType ) ) ) ;
. orElseGet ( ( ) - > Mono . error ( unsupportedError ( bodyType , context , mediaType ) ) ) ;
}
}