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.servlet.mvc.method.annotation;
018
019import org.springframework.core.MethodParameter;
020import org.springframework.lang.Nullable;
021import org.springframework.util.PatternMatchUtils;
022import org.springframework.web.context.request.NativeWebRequest;
023import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
024import org.springframework.web.method.support.ModelAndViewContainer;
025import org.springframework.web.servlet.ModelAndView;
026import org.springframework.web.servlet.SmartView;
027import org.springframework.web.servlet.View;
028
029/**
030 * Handles return values of type {@link ModelAndView} copying view and model
031 * information to the {@link ModelAndViewContainer}.
032 *
033 * <p>If the return value is {@code null}, the
034 * {@link ModelAndViewContainer#setRequestHandled(boolean)} flag is set to
035 * {@code true} to indicate the request was handled directly.
036 *
037 * <p>A {@link ModelAndView} return type has a set purpose. Therefore this
038 * handler should be configured ahead of handlers that support any return
039 * value type annotated with {@code @ModelAttribute} or {@code @ResponseBody}
040 * to ensure they don't take over.
041 *
042 * @author Rossen Stoyanchev
043 * @since 3.1
044 */
045public class ModelAndViewMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
046
047        @Nullable
048        private String[] redirectPatterns;
049
050
051        /**
052         * Configure one more simple patterns (as described in {@link PatternMatchUtils#simpleMatch})
053         * to use in order to recognize custom redirect prefixes in addition to "redirect:".
054         * <p>Note that simply configuring this property will not make a custom redirect prefix work.
055         * There must be a custom {@link View} that recognizes the prefix as well.
056         * @since 4.1
057         */
058        public void setRedirectPatterns(@Nullable String... redirectPatterns) {
059                this.redirectPatterns = redirectPatterns;
060        }
061
062        /**
063         * Return the configured redirect patterns, if any.
064         * @since 4.1
065         */
066        @Nullable
067        public String[] getRedirectPatterns() {
068                return this.redirectPatterns;
069        }
070
071
072        @Override
073        public boolean supportsReturnType(MethodParameter returnType) {
074                return ModelAndView.class.isAssignableFrom(returnType.getParameterType());
075        }
076
077        @Override
078        public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
079                        ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
080
081                if (returnValue == null) {
082                        mavContainer.setRequestHandled(true);
083                        return;
084                }
085
086                ModelAndView mav = (ModelAndView) returnValue;
087                if (mav.isReference()) {
088                        String viewName = mav.getViewName();
089                        mavContainer.setViewName(viewName);
090                        if (viewName != null && isRedirectViewName(viewName)) {
091                                mavContainer.setRedirectModelScenario(true);
092                        }
093                }
094                else {
095                        View view = mav.getView();
096                        mavContainer.setView(view);
097                        if (view instanceof SmartView && ((SmartView) view).isRedirectView()) {
098                                mavContainer.setRedirectModelScenario(true);
099                        }
100                }
101                mavContainer.setStatus(mav.getStatus());
102                mavContainer.addAllAttributes(mav.getModel());
103        }
104
105        /**
106         * Whether the given view name is a redirect view reference.
107         * The default implementation checks the configured redirect patterns and
108         * also if the view name starts with the "redirect:" prefix.
109         * @param viewName the view name to check, never {@code null}
110         * @return "true" if the given view name is recognized as a redirect view
111         * reference; "false" otherwise.
112         */
113        protected boolean isRedirectViewName(String viewName) {
114                return (PatternMatchUtils.simpleMatch(this.redirectPatterns, viewName) || viewName.startsWith("redirect:"));
115        }
116
117}