001/* 002 * Copyright 2002-2019 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; 018 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.Comparator; 022import java.util.LinkedHashSet; 023import java.util.List; 024import java.util.Set; 025import java.util.function.Supplier; 026 027import org.apache.commons.logging.Log; 028import org.apache.commons.logging.LogFactory; 029 030import org.springframework.core.Ordered; 031import org.springframework.core.ReactiveAdapter; 032import org.springframework.core.ReactiveAdapterRegistry; 033import org.springframework.http.MediaType; 034import org.springframework.lang.Nullable; 035import org.springframework.util.Assert; 036import org.springframework.web.reactive.HandlerMapping; 037import org.springframework.web.reactive.HandlerResult; 038import org.springframework.web.reactive.accept.RequestedContentTypeResolver; 039import org.springframework.web.server.ServerWebExchange; 040 041/** 042 * Base class for {@link org.springframework.web.reactive.HandlerResultHandler 043 * HandlerResultHandler} with support for content negotiation and access to a 044 * {@code ReactiveAdapter} registry. 045 * 046 * @author Rossen Stoyanchev 047 * @since 5.0 048 */ 049public abstract class HandlerResultHandlerSupport implements Ordered { 050 051 private static final List<MediaType> ALL_APPLICATION_MEDIA_TYPES = 052 Arrays.asList(MediaType.ALL, new MediaType("application")); 053 054 055 protected final Log logger = LogFactory.getLog(getClass()); 056 057 private final RequestedContentTypeResolver contentTypeResolver; 058 059 private final ReactiveAdapterRegistry adapterRegistry; 060 061 private int order = LOWEST_PRECEDENCE; 062 063 064 protected HandlerResultHandlerSupport(RequestedContentTypeResolver contentTypeResolver, 065 ReactiveAdapterRegistry adapterRegistry) { 066 067 Assert.notNull(contentTypeResolver, "RequestedContentTypeResolver is required"); 068 Assert.notNull(adapterRegistry, "ReactiveAdapterRegistry is required"); 069 this.contentTypeResolver = contentTypeResolver; 070 this.adapterRegistry = adapterRegistry; 071 } 072 073 074 /** 075 * Return the configured {@link ReactiveAdapterRegistry}. 076 */ 077 public ReactiveAdapterRegistry getAdapterRegistry() { 078 return this.adapterRegistry; 079 } 080 081 /** 082 * Return the configured {@link RequestedContentTypeResolver}. 083 */ 084 public RequestedContentTypeResolver getContentTypeResolver() { 085 return this.contentTypeResolver; 086 } 087 088 /** 089 * Set the order for this result handler relative to others. 090 * <p>By default set to {@link Ordered#LOWEST_PRECEDENCE}, however see 091 * Javadoc of sub-classes which may change this default. 092 * @param order the order 093 */ 094 public void setOrder(int order) { 095 this.order = order; 096 } 097 098 @Override 099 public int getOrder() { 100 return this.order; 101 } 102 103 104 /** 105 * Get a {@code ReactiveAdapter} for the top-level return value type. 106 * @return the matching adapter, or {@code null} if none 107 */ 108 @Nullable 109 protected ReactiveAdapter getAdapter(HandlerResult result) { 110 return getAdapterRegistry().getAdapter(result.getReturnType().resolve(), result.getReturnValue()); 111 } 112 113 /** 114 * Select the best media type for the current request through a content negotiation algorithm. 115 * @param exchange the current request 116 * @param producibleTypesSupplier the media types that can be produced for the current request 117 * @return the selected media type, or {@code null} if none 118 */ 119 @Nullable 120 protected MediaType selectMediaType( 121 ServerWebExchange exchange, Supplier<List<MediaType>> producibleTypesSupplier) { 122 123 MediaType contentType = exchange.getResponse().getHeaders().getContentType(); 124 if (contentType != null && contentType.isConcrete()) { 125 if (logger.isDebugEnabled()) { 126 logger.debug(exchange.getLogPrefix() + "Found 'Content-Type:" + contentType + "' in response"); 127 } 128 return contentType; 129 } 130 131 List<MediaType> acceptableTypes = getAcceptableTypes(exchange); 132 List<MediaType> producibleTypes = getProducibleTypes(exchange, producibleTypesSupplier); 133 134 Set<MediaType> compatibleMediaTypes = new LinkedHashSet<>(); 135 for (MediaType acceptable : acceptableTypes) { 136 for (MediaType producible : producibleTypes) { 137 if (acceptable.isCompatibleWith(producible)) { 138 compatibleMediaTypes.add(selectMoreSpecificMediaType(acceptable, producible)); 139 } 140 } 141 } 142 143 List<MediaType> result = new ArrayList<>(compatibleMediaTypes); 144 MediaType.sortBySpecificityAndQuality(result); 145 146 MediaType selected = null; 147 for (MediaType mediaType : result) { 148 if (mediaType.isConcrete()) { 149 selected = mediaType; 150 break; 151 } 152 else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) { 153 selected = MediaType.APPLICATION_OCTET_STREAM; 154 break; 155 } 156 } 157 158 if (selected != null) { 159 selected = selected.removeQualityValue(); 160 if (logger.isDebugEnabled()) { 161 logger.debug("Using '" + selected + "' given " + acceptableTypes + 162 " and supported " + producibleTypes); 163 } 164 } 165 else if (logger.isDebugEnabled()) { 166 logger.debug(exchange.getLogPrefix() + 167 "No match for " + acceptableTypes + ", supported: " + producibleTypes); 168 } 169 170 return selected; 171 } 172 173 private List<MediaType> getAcceptableTypes(ServerWebExchange exchange) { 174 return getContentTypeResolver().resolveMediaTypes(exchange); 175 } 176 177 private List<MediaType> getProducibleTypes( 178 ServerWebExchange exchange, Supplier<List<MediaType>> producibleTypesSupplier) { 179 180 Set<MediaType> mediaTypes = exchange.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE); 181 return (mediaTypes != null ? new ArrayList<>(mediaTypes) : producibleTypesSupplier.get()); 182 } 183 184 private MediaType selectMoreSpecificMediaType(MediaType acceptable, MediaType producible) { 185 producible = producible.copyQualityValue(acceptable); 186 Comparator<MediaType> comparator = MediaType.SPECIFICITY_COMPARATOR; 187 return (comparator.compare(acceptable, producible) <= 0 ? acceptable : producible); 188 } 189 190}