BodyInserters.java 9.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
/*
 * Copyright 2002-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.web.reactive.function;

import java.util.Collections;
import java.util.function.BiFunction;
import java.util.function.Supplier;

import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono;

import org.springframework.core.ResolvableType;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.http.codec.ResourceHttpMessageWriter;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.http.codec.ServerSentEventHttpMessageWriter;
import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

/**
40
 * Implementations of {@link BodyInserter} that write various bodies, such a reactive streams,
41 42 43 44 45
 * server-sent events, resources, etc.
 *
 * @author Arjen Poutsma
 * @since 5.0
 */
46
public abstract class BodyInserters {
47 48 49 50 51 52 53 54

	private static final ResolvableType RESOURCE_TYPE = ResolvableType.forClass(Resource.class);

	private static final ResolvableType SERVER_SIDE_EVENT_TYPE =
			ResolvableType.forClass(ServerSentEvent.class);

	private static final boolean jackson2Present =
			ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper",
55
					BodyInserters.class.getClassLoader()) &&
56
					ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator",
57
							BodyInserters.class.getClassLoader());
58 59

	/**
60
	 * Return a {@code BodyInserter} that writes the given single object.
61
	 * @param body the body of the response
62
	 * @return a {@code BodyInserter} that writes a single object
63
	 */
64
	public static <T> BodyInserter<T> fromObject(T body) {
65
		Assert.notNull(body, "'body' must not be null");
66
		return BodyInserter.of(
67 68 69 70 71 72
				(response, configuration) -> writeWithMessageWriters(response, configuration,
						Mono.just(body), ResolvableType.forInstance(body)),
				() -> body);
	}

	/**
73
	 * Return a {@code BodyInserter} that writes the given {@link Publisher}.
74 75 76 77
	 * @param publisher the publisher to stream to the response body
	 * @param elementClass the class of elements contained in the publisher
	 * @param <T> the type of the elements contained in the publisher
	 * @param <S> the type of the {@code Publisher}.
78
	 * @return a {@code BodyInserter} that writes a {@code Publisher}
79
	 */
80
	public static <S extends Publisher<T>, T> BodyInserter<S> fromPublisher(S publisher,
81 82 83 84
			Class<T> elementClass) {

		Assert.notNull(publisher, "'publisher' must not be null");
		Assert.notNull(elementClass, "'elementClass' must not be null");
85
		return fromPublisher(publisher, ResolvableType.forClass(elementClass));
86 87 88
	}

	/**
89
	 * Return a {@code BodyInserter} that writes the given {@link Publisher}.
90 91 92 93
	 * @param publisher the publisher to stream to the response body
	 * @param elementType the type of elements contained in the publisher
	 * @param <T> the type of the elements contained in the publisher
	 * @param <S> the type of the {@code Publisher}.
94
	 * @return a {@code BodyInserter} that writes a {@code Publisher}
95
	 */
96
	public static <S extends Publisher<T>, T> BodyInserter<S> fromPublisher(S publisher,
97 98 99 100
			ResolvableType elementType) {

		Assert.notNull(publisher, "'publisher' must not be null");
		Assert.notNull(elementType, "'elementType' must not be null");
101
		return BodyInserter.of(
102 103 104 105 106 107 108
				(response, configuration) -> writeWithMessageWriters(response, configuration,
						publisher, elementType),
				() -> publisher
		);
	}

	/**
109
	 * Return a {@code BodyInserter} that writes the given {@code Resource}.
110 111 112 113 114
	 * If the resource can be resolved to a {@linkplain Resource#getFile() file}, it will be copied
	 * using
	 * <a href="https://en.wikipedia.org/wiki/Zero-copy">zero-copy</a>
	 * @param resource the resource to write to the response
	 * @param <T> the type of the {@code Resource}
115
	 * @return a {@code BodyInserter} that writes a {@code Publisher}
116
	 */
117
	public static <T extends Resource> BodyInserter<T> fromResource(T resource) {
118
		Assert.notNull(resource, "'resource' must not be null");
119
		return BodyInserter.of(
120 121 122 123 124 125 126 127 128 129 130
				(response, configuration) -> {
					ResourceHttpMessageWriter messageWriter = new ResourceHttpMessageWriter();
					MediaType contentType = response.getHeaders().getContentType();
					return messageWriter.write(Mono.just(resource), RESOURCE_TYPE, contentType,
							response, Collections.emptyMap());
				},
				() -> resource
		);
	}

	/**
131
	 * Return a {@code BodyInserter} that writes the given {@code ServerSentEvent} publisher.
132 133
	 * @param eventsPublisher the {@code ServerSentEvent} publisher to write to the response body
	 * @param <T> the type of the elements contained in the {@link ServerSentEvent}
134
	 * @return a {@code BodyInserter} that writes a {@code ServerSentEvent} publisher
135 136
	 * @see <a href="https://www.w3.org/TR/eventsource/">Server-Sent Events W3C recommendation</a>
	 */
137
	public static <T, S extends Publisher<ServerSentEvent<T>>> BodyInserter<S> fromServerSentEvents(
138 139 140
			S eventsPublisher) {

		Assert.notNull(eventsPublisher, "'eventsPublisher' must not be null");
141
		return BodyInserter.of(
142 143 144 145 146 147 148 149 150 151 152
				(response, configuration) -> {
					ServerSentEventHttpMessageWriter messageWriter = sseMessageWriter();
					MediaType contentType = response.getHeaders().getContentType();
					return messageWriter.write(eventsPublisher, SERVER_SIDE_EVENT_TYPE,
							contentType, response, Collections.emptyMap());
				},
				() -> eventsPublisher
		);
	}

	/**
153
	 * Return a {@code BodyInserter} that writes the given {@code Publisher} publisher as
154 155 156 157
	 * Server-Sent Events.
	 * @param eventsPublisher the publisher to write to the response body as Server-Sent Events
	 * @param eventClass the class of event contained in the publisher
	 * @param <T> the type of the elements contained in the publisher
158
	 * @return a {@code BodyInserter} that writes the given {@code Publisher} publisher as
159 160 161
	 * Server-Sent Events
	 * @see <a href="https://www.w3.org/TR/eventsource/">Server-Sent Events W3C recommendation</a>
	 */
162
	public static <T, S extends Publisher<T>> BodyInserter<S> fromServerSentEvents(S eventsPublisher,
163 164 165 166
			Class<T> eventClass) {

		Assert.notNull(eventsPublisher, "'eventsPublisher' must not be null");
		Assert.notNull(eventClass, "'eventClass' must not be null");
167
		return fromServerSentEvents(eventsPublisher, ResolvableType.forClass(eventClass));
168 169 170
	}

	/**
171
	 * Return a {@code BodyInserter} that writes the given {@code Publisher} publisher as
172 173 174 175
	 * Server-Sent Events.
	 * @param eventsPublisher the publisher to write to the response body as Server-Sent Events
	 * @param eventType the type of event contained in the publisher
	 * @param <T> the type of the elements contained in the publisher
176
	 * @return a {@code BodyInserter} that writes the given {@code Publisher} publisher as
177 178 179
	 * Server-Sent Events
	 * @see <a href="https://www.w3.org/TR/eventsource/">Server-Sent Events W3C recommendation</a>
	 */
180
	public static <T, S extends Publisher<T>> BodyInserter<S> fromServerSentEvents(S eventsPublisher,
181 182 183 184
			ResolvableType eventType) {

		Assert.notNull(eventsPublisher, "'eventsPublisher' must not be null");
		Assert.notNull(eventType, "'eventType' must not be null");
185
		return BodyInserter.of(
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
				(response, configuration) -> {
					ServerSentEventHttpMessageWriter messageWriter = sseMessageWriter();
					MediaType contentType = response.getHeaders().getContentType();
					return messageWriter.write(eventsPublisher, eventType, contentType, response,
							Collections.emptyMap());

				},
				() -> eventsPublisher
		);
	}

	private static ServerSentEventHttpMessageWriter sseMessageWriter() {
		return jackson2Present ? new ServerSentEventHttpMessageWriter(
				Collections.singletonList(new Jackson2JsonEncoder())) :
				new ServerSentEventHttpMessageWriter();
	}

	private static <T> Mono<Void> writeWithMessageWriters(ServerHttpResponse response,
			Configuration configuration,
			Publisher<T> body,
			ResolvableType bodyType) {

		// TODO: use ContentNegotiatingResultHandlerSupport
		MediaType contentType = response.getHeaders().getContentType();
		return configuration.messageWriters().get()
				.filter(messageWriter -> messageWriter.canWrite(bodyType, contentType, Collections
						.emptyMap()))
				.findFirst()
214
				.map(BodyInserters::cast)
215 216 217 218 219 220 221 222 223 224
				.map(messageWriter -> messageWriter
						.write(body, bodyType, contentType, response, Collections
								.emptyMap()))
				.orElseGet(() -> {
					response.setStatusCode(HttpStatus.NOT_ACCEPTABLE);
					return response.setComplete();
				});
	}

	@SuppressWarnings("unchecked")
225
	private static <T> HttpMessageWriter<T> cast(HttpMessageWriter<?> messageWriter) {
226 227 228
		return (HttpMessageWriter<T>) messageWriter;
	}

229
	static class DefaultBodyInserter<T> implements BodyInserter<T> {
230 231 232 233 234

		private final BiFunction<ServerHttpResponse, Configuration, Mono<Void>> writer;

		private final Supplier<T> supplier;

235
		public DefaultBodyInserter(
236 237 238 239 240 241 242
				BiFunction<ServerHttpResponse, Configuration, Mono<Void>> writer,
				Supplier<T> supplier) {
			this.writer = writer;
			this.supplier = supplier;
		}

		@Override
A
Arjen Poutsma 已提交
243 244
		public Mono<Void> insert(ServerHttpResponse response, Configuration configuration) {
			return this.writer.apply(response, configuration);
245 246 247
		}

		@Override
A
Arjen Poutsma 已提交
248 249
		public T t() {
			return this.supplier.get();
250
		}
A
Arjen Poutsma 已提交
251

252 253 254 255
	}


}