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}