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.result.method.annotation; 018 019import java.lang.reflect.Method; 020import java.util.ArrayList; 021import java.util.List; 022import java.util.Set; 023 024import kotlin.reflect.KFunction; 025import kotlin.reflect.jvm.ReflectJvmMapping; 026import org.reactivestreams.Publisher; 027import reactor.core.publisher.Mono; 028 029import org.springframework.core.KotlinDetector; 030import org.springframework.core.MethodParameter; 031import org.springframework.core.ReactiveAdapter; 032import org.springframework.core.ReactiveAdapterRegistry; 033import org.springframework.core.ResolvableType; 034import org.springframework.core.codec.Hints; 035import org.springframework.http.MediaType; 036import org.springframework.http.codec.HttpMessageWriter; 037import org.springframework.http.converter.HttpMessageNotWritableException; 038import org.springframework.lang.Nullable; 039import org.springframework.util.Assert; 040import org.springframework.util.CollectionUtils; 041import org.springframework.web.reactive.HandlerMapping; 042import org.springframework.web.reactive.accept.RequestedContentTypeResolver; 043import org.springframework.web.reactive.result.HandlerResultHandlerSupport; 044import org.springframework.web.server.NotAcceptableStatusException; 045import org.springframework.web.server.ServerWebExchange; 046 047/** 048 * Abstract base class for result handlers that handle return values by writing 049 * to the response with {@link HttpMessageWriter}. 050 * 051 * @author Rossen Stoyanchev 052 * @author Sebastien Deleuze 053 * @since 5.0 054 */ 055public abstract class AbstractMessageWriterResultHandler extends HandlerResultHandlerSupport { 056 057 private static final String COROUTINES_FLOW_CLASS_NAME = "kotlinx.coroutines.flow.Flow"; 058 059 private final List<HttpMessageWriter<?>> messageWriters; 060 061 062 /** 063 * Constructor with {@link HttpMessageWriter HttpMessageWriters} and a 064 * {@code RequestedContentTypeResolver}. 065 * @param messageWriters for serializing Objects to the response body stream 066 * @param contentTypeResolver for resolving the requested content type 067 */ 068 protected AbstractMessageWriterResultHandler(List<HttpMessageWriter<?>> messageWriters, 069 RequestedContentTypeResolver contentTypeResolver) { 070 071 this(messageWriters, contentTypeResolver, ReactiveAdapterRegistry.getSharedInstance()); 072 } 073 074 /** 075 * Constructor with an additional {@link ReactiveAdapterRegistry}. 076 * @param messageWriters for serializing Objects to the response body stream 077 * @param contentTypeResolver for resolving the requested content type 078 * @param adapterRegistry for adapting other reactive types (e.g. rx.Observable, 079 * rx.Single, etc.) to Flux or Mono 080 */ 081 protected AbstractMessageWriterResultHandler(List<HttpMessageWriter<?>> messageWriters, 082 RequestedContentTypeResolver contentTypeResolver, ReactiveAdapterRegistry adapterRegistry) { 083 084 super(contentTypeResolver, adapterRegistry); 085 Assert.notEmpty(messageWriters, "At least one message writer is required"); 086 this.messageWriters = messageWriters; 087 } 088 089 090 /** 091 * Return the configured message converters. 092 */ 093 public List<HttpMessageWriter<?>> getMessageWriters() { 094 return this.messageWriters; 095 } 096 097 098 /** 099 * Write a given body to the response with {@link HttpMessageWriter}. 100 * @param body the object to write 101 * @param bodyParameter the {@link MethodParameter} of the body to write 102 * @param exchange the current exchange 103 * @return indicates completion or error 104 * @see #writeBody(Object, MethodParameter, MethodParameter, ServerWebExchange) 105 */ 106 protected Mono<Void> writeBody(@Nullable Object body, MethodParameter bodyParameter, ServerWebExchange exchange) { 107 return this.writeBody(body, bodyParameter, null, exchange); 108 } 109 110 /** 111 * Write a given body to the response with {@link HttpMessageWriter}. 112 * @param body the object to write 113 * @param bodyParameter the {@link MethodParameter} of the body to write 114 * @param actualParam the actual return type of the method that returned the value; 115 * could be different from {@code bodyParameter} when processing {@code HttpEntity} 116 * for example 117 * @param exchange the current exchange 118 * @return indicates completion or error 119 * @since 5.0.2 120 */ 121 @SuppressWarnings({"unchecked", "rawtypes", "ConstantConditions"}) 122 protected Mono<Void> writeBody(@Nullable Object body, MethodParameter bodyParameter, 123 @Nullable MethodParameter actualParam, ServerWebExchange exchange) { 124 125 ResolvableType bodyType = ResolvableType.forMethodParameter(bodyParameter); 126 ResolvableType actualType = (actualParam != null ? ResolvableType.forMethodParameter(actualParam) : bodyType); 127 ReactiveAdapter adapter = getAdapterRegistry().getAdapter(bodyType.resolve(), body); 128 129 Publisher<?> publisher; 130 ResolvableType elementType; 131 ResolvableType actualElementType; 132 if (adapter != null) { 133 publisher = adapter.toPublisher(body); 134 boolean isUnwrapped = KotlinDetector.isKotlinReflectPresent() && 135 KotlinDetector.isKotlinType(bodyParameter.getContainingClass()) && 136 KotlinDelegate.isSuspend(bodyParameter.getMethod()) && 137 !COROUTINES_FLOW_CLASS_NAME.equals(bodyType.toClass().getName()); 138 ResolvableType genericType = isUnwrapped ? bodyType : bodyType.getGeneric(); 139 elementType = getElementType(adapter, genericType); 140 actualElementType = elementType; 141 } 142 else { 143 publisher = Mono.justOrEmpty(body); 144 actualElementType = body != null ? ResolvableType.forInstance(body) : bodyType; 145 elementType = (bodyType.toClass() == Object.class && body != null ? actualElementType : bodyType); 146 } 147 148 if (elementType.resolve() == void.class || elementType.resolve() == Void.class) { 149 return Mono.from((Publisher<Void>) publisher); 150 } 151 152 MediaType bestMediaType = selectMediaType(exchange, () -> getMediaTypesFor(elementType)); 153 if (bestMediaType != null) { 154 String logPrefix = exchange.getLogPrefix(); 155 if (logger.isDebugEnabled()) { 156 logger.debug(logPrefix + 157 (publisher instanceof Mono ? "0..1" : "0..N") + " [" + elementType + "]"); 158 } 159 for (HttpMessageWriter<?> writer : getMessageWriters()) { 160 if (writer.canWrite(actualElementType, bestMediaType)) { 161 return writer.write((Publisher) publisher, actualType, elementType, 162 bestMediaType, exchange.getRequest(), exchange.getResponse(), 163 Hints.from(Hints.LOG_PREFIX_HINT, logPrefix)); 164 } 165 } 166 } 167 168 MediaType contentType = exchange.getResponse().getHeaders().getContentType(); 169 boolean isPresentMediaType = (contentType != null && contentType.equals(bestMediaType)); 170 Set<MediaType> producibleTypes = exchange.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE); 171 if (isPresentMediaType || !CollectionUtils.isEmpty(producibleTypes)) { 172 return Mono.error(new HttpMessageNotWritableException( 173 "No Encoder for [" + elementType + "] with preset Content-Type '" + contentType + "'")); 174 } 175 176 List<MediaType> mediaTypes = getMediaTypesFor(elementType); 177 if (bestMediaType == null && mediaTypes.isEmpty()) { 178 return Mono.error(new IllegalStateException("No HttpMessageWriter for " + elementType)); 179 } 180 181 return Mono.error(new NotAcceptableStatusException(mediaTypes)); 182 } 183 184 private ResolvableType getElementType(ReactiveAdapter adapter, ResolvableType genericType) { 185 if (adapter.isNoValue()) { 186 return ResolvableType.forClass(Void.class); 187 } 188 else if (genericType != ResolvableType.NONE) { 189 return genericType; 190 } 191 else { 192 return ResolvableType.forClass(Object.class); 193 } 194 } 195 196 private List<MediaType> getMediaTypesFor(ResolvableType elementType) { 197 List<MediaType> writableMediaTypes = new ArrayList<>(); 198 for (HttpMessageWriter<?> converter : getMessageWriters()) { 199 if (converter.canWrite(elementType, null)) { 200 writableMediaTypes.addAll(converter.getWritableMediaTypes()); 201 } 202 } 203 return writableMediaTypes; 204 } 205 206 207 /** 208 * Inner class to avoid a hard dependency on Kotlin at runtime. 209 */ 210 private static class KotlinDelegate { 211 212 static private boolean isSuspend(Method method) { 213 KFunction<?> function = ReflectJvmMapping.getKotlinFunction(method); 214 return function != null && function.isSuspend(); 215 } 216 } 217 218}