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}