/* * Copyright 2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package grails.gsp.boot; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Properties; import org.codehaus.groovy.grails.commons.GrailsApplication; import org.codehaus.groovy.grails.commons.StandaloneGrailsApplication; import org.codehaus.groovy.grails.plugins.codecs.StandaloneCodecLookup; import org.codehaus.groovy.grails.support.encoding.CodecLookup; import org.codehaus.groovy.grails.web.pages.GroovyPagesTemplateEngine; import org.codehaus.groovy.grails.web.pages.GroovyPagesTemplateRenderer; import org.codehaus.groovy.grails.web.pages.StandaloneTagLibraryLookup; import org.codehaus.groovy.grails.web.pages.discovery.CachingGrailsConventionGroovyPageLocator; import org.codehaus.groovy.grails.web.pages.discovery.GrailsConventionGroovyPageLocator; import org.codehaus.groovy.grails.web.pages.discovery.GroovyPageLocator; import org.codehaus.groovy.grails.web.pages.ext.jsp.TagLibraryResolverImpl; import org.codehaus.groovy.grails.web.servlet.view.GrailsLayoutViewResolver; import org.codehaus.groovy.grails.web.servlet.view.GroovyPageViewResolver; import org.codehaus.groovy.grails.web.sitemesh.GroovyPageLayoutFinder; import org.springframework.beans.factory.annotation.Autowire; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.beans.factory.support.ManagedList; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration; import org.springframework.context.EnvironmentAware; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.EnumerablePropertySource; import org.springframework.core.env.Environment; import org.springframework.core.env.PropertiesPropertySource; import org.springframework.core.env.PropertySource; import org.springframework.core.type.AnnotationMetadata; import org.springframework.util.StringUtils; import org.springframework.web.servlet.ViewResolver; @Configuration @AutoConfigureAfter(WebMvcAutoConfiguration.class) public class GspAutoConfiguration { protected static abstract class AbstractGspConfig { @Value("${spring.gsp.reloadingEnabled:true}") boolean gspReloadingEnabled; @Value("${spring.gsp.view.cacheTimeout:1000}") long viewCacheTimeout; } @Configuration @Import({TagLibraryLookupRegistrar.class, RemoveDefaultViewResolverRegistrar.class}) protected static class GspTemplateEngineAutoConfiguration extends AbstractGspConfig { private static final String LOCAL_DIRECTORY_TEMPLATE_ROOT="./src/main/resources/templates"; private static final String CLASSPATH_TEMPLATE_ROOT="classpath:/templates"; @Value("${spring.gsp.templateRoots:}") String[] templateRoots; @Value("${spring.gsp.locator.cacheTimeout:5000}") long locatorCacheTimeout; @Value("${spring.gsp.layout.caching:true}") boolean gspLayoutCaching; @Value("${spring.gsp.layout.default:main}") String defaultLayoutName; @Bean(autowire=Autowire.BY_NAME) @ConditionalOnMissingBean(name="groovyPagesTemplateEngine") GroovyPagesTemplateEngine groovyPagesTemplateEngine() { GroovyPagesTemplateEngine templateEngine = new GroovyPagesTemplateEngine(); templateEngine.setReloadEnabled(gspReloadingEnabled); return templateEngine; } @Bean(autowire=Autowire.BY_NAME) @ConditionalOnMissingBean(name="groovyPageLocator") GroovyPageLocator groovyPageLocator() { final List<String> templateRootsCleaned=resolveTemplateRoots(); CachingGrailsConventionGroovyPageLocator pageLocator = new CachingGrailsConventionGroovyPageLocator() { protected List<String> resolveSearchPaths(String uri) { List<String> paths=new ArrayList<String>(templateRootsCleaned.size()); for(String rootPath : templateRootsCleaned) { paths.add(rootPath + cleanUri(uri)); } return paths; } protected String cleanUri(String uri) { uri = StringUtils.cleanPath(uri); if(!uri.startsWith("/")) { uri = "/" + uri; } return uri; } }; pageLocator.setReloadEnabled(gspReloadingEnabled); pageLocator.setCacheTimeout(gspReloadingEnabled ? locatorCacheTimeout : -1); return pageLocator; } protected List<String> resolveTemplateRoots() { if (templateRoots.length > 0) { List<String> rootPaths = new ArrayList<String>(templateRoots.length); for (String rootPath : templateRoots) { rootPath = rootPath.trim(); // remove trailing slash since uri will always be prefixed with a slash if(rootPath.endsWith("/")) { rootPath = rootPath.substring(0, rootPath.length()-1); } if(!StringUtils.isEmpty(rootPath)) { rootPaths.add(rootPath); } } return rootPaths; } else { if (gspReloadingEnabled) { File templateRootDirectory = new File(LOCAL_DIRECTORY_TEMPLATE_ROOT); if (templateRootDirectory.isDirectory()) { return Collections.singletonList("file:" + LOCAL_DIRECTORY_TEMPLATE_ROOT); } } return Collections.singletonList(CLASSPATH_TEMPLATE_ROOT); } } @Bean @ConditionalOnMissingBean(name = "groovyPageLayoutFinder") public GroovyPageLayoutFinder groovyPageLayoutFinder() { GroovyPageLayoutFinder groovyPageLayoutFinder = new GroovyPageLayoutFinder(); groovyPageLayoutFinder.setGspReloadEnabled(gspReloadingEnabled); groovyPageLayoutFinder.setCacheEnabled(gspLayoutCaching); groovyPageLayoutFinder.setEnableNonGspViews(false); groovyPageLayoutFinder.setDefaultDecoratorName(defaultLayoutName); return groovyPageLayoutFinder; } @Bean(autowire=Autowire.BY_NAME) @ConditionalOnMissingBean(name = "groovyPagesTemplateRenderer") GroovyPagesTemplateRenderer groovyPagesTemplateRenderer() { GroovyPagesTemplateRenderer groovyPagesTemplateRenderer = new GroovyPagesTemplateRenderer(); groovyPagesTemplateRenderer.setCacheEnabled(!gspReloadingEnabled); return groovyPagesTemplateRenderer; } } @Configuration protected static class GspViewResolverConfiguration extends AbstractGspConfig { @Autowired GroovyPagesTemplateEngine groovyPagesTemplateEngine; @Autowired GrailsConventionGroovyPageLocator groovyPageLocator; @Autowired GroovyPageLayoutFinder groovyPageLayoutFinder; @Bean @ConditionalOnMissingBean(name = "gspViewResolver") public GrailsLayoutViewResolver gspViewResolver() { return new GrailsLayoutViewResolver(innerGspViewResolver(), groovyPageLayoutFinder); } ViewResolver innerGspViewResolver() { GroovyPageViewResolver innerGspViewResolver = new GroovyPageViewResolver(groovyPagesTemplateEngine, groovyPageLocator); innerGspViewResolver.setAllowGrailsViewCaching(!gspReloadingEnabled || viewCacheTimeout != 0); innerGspViewResolver.setCacheTimeout(gspReloadingEnabled ? viewCacheTimeout : -1); return innerGspViewResolver; } } @Configuration protected static class CodecLookupConfiguration { @Bean @ConditionalOnMissingBean(name = "codecLookup") public CodecLookup codecLookup() { return new StandaloneCodecLookup(); } } @Configuration protected static class StandaloneGrailsApplicationConfiguration { @Bean @ConditionalOnMissingBean(name = "grailsApplication") public GrailsApplication grailsApplication() { return new SpringBootGrailsApplication(); } } /** * Makes Spring Boot application properties available in the GrailsApplication instance's flatConfig * */ public static class SpringBootGrailsApplication extends StandaloneGrailsApplication implements EnvironmentAware { private Environment environment; @SuppressWarnings({ "rawtypes", "unchecked" }) @Override public void updateFlatConfig() { super.updateFlatConfig(); if(this.environment instanceof ConfigurableEnvironment) { ConfigurableEnvironment configurableEnv = ((ConfigurableEnvironment)environment); for(PropertySource<?> propertySource : configurableEnv.getPropertySources()) { if(propertySource instanceof EnumerablePropertySource) { EnumerablePropertySource<?> enumerablePropertySource = (EnumerablePropertySource)propertySource; for(String propertyName : enumerablePropertySource.getPropertyNames()) { flatConfig.put(propertyName, enumerablePropertySource.getProperty(propertyName)); } } } } } @Override public void setEnvironment(Environment environment) { this.environment = environment; updateFlatConfig(); } } protected static class TagLibraryLookupRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { if(!registry.containsBeanDefinition("gspTagLibraryLookup")) { GenericBeanDefinition beanDefinition = createBeanDefinition(StandaloneTagLibraryLookup.class); ManagedList<BeanDefinition> list = new ManagedList<BeanDefinition>(); registerTagLibs(list); beanDefinition.getPropertyValues().addPropertyValue("tagLibInstances", list); registry.registerBeanDefinition("gspTagLibraryLookup", beanDefinition); registry.registerAlias("gspTagLibraryLookup", "tagLibraryLookup"); } } protected void registerTagLibs(ManagedList<BeanDefinition> list) { for(Class<?> taglibClazz : StandaloneTagLibraryLookup.DEFAULT_TAGLIB_CLASSES) { list.add(createBeanDefinition(taglibClazz)); } } protected GenericBeanDefinition createBeanDefinition(Class<?> beanClass) { GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(beanClass); beanDefinition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_NAME); return beanDefinition; } } /** * {@link WebMvcAutoConfiguration} adds defaultViewResolver and viewResolver beans. * * This ImportBeanDefinitionRegistrar removes the defaultViewResolver and replaces * the viewResolver bean with GSP view resolver by default. * * The behavior of this class can be controlled with spring.gsp.removeDefaultViewResolver and * spring.gsp.replaceViewResolverBean configuration properties. * */ protected static class RemoveDefaultViewResolverRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware { boolean removeDefaultViewResolverBean; boolean replaceViewResolverBean; @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { if(removeDefaultViewResolverBean) { if(registry.containsBeanDefinition("defaultViewResolver")) { registry.removeBeanDefinition("defaultViewResolver"); } } if(replaceViewResolverBean) { if(registry.containsBeanDefinition("viewResolver")) { registry.removeBeanDefinition("viewResolver"); } registry.registerAlias("gspViewResolver", "viewResolver"); } } @Override public void setEnvironment(Environment environment) { removeDefaultViewResolverBean = environment.getProperty("spring.gsp.removeDefaultViewResolverBean", Boolean.class, true); replaceViewResolverBean = environment.getProperty("spring.gsp.replaceViewResolverBean", Boolean.class, true); } } @ConditionalOnClass({javax.servlet.jsp.tagext.JspTag.class, TagLibraryResolverImpl.class}) @Configuration protected static class GspJspIntegrationConfiguration implements EnvironmentAware { @Bean(autowire = Autowire.BY_NAME) public TagLibraryResolverImpl jspTagLibraryResolver() { return new TagLibraryResolverImpl(); } @Override public void setEnvironment(Environment environment) { if(environment instanceof ConfigurableEnvironment) { ConfigurableEnvironment configEnv = (ConfigurableEnvironment) environment; Properties defaultProperties = createDefaultProperties(); configEnv.getPropertySources().addLast(new PropertiesPropertySource(GspJspIntegrationConfiguration.class.getName(), defaultProperties)); } } protected Properties createDefaultProperties() { Properties defaultProperties = new Properties(); // scan for spring JSP taglib tld files by default, also scan for defaultProperties.put("spring.gsp.tldScanPattern","classpath*:/META-INF/spring*.tld,classpath*:/META-INF/fmt.tld,classpath*:/META-INF/c.tld,classpath*:/META-INF/c-1_0-rt.tld"); return defaultProperties; } } }