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.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 VelocityConfigurerBeanDefinitionParser
062 * @see GroovyMarkupConfigurerBeanDefinitionParser
063 * @see ScriptTemplateConfigurerBeanDefinitionParser
064 */
065public class ViewResolversBeanDefinitionParser implements BeanDefinitionParser {
066
067        public static final String VIEW_RESOLVER_BEAN_NAME = "mvcViewResolver";
068
069
070        @SuppressWarnings("deprecation")
071        public BeanDefinition parse(Element element, ParserContext context) {
072                Object source = context.extractSource(element);
073                context.pushContainingComponent(new CompositeComponentDefinition(element.getTagName(), source));
074
075                ManagedList<Object> resolvers = new ManagedList<Object>(4);
076                resolvers.setSource(context.extractSource(element));
077                String[] names = new String[] {"jsp", "tiles", "bean-name", "freemarker", "velocity", "groovy", "script-template", "bean", "ref"};
078
079                for (Element resolverElement : DomUtils.getChildElementsByTagName(element, names)) {
080                        String name = resolverElement.getLocalName();
081                        if ("bean".equals(name) || "ref".equals(name)) {
082                                resolvers.add(context.getDelegate().parsePropertySubElement(resolverElement, null));
083                                continue;
084                        }
085                        RootBeanDefinition resolverBeanDef;
086                        if ("jsp".equals(name)) {
087                                resolverBeanDef = new RootBeanDefinition(InternalResourceViewResolver.class);
088                                resolverBeanDef.getPropertyValues().add("prefix", "/WEB-INF/");
089                                resolverBeanDef.getPropertyValues().add("suffix", ".jsp");
090                                addUrlBasedViewResolverProperties(resolverElement, resolverBeanDef);
091                        }
092                        else if ("tiles".equals(name)) {
093                                resolverBeanDef = new RootBeanDefinition(TilesViewResolver.class);
094                                addUrlBasedViewResolverProperties(resolverElement, resolverBeanDef);
095                        }
096                        else if ("freemarker".equals(name)) {
097                                resolverBeanDef = new RootBeanDefinition(FreeMarkerViewResolver.class);
098                                resolverBeanDef.getPropertyValues().add("suffix", ".ftl");
099                                addUrlBasedViewResolverProperties(resolverElement, resolverBeanDef);
100                        }
101                        else if ("velocity".equals(name)) {
102                                resolverBeanDef = new RootBeanDefinition(org.springframework.web.servlet.view.velocity.VelocityViewResolver.class);
103                                resolverBeanDef.getPropertyValues().add("suffix", ".vm");
104                                addUrlBasedViewResolverProperties(resolverElement, resolverBeanDef);
105                        }
106                        else if ("groovy".equals(name)) {
107                                resolverBeanDef = new RootBeanDefinition(GroovyMarkupViewResolver.class);
108                                resolverBeanDef.getPropertyValues().add("suffix", ".tpl");
109                                addUrlBasedViewResolverProperties(resolverElement, resolverBeanDef);
110                        }
111                        else if ("script-template".equals(name)) {
112                                resolverBeanDef = new RootBeanDefinition(ScriptTemplateViewResolver.class);
113                                addUrlBasedViewResolverProperties(resolverElement, resolverBeanDef);
114                        }
115                        else if ("bean-name".equals(name)) {
116                                resolverBeanDef = new RootBeanDefinition(BeanNameViewResolver.class);
117                        }
118                        else {
119                                // Should never happen
120                                throw new IllegalStateException("Unexpected element name: " + name);
121                        }
122                        resolverBeanDef.setSource(source);
123                        resolverBeanDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
124                        resolvers.add(resolverBeanDef);
125                }
126
127                String beanName = VIEW_RESOLVER_BEAN_NAME;
128                RootBeanDefinition compositeResolverBeanDef = new RootBeanDefinition(ViewResolverComposite.class);
129                compositeResolverBeanDef.setSource(source);
130                compositeResolverBeanDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
131
132                names = new String[] {"content-negotiation"};
133                List<Element> contentNegotiationElements = DomUtils.getChildElementsByTagName(element, names);
134                if (contentNegotiationElements.isEmpty()) {
135                        compositeResolverBeanDef.getPropertyValues().add("viewResolvers", resolvers);
136                }
137                else if (contentNegotiationElements.size() == 1) {
138                        BeanDefinition beanDef = createContentNegotiatingViewResolver(contentNegotiationElements.get(0), context);
139                        beanDef.getPropertyValues().add("viewResolvers", resolvers);
140                        ManagedList<Object> list = new ManagedList<Object>(1);
141                        list.add(beanDef);
142                        compositeResolverBeanDef.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
143                        compositeResolverBeanDef.getPropertyValues().add("viewResolvers", list);
144                }
145                else {
146                        throw new IllegalArgumentException("Only one <content-negotiation> element is allowed.");
147                }
148
149                if (element.hasAttribute("order")) {
150                        compositeResolverBeanDef.getPropertyValues().add("order", element.getAttribute("order"));
151                }
152
153                context.getReaderContext().getRegistry().registerBeanDefinition(beanName, compositeResolverBeanDef);
154                context.registerComponent(new BeanComponentDefinition(compositeResolverBeanDef, beanName));
155                context.popAndRegisterContainingComponent();
156                return null;
157        }
158
159        private void addUrlBasedViewResolverProperties(Element element, RootBeanDefinition beanDefinition) {
160                if (element.hasAttribute("prefix")) {
161                        beanDefinition.getPropertyValues().add("prefix", element.getAttribute("prefix"));
162                }
163                if (element.hasAttribute("suffix")) {
164                        beanDefinition.getPropertyValues().add("suffix", element.getAttribute("suffix"));
165                }
166                if (element.hasAttribute("cache-views")) {
167                        beanDefinition.getPropertyValues().add("cache", element.getAttribute("cache-views"));
168                }
169                if (element.hasAttribute("view-class")) {
170                        beanDefinition.getPropertyValues().add("viewClass", element.getAttribute("view-class"));
171                }
172                if (element.hasAttribute("view-names")) {
173                        beanDefinition.getPropertyValues().add("viewNames", element.getAttribute("view-names"));
174                }
175        }
176
177        private BeanDefinition createContentNegotiatingViewResolver(Element resolverElement, ParserContext context) {
178                RootBeanDefinition beanDef = new RootBeanDefinition(ContentNegotiatingViewResolver.class);
179                beanDef.setSource(context.extractSource(resolverElement));
180                beanDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
181                MutablePropertyValues values = beanDef.getPropertyValues();
182
183                List<Element> elements = DomUtils.getChildElementsByTagName(resolverElement, "default-views");
184                if (!elements.isEmpty()) {
185                        ManagedList<Object> list = new ManagedList<Object>();
186                        for (Element element : DomUtils.getChildElementsByTagName(elements.get(0), "bean", "ref")) {
187                                list.add(context.getDelegate().parsePropertySubElement(element, null));
188                        }
189                        values.add("defaultViews", list);
190                }
191                if (resolverElement.hasAttribute("use-not-acceptable")) {
192                        values.add("useNotAcceptableStatusCode", resolverElement.getAttribute("use-not-acceptable"));
193                }
194                Object manager = MvcNamespaceUtils.getContentNegotiationManager(context);
195                if (manager != null) {
196                        values.add("contentNegotiationManager", manager);
197                }
198                return beanDef;
199        }
200
201}