diff --git a/spring-boot-modules/pom.xml b/spring-boot-modules/pom.xml index f257ee306e..b11c2c9afb 100644 --- a/spring-boot-modules/pom.xml +++ b/spring-boot-modules/pom.xml @@ -55,6 +55,7 @@ spring-boot-mvc-2 spring-boot-mvc-3 spring-boot-mvc-4 + spring-boot-mvc-5 spring-boot-mvc-birt spring-boot-mvc-jersey spring-boot-nashorn diff --git a/spring-boot-modules/spring-boot-mvc-5/README.md b/spring-boot-modules/spring-boot-mvc-5/README.md new file mode 100644 index 0000000000..7cc3f8a1fe --- /dev/null +++ b/spring-boot-modules/spring-boot-mvc-5/README.md @@ -0,0 +1,5 @@ +## Spring Boot MVC + +This module contains articles about Spring Web MVC in Spring Boot projects. + +### Relevant Articles: \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-mvc-5/extra.properties b/spring-boot-modules/spring-boot-mvc-5/extra.properties new file mode 100644 index 0000000000..97837513b1 --- /dev/null +++ b/spring-boot-modules/spring-boot-mvc-5/extra.properties @@ -0,0 +1,2 @@ +endpoint.foo=true +endpoint.regex=.* \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-mvc-5/pom.xml b/spring-boot-modules/spring-boot-mvc-5/pom.xml new file mode 100644 index 0000000000..61b27ea8f1 --- /dev/null +++ b/spring-boot-modules/spring-boot-mvc-5/pom.xml @@ -0,0 +1,73 @@ + + + 4.0.0 + spring-boot-mvc-5 + spring-boot-mvc-5 + jar + Module For Spring Boot MVC Web + + + com.baeldung.spring-boot-modules + spring-boot-modules + 1.0.0-SNAPSHOT + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.cloud + spring-cloud-starter + + + commons-io + commons-io + ${commons-io.version} + + + commons-configuration + commons-configuration + 1.10 + + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + ${start-class} + JAR + + + + + + + 3.0.0 + com.baeldung.springboot.swagger.ArticleApplication + 2021.0.5 + + + \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/dynamicendpoints/DynamicEndpointApp.java b/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/dynamicendpoints/DynamicEndpointApp.java new file mode 100644 index 0000000000..cbfa7d8a06 --- /dev/null +++ b/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/dynamicendpoints/DynamicEndpointApp.java @@ -0,0 +1,47 @@ +package com.baeldung.dynamicendpoints; + +import java.io.File; +import java.util.Properties; + +import org.apache.commons.configuration.PropertiesConfiguration; +import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +import com.baeldung.dynamicendpoints.config.ReloadableProperties; + +@SpringBootApplication +@EnableWebMvc +public class DynamicEndpointApp { + public static void main(String[] args) { + SpringApplication.run(DynamicEndpointApp.class, args); + } + + +@Bean +@ConditionalOnProperty(name = "dynamic.endpoint.config.location", matchIfMissing = false) +public PropertiesConfiguration propertiesConfiguration( + @Value("${dynamic.endpoint.config.location}") String path, + @Value("${spring.properties.refreshDelay}") long refreshDelay) throws Exception { + String filePath = path.substring("file:".length()); + PropertiesConfiguration configuration = new PropertiesConfiguration(new File(filePath).getCanonicalPath()); + FileChangedReloadingStrategy fileChangedReloadingStrategy = new FileChangedReloadingStrategy(); + fileChangedReloadingStrategy.setRefreshDelay(refreshDelay); + configuration.setReloadingStrategy(fileChangedReloadingStrategy); + return configuration; +} + + @Bean + @ConditionalOnBean(PropertiesConfiguration.class) + @Primary + public Properties properties(PropertiesConfiguration propertiesConfiguration) throws Exception { + ReloadableProperties properties = new ReloadableProperties(propertiesConfiguration); + return properties; + } +} diff --git a/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/dynamicendpoints/config/EndpointRefreshConfigBean.java b/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/dynamicendpoints/config/EndpointRefreshConfigBean.java new file mode 100644 index 0000000000..1ea9febfca --- /dev/null +++ b/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/dynamicendpoints/config/EndpointRefreshConfigBean.java @@ -0,0 +1,35 @@ +package com.baeldung.dynamicendpoints.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.stereotype.Component; + +@Component +@RefreshScope +public class EndpointRefreshConfigBean { + + private boolean foo; + + private String regex; + + public EndpointRefreshConfigBean(@Value("${endpoint.foo}") boolean foo, @Value("${endpoint.regex}") String regex) { + this.foo = foo; + this.regex = regex; + } + + public boolean isFoo() { + return foo; + } + + public void setFoo(boolean foo) { + this.foo = foo; + } + + public String getRegex() { + return regex; + } + + public void setRegex(String regex) { + this.regex = regex; + } +} \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/dynamicendpoints/config/EnvironmentConfigBean.java b/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/dynamicendpoints/config/EnvironmentConfigBean.java new file mode 100644 index 0000000000..f89341d3f5 --- /dev/null +++ b/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/dynamicendpoints/config/EnvironmentConfigBean.java @@ -0,0 +1,27 @@ +package com.baeldung.dynamicendpoints.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; + +@Component +public class EnvironmentConfigBean { + + private final Environment environment; + + public EnvironmentConfigBean(@Autowired Environment environment) { + this.environment = environment; + } + + public String getEndpointRegex() { + return environment.getProperty("endpoint.regex"); + } + + public boolean isFooEndpointEnabled() { + return Boolean.parseBoolean(environment.getProperty("endpoint.foo")); + } + + public Environment getEnvironment() { + return environment; + } +} diff --git a/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/dynamicendpoints/config/PropertiesException.java b/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/dynamicendpoints/config/PropertiesException.java new file mode 100644 index 0000000000..67d634f341 --- /dev/null +++ b/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/dynamicendpoints/config/PropertiesException.java @@ -0,0 +1,10 @@ +package com.baeldung.dynamicendpoints.config; + +public class PropertiesException extends RuntimeException { + public PropertiesException() { + } + + public PropertiesException(Throwable cause) { + super(cause); + } +} diff --git a/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/dynamicendpoints/config/ReloadableProperties.java b/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/dynamicendpoints/config/ReloadableProperties.java new file mode 100644 index 0000000000..ef44863b17 --- /dev/null +++ b/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/dynamicendpoints/config/ReloadableProperties.java @@ -0,0 +1,23 @@ +package com.baeldung.dynamicendpoints.config; + +import java.io.FileReader; +import java.io.IOException; +import java.util.Properties; + +import org.apache.commons.configuration.PropertiesConfiguration; + +public class ReloadableProperties extends Properties { + private PropertiesConfiguration propertiesConfiguration; + + public ReloadableProperties(PropertiesConfiguration propertiesConfiguration) throws IOException { + super.load(new FileReader(propertiesConfiguration.getFile())); + this.propertiesConfiguration = propertiesConfiguration; + } + + @Override + public String getProperty(String key) { + String val = propertiesConfiguration.getString(key); + super.setProperty(key, val); + return val; + } +} diff --git a/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/dynamicendpoints/config/ReloadablePropertySource.java b/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/dynamicendpoints/config/ReloadablePropertySource.java new file mode 100644 index 0000000000..92cb4c3e55 --- /dev/null +++ b/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/dynamicendpoints/config/ReloadablePropertySource.java @@ -0,0 +1,32 @@ +package com.baeldung.dynamicendpoints.config; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.PropertiesConfiguration; +import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy; +import org.springframework.core.env.PropertySource; +import org.springframework.util.StringUtils; + +public class ReloadablePropertySource extends PropertySource { + + private final PropertiesConfiguration propertiesConfiguration; + + public ReloadablePropertySource(String name, PropertiesConfiguration propertiesConfiguration) { + super(name); + this.propertiesConfiguration = propertiesConfiguration; + } + + public ReloadablePropertySource(String name, String path) throws ConfigurationException { + super(StringUtils.hasText(name) ? path : name); + try { + this.propertiesConfiguration = new PropertiesConfiguration(path); + this.propertiesConfiguration.setReloadingStrategy(new FileChangedReloadingStrategy()); + } catch (PropertiesException e) { + throw e; + } + } + + @Override + public Object getProperty(String s) { + return propertiesConfiguration.getProperty(s); + } +} \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/dynamicendpoints/config/ReloadablePropertySourceConfig.java b/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/dynamicendpoints/config/ReloadablePropertySourceConfig.java new file mode 100644 index 0000000000..24864b8c78 --- /dev/null +++ b/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/dynamicendpoints/config/ReloadablePropertySourceConfig.java @@ -0,0 +1,28 @@ +package com.baeldung.dynamicendpoints.config; + +import org.apache.commons.configuration.PropertiesConfiguration; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.MutablePropertySources; + +@Configuration +public class ReloadablePropertySourceConfig { + + private ConfigurableEnvironment env; + + public ReloadablePropertySourceConfig(@Autowired ConfigurableEnvironment env) { + this.env = env; + } + + @Bean + @ConditionalOnProperty(name = "dynamic.endpoint.config.location", matchIfMissing = false) + public ReloadablePropertySource reloadablePropertySource(PropertiesConfiguration properties) { + ReloadablePropertySource reloadablePropertySource = new ReloadablePropertySource("toggle-endpoints", properties); + MutablePropertySources sources = env.getPropertySources(); + sources.addFirst(reloadablePropertySource); + return reloadablePropertySource; + } +} \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/dynamicendpoints/controller/AppController.java b/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/dynamicendpoints/controller/AppController.java new file mode 100644 index 0000000000..e39d893a7c --- /dev/null +++ b/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/dynamicendpoints/controller/AppController.java @@ -0,0 +1,58 @@ +package com.baeldung.dynamicendpoints.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.baeldung.dynamicendpoints.filter.DynamicEndpointFilter; +import com.baeldung.dynamicendpoints.config.EndpointRefreshConfigBean; +import com.baeldung.dynamicendpoints.config.EnvironmentConfigBean; + +@RestController +@RequestMapping("/") +public class AppController { + + private EndpointRefreshConfigBean endpointRefreshConfigBean; + private EnvironmentConfigBean environmentConfigBean; + + @Autowired + public AppController(EndpointRefreshConfigBean endpointRefreshConfigBean, EnvironmentConfigBean environmentConfigBean) { + this.endpointRefreshConfigBean = endpointRefreshConfigBean; + this.environmentConfigBean = environmentConfigBean; + } + + @GetMapping("/foo") + public ResponseEntity fooHandler() { + if (endpointRefreshConfigBean.isFoo()) { + return ResponseEntity.status(200) + .body("foo"); + } else { + return ResponseEntity.status(503) + .body("endpoint is unavailable"); + } + } + + @GetMapping("/bar1") + public String bar1Handler() { + return "bar1"; + } + + @GetMapping("/bar2") + public String bar2Handler() { + return "bar2"; + } + + @Bean + @ConditionalOnBean(EnvironmentConfigBean.class) + public FilterRegistrationBean dynamicEndpointFilterFilterRegistrationBean(EnvironmentConfigBean environmentConfigBean) { + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); + registrationBean.setFilter(new DynamicEndpointFilter(environmentConfigBean.getEnvironment())); + registrationBean.addUrlPatterns("*"); + return registrationBean; + } +} diff --git a/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/dynamicendpoints/filter/DynamicEndpointFilter.java b/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/dynamicendpoints/filter/DynamicEndpointFilter.java new file mode 100644 index 0000000000..7ec8a1f42b --- /dev/null +++ b/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/dynamicendpoints/filter/DynamicEndpointFilter.java @@ -0,0 +1,38 @@ +package com.baeldung.dynamicendpoints.filter; + +import java.io.IOException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.core.env.Environment; +import org.springframework.http.HttpStatus; +import org.springframework.web.filter.OncePerRequestFilter; + +public class DynamicEndpointFilter extends OncePerRequestFilter { + + private Environment environment; + + public DynamicEndpointFilter(Environment environment) { + this.environment = environment; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + String path = request.getRequestURI(); + String regex = this.environment.getProperty("endpoint.regex"); + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(path); + boolean matches = matcher.matches(); + + if (!matches) { + response.sendError(HttpStatus.SERVICE_UNAVAILABLE.value(), "Service is unavailable"); + } else { + filterChain.doFilter(request, response); + } + } +} diff --git a/spring-boot-modules/spring-boot-mvc-5/src/main/resources/application.properties b/spring-boot-modules/spring-boot-mvc-5/src/main/resources/application.properties new file mode 100644 index 0000000000..d8bacac436 --- /dev/null +++ b/spring-boot-modules/spring-boot-mvc-5/src/main/resources/application.properties @@ -0,0 +1,12 @@ +server.port=9090 +management.server.port=8081 +management.server.address=127.0.0.1 + +management.endpoints.web.exposure.include=* + +logging.level.org.springframework=INFO + +#Dynamic Endpoint +spring.main.allow-bean-definition-overriding=true +dynamic.endpoint.config.location=file:extra.properties +spring.properties.refreshDelay=1 \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-mvc-5/src/main/resources/logback.xml b/spring-boot-modules/spring-boot-mvc-5/src/main/resources/logback.xml new file mode 100644 index 0000000000..7d900d8ea8 --- /dev/null +++ b/spring-boot-modules/spring-boot-mvc-5/src/main/resources/logback.xml @@ -0,0 +1,13 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + \ No newline at end of file