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.view;
018
019import java.util.Locale;
020
021import org.springframework.beans.BeansException;
022import org.springframework.beans.factory.BeanFactory;
023import org.springframework.beans.factory.DisposableBean;
024import org.springframework.beans.factory.InitializingBean;
025import org.springframework.beans.factory.NoSuchBeanDefinitionException;
026import org.springframework.beans.factory.xml.ResourceEntityResolver;
027import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
028import org.springframework.context.ApplicationContext;
029import org.springframework.context.ConfigurableApplicationContext;
030import org.springframework.core.Ordered;
031import org.springframework.core.io.Resource;
032import org.springframework.lang.Nullable;
033import org.springframework.web.context.support.GenericWebApplicationContext;
034import org.springframework.web.servlet.View;
035
036/**
037 * A {@link org.springframework.web.servlet.ViewResolver} implementation that uses
038 * bean definitions in a dedicated XML file for view definitions, specified by
039 * resource location. The file will typically be located in the WEB-INF directory;
040 * the default is "/WEB-INF/views.xml".
041 *
042 * <p>This {@code ViewResolver} does not support internationalization at the level
043 * of its definition resources. Consider {@link ResourceBundleViewResolver} if you
044 * need to apply different view resources per locale.
045 *
046 * <p>Note: This {@code ViewResolver} implements the {@link Ordered} interface
047 * in order to allow for flexible participation in {@code ViewResolver} chaining.
048 * For example, some special views could be defined via this {@code ViewResolver}
049 * (giving it 0 as "order" value), while all remaining views could be resolved by
050 * a {@link UrlBasedViewResolver}.
051 *
052 * @author Juergen Hoeller
053 * @since 18.06.2003
054 * @see org.springframework.context.ApplicationContext#getResource
055 * @see ResourceBundleViewResolver
056 * @see UrlBasedViewResolver
057 */
058public class XmlViewResolver extends AbstractCachingViewResolver
059                implements Ordered, InitializingBean, DisposableBean {
060
061        /** Default if no other location is supplied. */
062        public static final String DEFAULT_LOCATION = "/WEB-INF/views.xml";
063
064
065        @Nullable
066        private Resource location;
067
068        @Nullable
069        private ConfigurableApplicationContext cachedFactory;
070
071        private int order = Ordered.LOWEST_PRECEDENCE;  // default: same as non-Ordered
072
073
074        /**
075         * Set the location of the XML file that defines the view beans.
076         * <p>The default is "/WEB-INF/views.xml".
077         * @param location the location of the XML file.
078         */
079        public void setLocation(Resource location) {
080                this.location = location;
081        }
082
083        /**
084         * Specify the order value for this ViewResolver bean.
085         * <p>The default value is {@code Ordered.LOWEST_PRECEDENCE}, meaning non-ordered.
086         * @see org.springframework.core.Ordered#getOrder()
087         */
088        public void setOrder(int order) {
089                this.order = order;
090        }
091
092        @Override
093        public int getOrder() {
094                return this.order;
095        }
096
097        /**
098         * Pre-initialize the factory from the XML file.
099         * Only effective if caching is enabled.
100         */
101        @Override
102        public void afterPropertiesSet() throws BeansException {
103                if (isCache()) {
104                        initFactory();
105                }
106        }
107
108
109        /**
110         * This implementation returns just the view name,
111         * as XmlViewResolver doesn't support localized resolution.
112         */
113        @Override
114        protected Object getCacheKey(String viewName, Locale locale) {
115                return viewName;
116        }
117
118        @Override
119        protected View loadView(String viewName, Locale locale) throws BeansException {
120                BeanFactory factory = initFactory();
121                try {
122                        return factory.getBean(viewName, View.class);
123                }
124                catch (NoSuchBeanDefinitionException ex) {
125                        // Allow for ViewResolver chaining...
126                        return null;
127                }
128        }
129
130        /**
131         * Initialize the view bean factory from the XML file.
132         * Synchronized because of access by parallel threads.
133         * @throws BeansException in case of initialization errors
134         */
135        protected synchronized BeanFactory initFactory() throws BeansException {
136                if (this.cachedFactory != null) {
137                        return this.cachedFactory;
138                }
139
140                ApplicationContext applicationContext = obtainApplicationContext();
141
142                Resource actualLocation = this.location;
143                if (actualLocation == null) {
144                        actualLocation = applicationContext.getResource(DEFAULT_LOCATION);
145                }
146
147                // Create child ApplicationContext for views.
148                GenericWebApplicationContext factory = new GenericWebApplicationContext();
149                factory.setParent(applicationContext);
150                factory.setServletContext(getServletContext());
151
152                // Load XML resource with context-aware entity resolver.
153                XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
154                reader.setEnvironment(applicationContext.getEnvironment());
155                reader.setEntityResolver(new ResourceEntityResolver(applicationContext));
156                reader.loadBeanDefinitions(actualLocation);
157
158                factory.refresh();
159
160                if (isCache()) {
161                        this.cachedFactory = factory;
162                }
163                return factory;
164        }
165
166
167        /**
168         * Close the view bean factory on context shutdown.
169         */
170        @Override
171        public void destroy() throws BeansException {
172                if (this.cachedFactory != null) {
173                        this.cachedFactory.close();
174                }
175        }
176
177}