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