001/* 002 * Copyright 2002-2018 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.accept; 018 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.Collections; 022import java.util.HashMap; 023import java.util.List; 024import java.util.Map; 025import java.util.function.Supplier; 026import java.util.stream.Collectors; 027 028import org.springframework.http.MediaType; 029import org.springframework.lang.Nullable; 030 031/** 032 * Builder for a composite {@link RequestedContentTypeResolver} that delegates 033 * to other resolvers each implementing a different strategy to determine the 034 * requested content type -- e.g. Accept header, query parameter, or other. 035 * 036 * <p>Use builder methods to add resolvers in the desired order. For a given 037 * request he first resolver to return a list that is not empty and does not 038 * consist of just {@link MediaType#ALL}, will be used. 039 * 040 * <p>By default, if no resolvers are explicitly configured, the builder will 041 * add {@link HeaderContentTypeResolver}. 042 * 043 * @author Rossen Stoyanchev 044 * @since 5.0 045 */ 046public class RequestedContentTypeResolverBuilder { 047 048 private final List<Supplier<RequestedContentTypeResolver>> candidates = new ArrayList<>(); 049 050 051 /** 052 * Add a resolver to get the requested content type from a query parameter. 053 * By default the query parameter name is {@code "format"}. 054 */ 055 public ParameterResolverConfigurer parameterResolver() { 056 ParameterResolverConfigurer parameterBuilder = new ParameterResolverConfigurer(); 057 this.candidates.add(parameterBuilder::createResolver); 058 return parameterBuilder; 059 } 060 061 /** 062 * Add resolver to get the requested content type from the 063 * {@literal "Accept"} header. 064 */ 065 public void headerResolver() { 066 this.candidates.add(HeaderContentTypeResolver::new); 067 } 068 069 /** 070 * Add resolver that returns a fixed set of media types. 071 * @param mediaTypes the media types to use 072 */ 073 public void fixedResolver(MediaType... mediaTypes) { 074 this.candidates.add(() -> new FixedContentTypeResolver(Arrays.asList(mediaTypes))); 075 } 076 077 /** 078 * Add a custom resolver. 079 * @param resolver the resolver to add 080 */ 081 public void resolver(RequestedContentTypeResolver resolver) { 082 this.candidates.add(() -> resolver); 083 } 084 085 /** 086 * Build a {@link RequestedContentTypeResolver} that delegates to the list 087 * of resolvers configured through this builder. 088 */ 089 public RequestedContentTypeResolver build() { 090 List<RequestedContentTypeResolver> resolvers = (!this.candidates.isEmpty() ? 091 this.candidates.stream().map(Supplier::get).collect(Collectors.toList()) : 092 Collections.singletonList(new HeaderContentTypeResolver())); 093 094 return exchange -> { 095 for (RequestedContentTypeResolver resolver : resolvers) { 096 List<MediaType> mediaTypes = resolver.resolveMediaTypes(exchange); 097 if (mediaTypes.equals(RequestedContentTypeResolver.MEDIA_TYPE_ALL_LIST)) { 098 continue; 099 } 100 return mediaTypes; 101 } 102 return RequestedContentTypeResolver.MEDIA_TYPE_ALL_LIST; 103 }; 104 } 105 106 107 /** 108 * Helper to create and configure {@link ParameterContentTypeResolver}. 109 */ 110 public static class ParameterResolverConfigurer { 111 112 private final Map<String, MediaType> mediaTypes = new HashMap<>(); 113 114 @Nullable 115 private String parameterName; 116 117 /** 118 * Configure a mapping between a lookup key (extracted from a query 119 * parameter value) and a corresponding {@code MediaType}. 120 * @param key the lookup key 121 * @param mediaType the MediaType for that key 122 */ 123 public ParameterResolverConfigurer mediaType(String key, MediaType mediaType) { 124 this.mediaTypes.put(key, mediaType); 125 return this; 126 } 127 128 /** 129 * Map-based variant of {@link #mediaType(String, MediaType)}. 130 * @param mediaTypes the mappings to copy 131 */ 132 public ParameterResolverConfigurer mediaType(Map<String, MediaType> mediaTypes) { 133 this.mediaTypes.putAll(mediaTypes); 134 return this; 135 } 136 137 /** 138 * Set the name of the parameter to use to determine requested media types. 139 * <p>By default this is set to {@literal "format"}. 140 */ 141 public ParameterResolverConfigurer parameterName(String parameterName) { 142 this.parameterName = parameterName; 143 return this; 144 } 145 146 /** 147 * Private factory method to create the resolver. 148 */ 149 private RequestedContentTypeResolver createResolver() { 150 ParameterContentTypeResolver resolver = new ParameterContentTypeResolver(this.mediaTypes); 151 if (this.parameterName != null) { 152 resolver.setParameterName(this.parameterName); 153 } 154 return resolver; 155 } 156 } 157 158}