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.reactive.config;
018
019import java.util.ArrayList;
020import java.util.Arrays;
021import java.util.List;
022
023import org.springframework.beans.factory.BeanFactoryUtils;
024import org.springframework.beans.factory.BeanInitializationException;
025import org.springframework.context.ApplicationContext;
026import org.springframework.core.Ordered;
027import org.springframework.lang.Nullable;
028import org.springframework.util.ObjectUtils;
029import org.springframework.web.reactive.result.view.HttpMessageWriterView;
030import org.springframework.web.reactive.result.view.UrlBasedViewResolver;
031import org.springframework.web.reactive.result.view.View;
032import org.springframework.web.reactive.result.view.ViewResolver;
033import org.springframework.web.reactive.result.view.freemarker.FreeMarkerConfigurer;
034import org.springframework.web.reactive.result.view.freemarker.FreeMarkerViewResolver;
035import org.springframework.web.reactive.result.view.script.ScriptTemplateConfigurer;
036import org.springframework.web.reactive.result.view.script.ScriptTemplateViewResolver;
037
038/**
039 * Assist with the configuration of a chain of {@link ViewResolver}'s supporting
040 * different template mechanisms.
041 *
042 * <p>In addition, you can also configure {@link #defaultViews(View...)
043 * defaultViews} for rendering according to the requested content type, e.g.
044 * JSON, XML, etc.
045 *
046 * @author Rossen Stoyanchev
047 * @author Sebastien Deleuze
048 * @since 5.0
049 */
050public class ViewResolverRegistry {
051
052        @Nullable
053        private final ApplicationContext applicationContext;
054
055        private final List<ViewResolver> viewResolvers = new ArrayList<>(4);
056
057        private final List<View> defaultViews = new ArrayList<>(4);
058
059        @Nullable
060        private Integer order;
061
062
063        public ViewResolverRegistry(@Nullable ApplicationContext applicationContext) {
064                this.applicationContext = applicationContext;
065        }
066
067
068        /**
069         * Register a {@code FreeMarkerViewResolver} with a ".ftl" suffix.
070         * <p><strong>Note</strong> that you must also configure FreeMarker by
071         * adding a {@link FreeMarkerConfigurer} bean.
072         */
073        public UrlBasedViewResolverRegistration freeMarker() {
074                if (!checkBeanOfType(FreeMarkerConfigurer.class)) {
075                        throw new BeanInitializationException("In addition to a FreeMarker view resolver " +
076                                        "there must also be a single FreeMarkerConfig bean in this web application context " +
077                                        "(or its parent): FreeMarkerConfigurer is the usual implementation. " +
078                                        "This bean may be given any name.");
079                }
080                FreeMarkerRegistration registration = new FreeMarkerRegistration();
081                UrlBasedViewResolver resolver = registration.getViewResolver();
082                if (this.applicationContext != null) {
083                        resolver.setApplicationContext(this.applicationContext);
084                }
085                this.viewResolvers.add(resolver);
086                return registration;
087        }
088
089        /**
090         * Register a script template view resolver with an empty default view name prefix and suffix.
091         * <p><strong>Note</strong> that you must also configure script templating by
092         * adding a {@link ScriptTemplateConfigurer} bean.
093         * @since 5.0.4
094         */
095        public UrlBasedViewResolverRegistration scriptTemplate() {
096                if (!checkBeanOfType(ScriptTemplateConfigurer.class)) {
097                        throw new BeanInitializationException("In addition to a script template view resolver " +
098                                        "there must also be a single ScriptTemplateConfig bean in this web application context " +
099                                        "(or its parent): ScriptTemplateConfigurer is the usual implementation. " +
100                                        "This bean may be given any name.");
101                }
102                ScriptRegistration registration = new ScriptRegistration();
103                UrlBasedViewResolver resolver = registration.getViewResolver();
104                if (this.applicationContext != null) {
105                        resolver.setApplicationContext(this.applicationContext);
106                }
107                this.viewResolvers.add(resolver);
108                return registration;
109        }
110
111        /**
112         * Register a {@link ViewResolver} bean instance. This may be useful to
113         * configure a 3rd party resolver implementation or as an alternative to
114         * other registration methods in this class when they don't expose some
115         * more advanced property that needs to be set.
116         */
117        public void viewResolver(ViewResolver viewResolver) {
118                this.viewResolvers.add(viewResolver);
119        }
120
121        /**
122         * Set default views associated with any view name and selected based on the
123         * best match for the requested content type.
124         * <p>Use {@link HttpMessageWriterView
125         * HttpMessageWriterView} to adapt and use any existing
126         * {@code HttpMessageWriter} (e.g. JSON, XML) as a {@code View}.
127         */
128        public void defaultViews(View... defaultViews) {
129                this.defaultViews.addAll(Arrays.asList(defaultViews));
130        }
131
132        /**
133         * Whether any view resolvers have been registered.
134         */
135        public boolean hasRegistrations() {
136                return (!this.viewResolvers.isEmpty());
137        }
138
139        /**
140         * Set the order for the
141         * {@link org.springframework.web.reactive.result.view.ViewResolutionResultHandler
142         * ViewResolutionResultHandler}.
143         * <p>By default this property is not set, which means the result handler is
144         * ordered at {@link Ordered#LOWEST_PRECEDENCE}.
145         */
146        public void order(int order) {
147                this.order = order;
148        }
149
150
151        private boolean checkBeanOfType(Class<?> beanType) {
152                return (this.applicationContext == null ||
153                                !ObjectUtils.isEmpty(BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
154                                                this.applicationContext, beanType, false, false)));
155        }
156
157        protected int getOrder() {
158                return (this.order != null ? this.order : Ordered.LOWEST_PRECEDENCE);
159        }
160
161        protected List<ViewResolver> getViewResolvers() {
162                return this.viewResolvers;
163        }
164
165        protected List<View> getDefaultViews() {
166                return this.defaultViews;
167        }
168
169
170        private static class FreeMarkerRegistration extends UrlBasedViewResolverRegistration {
171
172                public FreeMarkerRegistration() {
173                        super(new FreeMarkerViewResolver());
174                        getViewResolver().setSuffix(".ftl");
175                }
176        }
177
178        private static class ScriptRegistration extends UrlBasedViewResolverRegistration {
179
180                public ScriptRegistration() {
181                        super(new ScriptTemplateViewResolver());
182                        getViewResolver();
183                }
184        }
185
186}