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}