All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.frameworkset.web.servlet.view.ContentNegotiatingViewResolver Maven / Gradle / Ivy

Go to download

bboss is a j2ee framework include aop/ioc,mvc,persistent,taglib,rpc,event ,bean-xml serializable and so on.http://www.bbossgroups.com

The newest version!
/*
 *  Copyright 2008-2010 biaoping.yin
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.frameworkset.web.servlet.view;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import javax.activation.FileTypeMap;
import javax.activation.MimetypesFileTypeMap;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.frameworkset.http.MediaType;
import org.frameworkset.util.Assert;
import org.frameworkset.util.ClassUtils;
import org.frameworkset.util.CollectionUtils;
import org.frameworkset.util.io.ClassPathResource;
import org.frameworkset.util.io.Resource;
import org.frameworkset.web.servlet.context.RequestAttributes;
import org.frameworkset.web.servlet.context.RequestContextHolder;
import org.frameworkset.web.servlet.context.ServletRequestAttributes;
import org.frameworkset.web.servlet.support.WebApplicationObjectSupport;
import org.frameworkset.web.util.UrlPathHelper;
import org.frameworkset.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.frameworkset.util.StringUtil;

/**
 * Implementation of {@link ViewResolver} that resolves a view based on the request file name or {@code Accept} header.
 *
 * 

The {@code ContentNegotiatingViewResolver} does not resolve views itself, but delegates to other {@link * ViewResolver}s. By default, these other view resolvers are picked up automatically from the application context, * though they can also be set explicitly by using the {@link #setViewResolvers(List) viewResolvers} property. * Note that in order for this view resolver to work properly, the {@link #setOrder(int) order} * property needs to be set to a higher precedence than the others (the default is {@link Ordered#HIGHEST_PRECEDENCE}.) * *

This view resolver uses the requested {@linkplain MediaType media type} to select a suitable {@link View} for a * request. This media type is determined by using the following criteria: *

    *
  1. If the requested path has a file extension and if the {@link #setFavorPathExtension(boolean)} property is * {@code true}, the {@link #setMediaTypes(Map) mediaTypes} property is inspected for a matching media type.
  2. *
  3. If the request contains a parameter defining the extension and if the {@link #setFavorParameter(boolean)} * property is true, the {@link #setMediaTypes(Map) mediaTypes} property is inspected for a matching * media type. The default name of the parameter is format and it can be configured using the * {@link #setParameterName(String) parameterName} property.
  4. *
  5. If there is no match in the {@link #setMediaTypes(Map) mediaTypes} property and if the Java Activation * Framework (JAF) is both {@linkplain #setUseJaf(boolean) enabled} and present on the class path, * {@link FileTypeMap#getContentType(String)} is used instead.
  6. *
  7. If the previous steps did not result in a media type, and * {@link #setIgnoreAcceptHeader(boolean) ignoreAcceptHeader} is {@code false}, the request {@code Accept} header is * used.
  8. *
* * Once the requested media type has been determined, this resolver queries each delegate view resolver for a * {@link View} and determines if the requested media type is {@linkplain MediaType#includes(MediaType) compatible} * with the view's {@linkplain View#getContentType() content type}). The most compatible view is returned. * *

Additionally, this view resolver exposes the {@link #setDefaultViews(List) defaultViews} property, allowing you to * override the views provided by the view resolvers. Note that these default views are offered as candicates, and * still need have the content type requested (via file extension, parameter, or {@code Accept} header, described above). * You can also set the {@linkplain #setDefaultContentType(MediaType) default content type} directly, which will be * returned when the other mechanisms ({@code Accept} header, file extension or parameter) do not result in a match. * *

For example, if the request path is {@code /view.html}, this view resolver will look for a view that has the * {@code text/html} content type (based on the {@code html} file extension). A request for {@code /view} with a {@code * text/html} request {@code Accept} header has the same result. * * @author Arjen Poutsma * @author Juergen Hoeller * @since 3.0 * @see ViewResolver * @see InternalResourceViewResolver * @see BeanNameViewResolver */ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport implements ViewResolver { private final static Logger logger = LoggerFactory.getLogger(ContentNegotiatingViewResolver.class); private static final String ACCEPT_HEADER = "Accept"; private static final boolean jafPresent = ClassUtils.isPresent("javax.activation.FileTypeMap", ContentNegotiatingViewResolver.class.getClassLoader()); private static final UrlPathHelper urlPathHelper = new UrlPathHelper(); private boolean favorPathExtension = true; private boolean favorParameter = false; private String parameterName = "format"; private boolean useNotAcceptableStatusCode = false; private boolean ignoreAcceptHeader = false; private boolean useJaf = true; private ConcurrentMap mediaTypes = new ConcurrentHashMap(); private List defaultViews; private MediaType defaultContentType; private List viewResolvers; /** * Indicates whether the extension of the request path should be used to determine the requested media type, * in favor of looking at the {@code Accept} header. The default value is {@code true}. *

For instance, when this flag is true (the default), a request for {@code /hotels.pdf} * will result in an {@code AbstractPdfView} being resolved, while the {@code Accept} header can be the * browser-defined {@code text/html,application/xhtml+xml}. */ public void setFavorPathExtension(boolean favorPathExtension) { this.favorPathExtension = favorPathExtension; } /** * Indicates whether a request parameter should be used to determine the requested media type, * in favor of looking at the {@code Accept} header. The default value is {@code false}. *

For instance, when this flag is true, a request for {@code /hotels?format=pdf} will result * in an {@code AbstractPdfView} being resolved, while the {@code Accept} header can be the browser-defined * {@code text/html,application/xhtml+xml}. */ public void setFavorParameter(boolean favorParameter) { this.favorParameter = favorParameter; } /** * Sets the parameter name that can be used to determine the requested media type if the {@link * #setFavorParameter(boolean)} property is {@code true}. The default parameter name is {@code format}. */ public void setParameterName(String parameterName) { this.parameterName = parameterName; } /** * Indicates whether the HTTP {@code Accept} header should be ignored. Default is {@code false}. * If set to {@code true}, this view resolver will only refer to the file extension and/or paramter, * as indicated by the {@link #setFavorPathExtension(boolean) favorPathExtension} and * {@link #setFavorParameter(boolean) favorParameter} properties. */ public void setIgnoreAcceptHeader(boolean ignoreAcceptHeader) { this.ignoreAcceptHeader = ignoreAcceptHeader; } /** * Indicates whether a {@link HttpServletResponse#SC_NOT_ACCEPTABLE 406 Not Acceptable} status code should be * returned if no suitable view can be found. * *

Default is {@code false}, meaning that this view resolver returns {@code null} for * {@link #resolveViewName(String, Locale)} when an acceptable view cannot be found. This will allow for view * resolvers chaining. When this property is set to {@code true}, * {@link #resolveViewName(String, Locale)} will respond with a view that sets the response status to * {@code 406 Not Acceptable} instead. */ public void setUseNotAcceptableStatusCode(boolean useNotAcceptableStatusCode) { this.useNotAcceptableStatusCode = useNotAcceptableStatusCode; } /** * Sets the mapping from file extensions to media types. *

When this mapping is not set or when an extension is not present, this view resolver * will fall back to using a {@link FileTypeMap} when the Java Action Framework is available. */ public void setMediaTypes(Map mediaTypes) { Assert.notNull(mediaTypes, "'mediaTypes' must not be null"); for (Map.Entry entry : mediaTypes.entrySet()) { String extension = entry.getKey().toLowerCase(Locale.ENGLISH); MediaType mediaType = MediaType.parseMediaType(entry.getValue()); this.mediaTypes.put(extension, mediaType); } } /** * Sets the default views to use when a more specific view can not be obtained * from the {@link ViewResolver} chain. */ public void setDefaultViews(List defaultViews) { this.defaultViews = defaultViews; } /** * Sets the default content type. *

This content type will be used when file extension, parameter, nor {@code Accept} * header define a content-type, either through being disabled or empty. */ public void setDefaultContentType(MediaType defaultContentType) { this.defaultContentType = defaultContentType; } /** * Indicates whether to use the Java Activation Framework to map from file extensions to media types. *

Default is {@code true}, i.e. the Java Activation Framework is used (if available). */ public void setUseJaf(boolean useJaf) { this.useJaf = useJaf; } /** * Sets the view resolvers to be wrapped by this view resolver. *

If this property is not set, view resolvers will be detected automatically. */ public void setViewResolvers(List viewResolvers) { this.viewResolvers = viewResolvers; } @Override protected void initServletContext(ServletContext servletContext) { if (this.viewResolvers == null) { // Map matchingBeans = // BeanFactoryUtils.beansOfTypeIncludingAncestors(getApplicationContext(), ViewResolver.class); // this.viewResolvers = new ArrayList(matchingBeans.size()); // for (ViewResolver viewResolver : matchingBeans.values()) { // if (this != viewResolver) { // this.viewResolvers.add(viewResolver); // } // } } if (this.viewResolvers.isEmpty()) { logger.warn("Did not find any ViewResolvers to delegate to; please configure them using the " + "'viewResolvers' property on the ContentNegotiatingViewResolver"); } // OrderComparator.sort(this.viewResolvers); } /** * Determines the list of {@link MediaType} for the given {@link HttpServletRequest}. *

The default implementation invokes {@link #getMediaTypeFromFilename(String)} if {@linkplain * #setFavorPathExtension(boolean) favorPathExtension} property is true. If the property is * false, or when a media type cannot be determined from the request path, this method will * inspect the {@code Accept} header of the request. *

This method can be overriden to provide a different algorithm. * @param request the current servlet request * @return the list of media types requested, if any */ protected List getMediaTypes(HttpServletRequest request) { if (this.favorPathExtension) { String requestUri = urlPathHelper.getRequestUri(request); String filename = WebUtils.extractFullFilenameFromUrlPath(requestUri); MediaType mediaType = getMediaTypeFromFilename(filename); if (mediaType != null) { if (logger.isDebugEnabled()) { logger.debug("Requested media type is '" + mediaType + "' (based on filename '" + filename + "')"); } return Collections.singletonList(mediaType); } } if (this.favorParameter) { if (request.getParameter(this.parameterName) != null) { String parameterValue = request.getParameter(this.parameterName); MediaType mediaType = getMediaTypeFromParameter(parameterValue); if (mediaType != null) { if (logger.isDebugEnabled()) { logger.debug("Requested media type is '" + mediaType + "' (based on parameter '" + this.parameterName + "'='" + parameterValue + "')"); } return Collections.singletonList(mediaType); } } } if (!this.ignoreAcceptHeader) { String acceptHeader = request.getHeader(ACCEPT_HEADER); if (StringUtil.hasText(acceptHeader)) { List mediaTypes = MediaType.parseMediaTypes(acceptHeader); MediaType.sortByQualityValue(mediaTypes); if (logger.isDebugEnabled()) { logger.debug("Requested media types are " + mediaTypes + " (based on Accept header)"); } return mediaTypes; } } if (this.defaultContentType != null) { if (logger.isDebugEnabled()) { logger.debug("Requested media types is " + this.defaultContentType + " (based on defaultContentType property)"); } return Collections.singletonList(this.defaultContentType); } else { return Collections.emptyList(); } } /** * Determines the {@link MediaType} for the given filename. *

The default implementation will check the {@linkplain #setMediaTypes(Map) media types} * property first for a defined mapping. If not present, and if the Java Activation Framework * can be found on the classpath, it will call {@link FileTypeMap#getContentType(String)} *

This method can be overriden to provide a different algorithm. * @param filename the current request file name (i.e. {@code hotels.html}) * @return the media type, if any */ protected MediaType getMediaTypeFromFilename(String filename) { String extension = StringUtil.getFilenameExtension(filename); if (!StringUtil.hasText(extension)) { return null; } extension = extension.toLowerCase(Locale.ENGLISH); MediaType mediaType = this.mediaTypes.get(extension); if (mediaType == null && this.useJaf && jafPresent) { mediaType = ActivationMediaTypeFactory.getMediaType(filename); if (mediaType != null) { this.mediaTypes.putIfAbsent(extension, mediaType); } } return mediaType; } /** * Determines the {@link MediaType} for the given parameter value. *

The default implementation will check the {@linkplain #setMediaTypes(Map) media types} * property for a defined mapping. *

This method can be overriden to provide a different algorithm. * @param parameterValue the parameter value (i.e. {@code pdf}). * @return the media type, if any */ protected MediaType getMediaTypeFromParameter(String parameterValue) { return this.mediaTypes.get(parameterValue.toLowerCase(Locale.ENGLISH)); } public View resolveViewName(String viewName, Locale locale) throws Exception { RequestAttributes attrs = RequestContextHolder.getRequestAttributes(); Assert.isInstanceOf(ServletRequestAttributes.class, attrs); List requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest()); List candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes); View bestView = getBestView(candidateViews, requestedMediaTypes); if (bestView != null) { return bestView; } else { if (this.useNotAcceptableStatusCode) { if (logger.isDebugEnabled()) { logger.debug("No acceptable view found; returning 406 (Not Acceptable) status code"); } return NOT_ACCEPTABLE_VIEW; } else { if (logger.isDebugEnabled()) { logger.debug("No acceptable view found; returning null"); } return null; } } } private List getCandidateViews(String viewName, Locale locale, List requestedMediaTypes) throws Exception { List candidateViews = new ArrayList(); for (ViewResolver viewResolver : this.viewResolvers) { View view = viewResolver.resolveViewName(viewName, locale); if (view != null) { candidateViews.add(view); } for (MediaType requestedMediaType : requestedMediaTypes) { List extensions = getExtensionsForMediaType(requestedMediaType); for (String extension : extensions) { String viewNameWithExtension = viewName + "." + extension; view = viewResolver.resolveViewName(viewNameWithExtension, locale); if (view != null) { candidateViews.add(view); } } } } if (!CollectionUtils.isEmpty(this.defaultViews)) { candidateViews.addAll(this.defaultViews); } return candidateViews; } private List getExtensionsForMediaType(MediaType requestedMediaType) { List result = new ArrayList(); for (Entry entry : mediaTypes.entrySet()) { if (requestedMediaType.includes(entry.getValue())) { result.add(entry.getKey()); } } return result; } private View getBestView(List candidateViews, List requestedMediaTypes) { MediaType bestRequestedMediaType = null; View bestView = null; for (MediaType requestedMediaType : requestedMediaTypes) { for (View candidateView : candidateViews) { if (StringUtil.hasText(candidateView.getContentType())) { MediaType candidateContentType = MediaType.parseMediaType(candidateView.getContentType()); if (requestedMediaType.includes(candidateContentType)) { bestRequestedMediaType = requestedMediaType; bestView = candidateView; break; } } } if (bestView != null) { if (logger.isDebugEnabled()) { logger.debug( "Returning [" + bestView + "] based on requested media type '" + bestRequestedMediaType + "'"); } break; } } return bestView; } /** * Inner class to avoid hard-coded JAF dependency. */ private static class ActivationMediaTypeFactory { private static final FileTypeMap fileTypeMap; static { fileTypeMap = loadFileTypeMapFromContextSupportModule(); } private static FileTypeMap loadFileTypeMapFromContextSupportModule() { // see if we can find the extended mime.types from the context-support module Resource mappingLocation = new ClassPathResource("org/frameworkset/web/servlet/mime.types"); if (mappingLocation.exists()) { // if (logger.isTraceEnabled()) { logger.info("Loading Java Activation Framework FileTypeMap from " + mappingLocation); } InputStream inputStream = null; try { inputStream = mappingLocation.getInputStream(); return new MimetypesFileTypeMap(inputStream); } catch (IOException ex) { // ignore } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException ex) { // ignore } } } } // if (logger.isTraceEnabled()) { logger.info("Loading default Java Activation Framework FileTypeMap"); } return FileTypeMap.getDefaultFileTypeMap(); } public static MediaType getMediaType(String fileName) { String mediaType = fileTypeMap.getContentType(fileName); return StringUtil.hasText(mediaType) ? MediaType.parseMediaType(mediaType) : null; } } private static final View NOT_ACCEPTABLE_VIEW = new View() { public String getContentType() { return null; } public void render(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception { response.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE); } }; }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy