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.accept; 018 019import java.util.Collections; 020import java.util.List; 021import java.util.Map; 022import java.util.Optional; 023 024import org.apache.commons.logging.Log; 025import org.apache.commons.logging.LogFactory; 026 027import org.springframework.http.MediaType; 028import org.springframework.http.MediaTypeFactory; 029import org.springframework.lang.Nullable; 030import org.springframework.util.StringUtils; 031import org.springframework.web.HttpMediaTypeNotAcceptableException; 032import org.springframework.web.context.request.NativeWebRequest; 033 034/** 035 * Base class for {@code ContentNegotiationStrategy} implementations with the 036 * steps to resolve a request to media types. 037 * 038 * <p>First a key (e.g. "json", "pdf") must be extracted from the request (e.g. 039 * file extension, query param). The key must then be resolved to media type(s) 040 * through the base class {@link MappingMediaTypeFileExtensionResolver} which 041 * stores such mappings. 042 * 043 * <p>The method {@link #handleNoMatch} allow sub-classes to plug in additional 044 * ways of looking up media types (e.g. through the Java Activation framework, 045 * or {@link javax.servlet.ServletContext#getMimeType}. Media types resolved 046 * via base classes are then added to the base class 047 * {@link MappingMediaTypeFileExtensionResolver}, i.e. cached for new lookups. 048 * 049 * @author Rossen Stoyanchev 050 * @since 3.2 051 */ 052public abstract class AbstractMappingContentNegotiationStrategy extends MappingMediaTypeFileExtensionResolver 053 implements ContentNegotiationStrategy { 054 055 protected final Log logger = LogFactory.getLog(getClass()); 056 057 private boolean useRegisteredExtensionsOnly = false; 058 059 private boolean ignoreUnknownExtensions = false; 060 061 062 /** 063 * Create an instance with the given map of file extensions and media types. 064 */ 065 public AbstractMappingContentNegotiationStrategy(@Nullable Map<String, MediaType> mediaTypes) { 066 super(mediaTypes); 067 } 068 069 070 /** 071 * Whether to only use the registered mappings to look up file extensions, 072 * or also to use dynamic resolution (e.g. via {@link MediaTypeFactory}. 073 * <p>By default this is set to {@code false}. 074 */ 075 public void setUseRegisteredExtensionsOnly(boolean useRegisteredExtensionsOnly) { 076 this.useRegisteredExtensionsOnly = useRegisteredExtensionsOnly; 077 } 078 079 public boolean isUseRegisteredExtensionsOnly() { 080 return this.useRegisteredExtensionsOnly; 081 } 082 083 /** 084 * Whether to ignore requests with unknown file extension. Setting this to 085 * {@code false} results in {@code HttpMediaTypeNotAcceptableException}. 086 * <p>By default this is set to {@literal false} but is overridden in 087 * {@link PathExtensionContentNegotiationStrategy} to {@literal true}. 088 */ 089 public void setIgnoreUnknownExtensions(boolean ignoreUnknownExtensions) { 090 this.ignoreUnknownExtensions = ignoreUnknownExtensions; 091 } 092 093 public boolean isIgnoreUnknownExtensions() { 094 return this.ignoreUnknownExtensions; 095 } 096 097 098 @Override 099 public List<MediaType> resolveMediaTypes(NativeWebRequest webRequest) 100 throws HttpMediaTypeNotAcceptableException { 101 102 return resolveMediaTypeKey(webRequest, getMediaTypeKey(webRequest)); 103 } 104 105 /** 106 * An alternative to {@link #resolveMediaTypes(NativeWebRequest)} that accepts 107 * an already extracted key. 108 * @since 3.2.16 109 */ 110 public List<MediaType> resolveMediaTypeKey(NativeWebRequest webRequest, @Nullable String key) 111 throws HttpMediaTypeNotAcceptableException { 112 113 if (StringUtils.hasText(key)) { 114 MediaType mediaType = lookupMediaType(key); 115 if (mediaType != null) { 116 handleMatch(key, mediaType); 117 return Collections.singletonList(mediaType); 118 } 119 mediaType = handleNoMatch(webRequest, key); 120 if (mediaType != null) { 121 addMapping(key, mediaType); 122 return Collections.singletonList(mediaType); 123 } 124 } 125 return MEDIA_TYPE_ALL_LIST; 126 } 127 128 129 /** 130 * Extract a key from the request to use to look up media types. 131 * @return the lookup key, or {@code null} if none 132 */ 133 @Nullable 134 protected abstract String getMediaTypeKey(NativeWebRequest request); 135 136 /** 137 * Override to provide handling when a key is successfully resolved via 138 * {@link #lookupMediaType}. 139 */ 140 protected void handleMatch(String key, MediaType mediaType) { 141 } 142 143 /** 144 * Override to provide handling when a key is not resolved via. 145 * {@link #lookupMediaType}. Sub-classes can take further steps to 146 * determine the media type(s). If a MediaType is returned from 147 * this method it will be added to the cache in the base class. 148 */ 149 @Nullable 150 protected MediaType handleNoMatch(NativeWebRequest request, String key) 151 throws HttpMediaTypeNotAcceptableException { 152 153 if (!isUseRegisteredExtensionsOnly()) { 154 Optional<MediaType> mediaType = MediaTypeFactory.getMediaType("file." + key); 155 if (mediaType.isPresent()) { 156 return mediaType.get(); 157 } 158 } 159 if (isIgnoreUnknownExtensions()) { 160 return null; 161 } 162 throw new HttpMediaTypeNotAcceptableException(getAllMediaTypes()); 163 } 164 165}