diff --git a/pom.xml b/pom.xml
index 25848f4e83..508b6921a7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -790,6 +790,7 @@
spring-mvc-kotlin
spring-mvc-simple
spring-mvc-simple-2
+ spring-mvc-views
spring-mvc-velocity
spring-mvc-webflow
spring-mvc-xml
@@ -1393,6 +1394,7 @@
spring-mvc-kotlin
spring-mvc-simple
spring-mvc-simple-2
+ spring-mvc-views
spring-mvc-velocity
spring-mvc-webflow
spring-mvc-xml
diff --git a/spring-mvc-views/pom.xml b/spring-mvc-views/pom.xml
new file mode 100644
index 0000000000..c3a3540fce
--- /dev/null
+++ b/spring-mvc-views/pom.xml
@@ -0,0 +1,120 @@
+
+
+ 4.0.0
+ spring-mvc-views
+ war
+ spring-mvc-views
+
+
+ com.baeldung
+ parent-spring-5
+ 0.0.1-SNAPSHOT
+ ../parent-spring-5
+
+
+
+
+ 1.8
+ 2.3.3
+ 4.0.1
+ 5.2.1.RELEASE
+ 5.2.1.RELEASE
+ 2.2.2.RELEASE
+ 2.2.2
+ 2.5.0
+ 5.4.9.Final
+ enter-location-of-server
+
+
+
+
+ org.springframework
+ spring-webmvc
+ ${spring.version}
+
+
+ org.springframework
+ spring-context
+ ${spring.version}
+
+
+ javax.servlet
+ javax.servlet-api
+ ${javax.servlet-api.version}
+
+
+ javax.servlet.jsp
+ javax.servlet.jsp-api
+ ${javax.servlet.jsp-api.version}
+
+
+ javax.servlet
+ jstl
+ ${jstl.version}
+
+
+ org.springframework.data
+ spring-data-jpa
+ ${spring.data.version}
+
+
+ org.hibernate
+ hibernate-core
+ ${hibernate.version}
+
+
+
+ org.hsqldb
+ hsqldb
+ ${hsqldb.version}
+
+
+
+ org.springframework.security
+ spring-security-web
+ ${spring.security.version}
+
+
+
+ org.springframework.security
+ spring-security-config
+ ${spring.security.version}
+
+
+
+ org.springframework.security
+ spring-security-taglibs
+ ${spring.security.version}
+
+
+
+
+
+ spring-mvc-theme
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 2.0.2
+
+ ${java.version}
+ ${java.version}
+
+
+
+
+ org.apache.maven.plugins
+ maven-war-plugin
+ ${maven-war-plugin.version}
+
+ src/main/webapp
+ spring-mvc-views
+ false
+ ${deploy-path}
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-mvc-views/src/main/java/com/baeldung/config/DataSourceConfig.java b/spring-mvc-views/src/main/java/com/baeldung/config/DataSourceConfig.java
new file mode 100644
index 0000000000..803c30f29d
--- /dev/null
+++ b/spring-mvc-views/src/main/java/com/baeldung/config/DataSourceConfig.java
@@ -0,0 +1,44 @@
+package com.baeldung.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
+import org.springframework.orm.jpa.JpaTransactionManager;
+import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
+import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
+import org.springframework.transaction.PlatformTransactionManager;
+
+import javax.persistence.EntityManagerFactory;
+import javax.sql.DataSource;
+
+@EnableJpaRepositories(basePackages = "com.baeldung")
+public class DataSourceConfig {
+ @Bean
+ public DataSource dataSource() {
+ EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
+ return builder.setType(EmbeddedDatabaseType.HSQL)
+ .addScripts("db/sql/create-db.sql", "db/sql/insert-data.sql")
+ .build();
+ }
+
+ @Bean
+ public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
+ LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
+ em.setDataSource(dataSource);
+ em.setPackagesToScan("com.baeldung.domain");
+ em.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
+ return em;
+ }
+
+ @Bean
+ public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
+ return new JpaTransactionManager(emf);
+ }
+
+ @Bean
+ public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){
+ return new PersistenceExceptionTranslationPostProcessor();
+ }
+}
diff --git a/spring-mvc-views/src/main/java/com/baeldung/config/InitSecurity.java b/spring-mvc-views/src/main/java/com/baeldung/config/InitSecurity.java
new file mode 100644
index 0000000000..2bf659f476
--- /dev/null
+++ b/spring-mvc-views/src/main/java/com/baeldung/config/InitSecurity.java
@@ -0,0 +1,6 @@
+package com.baeldung.config;
+
+import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
+
+public class InitSecurity extends AbstractSecurityWebApplicationInitializer {
+}
diff --git a/spring-mvc-views/src/main/java/com/baeldung/config/SecurityConfig.java b/spring-mvc-views/src/main/java/com/baeldung/config/SecurityConfig.java
new file mode 100644
index 0000000000..2e0a413cf3
--- /dev/null
+++ b/spring-mvc-views/src/main/java/com/baeldung/config/SecurityConfig.java
@@ -0,0 +1,56 @@
+package com.baeldung.config;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.builders.WebSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+import javax.sql.DataSource;
+
+@EnableWebSecurity
+public class SecurityConfig extends WebSecurityConfigurerAdapter {
+
+ @Autowired
+ DataSource dataSource;
+
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
+
+ @Override
+ protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+ auth.jdbcAuthentication()
+ .passwordEncoder(passwordEncoder())
+ .dataSource(dataSource);
+ }
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+
+ http.csrf()
+ .disable()
+ .authorizeRequests()
+ .antMatchers("/anonymous*").anonymous()
+ .antMatchers("/login*").permitAll()
+ .anyRequest().authenticated()
+ .and()
+ .formLogin()
+ .and()
+ .logout()
+ .logoutUrl("/logout.do")
+ .invalidateHttpSession(true)
+ .clearAuthentication(true);
+ }
+
+ @Override
+ public void configure(WebSecurity web) throws Exception {
+ web.ignoring().antMatchers("/themes/**");
+ }
+
+}
diff --git a/spring-mvc-views/src/main/java/com/baeldung/config/ThemeMVCConfig.java b/spring-mvc-views/src/main/java/com/baeldung/config/ThemeMVCConfig.java
new file mode 100644
index 0000000000..86f6f54195
--- /dev/null
+++ b/spring-mvc-views/src/main/java/com/baeldung/config/ThemeMVCConfig.java
@@ -0,0 +1,53 @@
+package com.baeldung.config;
+
+import com.baeldung.theme.resolver.UserPreferenceThemeResolver;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.ui.context.support.ResourceBundleThemeSource;
+import org.springframework.web.servlet.ThemeResolver;
+import org.springframework.web.servlet.config.annotation.*;
+import org.springframework.web.servlet.theme.ThemeChangeInterceptor;
+
+@Configuration
+@ComponentScan(basePackages="com.baeldung")
+@EnableWebMvc
+public class ThemeMVCConfig implements WebMvcConfigurer {
+
+ @Override
+ public void addResourceHandlers(ResourceHandlerRegistry registry) {
+ registry.addResourceHandler("/themes/**").addResourceLocations("classpath:/themes/");
+ }
+
+ @Override
+ public void addInterceptors(InterceptorRegistry registry) {
+ registry.addInterceptor(themeChangeInterceptor());
+ }
+
+ @Bean
+ public ThemeChangeInterceptor themeChangeInterceptor() {
+ ThemeChangeInterceptor interceptor = new ThemeChangeInterceptor();
+ interceptor.setParamName("theme");
+ return interceptor;
+ }
+
+ @Bean
+ public ResourceBundleThemeSource resourceBundleThemeSource() {
+ ResourceBundleThemeSource themeSource = new ResourceBundleThemeSource();
+ themeSource.setFallbackToSystemLocale(true);
+ return themeSource;
+ }
+
+ @Bean
+ public ThemeResolver themeResolver() {
+ UserPreferenceThemeResolver themeResolver = new UserPreferenceThemeResolver();
+ themeResolver.setDefaultThemeName("light");
+ return themeResolver;
+ }
+
+
+ @Override
+ public void configureViewResolvers(ViewResolverRegistry resolverRegistry) {
+ resolverRegistry.jsp();
+ }
+}
diff --git a/spring-mvc-views/src/main/java/com/baeldung/config/WebInitializer.java b/spring-mvc-views/src/main/java/com/baeldung/config/WebInitializer.java
new file mode 100644
index 0000000000..5516fb7b3c
--- /dev/null
+++ b/spring-mvc-views/src/main/java/com/baeldung/config/WebInitializer.java
@@ -0,0 +1,28 @@
+package com.baeldung.config;
+
+import org.springframework.web.WebApplicationInitializer;
+import org.springframework.web.context.ContextLoaderListener;
+import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
+import org.springframework.web.servlet.DispatcherServlet;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletRegistration;
+
+public class WebInitializer implements WebApplicationInitializer {
+ @Override
+ public void onStartup(javax.servlet.ServletContext servletContext) throws ServletException {
+ AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
+ context.register(DataSourceConfig.class);
+ context.register(ThemeMVCConfig.class);
+
+
+ servletContext.addListener(new ContextLoaderListener(context));
+ servletContext.setInitParameter("spring.profiles.active", "database");
+
+ ServletRegistration.Dynamic servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(context));
+
+ servlet.setLoadOnStartup(1);
+ servlet.addMapping("/");
+
+ }
+}
diff --git a/spring-mvc-views/src/main/java/com/baeldung/controllers/AppController.java b/spring-mvc-views/src/main/java/com/baeldung/controllers/AppController.java
new file mode 100644
index 0000000000..31343492e1
--- /dev/null
+++ b/spring-mvc-views/src/main/java/com/baeldung/controllers/AppController.java
@@ -0,0 +1,13 @@
+package com.baeldung.controllers;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+@Controller
+public class AppController {
+
+ @RequestMapping("/")
+ public String home() {
+ return "index";
+ }
+}
diff --git a/spring-mvc-views/src/main/java/com/baeldung/domain/UserPreference.java b/spring-mvc-views/src/main/java/com/baeldung/domain/UserPreference.java
new file mode 100644
index 0000000000..81034de947
--- /dev/null
+++ b/spring-mvc-views/src/main/java/com/baeldung/domain/UserPreference.java
@@ -0,0 +1,30 @@
+package com.baeldung.domain;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+@Entity
+@Table(name = "preferences")
+public class UserPreference {
+ @Id
+ private String username;
+
+ private String theme;
+
+ public String getTheme() {
+ return theme;
+ }
+
+ public void setTheme(String theme) {
+ this.theme = theme;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+}
diff --git a/spring-mvc-views/src/main/java/com/baeldung/repository/UserPreferenceRepository.java b/spring-mvc-views/src/main/java/com/baeldung/repository/UserPreferenceRepository.java
new file mode 100644
index 0000000000..77e5da0498
--- /dev/null
+++ b/spring-mvc-views/src/main/java/com/baeldung/repository/UserPreferenceRepository.java
@@ -0,0 +1,7 @@
+package com.baeldung.repository;
+
+import com.baeldung.domain.UserPreference;
+import org.springframework.data.repository.PagingAndSortingRepository;
+
+public interface UserPreferenceRepository extends PagingAndSortingRepository {
+}
diff --git a/spring-mvc-views/src/main/java/com/baeldung/theme/resolver/UserPreferenceThemeResolver.java b/spring-mvc-views/src/main/java/com/baeldung/theme/resolver/UserPreferenceThemeResolver.java
new file mode 100644
index 0000000000..4c59734d41
--- /dev/null
+++ b/spring-mvc-views/src/main/java/com/baeldung/theme/resolver/UserPreferenceThemeResolver.java
@@ -0,0 +1,74 @@
+package com.baeldung.theme.resolver;
+
+import com.baeldung.domain.UserPreference;
+import com.baeldung.repository.UserPreferenceRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.util.StringUtils;
+import org.springframework.web.servlet.ThemeResolver;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.Optional;
+
+public class UserPreferenceThemeResolver implements ThemeResolver {
+
+ public static final String THEME_REQUEST_ATTRIBUTE_NAME = UserPreferenceThemeResolver.class.getName() + ".THEME";
+
+ @Autowired(required = false)
+ Authentication authentication;
+
+ @Autowired
+ UserPreferenceRepository userPreferenceRepository;
+
+ private String defaultThemeName;
+
+ public String getDefaultThemeName() {
+ return defaultThemeName;
+ }
+
+ public void setDefaultThemeName(String defaultThemeName) {
+ this.defaultThemeName = defaultThemeName;
+ }
+
+ @Override
+ public String resolveThemeName(HttpServletRequest request) {
+ String themeName = findThemeFromRequest(request).orElse(findUserPreferredTheme().orElse(getDefaultThemeName()));
+ request.setAttribute(THEME_REQUEST_ATTRIBUTE_NAME, themeName);
+ return themeName;
+ }
+
+ private Optional findUserPreferredTheme() {
+ Authentication authentication = SecurityContextHolder.getContext()
+ .getAuthentication();
+ UserPreference userPreference = getUserPreference(authentication).orElse(new UserPreference());
+ return Optional.ofNullable(userPreference.getTheme());
+ }
+
+ private Optional findThemeFromRequest(HttpServletRequest request) {
+ return Optional.ofNullable((String) request.getAttribute(THEME_REQUEST_ATTRIBUTE_NAME));
+ }
+
+ private Optional getUserPreference(Authentication authentication) {
+ return isAuthenticated(authentication) ? userPreferenceRepository.findById(((User) authentication.getPrincipal()).getUsername()) : Optional.empty();
+ }
+
+ private boolean isAuthenticated(Authentication authentication) {
+ return authentication != null && authentication.isAuthenticated();
+ }
+
+ @Override
+ public void setThemeName(HttpServletRequest request, HttpServletResponse response, String theme) {
+ Authentication authentication = SecurityContextHolder.getContext()
+ .getAuthentication();
+ if (isAuthenticated(authentication)) {
+ request.setAttribute(THEME_REQUEST_ATTRIBUTE_NAME, theme);
+ UserPreference userPreference = getUserPreference(authentication).orElse(new UserPreference());
+ userPreference.setUsername(((User) authentication.getPrincipal()).getUsername());
+ userPreference.setTheme(StringUtils.hasText(theme) ? theme : null);
+ userPreferenceRepository.save(userPreference);
+ }
+ }
+}
diff --git a/spring-mvc-views/src/main/resources/dark.properties b/spring-mvc-views/src/main/resources/dark.properties
new file mode 100644
index 0000000000..c82264da75
--- /dev/null
+++ b/spring-mvc-views/src/main/resources/dark.properties
@@ -0,0 +1,2 @@
+styleSheet=themes/black.css
+background=black
\ No newline at end of file
diff --git a/spring-mvc-views/src/main/resources/dark_en_US.properties b/spring-mvc-views/src/main/resources/dark_en_US.properties
new file mode 100644
index 0000000000..c82264da75
--- /dev/null
+++ b/spring-mvc-views/src/main/resources/dark_en_US.properties
@@ -0,0 +1,2 @@
+styleSheet=themes/black.css
+background=black
\ No newline at end of file
diff --git a/spring-mvc-views/src/main/resources/db/sql/create-db.sql b/spring-mvc-views/src/main/resources/db/sql/create-db.sql
new file mode 100644
index 0000000000..0a6fd5ad59
--- /dev/null
+++ b/spring-mvc-views/src/main/resources/db/sql/create-db.sql
@@ -0,0 +1,17 @@
+create table users (
+ username varchar(50) not null primary key,
+ password varchar(256) not null,
+ enabled boolean not null
+);
+
+
+create table authorities (
+ username varchar(50) not null,
+ authority varchar(50) not null
+);
+
+
+create table preferences (
+ username varchar(50) not null,
+ theme varchar(50)
+);
\ No newline at end of file
diff --git a/spring-mvc-views/src/main/resources/db/sql/insert-data.sql b/spring-mvc-views/src/main/resources/db/sql/insert-data.sql
new file mode 100644
index 0000000000..7ddf684d9a
--- /dev/null
+++ b/spring-mvc-views/src/main/resources/db/sql/insert-data.sql
@@ -0,0 +1,6 @@
+insert into users values('john', '$2a$10$cjcbIX/aLe12PpGZ.vQfweLiB7K1QTC5enTk3oD0deCMdtj2Sx.im', 1);
+insert into users values('admin', '$2a$10$cjcbIX/aLe12PpGZ.vQfweLiB7K1QTC5enTk3oD0deCMdtj2Sx.im', 1);
+
+insert into authorities values('john', 'USER');
+insert into authorities values('admin', 'USER');
+insert into authorities values('admin', 'ADMIN');
\ No newline at end of file
diff --git a/spring-mvc-views/src/main/resources/light.properties b/spring-mvc-views/src/main/resources/light.properties
new file mode 100644
index 0000000000..f6e0d10b4c
--- /dev/null
+++ b/spring-mvc-views/src/main/resources/light.properties
@@ -0,0 +1,2 @@
+styleSheet=themes/white.css
+background=black
\ No newline at end of file
diff --git a/spring-mvc-views/src/main/resources/light_en_US.properties b/spring-mvc-views/src/main/resources/light_en_US.properties
new file mode 100644
index 0000000000..f6e0d10b4c
--- /dev/null
+++ b/spring-mvc-views/src/main/resources/light_en_US.properties
@@ -0,0 +1,2 @@
+styleSheet=themes/white.css
+background=black
\ No newline at end of file
diff --git a/spring-mvc-views/src/main/resources/themes/black.css b/spring-mvc-views/src/main/resources/themes/black.css
new file mode 100644
index 0000000000..8a44cd969f
--- /dev/null
+++ b/spring-mvc-views/src/main/resources/themes/black.css
@@ -0,0 +1,8 @@
+body {
+ justify-content : center;
+ background-color : black;
+ color : white;
+ text-align : center;
+ margin-left : 15%;
+ margin-right : 15%;
+}
\ No newline at end of file
diff --git a/spring-mvc-views/src/main/resources/themes/white.css b/spring-mvc-views/src/main/resources/themes/white.css
new file mode 100644
index 0000000000..939bd1e4c9
--- /dev/null
+++ b/spring-mvc-views/src/main/resources/themes/white.css
@@ -0,0 +1,8 @@
+body {
+ justify-content : center;
+ background-color : white;
+ color : black;
+ text-align : center;
+ margin-left : 15%;
+ margin-right : 15%;
+}
\ No newline at end of file
diff --git a/spring-mvc-views/src/main/webapp/WEB-INF/index.jsp b/spring-mvc-views/src/main/webapp/WEB-INF/index.jsp
new file mode 100644
index 0000000000..b36c21eb71
--- /dev/null
+++ b/spring-mvc-views/src/main/webapp/WEB-INF/index.jsp
@@ -0,0 +1,46 @@
+<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
+<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
+<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>
+
+
+
+
+ Themed Application
+
+
+
+
+ Spring MVC Theme Demo
+
+ User :
+
+
+
+
+
+
+
+
+