001/*
002 * Copyright 2002-2018 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.config;
018
019import java.util.List;
020
021import org.w3c.dom.Element;
022
023import org.springframework.beans.MutablePropertyValues;
024import org.springframework.beans.factory.config.BeanDefinition;
025import org.springframework.beans.factory.parsing.BeanComponentDefinition;
026import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
027import org.springframework.beans.factory.support.ManagedList;
028import org.springframework.beans.factory.support.RootBeanDefinition;
029import org.springframework.beans.factory.xml.BeanDefinitionParser;
030import org.springframework.beans.factory.xml.ParserContext;
031import org.springframework.core.Ordered;
032import org.springframework.util.xml.DomUtils;
033import org.springframework.web.servlet.view.BeanNameViewResolver;
034import org.springframework.web.servlet.view.ContentNegotiatingViewResolver;
035import org.springframework.web.servlet.view.InternalResourceViewResolver;
036import org.springframework.web.servlet.view.ViewResolverComposite;
037import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver;
038import org.springframework.web.servlet.view.groovy.GroovyMarkupViewResolver;
039import org.springframework.web.servlet.view.script.ScriptTemplateViewResolver;
040import org.springframework.web.servlet.view.tiles3.TilesViewResolver;
041
042/**
043 * Parse the {@code view-resolvers} MVC namespace element and register
044 * {@link org.springframework.web.servlet.ViewResolver} bean definitions.
045 *
046 * <p>All registered resolvers are wrapped in a single (composite) ViewResolver
047 * with its order property set to 0 so that other external resolvers may be ordered
048 * before or after it.
049 *
050 * <p>When content negotiation is enabled the order property is set to highest priority
051 * instead with the ContentNegotiatingViewResolver encapsulating all other registered
052 * view resolver instances. That way the resolvers registered through the MVC namespace
053 * form self-encapsulated resolver chain.
054 *
055 * @author Sivaprasad Valluru
056 * @author Sebastien Deleuze
057 * @author Rossen Stoyanchev
058 * @since 4.1
059 * @see TilesConfigurerBeanDefinitionParser
060 * @see FreeMarkerConfigurerBeanDefinitionParser
061 * @see GroovyMarkupConfigurerBeanDefinitionParser
062 * @see ScriptTemplateConfigurerBeanDefinitionParser
063 */
064public class ViewResolversBeanDefinitionParser implements BeanDefinitionParser {
065
066        /**
067         * The bean name used for the {@code ViewResolverComposite}.
068         */
069        public static final String VIEW_RESOLVER_BEAN_NAME = "mvcViewResolver";
070
071
072        @Override
073        public BeanDefinition parse(Element element, ParserContext context) {
074                Object source = context.extractSource(element);
075                context.pushContainingComponent(new CompositeComponentDefinition(element.getTagName(), source));
076
077                ManagedList<Object> resolvers = new ManagedList<>(4);
078                resolvers.setSource(context.extractSource(element));
079                String[] names = new String[] {
080                                "jsp", "tiles", "bean-name", "freemarker", "groovy", "script-template", "bean", "ref"};
081
082                for (Element resolverElement : DomUtils.getChildElementsByTagName(element, names)) {
083                        String name = resolverElement.getLocalName();
084                        if ("bean".equals(name) || "ref".equals(name)) {
085                                resolvers.add(context.getDelegate().parsePropertySubElement(resolverElement, null));
086                                continue;
087                        }
088                        RootBeanDefinition resolverBeanDef;
089                        if ("jsp".equals(name)) {
090                                resolverBeanDef = new RootBeanDefinition(InternalResourceViewResolver.class);
091                                resolverBeanDef.getPropertyValues().add("prefix", "/WEB-INF/");
092                                resolverBeanDef.getPropertyValues().add("suffix", ".jsp");
093                                addUrlBasedViewResolverProperties(resolverElement, resolverBeanDef);
094                        }
095                        else if ("tiles".equals(name)) {
096                                resolverBeanDef = new RootBeanDefinition(TilesViewResolver.class);
097                                addUrlBasedViewResolverProperties(resolverElement, resolverBeanDef);
098                        }
099                        else if ("freemarker".equals(name)) {
100                                resolverBeanDef = new RootBeanDefinition(FreeMarkerViewResolver.class);
101                                resolverBeanDef.getPropertyValues().add("suffix", ".ftl");
102                                addUrlBasedViewResolverProperties(resolverElement, resolverBeanDef);
103                        }
104                        else if ("groovy".equals(name)) {
105                                resolverBeanDef = new RootBeanDefinition(GroovyMarkupViewResolver.class);
106                                resolverBeanDef.getPropertyValues().add("suffix", ".tpl");
107                                addUrlBasedViewResolverProperties(resolverElement, resolverBeanDef);
108                        }
109                        else if ("script-template".equals(name)) {
110                                resolverBeanDef = new RootBeanDefinition(ScriptTemplateViewResolver.class);
111                                addUrlBasedViewResolverProperties(resolverElement, resolverBeanDef);
112                        }
113                        else if ("bean-name".equals(name)) {
114                                resolverBeanDef = new RootBeanDefinition(BeanNameViewResolver.class);
115                        }
116                        else {
117                                // Should never happen
118                                throw new IllegalStateException("Unexpected element name: " + name);
119                        }
120                        resolverBeanDef.setSource(source);
121                        resolverBeanDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
122                        resolvers.add(resolverBeanDef);
123                }
124
125                String beanName = VIEW_RESOLVER_BEAN_NAME;
126                RootBeanDefinition compositeResolverBeanDef = new RootBeanDefinition(ViewResolverComposite.class);
127                compositeResolverBeanDef.setSource(source);
128                compositeResolverBeanDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
129
130                names = new String[] {"content-negotiation"};
131                List<Element> contentNegotiationElements = DomUtils.getChildElementsByTagName(element, names);
132                if (contentNegotiationElements.isEmpty()) {
133                        compositeResolverBeanDef.getPropertyValues().add("viewResolvers", resolvers);
134                }
135                else if (contentNegotiationElements.size() == 1) {
136                        BeanDefinition beanDef = createContentNegotiatingViewResolver(contentNegotiationElements.get(0), context);
137                        beanDef.getPropertyValues().add("viewResolvers", resolvers);
138                        ManagedList<Object> list = new ManagedList<>(1);
139                        list.add(beanDef);
140                        compositeResolverBeanDef.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
141                        compositeResolverBeanDef.getPropertyValues().add("viewResolvers", list);
142                }
143                else {
144                        throw new IllegalArgumentException("Only one <content-negotiation> element is allowed.");
145                }
146
147                if (element.hasAttribute("order")) {
148                        compositeResolverBeanDef.getPropertyValues().add("order", element.getAttribute("order"));
149                }
150
151                context.getReaderContext().getRegistry().registerBeanDefinition(beanName, compositeResolverBeanDef);
152                context.registerComponent(new BeanComponentDefinition(compositeResolverBeanDef, beanName));
153                context.popAndRegisterContainingComponent();
154                return null;
155        }
156
157        private void addUrlBasedViewResolverProperties(Element element, RootBeanDefinition beanDefinition) {
158                if (element.hasAttribute("prefix")) {
159                        beanDefinition.getPropertyValues().add("prefix", element.getAttribute("prefix"));
160                }
161                if (element.hasAttribute("suffix")) {
162                        beanDefinition.getPropertyValues().add("suffix", element.getAttribute("suffix"));
163                }
164                if (element.hasAttribute("cache-views")) {
165                        beanDefinition.getPropertyValues().add("cache", element.getAttribute("cache-views"));
166                }
167                if (element.hasAttribute("view-class")) {
168                        beanDefinition.getPropertyValues().add("viewClass", element.getAttribute("view-class"));
169                }
170                if (element.hasAttribute("view-names")) {
171                        beanDefinition.getPropertyValues().add("viewNames", element.getAttribute("view-names"));
172                }
173        }
174
175        private BeanDefinition createContentNegotiatingViewResolver(Element resolverElement, ParserContext context) {
176                RootBeanDefinition beanDef = new RootBeanDefinition(ContentNegotiatingViewResolver.class);
177                beanDef.setSource(context.extractSource(resolverElement));
178                beanDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
179                MutablePropertyValues values = beanDef.getPropertyValues();
180
181                List<Element> elements = DomUtils.getChildElementsByTagName(resolverElement, "default-views");
182                if (!elements.isEmpty()) {
183                        ManagedList<Object> list = new ManagedList<>();
184                        for (Element element : DomUtils.getChildElementsByTagName(elements.get(0), "bean", "ref")) {
185                                list.add(context.getDelegate().parsePropertySubElement(element, null));
186                        }
187                        values.add("defaultViews", list);
188                }
189                if (resolverElement.hasAttribute("use-not-acceptable")) {
190                        values.add("useNotAcceptableStatusCode", resolverElement.getAttribute("use-not-acceptable"));
191                }
192                Object manager = MvcNamespaceUtils.getContentNegotiationManager(context);
193                if (manager != null) {
194                        values.add("contentNegotiationManager", manager);
195                }
196                return beanDef;
197        }
198
199}