diff --git a/messanger/ms-teams/dist.txt b/messanger/ms-teams/dist.txt index a7ceaff36..f14c406b6 100644 --- a/messanger/ms-teams/dist.txt +++ b/messanger/ms-teams/dist.txt @@ -1,3 +1,6 @@ +https://github.com/IsmaelMartinez/teams-for-linux + https://flathub.org/apps/com.github.IsmaelMartinez.teams_for_linux + https://techcommunity.microsoft.com/t5/microsoft-teams-blog/microsoft-teams-progressive-web-app-now-available-on-linux/ba-p/3669846 chrome://apps diff --git a/pl/cross/methodologies/microservice/patterns/saga.txt b/pl/cross/methodologies/microservice/patterns/saga.txt index b3cb9a35f..78d0a2d30 100644 --- a/pl/cross/methodologies/microservice/patterns/saga.txt +++ b/pl/cross/methodologies/microservice/patterns/saga.txt @@ -4,6 +4,8 @@ https://docs.axoniq.io/reference-guide/axon-framework/sagas/implementation https://github.com/eventuate-tram/eventuate-tram-sagas 2023 +https://habr.com/ru/articles/744460/ + axon https://www.baeldung.com/cs/saga-pattern-microservices https://www.vinsguru.com/choreography-saga-pattern-with-spring-boot/ https://www.vinsguru.com/orchestration-saga-pattern-with-spring-boot/ diff --git a/pl/java/libfws/spring/boot/mvc/docs/courses/linkedin.txt b/pl/java/libfws/spring/boot/mvc/docs/courses/linkedin.txt new file mode 100644 index 000000000..7e4f65a76 --- /dev/null +++ b/pl/java/libfws/spring/boot/mvc/docs/courses/linkedin.txt @@ -0,0 +1,380 @@ +2019 +https://www.linkedin.com/learning/spring-spring-mvc-2 + Incoming HTTP Request is Processed by + DispatcherServlet->HandlerMapping->Controller (Front Controller DP) + ->Controller->...processing...->back-to-DispatcherServlet + ->ViewResolver + + Instead of String method at Controller we can use + ModelAndView + .setViewName + .setStatus + .addObject(name, value) + SearchController { + @GetMapping("/search") + public String search(@RequestParam("search") String search, Model model) { + ... + List products = searchRepository.searchByName(search); + model.addAttribute("products", products); + ... + } + } + RegistrationController { + @PostMapping("/registeruser") + public String registerUser(@ModelAttribute("newuser") User user) { //
+ + } + } + HomeController { + ... + @ModelAttribute("newuser") + public User getDefaultUser() { return new User(); } + @ModelAttribute("genderItems") + public List getGenderItems() { return List.of("Male", "Female", "Other"); } + } + + javax.validation -> hibernate validator + annotate with @Valid our beans at controller and add BindingResult param right after the annotated one + and add to our form elements + in order to localize error messages, add resources/ValidationMessages.properties, add msg.key=msg.value + and use ... message="{msg.key}" + + binders extend ModelAttribute-s + User { + + private Date dateOfBirth; // + } + RegistrationController { + @InitBinder + public void initBinder(WebDataBinder binder) { + binder.registerCustomEditor(Date.class, "dateOfBirth", new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"))); + } + } + + converters extend binders concept, transform request data to desired types + built in converters present, but we can provide custom ones + + public enum Gender { MALE, FEMALE, OTHER } + User { + @Enumerated(EnumType.STRING) + private Gender gender; + } + + public class StringToEnumConverter implements .core.convert.Converter { + @Override + public Gender convert(String s) { + ... regurn Gender.GGG ...; + } + } + + ApplicationConfig { + ... + @Override + protected void addFormatters(FormatterRegistry registry) { + registry.addConverter(new StringToEnumConverter()); + } + } + + HandlerMapping - maps request to handler + HandlerAdapter - invokes the handler + ViewResolver - helps resolve views + LocaleResolver - helps with l10n and i18n support + ThemeResolver + HandlerExceptionResolver - helps with exception handling + ExceptionHandlerExceptionResolver - define ex-handling methods in controllers (annotated by @ExceptionHandler) + SimpleMappingExceptionResolver - maps each ex-n class with an error page + DefaultHandlerExceptionResolver - default, maps exceptions to error codes + ResponseStatusExceptionResolver - resolves custom exceptions using status code defined in @ResponseStatus (for ex-ns) + to disable standard error resolver, we need to add property + spring.autoconfigure.exclude=ord.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration + + public class Login { + private String login; + private String password; + } + HomeController { + ... @ModelAttribute("login") public Login getDefaultLogin() { return new Login(); } + } + + public class ApplicationException extends RuntimeException { ... } + + LoginController { + @PostMapping("/login") + public String login(@ModelAttribute("login") Login login) { + User user = userRepository.searchByName(login.getUsername()); + if (user == null) { + throw new ApplicationException("User not found"); + } + } + @ExceptionHandler(ApplicationException.class) + public String handleException(ApplicationException ex, WebRequest request) { + return "error"; + } + } + for global handler + @ControllerAdvice + public class ApplicationExceptionHandler { + + @ExceptionHandler(ApplicationException.class) + public String handleException(ApplicationException ex, WebRequest request) { + return "error"; + } + } + ... + @ControllerAdvice + public class DefaultModelAttributeController { + @ModelAttribute("newuser") + public User getDefaultUser() { return new User(); } + @ModelAttribute("genderItems") + public List getGenderItems() { return List.of("Male", "Female", "Other"); } + ... + } + + @EnableAsync + https://stackoverflow.com/questions/36768234/spring-enableasync-breaks-bean-initialization-order + https://www.baeldung.com/spring-async + ...ApplicationAsync { + @Override + protected void configureAsyncSupport(AsyncSupportConfigurer configurer) + configurer.setTaskExecutor(mvcTaskExecutor()); + configurer.setDefaultTimeout(5000); + } + @Bean public AsyncTaskExecutor mvcTaskExecutor() { ThreadPoolTaskExecutor... } + } + ... SearchController { + public Callable search(..., HttpServletRequest request) { + log.info(request.isAsyncSupported(), Thread.currentThread().getName()); + return () -> { + log.info(Thread.currentThread().getName()); + all the business logic + } + + we can also use DeferredResult + @Autowired + private AsyncTaskExecutor executor; + + + public DeferredResult search(..., HttpServletRequest request) { + DeferredResult defferedResult = new DeferredResult<>(); + ... + executor.execute(() -> { + ... + defferedResult.setResult(search); + }); + return defferedResult; + } + } + + View + ViewResolver - resp by resolving views by names + InternalResourceViewResolver + ResourceBundleViewResolver + XmlViewResolver + VelocityViewResolver, FreeMakerViewResolver, ... + ChainedViewResolver + + DispatcherServlet + HandlerMapping, HandlerAdapter (calls interceptors) + Pre-process Interceptors 1..n + and then (iff all are OK) - calls a Controller + after all business-logic - + Post-process + + Impl-s of HandlerInterceptorAdapter base class + preHandle + postHandle + afterCompletion - after response is completed to the client + ThemeChangeInterceptor + LocaleChangeInterceptor + + public class LoggingInterceptor extends HandlerInterceptorAdapter { + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + ... + } + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { + ... + } + } + at ApplicationConfig { + @Override + protected void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(new LoggingInterceptor()).addPathPatterns("/*") + } + } + + ... @SessionAttributes, @SessionAttribute + @RequestAttribute + + @Controller + @SessionAttributes("login") // @ModelAttribute should exist + public class LoginController { + + // already exposed by ControllerAdvice + // @ModelAttribute("login") public Login getDefaultLogin() { return new Login(); } + } + + @Controller + public class UserProfileController { + @PostMapping("/userprofile") + public String getUserProfile(@SessionAttribute("login") Login login, Model model) { + // and similar for @RequestAttribute + model.setAttribute("username"); // or use ${sessionScope.username} at jsp + return "profile"; // use login in jsp + } + } + + LoginController { + ... + @PostMapping("/login") + { + session.setMaxInactiveTimeout(...) + ... + ... return "forward:/userprofile" + } + } + + @Controller + public class LogoutController { + @GetMapping("/logout") + public String logout(HttpSession session) { + session.invalidate(); + return "login"; + } + } + + @Controller + public class RedirectionController { + @GetMapping("/redirectToLinkedIn") + public String redirectToLinkedIn() { + return "redirect:http://www.linkedin.com"; // absolute URL is needed + } + } + + ThemeResolver facilitates theme resolution + ThemeSource and default impl - ResourceBundleThemeSource + CookieThemeResolver + SessionThemeResolver + FixedThemeResolver + + resources/client-theme1.properties + styleSheet=/css/style1.css + resources/client-theme2.properties + styleSheet=/css/style2.css + + ... ApplicationConfig { + @Bean + public ThemeResolver themeResolver() { + CookieThemeResolver cookieThemeResolver = new CookieThemeResolver(); + cookieThemeResolver.setCookieName("theme"); + cookieThemeResolver.setDefaultThemeName("client-theme1"); + return cookieThemeResolver; + } + ... // also it's important to add an interceptor + @Override + protected void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(new LoggingInterceptor()).addPathPatterns("/*") + registry.addInterceptor(new ThemeChangeInterceptor()); + } + } + // next thing - define at our jsp + <%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> + ... + + + ... + + // we can check with ...?theme=client-theme2... ... resolver will store this in cookie + + l10n - is to use props for msgs + i10n - is more than l10n, including locale treatment + + LocaleResolver + AcceptHeaderLocaleResolver - default one + CookieLocaleResolver + SessionLocaleResolver + + resources/messages.properties + label.home=home + label.search=search + label.login=login + label.linkedin=linkedin + resources/messages_sv.properties // make sure to have UTF-8 + label.home=hem + label.search=sok + label.login=logga in + label.linkedin=... + + // at jsp - use + // default resolver takes locale from client settings of browser + + ... ApplicationConfig { + @Bean + public LocaleResolver localeResolver() { + CookieLocaleResolver cookieLocaleResolver = new CookieLocaleResolver(); + cookieLocaleResolver.setCookieName("locale"); + cookieLocaleResolver.setDefaultLocale(Locale.US); + return cookieLocaleResolver; + } + ... // also it's important to add an interceptor + @Override + protected void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(new LoggingInterceptor()).addPathPatterns("/*") + registry.addInterceptor(new ThemeChangeInterceptor()); + registry.addInterceptor(new LocaleChangeInterceptor()); // can customize with setParamName if need non-default + } + } + + REST: spring uses HttpMessageConvertors for JSON (with Jackson), no view name needed + + + @Controller + public class ProductsRestController { + ... + @GetMapping("/rest/products") + @ResponseBody + public List getProducts() { + return productRepository.findAll(); + } + } + @RestController // implies @ResponseBody for every method + public class ProductsRestController { + ... + @GetMapping("/rest/products") + public ResponseEntity> getProductsByParam(@RequestParam("name") String name) { + List products = productRepository.searchByName(name) + return new ResponseEntity<>(products, HttpStatus.OK); + } + @GetMapping("/rest/products/{id}") + public ResponseEntity> getProductsByPathVar(@PathVariable("name") String name) { + List products = productRepository.searchByName(name) + return new ResponseEntity<>(products, HttpStatus.OK); + } + } + + public class LoginFailureException extends Exception { + ... + } + + @RestController + public class LoginRestController { + ... + + @PostMapping("/rest/loginuser") + public ResponseEntity loginUser(@RequestBody Login login) { + User user = userRepository.searchByName(login.getUsername()); + if (user == null) { + return new ResponseEntity.status(HttpStatus.NOT_FOUND).build(); + } + if (user.getUsername().equals(login.getUsername)) && ... pwd) { + return new ResponseEntity<>("Welcome " + user.getUsername(), HttpStatus.OK); + } else { + throw new LoginFailureException("username or password is incorrect") + } + } + + @ExceptionHandler(LoginFailureException.class) + public ResponseEntity handle Login(LoginFailureException ex) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).body(ex.getMessage()); + } + } diff --git a/pl/java/libfws/spring/boot/test/docs/courses.txt b/pl/java/libfws/spring/boot/test/docs/courses.txt index 6a086017f..723fa7f56 100644 --- a/pl/java/libfws/spring/boot/test/docs/courses.txt +++ b/pl/java/libfws/spring/boot/test/docs/courses.txt @@ -6,3 +6,53 @@ Udemy - Learn integration testing with Spring Boot Advanced Spring - Effective Integration Testing with Spring Boot magnet:?xt=urn:btih:afd24823c218c0cc876504cb86723cfc44b9ab29 ! 201.5m, non-complete + +https://www.linkedin.com/learning/spring-boot-test-driven-development + AopTestUtils + ReflectionTestUtils + MvcTestUtils, Mvc test client, MockMvc + + Unit Test + @ExtendsWith(MockitoExtension.class) + Integration + @SpringBootTest(classes=MyConfig.class) + @AutoConfigureTestDatabase(replace=Replace.ANY) + @AutoConfigureMockMvc + @Autowired + MockMvc mockMvc; + someTestMethod() { + mockMvc.perform(get("/someEndpoint")) + .andExpect(status().isOk()) + .andExpect(content().string(containsString("some-uuid"))) + } + +https://www.linkedin.com/learning/advanced-spring-effective-integration-testing-with-spring-boot + @SpringBootTest + @DataJpaTest (without web layer), @DataJdbcTest, ... @DataRedisTest + @WebMvcTest (loads web-layer only to the context without persistence layer), @RestClientTest, @WebServiceClientTest, @WebFluxTest + @MockBean ? for UT + BDDMockito lib + instead of when(methodCall).then(doSomething) + given(methodCall).will(doSomething) + + TestEntityManager mgr + mgr.persistFlushFind(obj) -> to bypass hbm L1 cache + + @ExtendsWith(MockitoExtension.class) + @InjectMock - uses constructor or setter injection + @Mock - replaces whole class entirely + @Spy - takes existing object and replaces only some methods + @MockBean and @SpyBean + + @ExceptionHandler+@ResponseStatus() + or @ResponseStatus(HttpStatus.NOT_FOUND) + public void someMethod(SomeEx-n) + mockito given().willThrow(...) + + @RestClientTest (MockRestServiceServer, only for RestTemplate) + WireMock is better + + SpringCloudContract (generate tests by contract def-n), + StubRunner fetches generated jar and spins stub-server at config-d port + @SpringBootTest + @AutoConfigureStubRunner(ids="group:artifact:+:8080", stubsMode=StubRunnerProperties.StubsMode.LOCAL) diff --git a/pl/java/libfws/spring/data/jpa/specification-arg-resolver.txt b/pl/java/libfws/spring/data/jpa/specification-arg-resolver.txt new file mode 100644 index 000000000..f35b8798c --- /dev/null +++ b/pl/java/libfws/spring/data/jpa/specification-arg-resolver.txt @@ -0,0 +1,6 @@ +https://github.com/tkaczmarzyk/specification-arg-resolver +https://github.com/tkaczmarzyk/specification-arg-resolver/tree/master/src/main/java/net/kaczmarzyk/spring/data/jpa/web/annotation +https://github.com/tkaczmarzyk/specification-arg-resolver/tree/master/src/main/java/net/kaczmarzyk/spring/data/jpa/domain + +2017 +https://blog.tratif.com/2017/11/23/effective-restful-search-api-in-spring/