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}