/* * Copyright 2016 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 org.togglz.spring.boot.autoconfigure; import com.github.heneke.thymeleaf.togglz.TogglzDialect; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.actuate.endpoint.Endpoint; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.*; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import org.togglz.console.TogglzConsoleServlet; import org.togglz.core.Feature; import org.togglz.core.activation.ActivationStrategyProvider; import org.togglz.core.activation.DefaultActivationStrategyProvider; import org.togglz.core.logging.Log; import org.togglz.core.logging.LogFactory; import org.togglz.core.manager.EmptyFeatureProvider; import org.togglz.core.manager.EnumBasedFeatureProvider; import org.togglz.core.manager.FeatureManager; import org.togglz.core.manager.FeatureManagerBuilder; import org.togglz.core.repository.StateRepository; import org.togglz.core.repository.cache.CachingStateRepository; import org.togglz.core.repository.composite.CompositeStateRepository; import org.togglz.core.repository.file.FileBasedStateRepository; import org.togglz.core.repository.mem.InMemoryStateRepository; import org.togglz.core.repository.property.PropertyBasedStateRepository; import org.togglz.core.repository.property.PropertySource; import org.togglz.core.spi.ActivationStrategy; import org.togglz.core.spi.FeatureProvider; import org.togglz.core.user.NoOpUserProvider; import org.togglz.core.user.UserProvider; import org.togglz.spring.listener.TogglzApplicationContextBinderApplicationListener; import org.togglz.spring.security.SpringSecurityUserProvider; import org.togglz.spring.web.FeatureInterceptor; import java.io.IOException; import java.util.List; import java.util.Map; import java.util.Properties; /** * {@link EnableAutoConfiguration Auto-configuration} for Togglz. * * @author Marcel Overdijk */ @Configuration @ConditionalOnProperty(prefix = "togglz", name = "enabled", matchIfMissing = true) @EnableConfigurationProperties(TogglzProperties.class) public class TogglzAutoConfiguration { private static final Log log = LogFactory.getLog(TogglzAutoConfiguration.class); @Bean public TogglzApplicationContextBinderApplicationListener togglzApplicationContextBinderApplicationListener() { return new TogglzApplicationContextBinderApplicationListener(); } @Configuration @ConditionalOnMissingBean(FeatureProvider.class) protected static class FeatureProviderConfiguration { @Autowired private TogglzProperties properties; @Bean public FeatureProvider featureProvider() { Class<? extends Feature>[] featureEnums = properties.getFeatureEnums(); if (featureEnums != null && featureEnums.length > 0) { return new EnumBasedFeatureProvider(featureEnums); } else { log.warn("Creating a dummy feature provider as neither a FeatureProvider bean was provided nor the 'togglz.feature-enums' property was set!"); return new EmptyFeatureProvider(); } } } @Configuration @ConditionalOnMissingBean(FeatureManager.class) protected static class FeatureManagerConfiguration { @Autowired private TogglzProperties properties; @Bean public FeatureManager featureManager(FeatureProvider featureProvider, List<StateRepository> stateRepositories, UserProvider userProvider, ActivationStrategyProvider activationStrategyProvider) { StateRepository stateRepository = null; if (stateRepositories.size() == 1) { stateRepository = stateRepositories.get(0); } else if (stateRepositories.size() > 1) { stateRepository = new CompositeStateRepository(stateRepositories.toArray(new StateRepository[stateRepositories.size()])); } // If caching is enabled wrap state repository in caching state repository. // Note that we explicitly check if the state repository is not already a caching state repository, // as the auto configuration of the state repository already creates a caching state repository if needed. // The below wrapping only occurs if the user provided the state repository manually and caching is enabled. if (properties.getCache().isEnabled() && !(stateRepository instanceof CachingStateRepository)) { stateRepository = new CachingStateRepository(stateRepository, properties.getCache().getTimeToLive(), properties.getCache().getTimeUnit()); } FeatureManagerBuilder featureManagerBuilder = new FeatureManagerBuilder(); String name = properties.getFeatureManagerName(); if (name != null && name.length() > 0) { featureManagerBuilder.name(name); } featureManagerBuilder .featureProvider(featureProvider) .stateRepository(stateRepository) .userProvider(userProvider) .activationStrategyProvider(activationStrategyProvider) .build(); return featureManagerBuilder.build(); } } @Configuration @ConditionalOnMissingBean(ActivationStrategyProvider.class) protected static class ActivationStrategyProviderConfiguration { @Autowired(required = false) private List<ActivationStrategy> activationStrategies; @Bean public ActivationStrategyProvider activationStrategyProvider() { DefaultActivationStrategyProvider provider = new DefaultActivationStrategyProvider(); if (activationStrategies != null && activationStrategies.size() > 0) { provider.addActivationStrategies(activationStrategies); } return provider; } } @Configuration @ConditionalOnMissingBean(StateRepository.class) protected static class StateRepositoryConfiguration { @Autowired private ResourceLoader resourceLoader = new DefaultResourceLoader(); @Autowired private TogglzProperties properties; @Bean public StateRepository stateRepository() throws IOException { StateRepository stateRepository; Map<String, String> features = properties.getFeatures(); String featuresFile = properties.getFeaturesFile(); if (featuresFile != null) { Resource resource = this.resourceLoader.getResource(featuresFile); Integer minCheckInterval = properties.getFeaturesFileMinCheckInterval(); if (minCheckInterval != null) { stateRepository = new FileBasedStateRepository(resource.getFile(), minCheckInterval); } else { stateRepository = new FileBasedStateRepository(resource.getFile()); } } else if (features != null && features.size() > 0) { Properties props = new Properties(); props.putAll(features); PropertySource propertySource = new PropertiesPropertySource(props); stateRepository = new PropertyBasedStateRepository(propertySource); } else { stateRepository = new InMemoryStateRepository(); } // If caching is enabled wrap state repository in caching state repository. if (properties.getCache().isEnabled()) { stateRepository = new CachingStateRepository(stateRepository, properties.getCache().getTimeToLive(), properties.getCache().getTimeUnit()); } return stateRepository; } } @Configuration @ConditionalOnMissingClass("org.springframework.security.config.annotation.web.configuration.EnableWebSecurity") @ConditionalOnMissingBean(UserProvider.class) protected static class UserProviderConfiguration { @Bean public UserProvider userProvider() { return new NoOpUserProvider(); } } @Configuration @ConditionalOnClass({EnableWebSecurity.class, AuthenticationEntryPoint.class, SpringSecurityUserProvider.class}) @ConditionalOnMissingBean(UserProvider.class) protected static class SpringSecurityUserProviderConfiguration { @Autowired private TogglzProperties properties; @Bean public UserProvider userProvider() { return new SpringSecurityUserProvider(properties.getConsole().getFeatureAdminAuthority()); } } @Configuration @ConditionalOnWebApplication @ConditionalOnClass(TogglzConsoleServlet.class) @Conditional(OnConsoleAndNotUseManagementPort.class) protected static class TogglzConsoleConfiguration { @Autowired private TogglzProperties properties; @Bean public ServletRegistrationBean togglzConsole() { String path = properties.getConsole().getPath(); String urlMapping = (path.endsWith("/") ? path + "*" : path + "/*"); TogglzConsoleServlet servlet = new TogglzConsoleServlet(); servlet.setSecured(properties.getConsole().isSecured()); return new ServletRegistrationBean(servlet, urlMapping); } } static class OnConsoleAndNotUseManagementPort extends AllNestedConditions { OnConsoleAndNotUseManagementPort() { super(ConfigurationPhase.REGISTER_BEAN); } @ConditionalOnProperty(prefix = "togglz.console", name = "enabled", matchIfMissing = true) static class OnConsole { } @ConditionalOnProperty(prefix = "togglz.console", name = "use-management-port", havingValue = "false") static class OnNotUseManagementPort { } } @Configuration @ConditionalOnWebApplication @ConditionalOnClass(HandlerInterceptorAdapter.class) @ConditionalOnProperty(prefix = "togglz.web", name = "registerFeatureInterceptor", havingValue = "true") protected static class TogglzFeatureInterceptorConfiguration extends WebMvcConfigurerAdapter { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new FeatureInterceptor()); } } @Configuration @ConditionalOnClass(Endpoint.class) @ConditionalOnMissingBean(TogglzEndpoint.class) @ConditionalOnProperty(prefix = "togglz.endpoint", name = "enabled", matchIfMissing = true) protected static class TogglzEndpointConfiguration { @Bean public TogglzEndpoint togglzEndpoint(FeatureManager featureManager) { return new TogglzEndpoint(featureManager); } } @Configuration @ConditionalOnClass(TogglzDialect.class) protected static class ThymeleafTogglzDialectConfiguration { @Bean @ConditionalOnMissingBean public TogglzDialect togglzDialect() { return new TogglzDialect(); } } }