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}