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.view.groovy; 018 019import java.io.IOException; 020import java.net.URL; 021import java.net.URLClassLoader; 022import java.util.ArrayList; 023import java.util.List; 024import java.util.Locale; 025 026import groovy.text.markup.MarkupTemplateEngine; 027import groovy.text.markup.TemplateConfiguration; 028import groovy.text.markup.TemplateResolver; 029 030import org.springframework.beans.factory.InitializingBean; 031import org.springframework.context.ApplicationContext; 032import org.springframework.context.ApplicationContextAware; 033import org.springframework.context.i18n.LocaleContextHolder; 034import org.springframework.core.io.Resource; 035import org.springframework.util.StringUtils; 036 037/** 038 * An extension of Groovy's {@link groovy.text.markup.TemplateConfiguration} and 039 * an implementation of Spring MVC's {@link GroovyMarkupConfig} for creating 040 * a {@code MarkupTemplateEngine} for use in a web application. The most basic 041 * way to configure this class is to set the "resourceLoaderPath". For example: 042 * 043 * <pre class="code"> 044 * 045 * // Add the following to an @Configuration class 046 * 047 * @Bean 048 * public GroovyMarkupConfig groovyMarkupConfigurer() { 049 * GroovyMarkupConfigurer configurer = new GroovyMarkupConfigurer(); 050 * configurer.setResourceLoaderPath("classpath:/WEB-INF/groovymarkup/"); 051 * return configurer; 052 * } 053 * </pre> 054 * 055 * By default this bean will create a {@link MarkupTemplateEngine} with: 056 * <ul> 057 * <li>a parent ClassLoader for loading Groovy templates with their references 058 * <li>the default configuration in the base class {@link TemplateConfiguration} 059 * <li>a {@link groovy.text.markup.TemplateResolver} for resolving template files 060 * </ul> 061 * 062 * You can provide the {@link MarkupTemplateEngine} instance directly to this bean 063 * in which case all other properties will not be effectively ignored. 064 * 065 * <p>This bean must be included in the application context of any application 066 * using the Spring MVC {@link GroovyMarkupView} for rendering. It exists purely 067 * for the purpose of configuring Groovy's Markup templates. It is not meant to be 068 * referenced by application components directly. It implements GroovyMarkupConfig 069 * to be found by GroovyMarkupView without depending on a bean name. Each 070 * DispatcherServlet can define its own GroovyMarkupConfigurer if desired. 071 * 072 * <p>Note that resource caching is enabled by default in {@link MarkupTemplateEngine}. 073 * Use the {@link #setCacheTemplates(boolean)} to configure that as necessary. 074 075 * <p>Spring's Groovy Markup template support requires Groovy 2.3.1 or higher. 076 * 077 * @author Brian Clozel 078 * @author Rossen Stoyanchev 079 * @since 4.1 080 * @see GroovyMarkupView 081 * @see <a href="http://groovy-lang.org/templating.html#_the_markuptemplateengine"> 082 * Groovy Markup Template engine documentation</a> 083 */ 084public class GroovyMarkupConfigurer extends TemplateConfiguration 085 implements GroovyMarkupConfig, ApplicationContextAware, InitializingBean { 086 087 private String resourceLoaderPath = "classpath:"; 088 089 private MarkupTemplateEngine templateEngine; 090 091 private ApplicationContext applicationContext; 092 093 094 /** 095 * Set the Groovy Markup Template resource loader path(s) via a Spring resource 096 * location. Accepts multiple locations as a comma-separated list of paths. 097 * Standard URLs like "file:" and "classpath:" and pseudo URLs are supported 098 * as understood by Spring's {@link org.springframework.core.io.ResourceLoader}. 099 * Relative paths are allowed when running in an ApplicationContext. 100 * 101 */ 102 public void setResourceLoaderPath(String resourceLoaderPath) { 103 this.resourceLoaderPath = resourceLoaderPath; 104 } 105 106 public String getResourceLoaderPath() { 107 return this.resourceLoaderPath; 108 } 109 110 /** 111 * Set a pre-configured MarkupTemplateEngine to use for the Groovy Markup 112 * Template web configuration. 113 * <p>Note that this engine instance has to be manually configured, since all 114 * other bean properties of this configurer will be ignored. 115 */ 116 public void setTemplateEngine(MarkupTemplateEngine templateEngine) { 117 this.templateEngine = templateEngine; 118 } 119 120 public MarkupTemplateEngine getTemplateEngine() { 121 return this.templateEngine; 122 } 123 124 @Override 125 public void setApplicationContext(ApplicationContext applicationContext) { 126 this.applicationContext = applicationContext; 127 } 128 129 protected ApplicationContext getApplicationContext() { 130 return this.applicationContext; 131 } 132 133 /** 134 * This method should not be used, since the considered Locale for resolving 135 * templates is the Locale for the current HTTP request. 136 */ 137 @Override 138 public void setLocale(Locale locale) { 139 super.setLocale(locale); 140 } 141 142 143 @Override 144 public void afterPropertiesSet() throws Exception { 145 if (this.templateEngine == null) { 146 this.templateEngine = createTemplateEngine(); 147 } 148 } 149 150 protected MarkupTemplateEngine createTemplateEngine() throws IOException { 151 if (this.templateEngine == null) { 152 ClassLoader templateClassLoader = createTemplateClassLoader(); 153 this.templateEngine = new MarkupTemplateEngine(templateClassLoader, this, new LocaleTemplateResolver()); 154 } 155 return this.templateEngine; 156 } 157 158 /** 159 * Create a parent ClassLoader for Groovy to use as parent ClassLoader 160 * when loading and compiling templates. 161 */ 162 protected ClassLoader createTemplateClassLoader() throws IOException { 163 String[] paths = StringUtils.commaDelimitedListToStringArray(getResourceLoaderPath()); 164 List<URL> urls = new ArrayList<URL>(); 165 for (String path : paths) { 166 Resource[] resources = getApplicationContext().getResources(path); 167 if (resources.length > 0) { 168 for (Resource resource : resources) { 169 if (resource.exists()) { 170 urls.add(resource.getURL()); 171 } 172 } 173 } 174 } 175 ClassLoader classLoader = getApplicationContext().getClassLoader(); 176 return (urls.size() > 0 ? new URLClassLoader(urls.toArray(new URL[urls.size()]), classLoader) : classLoader); 177 } 178 179 /** 180 * Resolve a template from the given template path. 181 * <p>The default implementation uses the Locale associated with the current request, 182 * as obtained through {@link org.springframework.context.i18n.LocaleContextHolder LocaleContextHolder}, 183 * to find the template file. Effectively the locale configured at the engine level is ignored. 184 * @see LocaleContextHolder 185 * @see #setLocale 186 */ 187 protected URL resolveTemplate(ClassLoader classLoader, String templatePath) throws IOException { 188 MarkupTemplateEngine.TemplateResource resource = MarkupTemplateEngine.TemplateResource.parse(templatePath); 189 Locale locale = LocaleContextHolder.getLocale(); 190 URL url = classLoader.getResource(resource.withLocale(locale.toString().replace("-", "_")).toString()); 191 if (url == null) { 192 url = classLoader.getResource(resource.withLocale(locale.getLanguage()).toString()); 193 } 194 if (url == null) { 195 url = classLoader.getResource(resource.withLocale(null).toString()); 196 } 197 if (url == null) { 198 throw new IOException("Unable to load template:" + templatePath); 199 } 200 return url; 201 } 202 203 204 /** 205 * Custom {@link TemplateResolver template resolver} that simply delegates to 206 * {@link #resolveTemplate(ClassLoader, String)}.. 207 */ 208 private class LocaleTemplateResolver implements TemplateResolver { 209 210 private ClassLoader classLoader; 211 212 @Override 213 public void configure(ClassLoader templateClassLoader, TemplateConfiguration configuration) { 214 this.classLoader = templateClassLoader; 215 } 216 217 @Override 218 public URL resolveTemplate(String templatePath) throws IOException { 219 return GroovyMarkupConfigurer.this.resolveTemplate(this.classLoader, templatePath); 220 } 221 } 222 223}