/*
* Copyright 2002-2017 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.result.method.annotation;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono;
import org.springframework.core.MethodParameter;
import org.springframework.core.ReactiveAdapter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.core.ResolvableType;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.Assert;
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
import org.springframework.web.reactive.result.HandlerResultHandlerSupport;
import org.springframework.web.server.NotAcceptableStatusException;
import org.springframework.web.server.ServerWebExchange;
/**
* Abstract base class for result handlers that handle return values by writing
* to the response with {@link HttpMessageWriter}.
*
* @author Rossen Stoyanchev
* @since 5.0
*/
public abstract class AbstractMessageWriterResultHandler extends HandlerResultHandlerSupport {
private final List<HttpMessageWriter<?>> messageWriters;
/**
* Constructor with {@link HttpMessageWriter}s and a
* {@code RequestedContentTypeResolver}.
* @param messageWriters for serializing Objects to the response body stream
* @param contentTypeResolver for resolving the requested content type
*/
protected AbstractMessageWriterResultHandler(List<HttpMessageWriter<?>> messageWriters,
RequestedContentTypeResolver contentTypeResolver) {
this(messageWriters, contentTypeResolver, new ReactiveAdapterRegistry());
}
/**
* Constructor with an additional {@link ReactiveAdapterRegistry}.
* @param messageWriters for serializing Objects to the response body stream
* @param contentTypeResolver for resolving the requested content type
* @param adapterRegistry for adapting other reactive types (e.g. rx.Observable,
* rx.Single, etc.) to Flux or Mono
*/
protected AbstractMessageWriterResultHandler(List<HttpMessageWriter<?>> messageWriters,
RequestedContentTypeResolver contentTypeResolver, ReactiveAdapterRegistry adapterRegistry) {
super(contentTypeResolver, adapterRegistry);
Assert.notEmpty(messageWriters, "At least one message writer is required");
this.messageWriters = messageWriters;
}
/**
* Return the configured message converters.
*/
public List<HttpMessageWriter<?>> getMessageWriters() {
return this.messageWriters;
}
@SuppressWarnings("unchecked")
protected Mono<Void> writeBody(Object body, MethodParameter bodyParameter, ServerWebExchange exchange) {
ResolvableType bodyType = ResolvableType.forMethodParameter(bodyParameter);
Class<?> bodyClass = bodyType.resolve();
ReactiveAdapter adapter = getAdapterRegistry().getAdapter(bodyClass, body);
Publisher<?> publisher;
ResolvableType elementType;
if (adapter != null) {
publisher = adapter.toPublisher(body);
elementType = adapter.isNoValue() ? ResolvableType.forClass(Void.class) : bodyType.getGeneric(0);
}
else {
publisher = Mono.justOrEmpty(body);
elementType = (bodyClass == null && body != null ? ResolvableType.forInstance(body) : bodyType);
}
if (void.class == elementType.getRawClass() || Void.class == elementType.getRawClass()) {
return Mono.from((Publisher<Void>) publisher);
}
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
MediaType bestMediaType = selectMediaType(exchange, () -> getProducibleMediaTypes(elementType));
if (bestMediaType != null) {
for (HttpMessageWriter<?> writer : getMessageWriters()) {
if (writer.canWrite(elementType, bestMediaType)) {
return writer.write((Publisher) publisher, bodyType, elementType,
bestMediaType, request, response, Collections.emptyMap());
}
}
}
else {
if (getProducibleMediaTypes(elementType).isEmpty()) {
return Mono.error(new IllegalStateException("No converter for return value type: " + elementType));
}
}
return Mono.error(new NotAcceptableStatusException(getProducibleMediaTypes(elementType)));
}
private List<MediaType> getProducibleMediaTypes(ResolvableType elementType) {
return getMessageWriters().stream()
.filter(converter -> converter.canWrite(elementType, null))
.flatMap(converter -> converter.getWritableMediaTypes().stream())
.collect(Collectors.toList());
}
}