001/*
002 * Copyright 2002-2020 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.web.reactive.function;
018
019import java.util.List;
020import java.util.stream.Collectors;
021
022import org.reactivestreams.Publisher;
023import reactor.core.publisher.Mono;
024
025import org.springframework.core.ParameterizedTypeReference;
026import org.springframework.core.ReactiveAdapter;
027import org.springframework.core.ReactiveAdapterRegistry;
028import org.springframework.core.ResolvableType;
029import org.springframework.core.io.Resource;
030import org.springframework.core.io.buffer.DataBuffer;
031import org.springframework.http.HttpEntity;
032import org.springframework.http.MediaType;
033import org.springframework.http.ReactiveHttpOutputMessage;
034import org.springframework.http.client.MultipartBodyBuilder;
035import org.springframework.http.client.reactive.ClientHttpRequest;
036import org.springframework.http.codec.HttpMessageWriter;
037import org.springframework.http.codec.ServerSentEvent;
038import org.springframework.http.server.reactive.ServerHttpResponse;
039import org.springframework.lang.Nullable;
040import org.springframework.util.Assert;
041import org.springframework.util.LinkedMultiValueMap;
042import org.springframework.util.MultiValueMap;
043
044/**
045 * Static factory methods for {@link BodyInserter} implementations.
046 *
047 * @author Arjen Poutsma
048 * @author Rossen Stoyanchev
049 * @author Sebastien Deleuze
050 * @since 5.0
051 */
052public abstract class BodyInserters {
053
054        private static final ResolvableType RESOURCE_TYPE = ResolvableType.forClass(Resource.class);
055
056        private static final ResolvableType SSE_TYPE = ResolvableType.forClass(ServerSentEvent.class);
057
058        private static final ResolvableType FORM_DATA_TYPE =
059                        ResolvableType.forClassWithGenerics(MultiValueMap.class, String.class, String.class);
060
061        private static final ResolvableType MULTIPART_DATA_TYPE = ResolvableType.forClassWithGenerics(
062                        MultiValueMap.class, String.class, Object.class);
063
064        private static final BodyInserter<Void, ReactiveHttpOutputMessage> EMPTY_INSERTER =
065                        (response, context) -> response.setComplete();
066
067        private static final ReactiveAdapterRegistry registry = ReactiveAdapterRegistry.getSharedInstance();
068
069
070        /**
071         * Inserter that does not write.
072         * @return the inserter
073         */
074        @SuppressWarnings("unchecked")
075        public static <T> BodyInserter<T, ReactiveHttpOutputMessage> empty() {
076                return (BodyInserter<T, ReactiveHttpOutputMessage>) EMPTY_INSERTER;
077        }
078
079        /**
080         * Inserter to write the given value.
081         * <p>Alternatively, consider using the {@code bodyValue(Object)} shortcuts on
082         * {@link org.springframework.web.reactive.function.client.WebClient WebClient} and
083         * {@link org.springframework.web.reactive.function.server.ServerResponse ServerResponse}.
084         * @param body the value to write
085         * @param <T> the type of the body
086         * @return the inserter to write a single value
087         * @throws IllegalArgumentException if {@code body} is a {@link Publisher} or an
088         * instance of a type supported by {@link ReactiveAdapterRegistry#getSharedInstance()},
089         * for which {@link #fromPublisher(Publisher, Class)} or
090         * {@link #fromProducer(Object, Class)} should be used.
091         * @see #fromPublisher(Publisher, Class)
092         * @see #fromProducer(Object, Class)
093         */
094        public static <T> BodyInserter<T, ReactiveHttpOutputMessage> fromValue(T body) {
095                Assert.notNull(body, "'body' must not be null");
096                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");
097                return (message, context) ->
098                                writeWithMessageWriters(message, context, Mono.just(body), ResolvableType.forInstance(body), null);
099        }
100
101        /**
102         * Inserter to write the given object.
103         * <p>Alternatively, consider using the {@code bodyValue(Object)} shortcuts on
104         * {@link org.springframework.web.reactive.function.client.WebClient WebClient} and
105         * {@link org.springframework.web.reactive.function.server.ServerResponse ServerResponse}.
106         * @param body the body to write to the response
107         * @param <T> the type of the body
108         * @return the inserter to write a single object
109         * @throws IllegalArgumentException if {@code body} is a {@link Publisher} or an
110         * instance of a type supported by {@link ReactiveAdapterRegistry#getSharedInstance()},
111         * for which {@link #fromPublisher(Publisher, Class)} or
112         * {@link #fromProducer(Object, Class)} should be used.
113         * @see #fromPublisher(Publisher, Class)
114         * @see #fromProducer(Object, Class)
115         * @deprecated As of Spring Framework 5.2, in favor of {@link #fromValue(Object)}
116         */
117        @Deprecated
118        public static <T> BodyInserter<T, ReactiveHttpOutputMessage> fromObject(T body) {
119                return fromValue(body);
120        }
121
122        /**
123         * Inserter to write the given producer of value(s) which must be a {@link Publisher}
124         * or another producer adaptable to a {@code Publisher} via
125         * {@link ReactiveAdapterRegistry}.
126         * <p>Alternatively, consider using the {@code body} shortcuts on
127         * {@link org.springframework.web.reactive.function.client.WebClient WebClient} and
128         * {@link org.springframework.web.reactive.function.server.ServerResponse ServerResponse}.
129         * @param <T> the type of the body
130         * @param producer the source of body value(s).
131         * @param elementClass the class of values to be produced
132         * @return the inserter to write a producer
133         * @since 5.2
134         */
135        public static <T> BodyInserter<T, ReactiveHttpOutputMessage> fromProducer(T producer, Class<?> elementClass) {
136                Assert.notNull(producer, "'producer' must not be null");
137                Assert.notNull(elementClass, "'elementClass' must not be null");
138                ReactiveAdapter adapter = ReactiveAdapterRegistry.getSharedInstance().getAdapter(producer.getClass());
139                Assert.notNull(adapter, "'producer' type is unknown to ReactiveAdapterRegistry");
140                return (message, context) ->
141                                writeWithMessageWriters(message, context, producer, ResolvableType.forClass(elementClass), adapter);
142        }
143
144        /**
145         * Inserter to write the given producer of value(s) which must be a {@link Publisher}
146         * or another producer adaptable to a {@code Publisher} via
147         * {@link ReactiveAdapterRegistry}.
148         * <p>Alternatively, consider using the {@code body} shortcuts on
149         * {@link org.springframework.web.reactive.function.client.WebClient WebClient} and
150         * {@link org.springframework.web.reactive.function.server.ServerResponse ServerResponse}.
151         * @param <T> the type of the body
152         * @param producer the source of body value(s).
153         * @param elementTypeRef the type of values to be produced
154         * @return the inserter to write a producer
155         * @since 5.2
156         */
157        public static <T> BodyInserter<T, ReactiveHttpOutputMessage> fromProducer(
158                        T producer, ParameterizedTypeReference<?> elementTypeRef) {
159
160                Assert.notNull(producer, "'producer' must not be null");
161                Assert.notNull(elementTypeRef, "'elementTypeRef' must not be null");
162                ReactiveAdapter adapter = ReactiveAdapterRegistry.getSharedInstance().getAdapter(producer.getClass());
163                Assert.notNull(adapter, "'producer' type is unknown to ReactiveAdapterRegistry");
164                return (message, context) ->
165                                writeWithMessageWriters(message, context, producer, ResolvableType.forType(elementTypeRef), adapter);
166        }
167
168        /**
169         * Inserter to write the given {@link Publisher}.
170         * <p>Alternatively, consider using the {@code body} shortcuts on
171         * {@link org.springframework.web.reactive.function.client.WebClient WebClient} and
172         * {@link org.springframework.web.reactive.function.server.ServerResponse ServerResponse}.
173         * @param publisher the publisher to write with
174         * @param elementClass the class of elements in the publisher
175         * @param <T> the type of the elements contained in the publisher
176         * @param <P> the {@code Publisher} type
177         * @return the inserter to write a {@code Publisher}
178         */
179        public static <T, P extends Publisher<T>> BodyInserter<P, ReactiveHttpOutputMessage> fromPublisher(
180                        P publisher, Class<T> elementClass) {
181
182                Assert.notNull(publisher, "'publisher' must not be null");
183                Assert.notNull(elementClass, "'elementClass' must not be null");
184                return (message, context) ->
185                                writeWithMessageWriters(message, context, publisher, ResolvableType.forClass(elementClass), null);
186        }
187
188        /**
189         * Inserter to write the given {@link Publisher}.
190         * <p>Alternatively, consider using the {@code body} shortcuts on
191         * {@link org.springframework.web.reactive.function.client.WebClient WebClient} and
192         * {@link org.springframework.web.reactive.function.server.ServerResponse ServerResponse}.
193         * @param publisher the publisher to write with
194         * @param elementTypeRef the type of elements contained in the publisher
195         * @param <T> the type of the elements contained in the publisher
196         * @param <P> the {@code Publisher} type
197         * @return the inserter to write a {@code Publisher}
198         */
199        public static <T, P extends Publisher<T>> BodyInserter<P, ReactiveHttpOutputMessage> fromPublisher(
200                        P publisher, ParameterizedTypeReference<T> elementTypeRef) {
201
202                Assert.notNull(publisher, "'publisher' must not be null");
203                Assert.notNull(elementTypeRef, "'elementTypeRef' must not be null");
204                return (message, context) ->
205                                writeWithMessageWriters(message, context, publisher, ResolvableType.forType(elementTypeRef.getType()), null);
206        }
207
208        /**
209         * Inserter to write the given {@code Resource}.
210         * <p>If the resource can be resolved to a {@linkplain Resource#getFile() file}, it will
211         * be copied using <a href="https://en.wikipedia.org/wiki/Zero-copy">zero-copy</a>.
212         * @param resource the resource to write to the output message
213         * @param <T> the type of the {@code Resource}
214         * @return the inserter to write a {@code Publisher}
215         */
216        public static <T extends Resource> BodyInserter<T, ReactiveHttpOutputMessage> fromResource(T resource) {
217                Assert.notNull(resource, "'resource' must not be null");
218                return (outputMessage, context) -> {
219                        ResolvableType elementType = RESOURCE_TYPE;
220                        HttpMessageWriter<Resource> writer = findWriter(context, elementType, null);
221                        MediaType contentType = outputMessage.getHeaders().getContentType();
222                        return write(Mono.just(resource), elementType, contentType, outputMessage, context, writer);
223                };
224        }
225
226        /**
227         * Inserter to write the given {@code ServerSentEvent} publisher.
228         * <p>Alternatively, you can provide event data objects via
229         * {@link #fromPublisher(Publisher, Class)} or {@link #fromProducer(Object, Class)},
230         * and set the "Content-Type" to {@link MediaType#TEXT_EVENT_STREAM text/event-stream}.
231         * @param eventsPublisher the {@code ServerSentEvent} publisher to write to the response body
232         * @param <T> the type of the data elements in the {@link ServerSentEvent}
233         * @return the inserter to write a {@code ServerSentEvent} publisher
234         * @see <a href="https://www.w3.org/TR/eventsource/">Server-Sent Events W3C recommendation</a>
235         */
236        // Parameterized for server-side use
237        public static <T, S extends Publisher<ServerSentEvent<T>>> BodyInserter<S, ServerHttpResponse> fromServerSentEvents(
238                        S eventsPublisher) {
239
240                Assert.notNull(eventsPublisher, "'eventsPublisher' must not be null");
241                return (serverResponse, context) -> {
242                        ResolvableType elementType = SSE_TYPE;
243                        MediaType mediaType = MediaType.TEXT_EVENT_STREAM;
244                        HttpMessageWriter<ServerSentEvent<T>> writer = findWriter(context, elementType, mediaType);
245                        return write(eventsPublisher, elementType, mediaType, serverResponse, context, writer);
246                };
247        }
248
249        /**
250         * Return a {@link FormInserter} to write the given {@code MultiValueMap}
251         * as URL-encoded form data. The returned inserter allows for additional
252         * entries to be added via {@link FormInserter#with(String, Object)}.
253         * <p>Note that you can also use the {@code bodyValue(Object)} method in the
254         * request builders of both the {@code WebClient} and {@code WebTestClient}.
255         * In that case the setting of the request content type is also not required,
256         * just be sure the map contains String values only or otherwise it would be
257         * interpreted as a multipart request.
258         * @param formData the form data to write to the output message
259         * @return the inserter that allows adding more form data
260         */
261        public static FormInserter<String> fromFormData(MultiValueMap<String, String> formData) {
262                return new DefaultFormInserter().with(formData);
263        }
264
265        /**
266         * Return a {@link FormInserter} to write the given key-value pair as
267         * URL-encoded form data. The returned inserter allows for additional
268         * entries to be added via {@link FormInserter#with(String, Object)}.
269         * @param name the key to add to the form
270         * @param value the value to add to the form
271         * @return the inserter that allows adding more form data
272         */
273        public static FormInserter<String> fromFormData(String name, String value) {
274                Assert.notNull(name, "'name' must not be null");
275                Assert.notNull(value, "'value' must not be null");
276                return new DefaultFormInserter().with(name, value);
277        }
278
279        /**
280         * Return a {@link MultipartInserter} to write the given
281         * {@code MultiValueMap} as multipart data. Values in the map can be an
282         * Object or an {@link HttpEntity}.
283         * <p>Note that you can also build the multipart data externally with
284         * {@link MultipartBodyBuilder}, and pass the resulting map directly to the
285         * {@code bodyValue(Object)} shortcut method in {@code WebClient}.
286         * @param multipartData the form data to write to the output message
287         * @return the inserter that allows adding more parts
288         * @see MultipartBodyBuilder
289         */
290        public static MultipartInserter fromMultipartData(MultiValueMap<String, ?> multipartData) {
291                Assert.notNull(multipartData, "'multipartData' must not be null");
292                return new DefaultMultipartInserter().withInternal(multipartData);
293        }
294
295        /**
296         * Return a {@link MultipartInserter} to write the given parts,
297         * as multipart data. Values in the map can be an Object or an
298         * {@link HttpEntity}.
299         * <p>Note that you can also build the multipart data externally with
300         * {@link MultipartBodyBuilder}, and pass the resulting map directly to the
301         * {@code bodyValue(Object)} shortcut method in {@code WebClient}.
302         * @param name the part name
303         * @param value the part value, an Object or {@code HttpEntity}
304         * @return the inserter that allows adding more parts
305         */
306        public static MultipartInserter fromMultipartData(String name, Object value) {
307                Assert.notNull(name, "'name' must not be null");
308                Assert.notNull(value, "'value' must not be null");
309                return new DefaultMultipartInserter().with(name, value);
310        }
311
312        /**
313         * Return a {@link MultipartInserter} to write the given asynchronous parts,
314         * as multipart data.
315         * <p>Note that you can also build the multipart data externally with
316         * {@link MultipartBodyBuilder}, and pass the resulting map directly to the
317         * {@code bodyValue(Object)} shortcut method in {@code WebClient}.
318         * @param name the part name
319         * @param publisher the publisher that forms the part value
320         * @param elementClass the class contained in the {@code publisher}
321         * @return the inserter that allows adding more parts
322         */
323        public static <T, P extends Publisher<T>> MultipartInserter fromMultipartAsyncData(
324                        String name, P publisher, Class<T> elementClass) {
325
326                return new DefaultMultipartInserter().withPublisher(name, publisher, elementClass);
327        }
328
329        /**
330         * Variant of {@link #fromMultipartAsyncData(String, Publisher, Class)} that
331         * accepts a {@link ParameterizedTypeReference} for the element type, which
332         * allows specifying generic type information.
333         * <p>Note that you can also build the multipart data externally with
334         * {@link MultipartBodyBuilder}, and pass the resulting map directly to the
335         * {@code bodyValue(Object)} shortcut method in {@code WebClient}.
336         * @param name the part name
337         * @param publisher the publisher that forms the part value
338         * @param typeReference the type contained in the {@code publisher}
339         * @return the inserter that allows adding more parts
340         */
341        public static <T, P extends Publisher<T>> MultipartInserter fromMultipartAsyncData(
342                        String name, P publisher, ParameterizedTypeReference<T> typeReference) {
343
344                return new DefaultMultipartInserter().withPublisher(name, publisher, typeReference);
345        }
346
347        /**
348         * Inserter to write the given {@code Publisher<DataBuffer>} to the body.
349         * @param publisher the data buffer publisher to write
350         * @param <T> the type of the publisher
351         * @return the inserter to write directly to the body
352         * @see ReactiveHttpOutputMessage#writeWith(Publisher)
353         */
354        public static <T extends Publisher<DataBuffer>> BodyInserter<T, ReactiveHttpOutputMessage> fromDataBuffers(
355                        T publisher) {
356
357                Assert.notNull(publisher, "'publisher' must not be null");
358                return (outputMessage, context) -> outputMessage.writeWith(publisher);
359        }
360
361
362        private static <M extends ReactiveHttpOutputMessage> Mono<Void> writeWithMessageWriters(
363                        M outputMessage, BodyInserter.Context context, Object body, ResolvableType bodyType, @Nullable ReactiveAdapter adapter) {
364
365                Publisher<?> publisher;
366                if (body instanceof Publisher) {
367                        publisher = (Publisher<?>) body;
368                }
369                else if (adapter != null) {
370                        publisher = adapter.toPublisher(body);
371                }
372                else {
373                        publisher = Mono.just(body);
374                }
375                MediaType mediaType = outputMessage.getHeaders().getContentType();
376                return context.messageWriters().stream()
377                                .filter(messageWriter -> messageWriter.canWrite(bodyType, mediaType))
378                                .findFirst()
379                                .map(BodyInserters::cast)
380                                .map(writer -> write(publisher, bodyType, mediaType, outputMessage, context, writer))
381                                .orElseGet(() -> Mono.error(unsupportedError(bodyType, context, mediaType)));
382        }
383
384        private static UnsupportedMediaTypeException unsupportedError(ResolvableType bodyType,
385                        BodyInserter.Context context, @Nullable MediaType mediaType) {
386
387                List<MediaType> supportedMediaTypes = context.messageWriters().stream()
388                                .flatMap(reader -> reader.getWritableMediaTypes().stream())
389                                .collect(Collectors.toList());
390
391                return new UnsupportedMediaTypeException(mediaType, supportedMediaTypes, bodyType);
392        }
393
394        private static <T> Mono<Void> write(Publisher<? extends T> input, ResolvableType type,
395                        @Nullable MediaType mediaType, ReactiveHttpOutputMessage message,
396                        BodyInserter.Context context, HttpMessageWriter<T> writer) {
397
398                return context.serverRequest()
399                                .map(request -> {
400                                        ServerHttpResponse response = (ServerHttpResponse) message;
401                                        return writer.write(input, type, type, mediaType, request, response, context.hints());
402                                })
403                                .orElseGet(() -> writer.write(input, type, mediaType, message, context.hints()));
404        }
405
406        private static <T> HttpMessageWriter<T> findWriter(
407                        BodyInserter.Context context, ResolvableType elementType, @Nullable MediaType mediaType) {
408
409                return context.messageWriters().stream()
410                                .filter(messageWriter -> messageWriter.canWrite(elementType, mediaType))
411                                .findFirst()
412                                .map(BodyInserters::<T>cast)
413                                .orElseThrow(() -> new IllegalStateException(
414                                                "No HttpMessageWriter for \"" + mediaType + "\" and \"" + elementType + "\""));
415        }
416
417        @SuppressWarnings("unchecked")
418        private static <T> HttpMessageWriter<T> cast(HttpMessageWriter<?> messageWriter) {
419                return (HttpMessageWriter<T>) messageWriter;
420        }
421
422
423        /**
424         * Extension of {@link BodyInserter} that allows for adding form data or
425         * multipart form data.
426         *
427         * @param <T> the value type
428         */
429        public interface FormInserter<T> extends BodyInserter<MultiValueMap<String, T>, ClientHttpRequest> {
430
431                // FormInserter is parameterized to ClientHttpRequest (for client-side use only)
432
433                /**
434                 * Adds the specified key-value pair to the form.
435                 * @param key the key to be added
436                 * @param value the value to be added
437                 * @return this inserter for adding more parts
438                 */
439                FormInserter<T> with(String key, T value);
440
441                /**
442                 * Adds the specified values to the form.
443                 * @param values the values to be added
444                 * @return this inserter for adding more parts
445                 */
446                FormInserter<T> with(MultiValueMap<String, T> values);
447
448        }
449
450
451        /**
452         * Extension of {@link FormInserter} that allows for adding asynchronous parts.
453         */
454        public interface MultipartInserter extends FormInserter<Object> {
455
456                /**
457                 * Add an asynchronous part with {@link Publisher}-based content.
458                 * @param name the name of the part to add
459                 * @param publisher the part contents
460                 * @param elementClass the type of elements contained in the publisher
461                 * @return this inserter for adding more parts
462                 */
463                <T, P extends Publisher<T>> MultipartInserter withPublisher(String name, P publisher,
464                                Class<T> elementClass);
465
466                /**
467                 * Variant of {@link #withPublisher(String, Publisher, Class)} that accepts a
468                 * {@link ParameterizedTypeReference} for the element type, which allows
469                 * specifying generic type information.
470                 * @param name the key to be added
471                 * @param publisher the publisher to be added as value
472                 * @param typeReference the type of elements contained in {@code publisher}
473                 * @return this inserter for adding more parts
474                 */
475                <T, P extends Publisher<T>> MultipartInserter withPublisher(String name, P publisher,
476                                ParameterizedTypeReference<T> typeReference);
477
478        }
479
480
481        private static class DefaultFormInserter implements FormInserter<String> {
482
483                private final MultiValueMap<String, String> data = new LinkedMultiValueMap<>();
484
485                @Override
486                public FormInserter<String> with(String key, @Nullable String value) {
487                        this.data.add(key, value);
488                        return this;
489                }
490
491                @Override
492                public FormInserter<String> with(MultiValueMap<String, String> values) {
493                        this.data.addAll(values);
494                        return this;
495                }
496
497                @Override
498                public Mono<Void> insert(ClientHttpRequest outputMessage, Context context) {
499                        HttpMessageWriter<MultiValueMap<String, String>> messageWriter =
500                                        findWriter(context, FORM_DATA_TYPE, MediaType.APPLICATION_FORM_URLENCODED);
501                        return messageWriter.write(Mono.just(this.data), FORM_DATA_TYPE,
502                                        MediaType.APPLICATION_FORM_URLENCODED,
503                                        outputMessage, context.hints());
504                }
505        }
506
507
508        private static class DefaultMultipartInserter implements MultipartInserter {
509
510                private final MultipartBodyBuilder builder = new MultipartBodyBuilder();
511
512                @Override
513                public MultipartInserter with(String key, Object value) {
514                        this.builder.part(key, value);
515                        return this;
516                }
517
518                @Override
519                public MultipartInserter with(MultiValueMap<String, Object> values) {
520                        return withInternal(values);
521                }
522
523                @SuppressWarnings("unchecked")
524                private MultipartInserter withInternal(MultiValueMap<String, ?> values) {
525                        values.forEach((key, valueList) -> {
526                                for (Object value : valueList) {
527                                        this.builder.part(key, value);
528                                }
529                        });
530                        return this;
531                }
532
533                @Override
534                public <T, P extends Publisher<T>> MultipartInserter withPublisher(
535                                String name, P publisher, Class<T> elementClass) {
536
537                        this.builder.asyncPart(name, publisher, elementClass);
538                        return this;
539                }
540
541                @Override
542                public <T, P extends Publisher<T>> MultipartInserter withPublisher(
543                                String name, P publisher, ParameterizedTypeReference<T> typeReference) {
544
545                        this.builder.asyncPart(name, publisher, typeReference);
546                        return this;
547                }
548
549                @Override
550                public Mono<Void> insert(ClientHttpRequest outputMessage, Context context) {
551                        HttpMessageWriter<MultiValueMap<String, HttpEntity<?>>> messageWriter =
552                                        findWriter(context, MULTIPART_DATA_TYPE, MediaType.MULTIPART_FORM_DATA);
553                        MultiValueMap<String, HttpEntity<?>> body = this.builder.build();
554                        return messageWriter.write(Mono.just(body), MULTIPART_DATA_TYPE,
555                                        MediaType.MULTIPART_FORM_DATA, outputMessage, context.hints());
556                }
557        }
558
559}