001/* 002 * Copyright 2002-2020 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.servlet.mvc; 018 019import java.util.Map; 020import java.util.concurrent.ConcurrentHashMap; 021 022import javax.servlet.http.HttpServletRequest; 023 024import org.springframework.lang.Nullable; 025import org.springframework.util.StringUtils; 026import org.springframework.web.servlet.HandlerMapping; 027 028/** 029 * Simple {@code Controller} implementation that transforms the virtual 030 * path of a URL into a view name and returns that view. 031 * 032 * <p>Can optionally prepend a {@link #setPrefix prefix} and/or append a 033 * {@link #setSuffix suffix} to build the viewname from the URL filename. 034 * 035 * <p>Find some examples below: 036 * <ol> 037 * <li>{@code "/index" -> "index"}</li> 038 * <li>{@code "/index.html" -> "index"}</li> 039 * <li>{@code "/index.html"} + prefix {@code "pre_"} and suffix {@code "_suf" -> "pre_index_suf"}</li> 040 * <li>{@code "/products/view.html" -> "products/view"}</li> 041 * </ol> 042 * 043 * <p>Thanks to David Barri for suggesting prefix/suffix support! 044 * 045 * @author Alef Arendsen 046 * @author Juergen Hoeller 047 * @author Rob Harrop 048 * @see #setPrefix 049 * @see #setSuffix 050 */ 051public class UrlFilenameViewController extends AbstractUrlViewController { 052 053 private String prefix = ""; 054 055 private String suffix = ""; 056 057 /** Request URL path String to view name String. */ 058 private final Map<String, String> viewNameCache = new ConcurrentHashMap<>(256); 059 060 061 /** 062 * Set the prefix to prepend to the request URL filename 063 * to build a view name. 064 */ 065 public void setPrefix(@Nullable String prefix) { 066 this.prefix = (prefix != null ? prefix : ""); 067 } 068 069 /** 070 * Return the prefix to prepend to the request URL filename. 071 */ 072 protected String getPrefix() { 073 return this.prefix; 074 } 075 076 /** 077 * Set the suffix to append to the request URL filename 078 * to build a view name. 079 */ 080 public void setSuffix(@Nullable String suffix) { 081 this.suffix = (suffix != null ? suffix : ""); 082 } 083 084 /** 085 * Return the suffix to append to the request URL filename. 086 */ 087 protected String getSuffix() { 088 return this.suffix; 089 } 090 091 092 /** 093 * Returns view name based on the URL filename, 094 * with prefix/suffix applied when appropriate. 095 * @see #extractViewNameFromUrlPath 096 * @see #setPrefix 097 * @see #setSuffix 098 */ 099 @Override 100 protected String getViewNameForRequest(HttpServletRequest request) { 101 String uri = extractOperableUrl(request); 102 return getViewNameForUrlPath(uri); 103 } 104 105 /** 106 * Extract a URL path from the given request, 107 * suitable for view name extraction. 108 * @param request current HTTP request 109 * @return the URL to use for view name extraction 110 */ 111 protected String extractOperableUrl(HttpServletRequest request) { 112 String urlPath = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE); 113 if (!StringUtils.hasText(urlPath)) { 114 urlPath = getUrlPathHelper().getLookupPathForRequest(request, HandlerMapping.LOOKUP_PATH); 115 } 116 return urlPath; 117 } 118 119 /** 120 * Returns view name based on the URL filename, 121 * with prefix/suffix applied when appropriate. 122 * @param uri the request URI; for example {@code "/index.html"} 123 * @return the extracted URI filename; for example {@code "index"} 124 * @see #extractViewNameFromUrlPath 125 * @see #postProcessViewName 126 */ 127 protected String getViewNameForUrlPath(String uri) { 128 return this.viewNameCache.computeIfAbsent(uri, u -> postProcessViewName(extractViewNameFromUrlPath(u))); 129 } 130 131 /** 132 * Extract the URL filename from the given request URI. 133 * @param uri the request URI; for example {@code "/index.html"} 134 * @return the extracted URI filename; for example {@code "index"} 135 */ 136 protected String extractViewNameFromUrlPath(String uri) { 137 int start = (uri.charAt(0) == '/' ? 1 : 0); 138 int lastIndex = uri.lastIndexOf('.'); 139 int end = (lastIndex < 0 ? uri.length() : lastIndex); 140 return uri.substring(start, end); 141 } 142 143 /** 144 * Build the full view name based on the given view name 145 * as indicated by the URL path. 146 * <p>The default implementation simply applies prefix and suffix. 147 * This can be overridden, for example, to manipulate upper case 148 * / lower case, etc. 149 * @param viewName the original view name, as indicated by the URL path 150 * @return the full view name to use 151 * @see #getPrefix() 152 * @see #getSuffix() 153 */ 154 protected String postProcessViewName(String viewName) { 155 return getPrefix() + viewName + getSuffix(); 156 } 157 158}