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 + + +
+

Themed Application

+
+
+
+

Spring MVC Theme Demo

+ +

User :

+
+
+
+

+ Change Theme +

+
+ +
+
+ +
+
+ +
+
+ + + +