package org.apereo.cas.services.web.config; import org.apereo.cas.configuration.CasConfigurationProperties; import org.apereo.cas.services.ServicesManager; import org.apereo.cas.services.web.RegisteredServiceThemeBasedViewResolver; import org.apereo.cas.services.web.ServiceThemeResolver; import org.apereo.cas.web.support.ArgumentExtractor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.ThemeResolver; import org.springframework.web.servlet.ViewResolver; import org.thymeleaf.dialect.IPostProcessorDialect; import org.thymeleaf.engine.AbstractTemplateHandler; import org.thymeleaf.model.ICloseElementTag; import org.thymeleaf.model.IOpenElementTag; import org.thymeleaf.model.IText; import org.thymeleaf.postprocessor.IPostProcessor; import org.thymeleaf.postprocessor.PostProcessor; import org.thymeleaf.spring4.SpringTemplateEngine; import org.thymeleaf.spring4.view.ThymeleafViewResolver; import org.thymeleaf.templatemode.TemplateMode; import java.util.Collections; import java.util.Map; import java.util.Set; /** * This is {@link CasThemesConfiguration}. * * @author Misagh Moayyed * @since 5.0.0 */ @Configuration("casThemesConfiguration") @EnableConfigurationProperties(CasConfigurationProperties.class) public class CasThemesConfiguration { @Autowired @Qualifier("servicesManager") private ServicesManager servicesManager; @Autowired private CasConfigurationProperties casProperties; @Autowired private ThymeleafProperties thymeleafProperties; @Autowired @Qualifier("thymeleafViewResolver") private ThymeleafViewResolver thymeleafViewResolver; @Autowired @Qualifier("argumentExtractor") private ArgumentExtractor argumentExtractors; @Autowired @Qualifier("serviceThemeResolverSupportedBrowsers") private Map serviceThemeResolverSupportedBrowsers; @Bean public ViewResolver registeredServiceViewResolver() { final RegisteredServiceThemeBasedViewResolver r = new RegisteredServiceThemeBasedViewResolver(servicesManager, argumentExtractors, thymeleafProperties.getPrefix(), thymeleafProperties.getSuffix()); r.setApplicationContext(this.thymeleafViewResolver.getApplicationContext()); r.setCache(this.thymeleafProperties.isCache()); if (!r.isCache()) { r.setCacheLimit(0); } r.setCacheUnresolved(this.thymeleafViewResolver.isCacheUnresolved()); r.setCharacterEncoding(this.thymeleafViewResolver.getCharacterEncoding()); r.setContentType(this.thymeleafViewResolver.getContentType()); r.setExcludedViewNames(this.thymeleafViewResolver.getExcludedViewNames()); r.setOrder(this.thymeleafViewResolver.getOrder()); r.setRedirectContextRelative(this.thymeleafViewResolver.isRedirectContextRelative()); r.setRedirectHttp10Compatible(this.thymeleafViewResolver.isRedirectHttp10Compatible()); r.setStaticVariables(this.thymeleafViewResolver.getStaticVariables()); final SpringTemplateEngine engine = SpringTemplateEngine.class.cast(this.thymeleafViewResolver.getTemplateEngine()); engine.addDialect(new IPostProcessorDialect() { @Override public int getDialectPostProcessorPrecedence() { return Integer.MAX_VALUE; } @Override public Set<IPostProcessor> getPostProcessors() { return Collections.singleton(new PostProcessor(TemplateMode.parse(thymeleafProperties.getMode()), CasThymeleafOutputTemplateHandler.class, Integer.MAX_VALUE)); } @Override public String getName() { return CasThymeleafOutputTemplateHandler.class.getSimpleName(); } }); r.setTemplateEngine(engine); r.setViewNames(this.thymeleafViewResolver.getViewNames()); return r; } @ConditionalOnMissingBean(name = "themeResolver") @Bean public ThemeResolver themeResolver() { final String defaultThemeName = casProperties.getTheme().getDefaultThemeName(); return new ServiceThemeResolver(defaultThemeName, servicesManager, serviceThemeResolverSupportedBrowsers); } /** * The Cas thymeleaf output template handler which attempts to compress the whitespace * produced by thymeleaf's conditional flags. */ public static class CasThymeleafOutputTemplateHandler extends AbstractTemplateHandler { private boolean writeWhitespace; public CasThymeleafOutputTemplateHandler() { } @Override public void handleText(final IText text) { final String contentString = text.getText(); if (!contentString.isEmpty() && contentString.trim().isEmpty()) { if (!writeWhitespace) { return; } writeWhitespace = false; } super.handleText(text); } @Override public void handleCloseElement(final ICloseElementTag tag) { super.handleCloseElement(tag); writeWhitespace = true; } @Override public void handleOpenElement(final IOpenElementTag openElementTag) { super.handleOpenElement(openElementTag); writeWhitespace = true; } } }