001/* 002 * Copyright 2002-2017 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.accept; 018 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.Collection; 022import java.util.Collections; 023import java.util.LinkedHashSet; 024import java.util.List; 025import java.util.Set; 026 027import org.springframework.http.MediaType; 028import org.springframework.util.Assert; 029import org.springframework.web.HttpMediaTypeNotAcceptableException; 030import org.springframework.web.context.request.NativeWebRequest; 031 032/** 033 * Central class to determine requested {@linkplain MediaType media types} 034 * for a request. This is done by delegating to a list of configured 035 * {@code ContentNegotiationStrategy} instances. 036 * 037 * <p>Also provides methods to look up file extensions for a media type. 038 * This is done by delegating to the list of configured 039 * {@code MediaTypeFileExtensionResolver} instances. 040 * 041 * @author Rossen Stoyanchev 042 * @author Juergen Hoeller 043 * @since 3.2 044 */ 045public class ContentNegotiationManager implements ContentNegotiationStrategy, MediaTypeFileExtensionResolver { 046 047 private static final List<MediaType> MEDIA_TYPE_ALL = Collections.<MediaType>singletonList(MediaType.ALL); 048 049 050 private final List<ContentNegotiationStrategy> strategies = new ArrayList<ContentNegotiationStrategy>(); 051 052 private final Set<MediaTypeFileExtensionResolver> resolvers = new LinkedHashSet<MediaTypeFileExtensionResolver>(); 053 054 055 /** 056 * Create an instance with the given list of 057 * {@code ContentNegotiationStrategy} strategies each of which may also be 058 * an instance of {@code MediaTypeFileExtensionResolver}. 059 * @param strategies the strategies to use 060 */ 061 public ContentNegotiationManager(ContentNegotiationStrategy... strategies) { 062 this(Arrays.asList(strategies)); 063 } 064 065 /** 066 * A collection-based alternative to 067 * {@link #ContentNegotiationManager(ContentNegotiationStrategy...)}. 068 * @param strategies the strategies to use 069 * @since 3.2.2 070 */ 071 public ContentNegotiationManager(Collection<ContentNegotiationStrategy> strategies) { 072 Assert.notEmpty(strategies, "At least one ContentNegotiationStrategy is expected"); 073 this.strategies.addAll(strategies); 074 for (ContentNegotiationStrategy strategy : this.strategies) { 075 if (strategy instanceof MediaTypeFileExtensionResolver) { 076 this.resolvers.add((MediaTypeFileExtensionResolver) strategy); 077 } 078 } 079 } 080 081 /** 082 * Create a default instance with a {@link HeaderContentNegotiationStrategy}. 083 */ 084 public ContentNegotiationManager() { 085 this(new HeaderContentNegotiationStrategy()); 086 } 087 088 089 /** 090 * Return the configured content negotiation strategies. 091 * @since 3.2.16 092 */ 093 public List<ContentNegotiationStrategy> getStrategies() { 094 return this.strategies; 095 } 096 097 /** 098 * Find a {@code ContentNegotiationStrategy} of the given type. 099 * @param strategyType the strategy type 100 * @return the first matching strategy, or {@code null} if none 101 * @since 4.3 102 */ 103 @SuppressWarnings("unchecked") 104 public <T extends ContentNegotiationStrategy> T getStrategy(Class<T> strategyType) { 105 for (ContentNegotiationStrategy strategy : getStrategies()) { 106 if (strategyType.isInstance(strategy)) { 107 return (T) strategy; 108 } 109 } 110 return null; 111 } 112 113 /** 114 * Register more {@code MediaTypeFileExtensionResolver} instances in addition 115 * to those detected at construction. 116 * @param resolvers the resolvers to add 117 */ 118 public void addFileExtensionResolvers(MediaTypeFileExtensionResolver... resolvers) { 119 this.resolvers.addAll(Arrays.asList(resolvers)); 120 } 121 122 @Override 123 public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException { 124 for (ContentNegotiationStrategy strategy : this.strategies) { 125 List<MediaType> mediaTypes = strategy.resolveMediaTypes(request); 126 if (mediaTypes.isEmpty() || mediaTypes.equals(MEDIA_TYPE_ALL)) { 127 continue; 128 } 129 return mediaTypes; 130 } 131 return Collections.emptyList(); 132 } 133 134 @Override 135 public List<String> resolveFileExtensions(MediaType mediaType) { 136 Set<String> result = new LinkedHashSet<String>(); 137 for (MediaTypeFileExtensionResolver resolver : this.resolvers) { 138 result.addAll(resolver.resolveFileExtensions(mediaType)); 139 } 140 return new ArrayList<String>(result); 141 } 142 143 /** 144 * {@inheritDoc} 145 * <p>At startup this method returns extensions explicitly registered with 146 * either {@link PathExtensionContentNegotiationStrategy} or 147 * {@link ParameterContentNegotiationStrategy}. At runtime if there is a 148 * "path extension" strategy and its 149 * {@link PathExtensionContentNegotiationStrategy#setUseJaf(boolean) 150 * useJaf} property is set to "true", the list of extensions may 151 * increase as file extensions are resolved via JAF and cached. 152 */ 153 @Override 154 public List<String> getAllFileExtensions() { 155 Set<String> result = new LinkedHashSet<String>(); 156 for (MediaTypeFileExtensionResolver resolver : this.resolvers) { 157 result.addAll(resolver.getAllFileExtensions()); 158 } 159 return new ArrayList<String>(result); 160 } 161 162}