From 3a4990089fd558999f3f8486f70a57726a236e99 Mon Sep 17 00:00:00 2001 From: kataus Date: Wed, 25 Nov 2020 18:36:37 +0300 Subject: [PATCH 1/3] =?UTF-8?q?=D0=9F=D1=80=D0=B8=D0=BC=D0=B5=D1=80=D1=8B?= =?UTF-8?q?=20=D0=BA=20=D0=B7=D0=B0=D0=BD=D1=8F=D1=82=D0=B8=D1=8E=2024=20(?= =?UTF-8?q?Spring=20Security=20ACL)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spring/rest/SecurityControllerAdvice.java | 16 ++++ 2020-08/spring-24-SS-ACL/pom.xml | 82 +++++++++++++++++++ .../src/main/java/ru/otus/spring/Main.java | 14 ++++ .../ru/otus/spring/config/SwaggerConfig.java | 23 ++++++ .../ru/otus/spring/model/NoticeMessage.java | 33 ++++++++ .../repository/NoticeMessageRepository.java | 25 ++++++ .../spring/rest/NoticeMessageController.java | 35 ++++++++ .../ru/otus/spring/security/AclConfig.java | 79 ++++++++++++++++++ .../AclMethodSecurityConfiguration.java | 22 +++++ .../security/SecurityConfiguration.java | 60 ++++++++++++++ .../ru/otus/spring/service/NoticeService.java | 42 ++++++++++ .../src/main/resources/application.yml | 7 ++ .../src/main/resources/data.sql | 26 ++++++ .../src/main/resources/schema.sql | 58 +++++++++++++ .../src/main/resources/templates/error.html | 9 ++ .../src/main/resources/templates/index.html | 15 ++++ 2020-08/spring-24-WebFlux/pom.xml | 72 ++++++++++++++++ .../src/main/java/ru/otus/spring/Main.java | 22 +++++ .../main/java/ru/otus/spring/data/Person.java | 41 ++++++++++ .../ru/otus/spring/data/PersonRepository.java | 11 +++ .../ru/otus/spring/rest/PersonController.java | 34 ++++++++ .../security/SecurityConfiguration.java | 49 +++++++++++ .../main/resources/static/authenticated.html | 9 ++ .../src/main/resources/static/index.html | 11 +++ .../src/main/resources/static/public.html | 9 ++ 25 files changed, 804 insertions(+) create mode 100644 2020-08/spring-23/src/main/java/ru/otus/spring/rest/SecurityControllerAdvice.java create mode 100644 2020-08/spring-24-SS-ACL/pom.xml create mode 100644 2020-08/spring-24-SS-ACL/src/main/java/ru/otus/spring/Main.java create mode 100644 2020-08/spring-24-SS-ACL/src/main/java/ru/otus/spring/config/SwaggerConfig.java create mode 100644 2020-08/spring-24-SS-ACL/src/main/java/ru/otus/spring/model/NoticeMessage.java create mode 100644 2020-08/spring-24-SS-ACL/src/main/java/ru/otus/spring/repository/NoticeMessageRepository.java create mode 100644 2020-08/spring-24-SS-ACL/src/main/java/ru/otus/spring/rest/NoticeMessageController.java create mode 100644 2020-08/spring-24-SS-ACL/src/main/java/ru/otus/spring/security/AclConfig.java create mode 100644 2020-08/spring-24-SS-ACL/src/main/java/ru/otus/spring/security/AclMethodSecurityConfiguration.java create mode 100644 2020-08/spring-24-SS-ACL/src/main/java/ru/otus/spring/security/SecurityConfiguration.java create mode 100644 2020-08/spring-24-SS-ACL/src/main/java/ru/otus/spring/service/NoticeService.java create mode 100644 2020-08/spring-24-SS-ACL/src/main/resources/application.yml create mode 100644 2020-08/spring-24-SS-ACL/src/main/resources/data.sql create mode 100644 2020-08/spring-24-SS-ACL/src/main/resources/schema.sql create mode 100644 2020-08/spring-24-SS-ACL/src/main/resources/templates/error.html create mode 100644 2020-08/spring-24-SS-ACL/src/main/resources/templates/index.html create mode 100644 2020-08/spring-24-WebFlux/pom.xml create mode 100644 2020-08/spring-24-WebFlux/src/main/java/ru/otus/spring/Main.java create mode 100644 2020-08/spring-24-WebFlux/src/main/java/ru/otus/spring/data/Person.java create mode 100644 2020-08/spring-24-WebFlux/src/main/java/ru/otus/spring/data/PersonRepository.java create mode 100644 2020-08/spring-24-WebFlux/src/main/java/ru/otus/spring/rest/PersonController.java create mode 100644 2020-08/spring-24-WebFlux/src/main/java/ru/otus/spring/security/SecurityConfiguration.java create mode 100644 2020-08/spring-24-WebFlux/src/main/resources/static/authenticated.html create mode 100644 2020-08/spring-24-WebFlux/src/main/resources/static/index.html create mode 100644 2020-08/spring-24-WebFlux/src/main/resources/static/public.html diff --git a/2020-08/spring-23/src/main/java/ru/otus/spring/rest/SecurityControllerAdvice.java b/2020-08/spring-23/src/main/java/ru/otus/spring/rest/SecurityControllerAdvice.java new file mode 100644 index 00000000..267b080d --- /dev/null +++ b/2020-08/spring-23/src/main/java/ru/otus/spring/rest/SecurityControllerAdvice.java @@ -0,0 +1,16 @@ +package ru.otus.spring.rest; + +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +import java.util.Optional; + +@ControllerAdvice +public class SecurityControllerAdvice { + @ExceptionHandler(AccessDeniedException.class) + public ResponseEntity accessError(){ + return ResponseEntity.of( Optional.of( "Неудачник" )); + } +} diff --git a/2020-08/spring-24-SS-ACL/pom.xml b/2020-08/spring-24-SS-ACL/pom.xml new file mode 100644 index 00000000..620ed464 --- /dev/null +++ b/2020-08/spring-24-SS-ACL/pom.xml @@ -0,0 +1,82 @@ + + + 4.0.0 + + ru.otus + spring-framework-24-acl + 1.0-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-parent + 2.2.6.RELEASE + + + + 2.6.11 + 2.8.0 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.security + spring-security-acl + + + net.sf.ehcache + ehcache-core + ${ehcache-core.version} + jar + + + org.springframework + spring-context-support + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + com.h2database + h2 + + + + io.springfox + springfox-swagger2 + ${swagger.version} + + + io.springfox + springfox-swagger-ui + ${swagger.version} + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/2020-08/spring-24-SS-ACL/src/main/java/ru/otus/spring/Main.java b/2020-08/spring-24-SS-ACL/src/main/java/ru/otus/spring/Main.java new file mode 100644 index 00000000..baca49d5 --- /dev/null +++ b/2020-08/spring-24-SS-ACL/src/main/java/ru/otus/spring/Main.java @@ -0,0 +1,14 @@ +package ru.otus.spring; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + +@SpringBootApplication +public class Main { + + public static void main(String[] args) { + SpringApplication.run(Main.class); + } +} diff --git a/2020-08/spring-24-SS-ACL/src/main/java/ru/otus/spring/config/SwaggerConfig.java b/2020-08/spring-24-SS-ACL/src/main/java/ru/otus/spring/config/SwaggerConfig.java new file mode 100644 index 00000000..92854b5f --- /dev/null +++ b/2020-08/spring-24-SS-ACL/src/main/java/ru/otus/spring/config/SwaggerConfig.java @@ -0,0 +1,23 @@ +package ru.otus.spring.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import ru.otus.spring.rest.NoticeMessageController; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2; + +@Configuration +@EnableSwagger2 +public class SwaggerConfig { + + @Bean + public Docket productApi() { + return new Docket(DocumentationType.SWAGGER_2) + .useDefaultResponseMessages(false) + .select() + .apis(RequestHandlerSelectors.basePackage(NoticeMessageController.class.getPackage().getName())) + .build(); + } +} diff --git a/2020-08/spring-24-SS-ACL/src/main/java/ru/otus/spring/model/NoticeMessage.java b/2020-08/spring-24-SS-ACL/src/main/java/ru/otus/spring/model/NoticeMessage.java new file mode 100644 index 00000000..21457970 --- /dev/null +++ b/2020-08/spring-24-SS-ACL/src/main/java/ru/otus/spring/model/NoticeMessage.java @@ -0,0 +1,33 @@ +package ru.otus.spring.model; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table(name = "system_message") +public class NoticeMessage { + + @Id + @Column + private Integer id; + @Column + private String content; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } +} diff --git a/2020-08/spring-24-SS-ACL/src/main/java/ru/otus/spring/repository/NoticeMessageRepository.java b/2020-08/spring-24-SS-ACL/src/main/java/ru/otus/spring/repository/NoticeMessageRepository.java new file mode 100644 index 00000000..4588eb21 --- /dev/null +++ b/2020-08/spring-24-SS-ACL/src/main/java/ru/otus/spring/repository/NoticeMessageRepository.java @@ -0,0 +1,25 @@ +package ru.otus.spring.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.repository.query.Param; +import org.springframework.security.access.prepost.PostAuthorize; +import org.springframework.security.access.prepost.PostFilter; +import org.springframework.security.access.prepost.PreAuthorize; +import ru.otus.spring.model.NoticeMessage; + +import java.util.List; +import java.util.Optional; + +public interface NoticeMessageRepository extends JpaRepository { + + @PostFilter("hasPermission(filterObject, 'READ')") + List findAll(); + + @PostAuthorize("hasPermission(returnObject, 'READ')") + NoticeMessage getById(Integer id); + + @SuppressWarnings("unchecked") + @PreAuthorize("hasPermission(#noticeMessage, 'WRITE')") + NoticeMessage save(@Param("noticeMessage") NoticeMessage noticeMessage); + +} diff --git a/2020-08/spring-24-SS-ACL/src/main/java/ru/otus/spring/rest/NoticeMessageController.java b/2020-08/spring-24-SS-ACL/src/main/java/ru/otus/spring/rest/NoticeMessageController.java new file mode 100644 index 00000000..fbf59c52 --- /dev/null +++ b/2020-08/spring-24-SS-ACL/src/main/java/ru/otus/spring/rest/NoticeMessageController.java @@ -0,0 +1,35 @@ +package ru.otus.spring.rest; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RestController; +import ru.otus.spring.model.NoticeMessage; +import ru.otus.spring.repository.NoticeMessageRepository; + +import java.util.List; + +@RestController +public class NoticeMessageController { + + private final NoticeMessageRepository repository; + + public NoticeMessageController(NoticeMessageRepository repository) { + this.repository = repository; + } + + @GetMapping("/message") + public List getAll() { + return repository.findAll(); + } + + @GetMapping("/message/{id}") + public NoticeMessage getById(@PathVariable("id") Integer id) { + return repository.getById(id); + } + + @PutMapping("/message") + public NoticeMessage getById(NoticeMessage message) { + return repository.save(message); + } +} diff --git a/2020-08/spring-24-SS-ACL/src/main/java/ru/otus/spring/security/AclConfig.java b/2020-08/spring-24-SS-ACL/src/main/java/ru/otus/spring/security/AclConfig.java new file mode 100644 index 00000000..36d7376a --- /dev/null +++ b/2020-08/spring-24-SS-ACL/src/main/java/ru/otus/spring/security/AclConfig.java @@ -0,0 +1,79 @@ +package ru.otus.spring.security; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.ehcache.EhCacheFactoryBean; +import org.springframework.cache.ehcache.EhCacheManagerFactoryBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; +import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; +import org.springframework.security.acls.AclPermissionCacheOptimizer; +import org.springframework.security.acls.AclPermissionEvaluator; +import org.springframework.security.acls.domain.*; +import org.springframework.security.acls.jdbc.BasicLookupStrategy; +import org.springframework.security.acls.jdbc.JdbcMutableAclService; +import org.springframework.security.acls.jdbc.LookupStrategy; +import org.springframework.security.acls.model.PermissionGrantingStrategy; +import org.springframework.security.core.authority.SimpleGrantedAuthority; + +import javax.sql.DataSource; +import java.util.Objects; + +@Configuration +public class AclConfig { + + @SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection") + @Autowired + private DataSource dataSource; + + @Bean + public EhCacheBasedAclCache aclCache() { + return new EhCacheBasedAclCache( + Objects.requireNonNull(aclEhCacheFactoryBean().getObject()), + permissionGrantingStrategy(), + aclAuthorizationStrategy() + ); + } + + @Bean + public EhCacheFactoryBean aclEhCacheFactoryBean() { + EhCacheFactoryBean ehCacheFactoryBean = new EhCacheFactoryBean(); + ehCacheFactoryBean.setCacheManager(aclCacheManager().getObject()); + ehCacheFactoryBean.setCacheName("aclCache"); + return ehCacheFactoryBean; + } + + @Bean + public EhCacheManagerFactoryBean aclCacheManager() { + return new EhCacheManagerFactoryBean(); + } + + @Bean + public PermissionGrantingStrategy permissionGrantingStrategy() { + return new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger()); + } + + @Bean + public AclAuthorizationStrategy aclAuthorizationStrategy() { + return new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_EDITOR")); + } + + @Bean + public MethodSecurityExpressionHandler defaultMethodSecurityExpressionHandler() { + DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler(); + AclPermissionEvaluator permissionEvaluator = new AclPermissionEvaluator(aclService()); + expressionHandler.setPermissionEvaluator(permissionEvaluator); + expressionHandler.setPermissionCacheOptimizer(new AclPermissionCacheOptimizer(aclService())); + return expressionHandler; + } + + @Bean + public LookupStrategy lookupStrategy() { + return new BasicLookupStrategy(dataSource, aclCache(), aclAuthorizationStrategy(), new ConsoleAuditLogger()); + } + + @Bean + public JdbcMutableAclService aclService() { + return new JdbcMutableAclService(dataSource, lookupStrategy(), aclCache()); + } +} diff --git a/2020-08/spring-24-SS-ACL/src/main/java/ru/otus/spring/security/AclMethodSecurityConfiguration.java b/2020-08/spring-24-SS-ACL/src/main/java/ru/otus/spring/security/AclMethodSecurityConfiguration.java new file mode 100644 index 00000000..b66620ba --- /dev/null +++ b/2020-08/spring-24-SS-ACL/src/main/java/ru/otus/spring/security/AclMethodSecurityConfiguration.java @@ -0,0 +1,22 @@ +package ru.otus.spring.security; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration; + +@Configuration +@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) +public class AclMethodSecurityConfiguration extends GlobalMethodSecurityConfiguration { + + @SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection") + @Autowired + MethodSecurityExpressionHandler defaultMethodSecurityExpressionHandler; + + @Override + protected MethodSecurityExpressionHandler createExpressionHandler() { + return defaultMethodSecurityExpressionHandler; + } + +} diff --git a/2020-08/spring-24-SS-ACL/src/main/java/ru/otus/spring/security/SecurityConfiguration.java b/2020-08/spring-24-SS-ACL/src/main/java/ru/otus/spring/security/SecurityConfiguration.java new file mode 100644 index 00000000..ecdfd7f1 --- /dev/null +++ b/2020-08/spring-24-SS-ACL/src/main/java/ru/otus/spring/security/SecurityConfiguration.java @@ -0,0 +1,60 @@ +package ru.otus.spring.security; + +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.password.PasswordEncoder; + +@EnableWebSecurity +public class SecurityConfiguration extends WebSecurityConfigurerAdapter { + + @Override + public void configure(WebSecurity web) { + web.ignoring().antMatchers("/swagger-ui.html") + .antMatchers("/webjars/springfox-swagger-ui/**") + .antMatchers("/swagger-resources/**") + .antMatchers("/v2/api-docs") + .antMatchers("/h2-console/**"); + } + + @Override + public void configure(HttpSecurity http) throws Exception { + http.csrf().disable() + //.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) + //.and() + .authorizeRequests().antMatchers("/**").permitAll() + .and() + .formLogin() + .and() + .logout().logoutUrl("/logout"); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new PasswordEncoder() { + @Override + public String encode(CharSequence charSequence) { + return charSequence.toString(); + } + + @Override + public boolean matches(CharSequence charSequence, String s) { + return charSequence.toString().equals(s); + } + }; + } + + @Autowired + public void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.inMemoryAuthentication() + .withUser("admin").password("password").roles("EDITOR") + .and() + .withUser("user").password("password").roles("USER") + .and() + .withUser("someone").password("password").roles("SOMEONE"); + } +} diff --git a/2020-08/spring-24-SS-ACL/src/main/java/ru/otus/spring/service/NoticeService.java b/2020-08/spring-24-SS-ACL/src/main/java/ru/otus/spring/service/NoticeService.java new file mode 100644 index 00000000..f7f878fc --- /dev/null +++ b/2020-08/spring-24-SS-ACL/src/main/java/ru/otus/spring/service/NoticeService.java @@ -0,0 +1,42 @@ +package ru.otus.spring.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.acls.domain.BasePermission; +import org.springframework.security.acls.domain.GrantedAuthoritySid; +import org.springframework.security.acls.domain.ObjectIdentityImpl; +import org.springframework.security.acls.domain.PrincipalSid; +import org.springframework.security.acls.model.MutableAcl; +import org.springframework.security.acls.model.MutableAclService; +import org.springframework.security.acls.model.ObjectIdentity; +import org.springframework.security.acls.model.Sid; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; +import ru.otus.spring.model.NoticeMessage; +import ru.otus.spring.repository.NoticeMessageRepository; + +@Service +public class NoticeService { + @Autowired + protected MutableAclService mutableAclService; + + @Autowired + private NoticeMessageRepository repository; + + public void add( NoticeMessage noticeMessage ) { + repository.save( noticeMessage ); + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + final Sid owner = new PrincipalSid( authentication ); + ObjectIdentity oid = new ObjectIdentityImpl( noticeMessage.getClass(), noticeMessage.getId() ); + + final Sid admin = new GrantedAuthoritySid("ROLE_EDITOR"); + + MutableAcl acl = mutableAclService.createAcl( oid ); + acl.setOwner( owner ); + acl.insertAce( acl.getEntries().size(), BasePermission.ADMINISTRATION, admin, true ); + + mutableAclService.updateAcl( acl ); + + + } +} diff --git a/2020-08/spring-24-SS-ACL/src/main/resources/application.yml b/2020-08/spring-24-SS-ACL/src/main/resources/application.yml new file mode 100644 index 00000000..9a70d12a --- /dev/null +++ b/2020-08/spring-24-SS-ACL/src/main/resources/application.yml @@ -0,0 +1,7 @@ +spring: + h2: + console: + enabled: true + jpa: + hibernate: + ddl-auto: none diff --git a/2020-08/spring-24-SS-ACL/src/main/resources/data.sql b/2020-08/spring-24-SS-ACL/src/main/resources/data.sql new file mode 100644 index 00000000..42fe5631 --- /dev/null +++ b/2020-08/spring-24-SS-ACL/src/main/resources/data.sql @@ -0,0 +1,26 @@ +INSERT INTO acl_sid (id, principal, sid) VALUES +(1, 1, 'admin'), +(2, 1, 'user'), +(3, 0, 'ROLE_EDITOR'); + +INSERT INTO acl_class (id, class) VALUES +(1, 'ru.otus.spring.model.NoticeMessage'); + +INSERT INTO system_message(id,content) VALUES +(1,'First Level Message'), +(2,'Second Level Message'), +(3,'Third Level Message'); + +INSERT INTO acl_object_identity (id, object_id_class, object_id_identity, parent_object, owner_sid, entries_inheriting) VALUES +(1, 1, 1, NULL, 3, 0), +(2, 1, 2, NULL, 3, 0), +(3, 1, 3, NULL, 3, 0); + +INSERT INTO acl_entry (id, acl_object_identity, ace_order, sid, mask, granting, audit_success, audit_failure) VALUES +(1, 1, 1, 1, 1, 1, 1, 1), +(2, 1, 2, 1, 2, 1, 1, 1), +(3, 1, 3, 3, 1, 1, 1, 1), +(4, 2, 1, 2, 1, 1, 1, 1), +(5, 2, 2, 3, 1, 1, 1, 1), +(6, 3, 1, 3, 1, 1, 1, 1), +(7, 3, 2, 3, 2, 1, 1, 1); diff --git a/2020-08/spring-24-SS-ACL/src/main/resources/schema.sql b/2020-08/spring-24-SS-ACL/src/main/resources/schema.sql new file mode 100644 index 00000000..9f740482 --- /dev/null +++ b/2020-08/spring-24-SS-ACL/src/main/resources/schema.sql @@ -0,0 +1,58 @@ +create table IF NOT EXISTS system_message (id integer not null, content varchar(255), primary key (id)); + +CREATE TABLE IF NOT EXISTS acl_sid ( + id bigint(20) NOT NULL AUTO_INCREMENT, + principal tinyint(1) NOT NULL, + sid varchar(100) NOT NULL, + PRIMARY KEY (id), + UNIQUE KEY unique_uk_1 (sid,principal) +); + +CREATE TABLE IF NOT EXISTS acl_class ( + id bigint(20) NOT NULL AUTO_INCREMENT, + class varchar(255) NOT NULL, + PRIMARY KEY (id), + UNIQUE KEY unique_uk_2 (class) +); + +CREATE TABLE IF NOT EXISTS acl_entry ( + id bigint(20) NOT NULL AUTO_INCREMENT, + acl_object_identity bigint(20) NOT NULL, + ace_order int(11) NOT NULL, + sid bigint(20) NOT NULL, + mask int(11) NOT NULL, + granting tinyint(1) NOT NULL, + audit_success tinyint(1) NOT NULL, + audit_failure tinyint(1) NOT NULL, + PRIMARY KEY (id), + UNIQUE KEY unique_uk_4 (acl_object_identity,ace_order) +); + +CREATE TABLE IF NOT EXISTS acl_object_identity ( + id bigint(20) NOT NULL AUTO_INCREMENT, + object_id_class bigint(20) NOT NULL, + object_id_identity bigint(20) NOT NULL, + parent_object bigint(20) DEFAULT NULL, + owner_sid bigint(20) DEFAULT NULL, + entries_inheriting tinyint(1) NOT NULL, + PRIMARY KEY (id), + UNIQUE KEY unique_uk_3 (object_id_class,object_id_identity) +); + +ALTER TABLE acl_entry +ADD FOREIGN KEY (acl_object_identity) REFERENCES acl_object_identity(id); + +ALTER TABLE acl_entry +ADD FOREIGN KEY (sid) REFERENCES acl_sid(id); + +-- +-- Constraints for table acl_object_identity +-- +ALTER TABLE acl_object_identity +ADD FOREIGN KEY (parent_object) REFERENCES acl_object_identity (id); + +ALTER TABLE acl_object_identity +ADD FOREIGN KEY (object_id_class) REFERENCES acl_class (id); + +ALTER TABLE acl_object_identity +ADD FOREIGN KEY (owner_sid) REFERENCES acl_sid (id); \ No newline at end of file diff --git a/2020-08/spring-24-SS-ACL/src/main/resources/templates/error.html b/2020-08/spring-24-SS-ACL/src/main/resources/templates/error.html new file mode 100644 index 00000000..f28b51df --- /dev/null +++ b/2020-08/spring-24-SS-ACL/src/main/resources/templates/error.html @@ -0,0 +1,9 @@ + + + + + + +Вам доступ запрещён! + + diff --git a/2020-08/spring-24-SS-ACL/src/main/resources/templates/index.html b/2020-08/spring-24-SS-ACL/src/main/resources/templates/index.html new file mode 100644 index 00000000..79347f42 --- /dev/null +++ b/2020-08/spring-24-SS-ACL/src/main/resources/templates/index.html @@ -0,0 +1,15 @@ + + + + + + +login +
+logout +
+h2-console +
+/swagger + + diff --git a/2020-08/spring-24-WebFlux/pom.xml b/2020-08/spring-24-WebFlux/pom.xml new file mode 100644 index 00000000..a4b5e268 --- /dev/null +++ b/2020-08/spring-24-WebFlux/pom.xml @@ -0,0 +1,72 @@ + + + 4.0.0 + + ru.otus + spring-framework-24-webflux + 1.0-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-parent + 2.2.6.RELEASE + + + + + org.springframework.boot + spring-boot-starter-webflux + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + org.springframework.security + spring-security-core + + + org.springframework.security + spring-security-config + + + org.springframework.security + spring-security-web + + + + + + + + + org.springframework.boot + spring-boot-starter-data-mongodb-reactive + + + + de.flapdoodle.embed + de.flapdoodle.embed.mongo + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + spring-milestone + http://repo.spring.io/milestone + + + diff --git a/2020-08/spring-24-WebFlux/src/main/java/ru/otus/spring/Main.java b/2020-08/spring-24-WebFlux/src/main/java/ru/otus/spring/Main.java new file mode 100644 index 00000000..490b4b3a --- /dev/null +++ b/2020-08/spring-24-WebFlux/src/main/java/ru/otus/spring/Main.java @@ -0,0 +1,22 @@ +package ru.otus.spring; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.core.io.ClassPathResource; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.RouterFunctions; +import org.springframework.web.reactive.function.server.ServerResponse; + +@SpringBootApplication +public class Main { + + public static void main(String[] args) { + SpringApplication.run(Main.class); + } + + @Bean + RouterFunction staticResourceRouter() { + return RouterFunctions.resources("/**.html", new ClassPathResource("static/")); + } +} diff --git a/2020-08/spring-24-WebFlux/src/main/java/ru/otus/spring/data/Person.java b/2020-08/spring-24-WebFlux/src/main/java/ru/otus/spring/data/Person.java new file mode 100644 index 00000000..067c0b64 --- /dev/null +++ b/2020-08/spring-24-WebFlux/src/main/java/ru/otus/spring/data/Person.java @@ -0,0 +1,41 @@ +package ru.otus.spring.data; + +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document +public class Person { + + @Id + private String id; + + private String name; + + public Person() { + } + + public Person(String name) { + this.name = name; + } + + public Person(String id, String name) { + this.id = id; + this.name = name; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/2020-08/spring-24-WebFlux/src/main/java/ru/otus/spring/data/PersonRepository.java b/2020-08/spring-24-WebFlux/src/main/java/ru/otus/spring/data/PersonRepository.java new file mode 100644 index 00000000..2f1994d3 --- /dev/null +++ b/2020-08/spring-24-WebFlux/src/main/java/ru/otus/spring/data/PersonRepository.java @@ -0,0 +1,11 @@ +package ru.otus.spring.data; + +import org.springframework.data.mongodb.repository.ReactiveMongoRepository; +import org.springframework.stereotype.Repository; +import reactor.core.publisher.Mono; + +@Repository +public interface PersonRepository extends ReactiveMongoRepository { + + Mono findByName(String string); +} diff --git a/2020-08/spring-24-WebFlux/src/main/java/ru/otus/spring/rest/PersonController.java b/2020-08/spring-24-WebFlux/src/main/java/ru/otus/spring/rest/PersonController.java new file mode 100644 index 00000000..f1d4333c --- /dev/null +++ b/2020-08/spring-24-WebFlux/src/main/java/ru/otus/spring/rest/PersonController.java @@ -0,0 +1,34 @@ +package ru.otus.spring.rest; + +import org.springframework.web.bind.annotation.*; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import ru.otus.spring.data.Person; +import ru.otus.spring.data.PersonRepository; + +@RestController +public class PersonController { + + private final PersonRepository personRepository; + + public PersonController(PersonRepository personRepository) { + this.personRepository = personRepository; + } + + @GetMapping("/person") + public Flux getAll() { + return personRepository.findAll(); + } + + @GetMapping("/person/find") + public Mono find(@RequestParam("name") String name) { + return personRepository.findByName(name) + .cache(); + } + + + @PostMapping("/person") + public Mono savePerson(@RequestBody Person person) { + return personRepository.save(person); + } +} diff --git a/2020-08/spring-24-WebFlux/src/main/java/ru/otus/spring/security/SecurityConfiguration.java b/2020-08/spring-24-WebFlux/src/main/java/ru/otus/spring/security/SecurityConfiguration.java new file mode 100644 index 00000000..b779f380 --- /dev/null +++ b/2020-08/spring-24-WebFlux/src/main/java/ru/otus/spring/security/SecurityConfiguration.java @@ -0,0 +1,49 @@ +package ru.otus.spring.security; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity; +import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; +import org.springframework.security.config.web.server.ServerHttpSecurity; +import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; +import org.springframework.security.core.userdetails.ReactiveUserDetailsService; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.crypto.password.NoOpPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.server.SecurityWebFilterChain; + +@Configuration +@EnableWebFluxSecurity +@EnableReactiveMethodSecurity +public class SecurityConfiguration { + + @Bean + public SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) { + return http + .authorizeExchange() + .pathMatchers(HttpMethod.GET, "/authenticated.html").hasRole("USER") + // anonymous + .anyExchange().permitAll() + .and() + .httpBasic() + .and() + .build(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return NoOpPasswordEncoder.getInstance(); + } + + @Bean + public ReactiveUserDetailsService userDetailsService() { + UserDetails user = User + .withUsername("user") + .password("password") + .roles("USER") + .build(); + return new MapReactiveUserDetailsService(user); + } +} diff --git a/2020-08/spring-24-WebFlux/src/main/resources/static/authenticated.html b/2020-08/spring-24-WebFlux/src/main/resources/static/authenticated.html new file mode 100644 index 00000000..e4756c01 --- /dev/null +++ b/2020-08/spring-24-WebFlux/src/main/resources/static/authenticated.html @@ -0,0 +1,9 @@ + + + + + + +Только для аторизованных + + diff --git a/2020-08/spring-24-WebFlux/src/main/resources/static/index.html b/2020-08/spring-24-WebFlux/src/main/resources/static/index.html new file mode 100644 index 00000000..a89ba331 --- /dev/null +++ b/2020-08/spring-24-WebFlux/src/main/resources/static/index.html @@ -0,0 +1,11 @@ + + + + + + +/public.html +
+/authenticated.html + + diff --git a/2020-08/spring-24-WebFlux/src/main/resources/static/public.html b/2020-08/spring-24-WebFlux/src/main/resources/static/public.html new file mode 100644 index 00000000..77188469 --- /dev/null +++ b/2020-08/spring-24-WebFlux/src/main/resources/static/public.html @@ -0,0 +1,9 @@ + + + + + + +Доступен всем + + From fa392112f945a66bddff5738029f21e8fa8944bc Mon Sep 17 00:00:00 2001 From: kataus Date: Wed, 25 Nov 2020 18:41:34 +0300 Subject: [PATCH 2/3] =?UTF-8?q?=D0=94=D0=BE=D0=BB=D0=B3=20=D0=BF=D0=BE=20S?= =?UTF-8?q?PeL=20=D0=BA=20=D0=B7=D0=B0=D0=BD=D1=8F=D1=82=D0=B8=D1=8E=2023?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/ru/otus/spring/service/MyService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/2020-08/spring-23/src/main/java/ru/otus/spring/service/MyService.java b/2020-08/spring-23/src/main/java/ru/otus/spring/service/MyService.java index 0c5fef62..064ad676 100644 --- a/2020-08/spring-23/src/main/java/ru/otus/spring/service/MyService.java +++ b/2020-08/spring-23/src/main/java/ru/otus/spring/service/MyService.java @@ -6,7 +6,7 @@ import org.springframework.stereotype.Service; @Service public class MyService { - @PreAuthorize("hasRole('ROLE_USER')") + @PreAuthorize("hasRole('ROLE_USER') && {new java.util.Random().nextInt()%2 == 0}") public String onlyUser() { return "My love"; } From 826305f05723a9cea739ebb93d1cdcecb03e0b4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=9E=D1=80=D1=83=D0=B4=D0=B6=D0=B5=D0=B2?= Date: Fri, 27 Nov 2020 17:43:56 +0400 Subject: [PATCH 3/3] 2020-08 spring-25-spring-batch added --- .../spring-batch-demo/.gitignore | 6 + .../spring-batch-demo/entries.csv | 16 ++ .../spring-batch-demo/pom.xml | 127 +++++++++++++ .../spring-batch-demo/spring-shell.log | 3 + .../ru/otus/example/springbatch/Main.java | 19 ++ .../chandgelogs/InitMongoDBDataChangeLog.java | 45 +++++ .../example/springbatch/config/AppProps.java | 14 ++ .../springbatch/config/BatchConfig.java | 19 ++ .../example/springbatch/config/JobConfig.java | 169 ++++++++++++++++++ .../example/springbatch/model/Person.java | 13 ++ .../service/HappyBirthdayService.java | 13 ++ .../springbatch/shell/BatchCommands.java | 60 +++++++ .../src/main/resources/application.yml | 37 ++++ .../springbatch/config/ImportUserJobTest.java | 73 ++++++++ .../src/test/resources/application.yml | 24 +++ .../test/resources/expected-test-output.dat | 16 ++ .../src/test/resources/test-entries.csv | 16 ++ 17 files changed, 670 insertions(+) create mode 100644 2020-08/spring-25-spring-batch/spring-batch-demo/.gitignore create mode 100644 2020-08/spring-25-spring-batch/spring-batch-demo/entries.csv create mode 100644 2020-08/spring-25-spring-batch/spring-batch-demo/pom.xml create mode 100644 2020-08/spring-25-spring-batch/spring-batch-demo/spring-shell.log create mode 100644 2020-08/spring-25-spring-batch/spring-batch-demo/src/main/java/ru/otus/example/springbatch/Main.java create mode 100644 2020-08/spring-25-spring-batch/spring-batch-demo/src/main/java/ru/otus/example/springbatch/chandgelogs/InitMongoDBDataChangeLog.java create mode 100644 2020-08/spring-25-spring-batch/spring-batch-demo/src/main/java/ru/otus/example/springbatch/config/AppProps.java create mode 100644 2020-08/spring-25-spring-batch/spring-batch-demo/src/main/java/ru/otus/example/springbatch/config/BatchConfig.java create mode 100644 2020-08/spring-25-spring-batch/spring-batch-demo/src/main/java/ru/otus/example/springbatch/config/JobConfig.java create mode 100644 2020-08/spring-25-spring-batch/spring-batch-demo/src/main/java/ru/otus/example/springbatch/model/Person.java create mode 100644 2020-08/spring-25-spring-batch/spring-batch-demo/src/main/java/ru/otus/example/springbatch/service/HappyBirthdayService.java create mode 100644 2020-08/spring-25-spring-batch/spring-batch-demo/src/main/java/ru/otus/example/springbatch/shell/BatchCommands.java create mode 100644 2020-08/spring-25-spring-batch/spring-batch-demo/src/main/resources/application.yml create mode 100644 2020-08/spring-25-spring-batch/spring-batch-demo/src/test/java/ru/otus/example/springbatch/config/ImportUserJobTest.java create mode 100644 2020-08/spring-25-spring-batch/spring-batch-demo/src/test/resources/application.yml create mode 100644 2020-08/spring-25-spring-batch/spring-batch-demo/src/test/resources/expected-test-output.dat create mode 100644 2020-08/spring-25-spring-batch/spring-batch-demo/src/test/resources/test-entries.csv diff --git a/2020-08/spring-25-spring-batch/spring-batch-demo/.gitignore b/2020-08/spring-25-spring-batch/spring-batch-demo/.gitignore new file mode 100644 index 00000000..04e0ae48 --- /dev/null +++ b/2020-08/spring-25-spring-batch/spring-batch-demo/.gitignore @@ -0,0 +1,6 @@ +.idea/ +*.iml + +target/ + +output.csv diff --git a/2020-08/spring-25-spring-batch/spring-batch-demo/entries.csv b/2020-08/spring-25-spring-batch/spring-batch-demo/entries.csv new file mode 100644 index 00000000..bde433ce --- /dev/null +++ b/2020-08/spring-25-spring-batch/spring-batch-demo/entries.csv @@ -0,0 +1,16 @@ +Ivan,23 +John,24 +Ivan,23 +John,24 +Ivan,23 +Mary,24 +Ivan,23 +John,24 +Sunny,23 +John,24 +Ivan,23 +John,24 +Ivan,23 +John,24 +Ivan,23 +John,24 diff --git a/2020-08/spring-25-spring-batch/spring-batch-demo/pom.xml b/2020-08/spring-25-spring-batch/spring-batch-demo/pom.xml new file mode 100644 index 00000000..3d35afd1 --- /dev/null +++ b/2020-08/spring-25-spring-batch/spring-batch-demo/pom.xml @@ -0,0 +1,127 @@ + + + 4.0.0 + + ru.otus.example + spring-batch-demo + 1.0-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-parent + 2.4.0 + + + + 11 + 11 + 11 + 4.1.17 + + 2.2.0 + + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-batch + + + + com.h2database + h2 + + + + org.springframework.boot + spring-boot-starter-data-mongodb + + + + + de.flapdoodle.embed + de.flapdoodle.embed.mongo + ${flapdoodle.version} + test + + + + com.github.cloudyrock.mongock + mongock-spring-v5 + ${mongock.version} + + + + com.github.cloudyrock.mongock + mongodb-springdata-v3-driver + ${mongock.version} + + + + + + org.projectlombok + lombok + true + + + + org.springframework.shell + spring-shell-starter + 2.0.1.RELEASE + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.springframework.batch + spring-batch-test + test + + + + org.junit.jupiter + junit-jupiter-api + ${junit-jupiter.version} + test + + + + org.junit.jupiter + junit-jupiter-engine + ${junit-jupiter.version} + test + + + + + spring-batch-demo + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/2020-08/spring-25-spring-batch/spring-batch-demo/spring-shell.log b/2020-08/spring-25-spring-batch/spring-batch-demo/spring-shell.log new file mode 100644 index 00000000..dce70a31 --- /dev/null +++ b/2020-08/spring-25-spring-batch/spring-batch-demo/spring-shell.log @@ -0,0 +1,3 @@ +1606482804292:sm-jo +1606482827241:sm-jl +1606484532264:sm-jo diff --git a/2020-08/spring-25-spring-batch/spring-batch-demo/src/main/java/ru/otus/example/springbatch/Main.java b/2020-08/spring-25-spring-batch/spring-batch-demo/src/main/java/ru/otus/example/springbatch/Main.java new file mode 100644 index 00000000..00a0d2d1 --- /dev/null +++ b/2020-08/spring-25-spring-batch/spring-batch-demo/src/main/java/ru/otus/example/springbatch/Main.java @@ -0,0 +1,19 @@ +package ru.otus.example.springbatch; + +import com.github.cloudyrock.spring.v5.EnableMongock; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.PropertySource; + +import java.io.IOException; + +@EnableMongock +@SpringBootApplication +public class Main { + + public static void main(String[] args) { + SpringApplication.run(Main.class, args); + } +} + + diff --git a/2020-08/spring-25-spring-batch/spring-batch-demo/src/main/java/ru/otus/example/springbatch/chandgelogs/InitMongoDBDataChangeLog.java b/2020-08/spring-25-spring-batch/spring-batch-demo/src/main/java/ru/otus/example/springbatch/chandgelogs/InitMongoDBDataChangeLog.java new file mode 100644 index 00000000..feb52399 --- /dev/null +++ b/2020-08/spring-25-spring-batch/spring-batch-demo/src/main/java/ru/otus/example/springbatch/chandgelogs/InitMongoDBDataChangeLog.java @@ -0,0 +1,45 @@ +package ru.otus.example.springbatch.chandgelogs; + +import com.github.cloudyrock.mongock.ChangeLog; +import com.github.cloudyrock.mongock.ChangeSet; +import com.github.cloudyrock.mongock.driver.mongodb.springdata.v3.decorator.impl.MongockTemplate; +import com.mongodb.client.MongoDatabase; +import ru.otus.example.springbatch.model.Person; + +@ChangeLog(order = "001") +public class InitMongoDBDataChangeLog { + + @ChangeSet(order = "000", id = "dropDB", author = "stvort", runAlways = true) + public void dropDB(MongoDatabase database){ + database.drop(); + } + + @ChangeSet(order = "001", id = "initPersons", author = "stvort", runAlways = true) + public void initPersons(MongockTemplate template){ + template.save(new Person("Джон", 21)); + template.save(new Person("Игорь", 32)); + template.save(new Person("Дмитрий", 52)); + template.save(new Person("Михаил", 22)); + template.save(new Person("Герман", 33)); + template.save(new Person("Джон", 21)); + template.save(new Person("Игорь", 32)); + template.save(new Person("Дмитрий", 52)); + template.save(new Person("Михаил", 22)); + template.save(new Person("Герман", 33)); + template.save(new Person("Джон", 21)); + template.save(new Person("Игорь", 32)); + template.save(new Person("Дмитрий", 52)); + template.save(new Person("Михаил", 22)); + template.save(new Person("Герман", 33)); + template.save(new Person("Джон", 21)); + template.save(new Person("Игорь", 32)); + template.save(new Person("Дмитрий", 52)); + template.save(new Person("Михаил", 22)); + template.save(new Person("Герман", 33)); + template.save(new Person("Джон", 21)); + template.save(new Person("Игорь", 32)); + template.save(new Person("Дмитрий", 52)); + template.save(new Person("Михаил", 22)); + template.save(new Person("Герман", 33)); + } +} diff --git a/2020-08/spring-25-spring-batch/spring-batch-demo/src/main/java/ru/otus/example/springbatch/config/AppProps.java b/2020-08/spring-25-spring-batch/spring-batch-demo/src/main/java/ru/otus/example/springbatch/config/AppProps.java new file mode 100644 index 00000000..5d9deb61 --- /dev/null +++ b/2020-08/spring-25-spring-batch/spring-batch-demo/src/main/java/ru/otus/example/springbatch/config/AppProps.java @@ -0,0 +1,14 @@ +package ru.otus.example.springbatch.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Data +@Component +@ConfigurationProperties("app") +public class AppProps { + private String inputFile; + private String outputFile; + +} diff --git a/2020-08/spring-25-spring-batch/spring-batch-demo/src/main/java/ru/otus/example/springbatch/config/BatchConfig.java b/2020-08/spring-25-spring-batch/spring-batch-demo/src/main/java/ru/otus/example/springbatch/config/BatchConfig.java new file mode 100644 index 00000000..54525aea --- /dev/null +++ b/2020-08/spring-25-spring-batch/spring-batch-demo/src/main/java/ru/otus/example/springbatch/config/BatchConfig.java @@ -0,0 +1,19 @@ +package ru.otus.example.springbatch.config; + +import org.springframework.batch.core.configuration.JobRegistry; +import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.configuration.support.JobRegistryBeanPostProcessor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@EnableBatchProcessing +@Configuration +public class BatchConfig { + @Bean + public JobRegistryBeanPostProcessor postProcessor(JobRegistry jobRegistry) { + var processor = new JobRegistryBeanPostProcessor(); + processor.setJobRegistry(jobRegistry); + return processor; + } + +} diff --git a/2020-08/spring-25-spring-batch/spring-batch-demo/src/main/java/ru/otus/example/springbatch/config/JobConfig.java b/2020-08/spring-25-spring-batch/spring-batch-demo/src/main/java/ru/otus/example/springbatch/config/JobConfig.java new file mode 100644 index 00000000..bf9df4bb --- /dev/null +++ b/2020-08/spring-25-spring-batch/spring-batch-demo/src/main/java/ru/otus/example/springbatch/config/JobConfig.java @@ -0,0 +1,169 @@ +package ru.otus.example.springbatch.config; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.batch.core.*; +import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; +import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; +import org.springframework.batch.core.configuration.annotation.StepScope; +import org.springframework.batch.core.launch.support.RunIdIncrementer; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.item.ItemProcessor; +import org.springframework.batch.item.ItemReader; +import org.springframework.batch.item.file.FlatFileItemReader; +import org.springframework.batch.item.file.FlatFileItemWriter; +import org.springframework.batch.item.file.builder.FlatFileItemReaderBuilder; +import org.springframework.batch.item.file.builder.FlatFileItemWriterBuilder; +import org.springframework.batch.item.file.transform.DelimitedLineAggregator; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.FileSystemResource; +import ru.otus.example.springbatch.model.Person; +import ru.otus.example.springbatch.service.HappyBirthdayService; + +import java.util.List; + + +@SuppressWarnings("all") +@Configuration +public class JobConfig { + private static final int CHUNK_SIZE = 5; + private final Logger logger = LoggerFactory.getLogger("Batch"); + + public static final String OUTPUT_FILE_NAME = "outputFileName"; + public static final String INPUT_FILE_NAME = "inputFileName"; + public static final String IMPORT_USER_JOB_NAME = "importUserJob"; + + @Autowired + private JobBuilderFactory jobBuilderFactory; + + @Autowired + private StepBuilderFactory stepBuilderFactory; + + @StepScope + @Bean + public FlatFileItemReader reader(@Value("#{jobParameters['" + INPUT_FILE_NAME + "']}") String inputFileName) { + return new FlatFileItemReaderBuilder() + .name("personItemReader") + .resource(new FileSystemResource(inputFileName)) + + // Работа через lineMapper + .lineMapper((s, i) -> { + String[] fieldsValues = s.split(","); + return new Person(fieldsValues[0], Integer.parseInt(fieldsValues[1])); + }) +/* + + // Работа через fieldSetMapper + .delimited() + .names("name", "age") + .fieldSetMapper(new BeanWrapperFieldSetMapper<>() {{ + setTargetType(Person.class); + }}) +*/ + + + .build(); + } + + @StepScope + @Bean + public ItemProcessor processor(HappyBirthdayService happyBirthdayService) { + return (ItemProcessor) happyBirthdayService::doHappyBirthday; + } + + @StepScope + @Bean + public FlatFileItemWriter writer(@Value("#{jobParameters['" + OUTPUT_FILE_NAME + "']}") String outputFileName) { + return new FlatFileItemWriterBuilder<>() + .name("personItemWriter") + .resource(new FileSystemResource(outputFileName)) + .lineAggregator(new DelimitedLineAggregator<>()) + .build(); + } + + @Bean + public Job importUserJob(Step step1) { + return jobBuilderFactory.get(IMPORT_USER_JOB_NAME) + .incrementer(new RunIdIncrementer()) + .flow(step1) + .end() + .listener(new JobExecutionListener() { + @Override + public void beforeJob(JobExecution jobExecution) { + logger.info("Начало job"); + } + + @Override + public void afterJob(JobExecution jobExecution) { + logger.info("Конец job"); + } + }) + .build(); + } + + @Bean + public Step step1(FlatFileItemWriter writer, ItemReader reader, ItemProcessor itemProcessor) { + return stepBuilderFactory.get("step1") + .chunk(CHUNK_SIZE) + .reader(reader) + .processor(itemProcessor) + .writer(writer) + .listener(new ItemReadListener() { + public void beforeRead() { + logger.info("Начало чтения"); + } + + public void afterRead(Object o) { + logger.info("Конец чтения"); + } + + public void onReadError(Exception e) { + logger.info("Ошибка чтения"); + } + }) + .listener(new ItemWriteListener() { + public void beforeWrite(List list) { + logger.info("Начало записи"); + } + + public void afterWrite(List list) { + logger.info("Конец записи"); + } + + public void onWriteError(Exception e, List list) { + logger.info("Ошибка записи"); + } + }) + .listener(new ItemProcessListener() { + public void beforeProcess(Object o) { + logger.info("Начало обработки"); + } + + public void afterProcess(Object o, Object o2) { + logger.info("Конец обработки"); + } + + public void onProcessError(Object o, Exception e) { + logger.info("Ошбка обработки"); + } + }) + .listener(new ChunkListener() { + public void beforeChunk(ChunkContext chunkContext) { + logger.info("Начало пачки"); + } + + public void afterChunk(ChunkContext chunkContext) { + logger.info("Конец пачки"); + } + + public void afterChunkError(ChunkContext chunkContext) { + logger.info("Ошибка пачки"); + } + }) +// .taskExecutor(new SimpleAsyncTaskExecutor()) + .build(); + } +} diff --git a/2020-08/spring-25-spring-batch/spring-batch-demo/src/main/java/ru/otus/example/springbatch/model/Person.java b/2020-08/spring-25-spring-batch/spring-batch-demo/src/main/java/ru/otus/example/springbatch/model/Person.java new file mode 100644 index 00000000..b95cb1ff --- /dev/null +++ b/2020-08/spring-25-spring-batch/spring-batch-demo/src/main/java/ru/otus/example/springbatch/model/Person.java @@ -0,0 +1,13 @@ +package ru.otus.example.springbatch.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@AllArgsConstructor +@Data +public class Person { + private String name; + private int age; +} diff --git a/2020-08/spring-25-spring-batch/spring-batch-demo/src/main/java/ru/otus/example/springbatch/service/HappyBirthdayService.java b/2020-08/spring-25-spring-batch/spring-batch-demo/src/main/java/ru/otus/example/springbatch/service/HappyBirthdayService.java new file mode 100644 index 00000000..00ec7287 --- /dev/null +++ b/2020-08/spring-25-spring-batch/spring-batch-demo/src/main/java/ru/otus/example/springbatch/service/HappyBirthdayService.java @@ -0,0 +1,13 @@ +package ru.otus.example.springbatch.service; + +import org.springframework.stereotype.Service; +import ru.otus.example.springbatch.model.Person; + +@Service +public class HappyBirthdayService { + + public Person doHappyBirthday(Person person){ + person.setAge(person.getAge() + 1); + return person; + } +} diff --git a/2020-08/spring-25-spring-batch/spring-batch-demo/src/main/java/ru/otus/example/springbatch/shell/BatchCommands.java b/2020-08/spring-25-spring-batch/spring-batch-demo/src/main/java/ru/otus/example/springbatch/shell/BatchCommands.java new file mode 100644 index 00000000..0e4296c4 --- /dev/null +++ b/2020-08/spring-25-spring-batch/spring-batch-demo/src/main/java/ru/otus/example/springbatch/shell/BatchCommands.java @@ -0,0 +1,60 @@ +package ru.otus.example.springbatch.shell; + +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.JobParametersInvalidException; +import org.springframework.batch.core.explore.JobExplorer; +import org.springframework.batch.core.launch.JobInstanceAlreadyExistsException; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.launch.JobOperator; +import org.springframework.batch.core.launch.NoSuchJobException; +import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; +import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; +import org.springframework.batch.core.repository.JobRestartException; +import org.springframework.shell.standard.ShellComponent; +import org.springframework.shell.standard.ShellMethod; +import ru.otus.example.springbatch.config.AppProps; + + +import static ru.otus.example.springbatch.config.JobConfig.*; + +@RequiredArgsConstructor +@ShellComponent +public class BatchCommands { + + private final AppProps appProps; + private final Job importUserJob; + + private final JobLauncher jobLauncher; + private final JobOperator jobOperator; + private final JobExplorer jobExplorer; + + //http://localhost:8080/h2-console/ + + @ShellMethod(value = "startMigrationJobWithJobLauncher", key = "sm-jl") + public void startMigrationJobWithJobLauncher() throws Exception { + JobExecution execution = jobLauncher.run(importUserJob, new JobParametersBuilder() + .addString(INPUT_FILE_NAME, appProps.getInputFile()) + .addString(OUTPUT_FILE_NAME, appProps.getOutputFile()) + .toJobParameters()); + System.out.println(execution); + } + + @ShellMethod(value = "startMigrationJobWithJobOperator", key = "sm-jo") + public void startMigrationJobWithJobOperator() throws Exception { + Long executionId = jobOperator.start(IMPORT_USER_JOB_NAME, + INPUT_FILE_NAME + "=" + appProps.getInputFile() + "\n" + + OUTPUT_FILE_NAME + "=" + appProps.getOutputFile() + ); + System.out.println(jobOperator.getSummary(executionId)); + } + + @ShellMethod(value = "showInfo", key = "i") + public void showInfo() { + System.out.println(jobExplorer.getJobNames()); + System.out.println(jobExplorer.getLastJobInstance(IMPORT_USER_JOB_NAME)); + } +} diff --git a/2020-08/spring-25-spring-batch/spring-batch-demo/src/main/resources/application.yml b/2020-08/spring-25-spring-batch/spring-batch-demo/src/main/resources/application.yml new file mode 100644 index 00000000..74c2b363 --- /dev/null +++ b/2020-08/spring-25-spring-batch/spring-batch-demo/src/main/resources/application.yml @@ -0,0 +1,37 @@ +spring: + batch: + job: + enabled: false + shell: + interactive: + enabled: true + + datasource: + url: jdbc:h2:mem:testdb + driverClassName: org.h2.Driver + username: sa + password: + + h2: + console: + enabled: true + path: /h2-console + + data: + mongodb: + #uri: mongodb://localhost + host: localhost + port: 27017 + database: SpringBatchExampleDB + +mongock: + runner-type: InitializingBean + change-logs-scan-package: + - ru.otus.example.springbatch.chandgelogs + +app: + ages-count-to-add: 1 + input-file: entries.csv + output-file: output.dat + +#debug: true diff --git a/2020-08/spring-25-spring-batch/spring-batch-demo/src/test/java/ru/otus/example/springbatch/config/ImportUserJobTest.java b/2020-08/spring-25-spring-batch/spring-batch-demo/src/test/java/ru/otus/example/springbatch/config/ImportUserJobTest.java new file mode 100644 index 00000000..97b1b035 --- /dev/null +++ b/2020-08/spring-25-spring-batch/spring-batch-demo/src/test/java/ru/otus/example/springbatch/config/ImportUserJobTest.java @@ -0,0 +1,73 @@ +package ru.otus.example.springbatch.config; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.test.AssertFile; +import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.batch.test.JobRepositoryTestUtils; +import org.springframework.batch.test.context.SpringBatchTest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.core.io.FileSystemResource; + + +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.Objects; + +import static org.assertj.core.api.Assertions.assertThat; +import static ru.otus.example.springbatch.config.JobConfig.*; + +@SpringBootTest +@SpringBatchTest +class ImportUserJobTest { + + private static final String TEST_INPUT_FILE_NAME = "test-entries.csv"; + private static final String EXPECTED_OUTPUT_FILE_NAME = "expected-test-output.dat"; + private static final String TEST_OUTPUT_FILE_NAME = "test-output.dat"; + + @Autowired + private JobLauncherTestUtils jobLauncherTestUtils; + + @Autowired + private JobRepositoryTestUtils jobRepositoryTestUtils; + + @BeforeEach + void clearMetaData() { + jobRepositoryTestUtils.removeJobExecutions(); + } + + @Test + void testJob() throws Exception { + var classLoader = ImportUserJobTest.class.getClassLoader(); + var testInputFileName = URLDecoder.decode( + Objects.requireNonNull(classLoader.getResource(TEST_INPUT_FILE_NAME)).getFile(), + StandardCharsets.UTF_8 + ); + var expectedResultFileName = URLDecoder.decode( + Objects.requireNonNull(classLoader.getResource(EXPECTED_OUTPUT_FILE_NAME)).getFile(), + StandardCharsets.UTF_8 + ); + + FileSystemResource expectedResult = new FileSystemResource(expectedResultFileName); + FileSystemResource actualResult = new FileSystemResource(TEST_OUTPUT_FILE_NAME); + + Job job = jobLauncherTestUtils.getJob(); + assertThat(job).isNotNull() + .extracting(Job::getName) + .isEqualTo(IMPORT_USER_JOB_NAME); + + JobParameters parameters = new JobParametersBuilder() + .addString(INPUT_FILE_NAME, testInputFileName) + .addString(OUTPUT_FILE_NAME, TEST_OUTPUT_FILE_NAME) + .toJobParameters(); + JobExecution jobExecution = jobLauncherTestUtils.launchJob(parameters); + + assertThat(jobExecution.getExitStatus().getExitCode()).isEqualTo("COMPLETED"); + AssertFile.assertFileEquals(expectedResult, actualResult); + } +} \ No newline at end of file diff --git a/2020-08/spring-25-spring-batch/spring-batch-demo/src/test/resources/application.yml b/2020-08/spring-25-spring-batch/spring-batch-demo/src/test/resources/application.yml new file mode 100644 index 00000000..49c12c2e --- /dev/null +++ b/2020-08/spring-25-spring-batch/spring-batch-demo/src/test/resources/application.yml @@ -0,0 +1,24 @@ +spring: + batch: + job: + enabled: false + shell: + interactive: + enabled: false + + datasource: + url: jdbc:h2:mem:testdb + driverClassName: org.h2.Driver + username: sa + password: + + data: + mongodb: + host: localhost + port: 0 + database: SpringBatchTestExampleDB + +mongock: + runner-type: InitializingBean + change-logs-scan-package: + - ru.otus.example.springbatch.chandgelogs \ No newline at end of file diff --git a/2020-08/spring-25-spring-batch/spring-batch-demo/src/test/resources/expected-test-output.dat b/2020-08/spring-25-spring-batch/spring-batch-demo/src/test/resources/expected-test-output.dat new file mode 100644 index 00000000..b4c926b1 --- /dev/null +++ b/2020-08/spring-25-spring-batch/spring-batch-demo/src/test/resources/expected-test-output.dat @@ -0,0 +1,16 @@ +Person(name=Ivan, age=24) +Person(name=John, age=25) +Person(name=Ivan, age=24) +Person(name=John, age=25) +Person(name=Ivan, age=24) +Person(name=Mary, age=25) +Person(name=Ivan, age=24) +Person(name=John, age=25) +Person(name=Sunny, age=24) +Person(name=John, age=25) +Person(name=Ivan, age=24) +Person(name=John, age=25) +Person(name=Ivan, age=24) +Person(name=John, age=25) +Person(name=Ivan, age=24) +Person(name=John, age=25) diff --git a/2020-08/spring-25-spring-batch/spring-batch-demo/src/test/resources/test-entries.csv b/2020-08/spring-25-spring-batch/spring-batch-demo/src/test/resources/test-entries.csv new file mode 100644 index 00000000..bde433ce --- /dev/null +++ b/2020-08/spring-25-spring-batch/spring-batch-demo/src/test/resources/test-entries.csv @@ -0,0 +1,16 @@ +Ivan,23 +John,24 +Ivan,23 +John,24 +Ivan,23 +Mary,24 +Ivan,23 +John,24 +Sunny,23 +John,24 +Ivan,23 +John,24 +Ivan,23 +John,24 +Ivan,23 +John,24