diff --git a/patterns-modules/.gitignore b/patterns-modules/.gitignore new file mode 100644 index 0000000000..1d420f6aaf --- /dev/null +++ b/patterns-modules/.gitignore @@ -0,0 +1 @@ +/product-service/ diff --git a/patterns-modules/README.md b/patterns-modules/README.md new file mode 100644 index 0000000000..654beb4cd7 --- /dev/null +++ b/patterns-modules/README.md @@ -0,0 +1,4 @@ +## Patterns Modules + +This module contains articles about design patterns. + diff --git a/patterns-modules/clean-architecture/README.md b/patterns-modules/clean-architecture/README.md new file mode 100644 index 0000000000..4ff1e4c93e --- /dev/null +++ b/patterns-modules/clean-architecture/README.md @@ -0,0 +1,4 @@ +### Relevant Articles: + +- [Clean Architecture with Spring Boot](https://www.baeldung.com/spring-boot-clean-architecture) +- [Anemic vs. Rich Domain Objects](https://www.baeldung.com/java-anemic-vs-rich-domain-objects) diff --git a/patterns-modules/clean-architecture/pom.xml b/patterns-modules/clean-architecture/pom.xml new file mode 100644 index 0000000000..5938b0e596 --- /dev/null +++ b/patterns-modules/clean-architecture/pom.xml @@ -0,0 +1,63 @@ + + + 4.0.0 + clean-architecture + 1.0 + clean-architecture + Project for clean architecture in java + + + com.baeldung + parent-boot-2 + 0.0.1-SNAPSHOT + ../../parent-boot-2 + + + + + com.h2database + h2 + runtime + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + org.junit.platform + junit-platform-engine + + + org.junit.platform + junit-platform-runner + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/CleanArchitectureApplication.java b/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/CleanArchitectureApplication.java new file mode 100644 index 0000000000..ebac2bacf3 --- /dev/null +++ b/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/CleanArchitectureApplication.java @@ -0,0 +1,40 @@ +package com.baeldung.pattern.cleanarchitecture; + +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.core.type.filter.TypeFilter; + +@SpringBootApplication +public class CleanArchitectureApplication { + + public static void main(String[] args) { + SpringApplication.run(CleanArchitectureApplication.class); + } + + @Bean + BeanFactoryPostProcessor beanFactoryPostProcessor(ApplicationContext beanRegistry) { + return beanFactory -> { + genericApplicationContext((BeanDefinitionRegistry) ((AnnotationConfigServletWebServerApplicationContext) beanRegistry).getBeanFactory()); + }; + } + + void genericApplicationContext(BeanDefinitionRegistry beanRegistry) { + ClassPathBeanDefinitionScanner beanDefinitionScanner = new ClassPathBeanDefinitionScanner(beanRegistry); + beanDefinitionScanner.addIncludeFilter(removeModelAndEntitiesFilter()); + beanDefinitionScanner.scan("com.baeldung.pattern.cleanarchitecture"); + } + + static TypeFilter removeModelAndEntitiesFilter() { + return (MetadataReader mr, MetadataReaderFactory mrf) -> !mr.getClassMetadata() + .getClassName() + .endsWith("Model"); + } +} diff --git a/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/CommonUser.java b/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/CommonUser.java new file mode 100644 index 0000000000..c4f105fad5 --- /dev/null +++ b/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/CommonUser.java @@ -0,0 +1,30 @@ +package com.baeldung.pattern.cleanarchitecture.usercreation; + +class CommonUser implements User { + + String name; + String password; + + CommonUser(String name, String password) { + this.name = name; + this.password = password; + } + + CommonUser() { + } + + @Override + public boolean passwordIsValid() { + return password != null && password.length() > 5; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getPassword() { + return password; + } +} diff --git a/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/CommonUserFactory.java b/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/CommonUserFactory.java new file mode 100644 index 0000000000..a2b851da94 --- /dev/null +++ b/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/CommonUserFactory.java @@ -0,0 +1,8 @@ +package com.baeldung.pattern.cleanarchitecture.usercreation; + +class CommonUserFactory implements UserFactory { + @Override + public User create(String name, String password) { + return new CommonUser(name, password); + } +} diff --git a/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/JpaUser.java b/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/JpaUser.java new file mode 100644 index 0000000000..20751f282a --- /dev/null +++ b/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/JpaUser.java @@ -0,0 +1,21 @@ +package com.baeldung.pattern.cleanarchitecture.usercreation; + +class JpaUser implements UserRegisterDsGateway { + + final JpaUserRepository repository; + + JpaUser(JpaUserRepository repository) { + this.repository = repository; + } + + @Override + public boolean existsByName(String name) { + return repository.existsById(name); + } + + @Override + public void save(UserDsRequestModel requestModel) { + UserDataMapper accountDataMapper = new UserDataMapper(requestModel.getName(), requestModel.getPassword(), requestModel.getCreationTime()); + repository.save(accountDataMapper); + } +} diff --git a/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/JpaUserRepository.java b/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/JpaUserRepository.java new file mode 100644 index 0000000000..8565ed7965 --- /dev/null +++ b/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/JpaUserRepository.java @@ -0,0 +1,8 @@ +package com.baeldung.pattern.cleanarchitecture.usercreation; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +interface JpaUserRepository extends JpaRepository { +} diff --git a/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/User.java b/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/User.java new file mode 100644 index 0000000000..aab652f2a1 --- /dev/null +++ b/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/User.java @@ -0,0 +1,9 @@ +package com.baeldung.pattern.cleanarchitecture.usercreation; + +interface User { + boolean passwordIsValid(); + + String getName(); + + String getPassword(); +} diff --git a/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserDataMapper.java b/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserDataMapper.java new file mode 100644 index 0000000000..44112de8a9 --- /dev/null +++ b/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserDataMapper.java @@ -0,0 +1,53 @@ +package com.baeldung.pattern.cleanarchitecture.usercreation; + +import java.time.LocalDateTime; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table(name = "user") +class UserDataMapper { + + @Id + String name; + + String password; + + LocalDateTime creationTime; + + public UserDataMapper() { + } + + public UserDataMapper(String name, String password, LocalDateTime creationTime) { + super(); + this.name = name; + this.password = password; + this.creationTime = creationTime; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public LocalDateTime getCreationTime() { + return creationTime; + } + + public void setCreationTime(LocalDateTime creationTime) { + this.creationTime = creationTime; + } +} diff --git a/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserDsRequestModel.java b/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserDsRequestModel.java new file mode 100644 index 0000000000..aa0f0b56d1 --- /dev/null +++ b/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserDsRequestModel.java @@ -0,0 +1,41 @@ +package com.baeldung.pattern.cleanarchitecture.usercreation; + +import java.time.LocalDateTime; + +class UserDsRequestModel { + + String name; + String password; + LocalDateTime creationTime; + + public UserDsRequestModel(String name, String password, LocalDateTime creationTime) { + this.name = name; + this.password = password; + this.creationTime = creationTime; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public LocalDateTime getCreationTime() { + return creationTime; + } + + public void setCreationTime(LocalDateTime creationTime) { + this.creationTime = creationTime; + } + +} diff --git a/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserFactory.java b/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserFactory.java new file mode 100644 index 0000000000..1ff29709be --- /dev/null +++ b/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserFactory.java @@ -0,0 +1,5 @@ +package com.baeldung.pattern.cleanarchitecture.usercreation; + +interface UserFactory { + User create(String name, String password); +} diff --git a/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserInputBoundary.java b/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserInputBoundary.java new file mode 100644 index 0000000000..e72c30f13c --- /dev/null +++ b/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserInputBoundary.java @@ -0,0 +1,5 @@ +package com.baeldung.pattern.cleanarchitecture.usercreation; + +public interface UserInputBoundary { + UserResponseModel create(UserRequestModel requestModel); +} diff --git a/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserPresenter.java b/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserPresenter.java new file mode 100644 index 0000000000..45d202643e --- /dev/null +++ b/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserPresenter.java @@ -0,0 +1,7 @@ +package com.baeldung.pattern.cleanarchitecture.usercreation; + +interface UserPresenter { + UserResponseModel prepareSuccessView(UserResponseModel user); + + UserResponseModel prepareFailView(String error); +} diff --git a/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserRegisterController.java b/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserRegisterController.java new file mode 100644 index 0000000000..039dc12910 --- /dev/null +++ b/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserRegisterController.java @@ -0,0 +1,20 @@ +package com.baeldung.pattern.cleanarchitecture.usercreation; + +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +class UserRegisterController { + + final UserInputBoundary userInput; + + UserRegisterController(UserInputBoundary accountGateway) { + this.userInput = accountGateway; + } + + @PostMapping("/user") + UserResponseModel create(@RequestBody UserRequestModel requestModel) { + return userInput.create(requestModel); + } +} diff --git a/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserRegisterDsGateway.java b/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserRegisterDsGateway.java new file mode 100644 index 0000000000..89c1b7e774 --- /dev/null +++ b/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserRegisterDsGateway.java @@ -0,0 +1,7 @@ +package com.baeldung.pattern.cleanarchitecture.usercreation; + +interface UserRegisterDsGateway { + boolean existsByName(String identifier); + + void save(UserDsRequestModel requestModel); +} diff --git a/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserRegisterInteractor.java b/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserRegisterInteractor.java new file mode 100644 index 0000000000..5137593dc3 --- /dev/null +++ b/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserRegisterInteractor.java @@ -0,0 +1,35 @@ +package com.baeldung.pattern.cleanarchitecture.usercreation; + +import java.time.LocalDateTime; + +class UserRegisterInteractor implements UserInputBoundary { + + final UserRegisterDsGateway userDsGateway; + final UserPresenter userPresenter; + final UserFactory userFactory; + + UserRegisterInteractor(UserRegisterDsGateway userRegisterDfGateway, UserPresenter userPresenter, + UserFactory userFactory) { + this.userDsGateway = userRegisterDfGateway; + this.userPresenter = userPresenter; + this.userFactory = userFactory; + } + + @Override + public UserResponseModel create(UserRequestModel requestModel) { + if (userDsGateway.existsByName(requestModel.getName())) { + return userPresenter.prepareFailView("User already exists."); + } + User user = userFactory.create(requestModel.getName(), requestModel.getPassword()); + if (!user.passwordIsValid()) { + return userPresenter.prepareFailView("User password must have more than 5 characters."); + } + LocalDateTime now = LocalDateTime.now(); + UserDsRequestModel userDsModel = new UserDsRequestModel(user.getName(), user.getPassword(), now); + + userDsGateway.save(userDsModel); + + UserResponseModel accountResponseModel = new UserResponseModel(user.getName(), now.toString()); + return userPresenter.prepareSuccessView(accountResponseModel); + } +} \ No newline at end of file diff --git a/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserRequestModel.java b/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserRequestModel.java new file mode 100644 index 0000000000..8317665c31 --- /dev/null +++ b/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserRequestModel.java @@ -0,0 +1,33 @@ +package com.baeldung.pattern.cleanarchitecture.usercreation; + +class UserRequestModel { + + String name; + String password; + + public UserRequestModel() { + super(); + } + + UserRequestModel(String name, String password) { + super(); + this.name = name; + this.password = password; + } + + String getName() { + return name; + } + + void setName(String name) { + this.name = name; + } + + String getPassword() { + return password; + } + + void setPassword(String password) { + this.password = password; + } +} diff --git a/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserResponseFormatter.java b/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserResponseFormatter.java new file mode 100644 index 0000000000..4842d44e22 --- /dev/null +++ b/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserResponseFormatter.java @@ -0,0 +1,22 @@ +package com.baeldung.pattern.cleanarchitecture.usercreation; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +import org.springframework.http.HttpStatus; +import org.springframework.web.server.ResponseStatusException; + +class UserResponseFormatter implements UserPresenter { + + @Override + public UserResponseModel prepareSuccessView(UserResponseModel response) { + LocalDateTime responseTime = LocalDateTime.parse(response.getCreationTime()); + response.setCreationTime(responseTime.format(DateTimeFormatter.ofPattern("hh:mm:ss"))); + return response; + } + + @Override + public UserResponseModel prepareFailView(String error) { + throw new ResponseStatusException(HttpStatus.CONFLICT, error); + } +} diff --git a/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserResponseModel.java b/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserResponseModel.java new file mode 100644 index 0000000000..73a3d8fb10 --- /dev/null +++ b/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserResponseModel.java @@ -0,0 +1,29 @@ +package com.baeldung.pattern.cleanarchitecture.usercreation; + +public class UserResponseModel { + + String login; + String creationTime; + + public UserResponseModel(String login, String creationTime) { + this.login = login; + this.creationTime = creationTime; + } + + public String getLogin() { + return login; + } + + public void setLogin(String login) { + this.login = login; + } + + public String getCreationTime() { + return creationTime; + } + + public void setCreationTime(String creationTime) { + this.creationTime = creationTime; + } + +} diff --git a/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/richdomainmodel/Player.java b/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/richdomainmodel/Player.java new file mode 100644 index 0000000000..f683b1158d --- /dev/null +++ b/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/richdomainmodel/Player.java @@ -0,0 +1,35 @@ +package com.baeldung.pattern.richdomainmodel; + +public class Player { + private int points ; + final String name; + + public Player(String name) { + this(name, 0); + } + + private Player(String name, int points) { + this.name = name; + this.points = 0; + } + + public void gainPoint() { + points++; + } + + public boolean hasScoreBiggerThan(Score score) { + return this.points > score.points(); + } + + public int pointsDifference(Player other) { + return points - other.points; + } + + public String name() { + return name; + } + + public String score() { + return Score.from(points).label(); + } +} \ No newline at end of file diff --git a/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/richdomainmodel/Score.java b/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/richdomainmodel/Score.java new file mode 100644 index 0000000000..da5823763c --- /dev/null +++ b/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/richdomainmodel/Score.java @@ -0,0 +1,29 @@ +package com.baeldung.pattern.richdomainmodel; + +import java.util.Arrays; + +public enum Score { + LOVE(0, "Love"), FIFTEEN(1, "Fifteen"), THIRTY(2, "Thirty"), FORTY(3, "Forty"); + + private final int points; + private final String label; + + Score(int points, String label) { + this.points = points; + this.label = label; + } + + public static Score from(int value) { + return Arrays.stream(values()) + .filter(v -> v.points == value) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("no such element: " + value)); + } + + public int points() { + return points; + } + public String label() { + return label; + } +} \ No newline at end of file diff --git a/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/richdomainmodel/TennisGame.java b/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/richdomainmodel/TennisGame.java new file mode 100644 index 0000000000..30811a2eb9 --- /dev/null +++ b/patterns-modules/clean-architecture/src/main/java/com/baeldung/pattern/richdomainmodel/TennisGame.java @@ -0,0 +1,71 @@ +package com.baeldung.pattern.richdomainmodel; + +public class TennisGame { + private final Player server; + private final Player receiver; + + public TennisGame(String server, String receiver) { + this.server = new Player(server); + this.receiver = new Player(receiver); + } + + public void wonPoint(String playerName) { + if(server.name().equals(playerName)) { + server.gainPoint(); + } else { + receiver.gainPoint(); + } + } + + public String getScore() { + if (gameContinues()) { + return getGameScore(); + } + return "Win for " + leadingPlayer().name(); + } + + private String getGameScore() { + if (isScoreEqual()) { + return getEqualScore(); + } + if (isAdvantage()) { + return "Advantage " + leadingPlayer().name(); + } + return getSimpleScore(); + } + + private boolean isScoreEqual() { + return server.pointsDifference(receiver) == 0; + } + + private boolean isAdvantage() { + return leadingPlayer().hasScoreBiggerThan(Score.FORTY) + && Math.abs(server.pointsDifference(receiver)) == 1; + } + + private boolean isGameFinished() { + return leadingPlayer().hasScoreBiggerThan(Score.FORTY) + && Math.abs(server.pointsDifference(receiver)) >= 2; + } + + private Player leadingPlayer() { + if (server.pointsDifference(receiver) > 0) { + return server; + } + return receiver; + } + + private boolean gameContinues() { + return !isGameFinished(); + } + private String getSimpleScore() { + return String.format("%s-%s", server.score(), receiver.score()); + } + + private String getEqualScore() { + if (server.hasScoreBiggerThan(Score.THIRTY)) { + return "Deuce"; + } + return String.format("%s-All", server.score()); + } +} diff --git a/patterns-modules/clean-architecture/src/main/resources/application.properties b/patterns-modules/clean-architecture/src/main/resources/application.properties new file mode 100644 index 0000000000..a5a02bb49d --- /dev/null +++ b/patterns-modules/clean-architecture/src/main/resources/application.properties @@ -0,0 +1,2 @@ +server.port=8080 +server.error.include-message=always \ No newline at end of file diff --git a/patterns-modules/clean-architecture/src/test/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserResponseFormatterUnitTest.java b/patterns-modules/clean-architecture/src/test/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserResponseFormatterUnitTest.java new file mode 100644 index 0000000000..1c37766185 --- /dev/null +++ b/patterns-modules/clean-architecture/src/test/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserResponseFormatterUnitTest.java @@ -0,0 +1,50 @@ +package com.baeldung.pattern.cleanarchitecture.usercreation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import static org.mockito.Mockito.mock; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.verify; + +import org.mockito.ArgumentCaptor; +import org.junit.jupiter.api.Test; +import org.springframework.web.server.ResponseStatusException; + +class UserResponseFormatterUnitTest { + + UserResponseFormatter userResponseFormatter = new UserResponseFormatter(); + UserRegisterDsGateway userDsGateway = mock(UserRegisterDsGateway.class); + UserPresenter userPresenter = mock(UserPresenter.class); + UserFactory userFactory = mock(UserFactory.class); + UserInputBoundary userInputBoundary = new UserRegisterInteractor(userDsGateway, userPresenter, userFactory); + ArgumentCaptor userRequestModelArgumentCaptor = ArgumentCaptor.forClass(String.class); + + @Test + void givenDateAnd3HourTime_whenPrepareSuccessView_thenReturnOnly3HourTime() { + UserResponseModel modelResponse = new UserResponseModel("baeldung", "2020-12-20T03:00:00.000"); + UserResponseModel formattedResponse = userResponseFormatter.prepareSuccessView(modelResponse); + + assertThat(formattedResponse.getCreationTime()).isEqualTo("03:00:00"); + } + + @Test + void whenPrepareFailView_thenThrowHttpConflictException() { + assertThatThrownBy(() -> userResponseFormatter.prepareFailView("Invalid password")).isInstanceOf(ResponseStatusException.class); + } + + @Test + void whenCreateUser_thenSuccess() { + + UserRequestModel userRequestModel = new UserRequestModel("baeldung", "123456"); + when(userFactory.create(anyString(), anyString())).thenReturn(new CommonUser("baeldung", "123456")); + + userInputBoundary.create(userRequestModel); + + verify(userDsGateway).existsByName(userRequestModelArgumentCaptor.capture()); + String name = userRequestModel.getName(); + assertEquals("baeldung", name); + } +} \ No newline at end of file diff --git a/patterns-modules/clean-architecture/src/test/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserUnitTest.java b/patterns-modules/clean-architecture/src/test/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserUnitTest.java new file mode 100644 index 0000000000..e2a9cd9d14 --- /dev/null +++ b/patterns-modules/clean-architecture/src/test/java/com/baeldung/pattern/cleanarchitecture/usercreation/UserUnitTest.java @@ -0,0 +1,36 @@ +package com.baeldung.pattern.cleanarchitecture.usercreation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.*; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.any; + +import org.junit.jupiter.api.Test; + +class UserUnitTest { + + UserRegisterDsGateway userDsGateway = mock(UserRegisterDsGateway.class); + UserPresenter userPresenter = mock(UserPresenter.class); + UserFactory userFactory = mock(UserFactory.class); + UserInputBoundary interactor = new UserRegisterInteractor(userDsGateway, userPresenter, userFactory); + + @Test + void given123Password_whenPasswordIsNotValid_thenIsFalse() { + User user = new CommonUser("Baeldung", "123"); + + assertThat(user.passwordIsValid()).isFalse(); + } + + @Test + void givenBaeldungUserAnd123456Password_whenCreate_thenSaveItAndPrepareSuccessView() { + + User user = new CommonUser("baeldung", "123456"); + UserRequestModel userRequestModel = new UserRequestModel(user.getName(), user.getPassword()); + when(userFactory.create(anyString(), anyString())).thenReturn(new CommonUser(user.getName(), user.getPassword())); + + interactor.create(userRequestModel); + + verify(userDsGateway, times(1)).save(any(UserDsRequestModel.class)); + verify(userPresenter, times(1)).prepareSuccessView(any(UserResponseModel.class)); + } +} diff --git a/patterns-modules/clean-architecture/src/test/java/com/baeldung/pattern/richdomainmodel/RichDomainModelUnitTest.java b/patterns-modules/clean-architecture/src/test/java/com/baeldung/pattern/richdomainmodel/RichDomainModelUnitTest.java new file mode 100644 index 0000000000..b620c7e0e2 --- /dev/null +++ b/patterns-modules/clean-architecture/src/test/java/com/baeldung/pattern/richdomainmodel/RichDomainModelUnitTest.java @@ -0,0 +1,33 @@ +package com.baeldung.pattern.richdomainmodel; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +public class RichDomainModelUnitTest { + + @Test + public void givenATennisGame_whenReceiverWinsThreePoints_thenScoreIsFortyLove() { + TennisGame game = new TennisGame("server", "receiver"); + + game.wonPoint("server"); + game.wonPoint("server"); + game.wonPoint("server"); + + assertThat(game.getScore()) + .isEqualTo("Forty-Love"); + } + + @Test + public void givenATennisGame_whenEachPlayerWonTwoPoints_thenScoreIsThirtyAll() { + TennisGame game = new TennisGame("server", "receiver"); + + game.wonPoint("server"); + game.wonPoint("server"); + game.wonPoint("receiver"); + game.wonPoint("receiver"); + + assertThat(game.getScore()) + .isEqualTo("Thirty-All"); + } +} \ No newline at end of file diff --git a/patterns-modules/coupling/README.md b/patterns-modules/coupling/README.md new file mode 100644 index 0000000000..2d39e74474 --- /dev/null +++ b/patterns-modules/coupling/README.md @@ -0,0 +1,2 @@ + +- [Coupling in Java](https://www.baeldung.com/java-coupling-classes-tight-loose) diff --git a/patterns-modules/coupling/pom.xml b/patterns-modules/coupling/pom.xml new file mode 100644 index 0000000000..56a4972ea9 --- /dev/null +++ b/patterns-modules/coupling/pom.xml @@ -0,0 +1,14 @@ + + + 4.0.0 + coupling + + + patterns-modules + com.baeldung + 1.0.0-SNAPSHOT + + + \ No newline at end of file diff --git a/patterns-modules/coupling/src/main/java/com/baeldung/loose/CSVExport.java b/patterns-modules/coupling/src/main/java/com/baeldung/loose/CSVExport.java new file mode 100644 index 0000000000..28c06e1831 --- /dev/null +++ b/patterns-modules/coupling/src/main/java/com/baeldung/loose/CSVExport.java @@ -0,0 +1,14 @@ +package com.baeldung.loose; + +import java.io.File; +import java.util.List; + +public class CSVExport implements ExportMetadata { + @Override + public File export(List metadata) { + System.out.println("Exporting data..."); + // Export Metadata + File outputCSV = null; + return outputCSV; + } +} diff --git a/patterns-modules/coupling/src/main/java/com/baeldung/loose/ExportMetadata.java b/patterns-modules/coupling/src/main/java/com/baeldung/loose/ExportMetadata.java new file mode 100644 index 0000000000..9a21c679b8 --- /dev/null +++ b/patterns-modules/coupling/src/main/java/com/baeldung/loose/ExportMetadata.java @@ -0,0 +1,8 @@ +package com.baeldung.loose; + +import java.io.File; +import java.util.List; + +public interface ExportMetadata { + File export(List metadata); +} diff --git a/patterns-modules/coupling/src/main/java/com/baeldung/loose/FetchMetadata.java b/patterns-modules/coupling/src/main/java/com/baeldung/loose/FetchMetadata.java new file mode 100644 index 0000000000..e9e39f4a65 --- /dev/null +++ b/patterns-modules/coupling/src/main/java/com/baeldung/loose/FetchMetadata.java @@ -0,0 +1,7 @@ +package com.baeldung.loose; + +import java.util.List; + +public interface FetchMetadata { + List fetchMetadata(); +} diff --git a/patterns-modules/coupling/src/main/java/com/baeldung/loose/JSONFetch.java b/patterns-modules/coupling/src/main/java/com/baeldung/loose/JSONFetch.java new file mode 100644 index 0000000000..4ae178e4eb --- /dev/null +++ b/patterns-modules/coupling/src/main/java/com/baeldung/loose/JSONFetch.java @@ -0,0 +1,12 @@ +package com.baeldung.loose; + +import java.util.ArrayList; +import java.util.List; + +public class JSONFetch implements FetchMetadata{ + @Override + public List fetchMetadata() { + System.out.println("Fetching some json data"); + return new ArrayList<>(); + } +} diff --git a/patterns-modules/coupling/src/main/java/com/baeldung/loose/MetadataCollector.java b/patterns-modules/coupling/src/main/java/com/baeldung/loose/MetadataCollector.java new file mode 100644 index 0000000000..4a159fc6ad --- /dev/null +++ b/patterns-modules/coupling/src/main/java/com/baeldung/loose/MetadataCollector.java @@ -0,0 +1,26 @@ +package com.baeldung.loose; + +import java.util.List; + +public class MetadataCollector { + private final FetchMetadata fetchMetadata; + private final ExportMetadata exportMetadata; + + public MetadataCollector(FetchMetadata fetchMetadata, ExportMetadata exportMetadata) { + this.fetchMetadata = fetchMetadata; + this.exportMetadata = exportMetadata; + } + + public void collectMetadata() { + List metadata = fetchMetadata.fetchMetadata(); + exportMetadata.export(metadata); + } + + public FetchMetadata getFetchMetadata() { + return fetchMetadata; + } + + public ExportMetadata getExportMetadata() { + return exportMetadata; + } +} diff --git a/patterns-modules/coupling/src/main/java/com/baeldung/loose/PDFExport.java b/patterns-modules/coupling/src/main/java/com/baeldung/loose/PDFExport.java new file mode 100644 index 0000000000..2ac3cfe536 --- /dev/null +++ b/patterns-modules/coupling/src/main/java/com/baeldung/loose/PDFExport.java @@ -0,0 +1,14 @@ +package com.baeldung.loose; + +import java.io.File; +import java.util.List; + +public class PDFExport implements ExportMetadata { + @Override + public File export(List metadata) { + System.out.println("PDF Export"); + // Some logic + File outputPDF = null; + return outputPDF; + } +} diff --git a/patterns-modules/coupling/src/main/java/com/baeldung/loose/XMLFetch.java b/patterns-modules/coupling/src/main/java/com/baeldung/loose/XMLFetch.java new file mode 100644 index 0000000000..173c5027e4 --- /dev/null +++ b/patterns-modules/coupling/src/main/java/com/baeldung/loose/XMLFetch.java @@ -0,0 +1,13 @@ +package com.baeldung.loose; + +import java.util.ArrayList; +import java.util.List; + +public class XMLFetch implements FetchMetadata { + @Override + public List fetchMetadata() { + List metadata = new ArrayList<>(); + // Do some stuff + return metadata; + } +} diff --git a/patterns-modules/coupling/src/main/java/com/baeldung/tight/CSVExport.java b/patterns-modules/coupling/src/main/java/com/baeldung/tight/CSVExport.java new file mode 100644 index 0000000000..1fe0625d08 --- /dev/null +++ b/patterns-modules/coupling/src/main/java/com/baeldung/tight/CSVExport.java @@ -0,0 +1,14 @@ +package com.baeldung.tight; + +import java.io.File; +import java.util.List; + +public class CSVExport { + + public File export(List metadata) { + System.out.println("Exporting data..."); + // Export Metadata + File outputCSV = null; + return outputCSV; + } +} diff --git a/patterns-modules/coupling/src/main/java/com/baeldung/tight/JSONFetch.java b/patterns-modules/coupling/src/main/java/com/baeldung/tight/JSONFetch.java new file mode 100644 index 0000000000..7db44dacf0 --- /dev/null +++ b/patterns-modules/coupling/src/main/java/com/baeldung/tight/JSONFetch.java @@ -0,0 +1,12 @@ +package com.baeldung.tight; + +import java.util.ArrayList; +import java.util.List; + +public class JSONFetch { + public List fetchMetadata() { + List metadata = new ArrayList<>(); + // Do some stuff + return metadata; + } +} diff --git a/patterns-modules/coupling/src/main/java/com/baeldung/tight/MetadataCollector.java b/patterns-modules/coupling/src/main/java/com/baeldung/tight/MetadataCollector.java new file mode 100644 index 0000000000..511da537c0 --- /dev/null +++ b/patterns-modules/coupling/src/main/java/com/baeldung/tight/MetadataCollector.java @@ -0,0 +1,35 @@ +package com.baeldung.tight; + +import java.util.List; + +public class MetadataCollector { + private XMLFetch xmlFetch = new XMLFetch(); + private JSONFetch jsonFetch = new JSONFetch(); + private CSVExport csvExport = new CSVExport(); + private PDFExport pdfExport = new PDFExport(); + + public void collectMetadata() { + List metadata = xmlFetch.fetchMetadata(); + csvExport.export(metadata); + } + + public void collectMetadata(int inputType, int outputType) { + if (outputType == 1) { + List metadata = null; + if (inputType == 1) { + metadata = xmlFetch.fetchMetadata(); + } else { + metadata = jsonFetch.fetchMetadata(); + } + csvExport.export(metadata); + } else { + List metadata = null; + if (inputType == 1) { + metadata = xmlFetch.fetchMetadata(); + } else { + metadata = jsonFetch.fetchMetadata(); + } + pdfExport.export(metadata); + } + } +} diff --git a/patterns-modules/coupling/src/main/java/com/baeldung/tight/PDFExport.java b/patterns-modules/coupling/src/main/java/com/baeldung/tight/PDFExport.java new file mode 100644 index 0000000000..d2d0fd4eae --- /dev/null +++ b/patterns-modules/coupling/src/main/java/com/baeldung/tight/PDFExport.java @@ -0,0 +1,13 @@ +package com.baeldung.tight; + +import java.io.File; +import java.util.List; + +public class PDFExport { + public File export(List metadata) { + System.out.println("Exporting data..."); + // Export Metadata + File outputPDF = null; + return outputPDF; + } +} diff --git a/patterns-modules/coupling/src/main/java/com/baeldung/tight/XMLFetch.java b/patterns-modules/coupling/src/main/java/com/baeldung/tight/XMLFetch.java new file mode 100644 index 0000000000..3bb8fa80b0 --- /dev/null +++ b/patterns-modules/coupling/src/main/java/com/baeldung/tight/XMLFetch.java @@ -0,0 +1,12 @@ +package com.baeldung.tight; + +import java.util.ArrayList; +import java.util.List; + +public class XMLFetch { + public List fetchMetadata() { + List metadata = new ArrayList<>(); + // Do some stuff + return metadata; + } +} diff --git a/patterns-modules/coupling/src/test/java/com/baeldung/loose/LooselyCouplingUnitTest.java b/patterns-modules/coupling/src/test/java/com/baeldung/loose/LooselyCouplingUnitTest.java new file mode 100644 index 0000000000..a67d7c0211 --- /dev/null +++ b/patterns-modules/coupling/src/test/java/com/baeldung/loose/LooselyCouplingUnitTest.java @@ -0,0 +1,38 @@ +package com.baeldung.loose; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class LooselyCouplingUnitTest { + + @Test + public void givenMetadataCollector_thenCollectMetadataXMLAndExportCSV() { + FetchMetadata metadata = new XMLFetch(); + ExportMetadata exportMetadata = new CSVExport(); + MetadataCollector collector = new MetadataCollector(metadata, exportMetadata); + collector.collectMetadata(); + assertTrue(collector.getExportMetadata() instanceof CSVExport); + assertTrue(collector.getFetchMetadata() instanceof XMLFetch); + } + + @Test + public void givenMetadataCollector_thenCollectMetadataUsingJSONAndExportPDF() { + FetchMetadata metadata = new JSONFetch(); + ExportMetadata exportMetadata = new PDFExport(); + MetadataCollector collector = new MetadataCollector(metadata, exportMetadata); + collector.collectMetadata(); + assertTrue(collector.getExportMetadata() instanceof PDFExport); + assertTrue(collector.getFetchMetadata() instanceof JSONFetch); + } + + @Test + public void givenMetadataCollector_thenCollectMetadataUsingXMLAndExportPDF() { + FetchMetadata metadata = new XMLFetch(); + ExportMetadata exportMetadata = new PDFExport(); + MetadataCollector collector = new MetadataCollector(metadata, exportMetadata); + collector.collectMetadata(); + assertTrue(collector.getExportMetadata() instanceof PDFExport); + assertTrue(collector.getFetchMetadata() instanceof XMLFetch); + } +} \ No newline at end of file diff --git a/patterns-modules/coupling/src/test/java/com/baeldung/tight/TightlyCouplingUnitTest.java b/patterns-modules/coupling/src/test/java/com/baeldung/tight/TightlyCouplingUnitTest.java new file mode 100644 index 0000000000..886da0a386 --- /dev/null +++ b/patterns-modules/coupling/src/test/java/com/baeldung/tight/TightlyCouplingUnitTest.java @@ -0,0 +1,22 @@ +package com.baeldung.tight; + +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; + +import org.junit.jupiter.api.Test; + +class TightlyCouplingUnitTest { + + @Test + public void givenMetadataCollector_thenCollectMetadata() { + MetadataCollector collector = mock(MetadataCollector.class); + doNothing().when(collector) + .collectMetadata(); + } + + @Test + public void givenMetadataCollectorWithDifferentInput_thenCollectMetadata() { + MetadataCollector collector = new MetadataCollector(); + collector.collectMetadata(1, 1); + } +} \ No newline at end of file diff --git a/patterns-modules/cqrs-es/README.md b/patterns-modules/cqrs-es/README.md new file mode 100644 index 0000000000..92570280ab --- /dev/null +++ b/patterns-modules/cqrs-es/README.md @@ -0,0 +1,5 @@ +This module contains articles about composing together CQRS and Event Sourcing + +## Relevant Articles + +- [CQRS and Event Sourcing in Java](https://www.baeldung.com/cqrs-event-sourcing-java) diff --git a/patterns-modules/cqrs-es/pom.xml b/patterns-modules/cqrs-es/pom.xml new file mode 100644 index 0000000000..9d53d27b43 --- /dev/null +++ b/patterns-modules/cqrs-es/pom.xml @@ -0,0 +1,23 @@ + + 4.0.0 + cqrs-es + 1.0-SNAPSHOT + cqrs-es + + + com.baeldung + patterns-modules + 1.0.0-SNAPSHOT + + + + + org.projectlombok + lombok + ${lombok.version} + + + + \ No newline at end of file diff --git a/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/cqrs/aggregates/UserAggregate.java b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/cqrs/aggregates/UserAggregate.java new file mode 100644 index 0000000000..96476dc15a --- /dev/null +++ b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/cqrs/aggregates/UserAggregate.java @@ -0,0 +1,30 @@ +package com.baeldung.patterns.cqrs.aggregates; + +import com.baeldung.patterns.cqrs.commands.CreateUserCommand; +import com.baeldung.patterns.cqrs.commands.UpdateUserCommand; +import com.baeldung.patterns.cqrs.repository.UserWriteRepository; +import com.baeldung.patterns.domain.User; + +public class UserAggregate { + + private UserWriteRepository writeRepository; + + public UserAggregate(UserWriteRepository repository) { + this.writeRepository = repository; + } + + public User handleCreateUserCommand(CreateUserCommand command) { + User user = new User(command.getUserId(), command.getFirstName(), command.getLastName()); + writeRepository.addUser(user.getUserid(), user); + return user; + } + + public User handleUpdateUserCommand(UpdateUserCommand command) { + User user = writeRepository.getUser(command.getUserId()); + user.setAddresses(command.getAddresses()); + user.setContacts(command.getContacts()); + writeRepository.addUser(user.getUserid(), user); + return user; + } + +} diff --git a/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/cqrs/commands/CreateUserCommand.java b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/cqrs/commands/CreateUserCommand.java new file mode 100644 index 0000000000..362804c610 --- /dev/null +++ b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/cqrs/commands/CreateUserCommand.java @@ -0,0 +1,14 @@ +package com.baeldung.patterns.cqrs.commands; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class CreateUserCommand { + + private String userId; + private String firstName; + private String lastName; + +} diff --git a/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/cqrs/commands/UpdateUserCommand.java b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/cqrs/commands/UpdateUserCommand.java new file mode 100644 index 0000000000..58a4889458 --- /dev/null +++ b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/cqrs/commands/UpdateUserCommand.java @@ -0,0 +1,20 @@ +package com.baeldung.patterns.cqrs.commands; + +import java.util.HashSet; +import java.util.Set; + +import com.baeldung.patterns.domain.Address; +import com.baeldung.patterns.domain.Contact; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class UpdateUserCommand { + + private String userId; + private Set
addresses = new HashSet<>(); + private Set contacts = new HashSet<>(); + +} diff --git a/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/cqrs/projections/UserProjection.java b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/cqrs/projections/UserProjection.java new file mode 100644 index 0000000000..40a56e727f --- /dev/null +++ b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/cqrs/projections/UserProjection.java @@ -0,0 +1,37 @@ +package com.baeldung.patterns.cqrs.projections; + +import java.util.Set; + +import com.baeldung.patterns.cqrs.queries.AddressByRegionQuery; +import com.baeldung.patterns.cqrs.queries.ContactByTypeQuery; +import com.baeldung.patterns.cqrs.repository.UserReadRepository; +import com.baeldung.patterns.domain.Address; +import com.baeldung.patterns.domain.Contact; +import com.baeldung.patterns.domain.UserAddress; +import com.baeldung.patterns.domain.UserContact; + +public class UserProjection { + + private UserReadRepository repository; + + public UserProjection(UserReadRepository repository) { + this.repository = repository; + } + + public Set handle(ContactByTypeQuery query) throws Exception { + UserContact userContact = repository.getUserContact(query.getUserId()); + if (userContact == null) + throw new Exception("User does not exist."); + return userContact.getContactByType() + .get(query.getContactType()); + } + + public Set
handle(AddressByRegionQuery query) throws Exception { + UserAddress userAddress = repository.getUserAddress(query.getUserId()); + if (userAddress == null) + throw new Exception("User does not exist."); + return userAddress.getAddressByRegion() + .get(query.getState()); + } + +} diff --git a/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/cqrs/projectors/UserProjector.java b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/cqrs/projectors/UserProjector.java new file mode 100644 index 0000000000..0344c352d9 --- /dev/null +++ b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/cqrs/projectors/UserProjector.java @@ -0,0 +1,49 @@ +package com.baeldung.patterns.cqrs.projectors; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import com.baeldung.patterns.cqrs.repository.UserReadRepository; +import com.baeldung.patterns.domain.Address; +import com.baeldung.patterns.domain.Contact; +import com.baeldung.patterns.domain.User; +import com.baeldung.patterns.domain.UserAddress; +import com.baeldung.patterns.domain.UserContact; + +public class UserProjector { + + UserReadRepository readRepository = new UserReadRepository(); + + public UserProjector(UserReadRepository readRepository) { + this.readRepository = readRepository; + } + + public void project(User user) { + UserContact userContact = Optional.ofNullable(readRepository.getUserContact(user.getUserid())) + .orElse(new UserContact()); + Map> contactByType = new HashMap<>(); + for (Contact contact : user.getContacts()) { + Set contacts = Optional.ofNullable(contactByType.get(contact.getType())) + .orElse(new HashSet<>()); + contacts.add(contact); + contactByType.put(contact.getType(), contacts); + } + userContact.setContactByType(contactByType); + readRepository.addUserContact(user.getUserid(), userContact); + + UserAddress userAddress = Optional.ofNullable(readRepository.getUserAddress(user.getUserid())) + .orElse(new UserAddress()); + Map> addressByRegion = new HashMap<>(); + for (Address address : user.getAddresses()) { + Set
addresses = Optional.ofNullable(addressByRegion.get(address.getState())) + .orElse(new HashSet<>()); + addresses.add(address); + addressByRegion.put(address.getState(), addresses); + } + userAddress.setAddressByRegion(addressByRegion); + readRepository.addUserAddress(user.getUserid(), userAddress); + } +} diff --git a/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/cqrs/queries/AddressByRegionQuery.java b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/cqrs/queries/AddressByRegionQuery.java new file mode 100644 index 0000000000..4a0f0769f2 --- /dev/null +++ b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/cqrs/queries/AddressByRegionQuery.java @@ -0,0 +1,12 @@ +package com.baeldung.patterns.cqrs.queries; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class AddressByRegionQuery { + + private String userId; + private String state; +} diff --git a/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/cqrs/queries/ContactByTypeQuery.java b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/cqrs/queries/ContactByTypeQuery.java new file mode 100644 index 0000000000..b6c271b472 --- /dev/null +++ b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/cqrs/queries/ContactByTypeQuery.java @@ -0,0 +1,12 @@ +package com.baeldung.patterns.cqrs.queries; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class ContactByTypeQuery { + + private String userId; + private String contactType; +} diff --git a/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/cqrs/repository/UserReadRepository.java b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/cqrs/repository/UserReadRepository.java new file mode 100644 index 0000000000..a7ef2f6f96 --- /dev/null +++ b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/cqrs/repository/UserReadRepository.java @@ -0,0 +1,31 @@ +package com.baeldung.patterns.cqrs.repository; + +import java.util.HashMap; +import java.util.Map; + +import com.baeldung.patterns.domain.UserAddress; +import com.baeldung.patterns.domain.UserContact; + +public class UserReadRepository { + + private Map userAddress = new HashMap<>(); + + private Map userContact = new HashMap<>(); + + public void addUserAddress(String id, UserAddress user) { + userAddress.put(id, user); + } + + public UserAddress getUserAddress(String id) { + return userAddress.get(id); + } + + public void addUserContact(String id, UserContact user) { + userContact.put(id, user); + } + + public UserContact getUserContact(String id) { + return userContact.get(id); + } + +} diff --git a/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/cqrs/repository/UserWriteRepository.java b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/cqrs/repository/UserWriteRepository.java new file mode 100644 index 0000000000..8636e36225 --- /dev/null +++ b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/cqrs/repository/UserWriteRepository.java @@ -0,0 +1,20 @@ +package com.baeldung.patterns.cqrs.repository; + +import java.util.HashMap; +import java.util.Map; + +import com.baeldung.patterns.domain.User; + +public class UserWriteRepository { + + private Map store = new HashMap<>(); + + public void addUser(String id, User user) { + store.put(id, user); + } + + public User getUser(String id) { + return store.get(id); + } + +} diff --git a/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/crud/repository/UserRepository.java b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/crud/repository/UserRepository.java new file mode 100644 index 0000000000..b22d40e6e0 --- /dev/null +++ b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/crud/repository/UserRepository.java @@ -0,0 +1,20 @@ +package com.baeldung.patterns.crud.repository; + +import java.util.HashMap; +import java.util.Map; + +import com.baeldung.patterns.domain.User; + +public class UserRepository { + + private Map store = new HashMap<>(); + + public void addUser(String id, User user) { + store.put(id, user); + } + + public User getUser(String id) { + return store.get(id); + } + +} diff --git a/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/crud/service/UserService.java b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/crud/service/UserService.java new file mode 100644 index 0000000000..d21a796304 --- /dev/null +++ b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/crud/service/UserService.java @@ -0,0 +1,55 @@ +package com.baeldung.patterns.crud.service; + +import java.util.Set; +import java.util.stream.Collectors; + +import com.baeldung.patterns.crud.repository.UserRepository; +import com.baeldung.patterns.domain.Address; +import com.baeldung.patterns.domain.Contact; +import com.baeldung.patterns.domain.User; + +public class UserService { + + private UserRepository repository; + + public UserService(UserRepository repository) { + this.repository = repository; + } + + public void createUser(String userId, String firstName, String lastName) { + User user = new User(userId, firstName, lastName); + repository.addUser(userId, user); + } + + public void updateUser(String userId, Set contacts, Set
addresses) throws Exception { + User user = repository.getUser(userId); + if (user == null) + throw new Exception("User does not exist."); + user.setContacts(contacts); + user.setAddresses(addresses); + repository.addUser(userId, user); + } + + public Set getContactByType(String userId, String contactType) throws Exception { + User user = repository.getUser(userId); + if (user == null) + throw new Exception("User does not exit."); + Set contacts = user.getContacts(); + return contacts.stream() + .filter(c -> c.getType() + .equals(contactType)) + .collect(Collectors.toSet()); + } + + public Set
getAddressByRegion(String userId, String state) throws Exception { + User user = repository.getUser(userId); + if (user == null) + throw new Exception("User does not exist."); + Set
addresses = user.getAddresses(); + return addresses.stream() + .filter(a -> a.getState() + .equals(state)) + .collect(Collectors.toSet()); + } + +} diff --git a/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/domain/Address.java b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/domain/Address.java new file mode 100644 index 0000000000..dfaa724434 --- /dev/null +++ b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/domain/Address.java @@ -0,0 +1,14 @@ +package com.baeldung.patterns.domain; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class Address { + + private String city; + private String state; + private String postcode; + +} diff --git a/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/domain/Contact.java b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/domain/Contact.java new file mode 100644 index 0000000000..a151cdb4ff --- /dev/null +++ b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/domain/Contact.java @@ -0,0 +1,13 @@ +package com.baeldung.patterns.domain; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class Contact { + + private String type; + private String detail; + +} diff --git a/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/domain/User.java b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/domain/User.java new file mode 100644 index 0000000000..8f59723894 --- /dev/null +++ b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/domain/User.java @@ -0,0 +1,22 @@ +package com.baeldung.patterns.domain; + +import java.util.HashSet; +import java.util.Set; + +import lombok.Data; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + +@Data +@RequiredArgsConstructor +public class User { + @NonNull + private String userid; + @NonNull + private String firstname; + @NonNull + private String lastname; + private Set contacts = new HashSet<>(); + private Set
addresses = new HashSet<>(); + +} diff --git a/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/domain/UserAddress.java b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/domain/UserAddress.java new file mode 100644 index 0000000000..8105a53265 --- /dev/null +++ b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/domain/UserAddress.java @@ -0,0 +1,14 @@ +package com.baeldung.patterns.domain; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import lombok.Data; + +@Data +public class UserAddress { + + private Map> addressByRegion = new HashMap<>(); + +} diff --git a/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/domain/UserContact.java b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/domain/UserContact.java new file mode 100644 index 0000000000..1768dadc39 --- /dev/null +++ b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/domain/UserContact.java @@ -0,0 +1,14 @@ +package com.baeldung.patterns.domain; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import lombok.Data; + +@Data +public class UserContact { + + private Map> contactByType = new HashMap<>(); + +} diff --git a/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/es/events/Event.java b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/es/events/Event.java new file mode 100644 index 0000000000..4718450cac --- /dev/null +++ b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/es/events/Event.java @@ -0,0 +1,15 @@ +package com.baeldung.patterns.es.events; + +import java.util.Date; +import java.util.UUID; + +import lombok.ToString; + +@ToString +public abstract class Event { + + public final UUID id = UUID.randomUUID(); + + public final Date created = new Date(); + +} diff --git a/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/es/events/UserAddressAddedEvent.java b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/es/events/UserAddressAddedEvent.java new file mode 100644 index 0000000000..caa640798b --- /dev/null +++ b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/es/events/UserAddressAddedEvent.java @@ -0,0 +1,16 @@ +package com.baeldung.patterns.es.events; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@AllArgsConstructor +@EqualsAndHashCode(callSuper = false) +public class UserAddressAddedEvent extends Event { + + private String city; + private String state; + private String postCode; + +} diff --git a/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/es/events/UserAddressRemovedEvent.java b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/es/events/UserAddressRemovedEvent.java new file mode 100644 index 0000000000..f0702d8810 --- /dev/null +++ b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/es/events/UserAddressRemovedEvent.java @@ -0,0 +1,16 @@ +package com.baeldung.patterns.es.events; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@AllArgsConstructor +@EqualsAndHashCode(callSuper = false) +public class UserAddressRemovedEvent extends Event { + + private String city; + private String state; + private String postCode; + +} diff --git a/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/es/events/UserContactAddedEvent.java b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/es/events/UserContactAddedEvent.java new file mode 100644 index 0000000000..0bce2f997e --- /dev/null +++ b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/es/events/UserContactAddedEvent.java @@ -0,0 +1,15 @@ +package com.baeldung.patterns.es.events; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@AllArgsConstructor +@EqualsAndHashCode(callSuper = false) +public class UserContactAddedEvent extends Event { + + private String contactType; + private String contactDetails; + +} diff --git a/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/es/events/UserContactRemovedEvent.java b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/es/events/UserContactRemovedEvent.java new file mode 100644 index 0000000000..9c043971e7 --- /dev/null +++ b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/es/events/UserContactRemovedEvent.java @@ -0,0 +1,15 @@ +package com.baeldung.patterns.es.events; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@AllArgsConstructor +@EqualsAndHashCode(callSuper = false) +public class UserContactRemovedEvent extends Event { + + private String contactType; + private String contactDetails; + +} diff --git a/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/es/events/UserCreatedEvent.java b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/es/events/UserCreatedEvent.java new file mode 100644 index 0000000000..8e725cd667 --- /dev/null +++ b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/es/events/UserCreatedEvent.java @@ -0,0 +1,16 @@ +package com.baeldung.patterns.es.events; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@AllArgsConstructor +@EqualsAndHashCode(callSuper = false) +public class UserCreatedEvent extends Event { + + private String userId; + private String firstName; + private String lastName; + +} diff --git a/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/es/repository/EventStore.java b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/es/repository/EventStore.java new file mode 100644 index 0000000000..87914d8c12 --- /dev/null +++ b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/es/repository/EventStore.java @@ -0,0 +1,29 @@ +package com.baeldung.patterns.es.repository; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.baeldung.patterns.es.events.Event; + +public class EventStore { + + private Map> store = new HashMap<>(); + + public void addEvent(String id, Event event) { + List events = store.get(id); + if (events == null) { + events = new ArrayList(); + events.add(event); + store.put(id, events); + } else { + events.add(event); + } + } + + public List getEvents(String id) { + return store.get(id); + } + +} diff --git a/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/es/service/UserService.java b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/es/service/UserService.java new file mode 100644 index 0000000000..9650c4294f --- /dev/null +++ b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/es/service/UserService.java @@ -0,0 +1,72 @@ +package com.baeldung.patterns.es.service; + +import java.util.Set; +import java.util.stream.Collectors; + +import com.baeldung.patterns.domain.Address; +import com.baeldung.patterns.domain.Contact; +import com.baeldung.patterns.domain.User; +import com.baeldung.patterns.es.events.UserAddressAddedEvent; +import com.baeldung.patterns.es.events.UserAddressRemovedEvent; +import com.baeldung.patterns.es.events.UserContactAddedEvent; +import com.baeldung.patterns.es.events.UserContactRemovedEvent; +import com.baeldung.patterns.es.events.UserCreatedEvent; +import com.baeldung.patterns.es.repository.EventStore; + +public class UserService { + + private EventStore repository; + + public UserService(EventStore repository) { + this.repository = repository; + } + + public void createUser(String userId, String firstName, String lastName) { + repository.addEvent(userId, new UserCreatedEvent(userId, firstName, lastName)); + } + + public void updateUser(String userId, Set contacts, Set
addresses) throws Exception { + User user = UserUtility.recreateUserState(repository, userId); + if (user == null) + throw new Exception("User does not exist."); + + user.getContacts() + .stream() + .filter(c -> !contacts.contains(c)) + .forEach(c -> repository.addEvent(userId, new UserContactRemovedEvent(c.getType(), c.getDetail()))); + contacts.stream() + .filter(c -> !user.getContacts() + .contains(c)) + .forEach(c -> repository.addEvent(userId, new UserContactAddedEvent(c.getType(), c.getDetail()))); + user.getAddresses() + .stream() + .filter(a -> !addresses.contains(a)) + .forEach(a -> repository.addEvent(userId, new UserAddressRemovedEvent(a.getCity(), a.getState(), a.getPostcode()))); + addresses.stream() + .filter(a -> !user.getAddresses() + .contains(a)) + .forEach(a -> repository.addEvent(userId, new UserAddressAddedEvent(a.getCity(), a.getState(), a.getPostcode()))); + } + + public Set getContactByType(String userId, String contactType) throws Exception { + User user = UserUtility.recreateUserState(repository, userId); + if (user == null) + throw new Exception("User does not exist."); + return user.getContacts() + .stream() + .filter(c -> c.getType() + .equals(contactType)) + .collect(Collectors.toSet()); + } + + public Set
getAddressByRegion(String userId, String state) throws Exception { + User user = UserUtility.recreateUserState(repository, userId); + if (user == null) + throw new Exception("User does not exist."); + return user.getAddresses() + .stream() + .filter(a -> a.getState() + .equals(state)) + .collect(Collectors.toSet()); + } +} diff --git a/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/es/service/UserUtility.java b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/es/service/UserUtility.java new file mode 100644 index 0000000000..0833457507 --- /dev/null +++ b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/es/service/UserUtility.java @@ -0,0 +1,61 @@ +package com.baeldung.patterns.es.service; + +import java.util.List; +import java.util.UUID; + +import com.baeldung.patterns.domain.Address; +import com.baeldung.patterns.domain.Contact; +import com.baeldung.patterns.domain.User; +import com.baeldung.patterns.es.events.Event; +import com.baeldung.patterns.es.events.UserAddressAddedEvent; +import com.baeldung.patterns.es.events.UserAddressRemovedEvent; +import com.baeldung.patterns.es.events.UserContactAddedEvent; +import com.baeldung.patterns.es.events.UserContactRemovedEvent; +import com.baeldung.patterns.es.events.UserCreatedEvent; +import com.baeldung.patterns.es.repository.EventStore; + +public class UserUtility { + + public static User recreateUserState(EventStore store, String userId) { + User user = null; + + List events = store.getEvents(userId); + for (Event event : events) { + if (event instanceof UserCreatedEvent) { + UserCreatedEvent e = (UserCreatedEvent) event; + user = new User(e.getUserId(), e.getFirstName(), e.getLastName()); + } + if (event instanceof UserAddressAddedEvent) { + UserAddressAddedEvent e = (UserAddressAddedEvent) event; + Address address = new Address(e.getCity(), e.getState(), e.getPostCode()); + if (user != null) + user.getAddresses() + .add(address); + } + if (event instanceof UserAddressRemovedEvent) { + UserAddressRemovedEvent e = (UserAddressRemovedEvent) event; + Address address = new Address(e.getCity(), e.getState(), e.getPostCode()); + if (user != null) + user.getAddresses() + .remove(address); + } + if (event instanceof UserContactAddedEvent) { + UserContactAddedEvent e = (UserContactAddedEvent) event; + Contact contact = new Contact(e.getContactType(), e.getContactDetails()); + if (user != null) + user.getContacts() + .add(contact); + } + if (event instanceof UserContactRemovedEvent) { + UserContactRemovedEvent e = (UserContactRemovedEvent) event; + Contact contact = new Contact(e.getContactType(), e.getContactDetails()); + if (user != null) + user.getContacts() + .remove(contact); + } + } + + return user; + } + +} diff --git a/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/escqrs/aggregates/UserAggregate.java b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/escqrs/aggregates/UserAggregate.java new file mode 100644 index 0000000000..04bfeb634d --- /dev/null +++ b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/escqrs/aggregates/UserAggregate.java @@ -0,0 +1,87 @@ +package com.baeldung.patterns.escqrs.aggregates; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import com.baeldung.patterns.cqrs.commands.CreateUserCommand; +import com.baeldung.patterns.cqrs.commands.UpdateUserCommand; +import com.baeldung.patterns.domain.Address; +import com.baeldung.patterns.domain.Contact; +import com.baeldung.patterns.domain.User; +import com.baeldung.patterns.es.events.Event; +import com.baeldung.patterns.es.events.UserAddressAddedEvent; +import com.baeldung.patterns.es.events.UserAddressRemovedEvent; +import com.baeldung.patterns.es.events.UserContactAddedEvent; +import com.baeldung.patterns.es.events.UserContactRemovedEvent; +import com.baeldung.patterns.es.events.UserCreatedEvent; +import com.baeldung.patterns.es.repository.EventStore; +import com.baeldung.patterns.es.service.UserUtility; + +public class UserAggregate { + + private EventStore writeRepository; + + public UserAggregate(EventStore repository) { + this.writeRepository = repository; + } + + public List handleCreateUserCommand(CreateUserCommand command) { + UserCreatedEvent event = new UserCreatedEvent(command.getUserId(), command.getFirstName(), command.getLastName()); + writeRepository.addEvent(command.getUserId(), event); + return Arrays.asList(event); + } + + public List handleUpdateUserCommand(UpdateUserCommand command) { + User user = UserUtility.recreateUserState(writeRepository, command.getUserId()); + List events = new ArrayList<>(); + + List contactsToRemove = user.getContacts() + .stream() + .filter(c -> !command.getContacts() + .contains(c)) + .collect(Collectors.toList()); + for (Contact contact : contactsToRemove) { + UserContactRemovedEvent contactRemovedEvent = new UserContactRemovedEvent(contact.getType(), contact.getDetail()); + events.add(contactRemovedEvent); + writeRepository.addEvent(command.getUserId(), contactRemovedEvent); + } + + List contactsToAdd = command.getContacts() + .stream() + .filter(c -> !user.getContacts() + .contains(c)) + .collect(Collectors.toList()); + for (Contact contact : contactsToAdd) { + UserContactAddedEvent contactAddedEvent = new UserContactAddedEvent(contact.getType(), contact.getDetail()); + events.add(contactAddedEvent); + writeRepository.addEvent(command.getUserId(), contactAddedEvent); + } + + List
addressesToRemove = user.getAddresses() + .stream() + .filter(a -> !command.getAddresses() + .contains(a)) + .collect(Collectors.toList()); + for (Address address : addressesToRemove) { + UserAddressRemovedEvent addressRemovedEvent = new UserAddressRemovedEvent(address.getCity(), address.getState(), address.getPostcode()); + events.add(addressRemovedEvent); + writeRepository.addEvent(command.getUserId(), addressRemovedEvent); + } + + List
addressesToAdd = command.getAddresses() + .stream() + .filter(a -> !user.getAddresses() + .contains(a)) + .collect(Collectors.toList()); + for (Address address : addressesToAdd) { + UserAddressAddedEvent addressAddedEvent = new UserAddressAddedEvent(address.getCity(), address.getState(), address.getPostcode()); + events.add(addressAddedEvent); + writeRepository.addEvent(command.getUserId(), addressAddedEvent); + } + + return events; + } + +} diff --git a/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/escqrs/projectors/UserProjector.java b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/escqrs/projectors/UserProjector.java new file mode 100644 index 0000000000..d388abe7cb --- /dev/null +++ b/patterns-modules/cqrs-es/src/main/java/com/baeldung/patterns/escqrs/projectors/UserProjector.java @@ -0,0 +1,91 @@ +package com.baeldung.patterns.escqrs.projectors; + +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import com.baeldung.patterns.cqrs.repository.UserReadRepository; +import com.baeldung.patterns.domain.Address; +import com.baeldung.patterns.domain.Contact; +import com.baeldung.patterns.domain.UserAddress; +import com.baeldung.patterns.domain.UserContact; +import com.baeldung.patterns.es.events.Event; +import com.baeldung.patterns.es.events.UserAddressAddedEvent; +import com.baeldung.patterns.es.events.UserAddressRemovedEvent; +import com.baeldung.patterns.es.events.UserContactAddedEvent; +import com.baeldung.patterns.es.events.UserContactRemovedEvent; + +public class UserProjector { + + UserReadRepository readRepository = new UserReadRepository(); + + public UserProjector(UserReadRepository readRepository) { + this.readRepository = readRepository; + } + + public void project(String userId, List events) { + + for (Event event : events) { + if (event instanceof UserAddressAddedEvent) + apply(userId, (UserAddressAddedEvent) event); + if (event instanceof UserAddressRemovedEvent) + apply(userId, (UserAddressRemovedEvent) event); + if (event instanceof UserContactAddedEvent) + apply(userId, (UserContactAddedEvent) event); + if (event instanceof UserContactRemovedEvent) + apply(userId, (UserContactRemovedEvent) event); + } + + } + + public void apply(String userId, UserAddressAddedEvent event) { + Address address = new Address(event.getCity(), event.getState(), event.getPostCode()); + UserAddress userAddress = Optional.ofNullable(readRepository.getUserAddress(userId)) + .orElse(new UserAddress()); + Set
addresses = Optional.ofNullable(userAddress.getAddressByRegion() + .get(address.getState())) + .orElse(new HashSet<>()); + addresses.add(address); + userAddress.getAddressByRegion() + .put(address.getState(), addresses); + readRepository.addUserAddress(userId, userAddress); + } + + public void apply(String userId, UserAddressRemovedEvent event) { + Address address = new Address(event.getCity(), event.getState(), event.getPostCode()); + UserAddress userAddress = readRepository.getUserAddress(userId); + if (userAddress != null) { + Set
addresses = userAddress.getAddressByRegion() + .get(address.getState()); + if (addresses != null) + addresses.remove(address); + readRepository.addUserAddress(userId, userAddress); + } + } + + public void apply(String userId, UserContactAddedEvent event) { + Contact contact = new Contact(event.getContactType(), event.getContactDetails()); + UserContact userContact = Optional.ofNullable(readRepository.getUserContact(userId)) + .orElse(new UserContact()); + Set contacts = Optional.ofNullable(userContact.getContactByType() + .get(contact.getType())) + .orElse(new HashSet<>()); + contacts.add(contact); + userContact.getContactByType() + .put(contact.getType(), contacts); + readRepository.addUserContact(userId, userContact); + } + + public void apply(String userId, UserContactRemovedEvent event) { + Contact contact = new Contact(event.getContactType(), event.getContactDetails()); + UserContact userContact = readRepository.getUserContact(userId); + if (userContact != null) { + Set contacts = userContact.getContactByType() + .get(contact.getType()); + if (contacts != null) + contacts.remove(contact); + readRepository.addUserContact(userId, userContact); + } + } +} diff --git a/patterns-modules/cqrs-es/src/test/java/com/baeldung/patterns/cqrs/ApplicationUnitTest.java b/patterns-modules/cqrs-es/src/test/java/com/baeldung/patterns/cqrs/ApplicationUnitTest.java new file mode 100644 index 0000000000..7f68a64274 --- /dev/null +++ b/patterns-modules/cqrs-es/src/test/java/com/baeldung/patterns/cqrs/ApplicationUnitTest.java @@ -0,0 +1,74 @@ +package com.baeldung.patterns.cqrs; + +import static org.junit.Assert.assertEquals; + +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.junit.Before; +import org.junit.Test; + +import com.baeldung.patterns.cqrs.aggregates.UserAggregate; +import com.baeldung.patterns.cqrs.commands.CreateUserCommand; +import com.baeldung.patterns.cqrs.commands.UpdateUserCommand; +import com.baeldung.patterns.cqrs.projections.UserProjection; +import com.baeldung.patterns.cqrs.projectors.UserProjector; +import com.baeldung.patterns.cqrs.queries.AddressByRegionQuery; +import com.baeldung.patterns.cqrs.queries.ContactByTypeQuery; +import com.baeldung.patterns.cqrs.repository.UserReadRepository; +import com.baeldung.patterns.cqrs.repository.UserWriteRepository; +import com.baeldung.patterns.domain.Address; +import com.baeldung.patterns.domain.Contact; +import com.baeldung.patterns.domain.User; + +public class ApplicationUnitTest { + + private UserWriteRepository writeRepository; + private UserReadRepository readRepository; + private UserProjector projector; + private UserAggregate userAggregate; + private UserProjection userProjection; + + @Before + public void setUp() { + writeRepository = new UserWriteRepository(); + readRepository = new UserReadRepository(); + projector = new UserProjector(readRepository); + userAggregate = new UserAggregate(writeRepository); + userProjection = new UserProjection(readRepository); + } + + @Test + public void givenCQRSApplication_whenCommandRun_thenQueryShouldReturnResult() throws Exception { + String userId = UUID.randomUUID() + .toString(); + User user = null; + CreateUserCommand createUserCommand = new CreateUserCommand(userId, "Tom", "Sawyer"); + user = userAggregate.handleCreateUserCommand(createUserCommand); + projector.project(user); + + UpdateUserCommand updateUserCommand = new UpdateUserCommand(user.getUserid(), Stream.of(new Address("New York", "NY", "10001"), new Address("Los Angeles", "CA", "90001")) + .collect(Collectors.toSet()), + Stream.of(new Contact("EMAIL", "tom.sawyer@gmail.com"), new Contact("EMAIL", "tom.sawyer@rediff.com")) + .collect(Collectors.toSet())); + user = userAggregate.handleUpdateUserCommand(updateUserCommand); + projector.project(user); + + updateUserCommand = new UpdateUserCommand(userId, Stream.of(new Address("New York", "NY", "10001"), new Address("Housten", "TX", "77001")) + .collect(Collectors.toSet()), + Stream.of(new Contact("EMAIL", "tom.sawyer@gmail.com"), new Contact("PHONE", "700-000-0001")) + .collect(Collectors.toSet())); + user = userAggregate.handleUpdateUserCommand(updateUserCommand); + projector.project(user); + + ContactByTypeQuery contactByTypeQuery = new ContactByTypeQuery(userId, "EMAIL"); + assertEquals(Stream.of(new Contact("EMAIL", "tom.sawyer@gmail.com")) + .collect(Collectors.toSet()), userProjection.handle(contactByTypeQuery)); + AddressByRegionQuery addressByRegionQuery = new AddressByRegionQuery(userId, "NY"); + assertEquals(Stream.of(new Address("New York", "NY", "10001")) + .collect(Collectors.toSet()), userProjection.handle(addressByRegionQuery)); + + } + +} diff --git a/patterns-modules/cqrs-es/src/test/java/com/baeldung/patterns/crud/ApplicationUnitTest.java b/patterns-modules/cqrs-es/src/test/java/com/baeldung/patterns/crud/ApplicationUnitTest.java new file mode 100644 index 0000000000..3fabfe405d --- /dev/null +++ b/patterns-modules/cqrs-es/src/test/java/com/baeldung/patterns/crud/ApplicationUnitTest.java @@ -0,0 +1,48 @@ +package com.baeldung.patterns.crud; + +import static org.junit.Assert.assertEquals; + +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.junit.Before; +import org.junit.Test; + +import com.baeldung.patterns.crud.repository.UserRepository; +import com.baeldung.patterns.crud.service.UserService; +import com.baeldung.patterns.domain.Address; +import com.baeldung.patterns.domain.Contact; + +public class ApplicationUnitTest { + + private UserRepository repository; + + @Before + public void setUp() { + repository = new UserRepository(); + } + + @Test + public void givenCRUDApplication_whenDataCreated_thenDataCanBeFetched() throws Exception { + UserService service = new UserService(repository); + String userId = UUID.randomUUID() + .toString(); + + service.createUser(userId, "Tom", "Sawyer"); + service.updateUser(userId, Stream.of(new Contact("EMAIL", "tom.sawyer@gmail.com"), new Contact("EMAIL", "tom.sawyer@rediff.com"), new Contact("PHONE", "700-000-0001")) + .collect(Collectors.toSet()), + Stream.of(new Address("New York", "NY", "10001"), new Address("Los Angeles", "CA", "90001"), new Address("Housten", "TX", "77001")) + .collect(Collectors.toSet())); + service.updateUser(userId, Stream.of(new Contact("EMAIL", "tom.sawyer@gmail.com"), new Contact("PHONE", "700-000-0001")) + .collect(Collectors.toSet()), + Stream.of(new Address("New York", "NY", "10001"), new Address("Housten", "TX", "77001")) + .collect(Collectors.toSet())); + + assertEquals(Stream.of(new Contact("EMAIL", "tom.sawyer@gmail.com")) + .collect(Collectors.toSet()), service.getContactByType(userId, "EMAIL")); + assertEquals(Stream.of(new Address("New York", "NY", "10001")) + .collect(Collectors.toSet()), service.getAddressByRegion(userId, "NY")); + } + +} diff --git a/patterns-modules/cqrs-es/src/test/java/com/baeldung/patterns/es/ApplicationUnitTest.java b/patterns-modules/cqrs-es/src/test/java/com/baeldung/patterns/es/ApplicationUnitTest.java new file mode 100644 index 0000000000..61e7b4c05a --- /dev/null +++ b/patterns-modules/cqrs-es/src/test/java/com/baeldung/patterns/es/ApplicationUnitTest.java @@ -0,0 +1,50 @@ +package com.baeldung.patterns.es; + +import static org.junit.Assert.assertEquals; + +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.junit.Before; +import org.junit.Test; + +import com.baeldung.patterns.domain.Address; +import com.baeldung.patterns.domain.Contact; +import com.baeldung.patterns.es.repository.EventStore; +import com.baeldung.patterns.es.service.UserService; + +public class ApplicationUnitTest { + + private EventStore repository; + private UserService service; + + @Before + public void setUp() { + repository = new EventStore(); + service = new UserService(repository); + } + + @Test + public void givenCRUDApplication_whenDataCreated_thenDataCanBeFetched() throws Exception { + String userId = UUID.randomUUID() + .toString(); + + service.createUser(userId, "Tom", "Sawyer"); + service.updateUser(userId, Stream.of(new Contact("EMAIL", "tom.sawyer@gmail.com"), new Contact("EMAIL", "tom.sawyer@rediff.com"), new Contact("PHONE", "700-000-0001")) + .collect(Collectors.toSet()), + Stream.of(new Address("New York", "NY", "10001"), new Address("Los Angeles", "CA", "90001"), new Address("Housten", "TX", "77001")) + .collect(Collectors.toSet())); + service.updateUser(userId, Stream.of(new Contact("EMAIL", "tom.sawyer@gmail.com"), new Contact("PHONE", "700-000-0001")) + .collect(Collectors.toSet()), + Stream.of(new Address("New York", "NY", "10001"), new Address("Housten", "TX", "77001")) + .collect(Collectors.toSet())); + + assertEquals(Stream.of(new Contact("EMAIL", "tom.sawyer@gmail.com")) + .collect(Collectors.toSet()), service.getContactByType(userId, "EMAIL")); + assertEquals(Stream.of(new Address("New York", "NY", "10001")) + .collect(Collectors.toSet()), service.getAddressByRegion(userId, "NY")); + + } + +} diff --git a/patterns-modules/cqrs-es/src/test/java/com/baeldung/patterns/escqrs/ApplicationUnitTest.java b/patterns-modules/cqrs-es/src/test/java/com/baeldung/patterns/escqrs/ApplicationUnitTest.java new file mode 100644 index 0000000000..e0460b2f12 --- /dev/null +++ b/patterns-modules/cqrs-es/src/test/java/com/baeldung/patterns/escqrs/ApplicationUnitTest.java @@ -0,0 +1,76 @@ +package com.baeldung.patterns.escqrs; + +import static org.junit.Assert.assertEquals; + +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.junit.Before; +import org.junit.Test; + +import com.baeldung.patterns.cqrs.commands.CreateUserCommand; +import com.baeldung.patterns.cqrs.commands.UpdateUserCommand; +import com.baeldung.patterns.cqrs.projections.UserProjection; +import com.baeldung.patterns.cqrs.queries.AddressByRegionQuery; +import com.baeldung.patterns.cqrs.queries.ContactByTypeQuery; +import com.baeldung.patterns.cqrs.repository.UserReadRepository; +import com.baeldung.patterns.domain.Address; +import com.baeldung.patterns.domain.Contact; +import com.baeldung.patterns.es.events.Event; +import com.baeldung.patterns.es.repository.EventStore; +import com.baeldung.patterns.escqrs.aggregates.UserAggregate; +import com.baeldung.patterns.escqrs.projectors.UserProjector; + +public class ApplicationUnitTest { + + private EventStore writeRepository; + private UserReadRepository readRepository; + private UserProjector projector; + private UserAggregate userAggregate; + private UserProjection userProjection; + + @Before + public void setUp() { + writeRepository = new EventStore(); + readRepository = new UserReadRepository(); + projector = new UserProjector(readRepository); + userAggregate = new UserAggregate(writeRepository); + userProjection = new UserProjection(readRepository); + } + + @Test + public void givenCQRSApplication_whenCommandRun_thenQueryShouldReturnResult() throws Exception { + String userId = UUID.randomUUID() + .toString(); + List events = null; + CreateUserCommand createUserCommand = new CreateUserCommand(userId, "Kumar", "Chandrakant"); + events = userAggregate.handleCreateUserCommand(createUserCommand); + + projector.project(userId, events); + + UpdateUserCommand updateUserCommand = new UpdateUserCommand(userId, Stream.of(new Address("New York", "NY", "10001"), new Address("Los Angeles", "CA", "90001")) + .collect(Collectors.toSet()), + Stream.of(new Contact("EMAIL", "tom.sawyer@gmail.com"), new Contact("EMAIL", "tom.sawyer@rediff.com")) + .collect(Collectors.toSet())); + events = userAggregate.handleUpdateUserCommand(updateUserCommand); + projector.project(userId, events); + + updateUserCommand = new UpdateUserCommand(userId, Stream.of(new Address("New York", "NY", "10001"), new Address("Housten", "TX", "77001")) + .collect(Collectors.toSet()), + Stream.of(new Contact("EMAIL", "tom.sawyer@gmail.com"), new Contact("PHONE", "700-000-0001")) + .collect(Collectors.toSet())); + events = userAggregate.handleUpdateUserCommand(updateUserCommand); + projector.project(userId, events); + + ContactByTypeQuery contactByTypeQuery = new ContactByTypeQuery(userId, "EMAIL"); + assertEquals(Stream.of(new Contact("EMAIL", "tom.sawyer@gmail.com")) + .collect(Collectors.toSet()), userProjection.handle(contactByTypeQuery)); + AddressByRegionQuery addressByRegionQuery = new AddressByRegionQuery(userId, "NY"); + assertEquals(Stream.of(new Address("New York", "NY", "10001")) + .collect(Collectors.toSet()), userProjection.handle(addressByRegionQuery)); + + } + +} diff --git a/patterns-modules/design-patterns-architectural/README.md b/patterns-modules/design-patterns-architectural/README.md new file mode 100644 index 0000000000..0f0e75afeb --- /dev/null +++ b/patterns-modules/design-patterns-architectural/README.md @@ -0,0 +1,7 @@ +### Relevant Articles: +- [Service Locator Pattern and Java Implementation](https://www.baeldung.com/java-service-locator-pattern) +- [The DAO Pattern in Java](https://www.baeldung.com/java-dao-pattern) +- [DAO vs Repository Patterns](https://www.baeldung.com/java-dao-vs-repository) +- [Difference Between MVC and MVP Patterns](https://www.baeldung.com/mvc-vs-mvp-pattern) +- [The DTO Pattern (Data Transfer Object)](https://www.baeldung.com/java-dto-pattern) +- [SEDA With Spring Integration and Apache Camel](https://www.baeldung.com/spring-apache-camel-seda-integration) diff --git a/patterns-modules/design-patterns-architectural/pom.xml b/patterns-modules/design-patterns-architectural/pom.xml new file mode 100644 index 0000000000..4efcca0c6b --- /dev/null +++ b/patterns-modules/design-patterns-architectural/pom.xml @@ -0,0 +1,77 @@ + + + 4.0.0 + design-patterns-architectural + 1.0 + design-patterns-architectural + jar + + + com.baeldung + patterns-modules + 1.0.0-SNAPSHOT + + + + + org.springframework.boot + spring-boot-starter-web + ${spring-boot.version} + + + org.springframework.boot + spring-boot-starter-test + ${spring-boot.version} + + + io.rest-assured + rest-assured + ${rest-assured.version} + + + org.hibernate + hibernate-core + ${hibernate-core.version} + + + mysql + mysql-connector-java + ${mysql-connector.version} + jar + + + org.springframework.boot + spring-boot-starter-integration + ${spring-boot-starter-integration.version} + + + org.springframework.integration + spring-integration-test + ${spring-integration-test.version} + + + org.apache.camel + camel-core + ${camel-core.version} + + + org.apache.camel + camel-test-junit5 + ${camel-test-junit5.version} + + + + + 5.2.16.Final + 6.0.6 + 2.7.5 + 5.3.0 + 2.7.5 + 5.5.14 + 3.20.4 + 3.14.0 + + + \ No newline at end of file diff --git a/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/dtopattern/Main.java b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/dtopattern/Main.java new file mode 100644 index 0000000000..3177c06c46 --- /dev/null +++ b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/dtopattern/Main.java @@ -0,0 +1,12 @@ +package com.baeldung.dtopattern; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Main { + public static void main(String[] args) { + SpringApplication.run(Main.class, args); + } +} + diff --git a/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/dtopattern/api/Mapper.java b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/dtopattern/api/Mapper.java new file mode 100644 index 0000000000..3440dad23c --- /dev/null +++ b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/dtopattern/api/Mapper.java @@ -0,0 +1,28 @@ +package com.baeldung.dtopattern.api; + +import com.baeldung.dtopattern.domain.Role; +import com.baeldung.dtopattern.domain.User; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +import static java.util.stream.Collectors.toList; + +@Component +class Mapper { + public UserDTO toDto(User user) { + String name = user.getName(); + List roles = user + .getRoles() + .stream() + .map(Role::getName) + .collect(toList()); + + return new UserDTO(name, roles); + } + + public User toUser(UserCreationDTO userDTO) { + return new User(userDTO.getName(), userDTO.getPassword(), new ArrayList<>()); + } +} diff --git a/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/dtopattern/api/UserController.java b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/dtopattern/api/UserController.java new file mode 100644 index 0000000000..2abd483c81 --- /dev/null +++ b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/dtopattern/api/UserController.java @@ -0,0 +1,51 @@ +package com.baeldung.dtopattern.api; + +import com.baeldung.dtopattern.domain.RoleService; +import com.baeldung.dtopattern.domain.User; +import com.baeldung.dtopattern.domain.UserService; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +import static java.util.stream.Collectors.toList; + +@RestController +@RequestMapping("/users") +class UserController { + + private UserService userService; + private RoleService roleService; + private Mapper mapper; + + public UserController(UserService userService, RoleService roleService, Mapper mapper) { + this.userService = userService; + this.roleService = roleService; + this.mapper = mapper; + } + + @GetMapping + @ResponseBody + public List getUsers() { + return userService.getAll() + .stream() + .map(mapper::toDto) + .collect(toList()); + } + + + @PostMapping + @ResponseBody + public UserIdDTO create(@RequestBody UserCreationDTO userDTO) { + User user = mapper.toUser(userDTO); + + userDTO.getRoles() + .stream() + .map(role -> roleService.getOrCreate(role)) + .forEach(user::addRole); + + userService.save(user); + + return new UserIdDTO(user.getId()); + } + +} diff --git a/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/dtopattern/api/UserCreationDTO.java b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/dtopattern/api/UserCreationDTO.java new file mode 100644 index 0000000000..47786a9f15 --- /dev/null +++ b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/dtopattern/api/UserCreationDTO.java @@ -0,0 +1,36 @@ +package com.baeldung.dtopattern.api; + +import java.util.List; + +public class UserCreationDTO { + + private String name; + private String password; + private List roles; + + UserCreationDTO() {} + + public String getName() { + return name; + } + + public String getPassword() { + return password; + } + + public List getRoles() { + return roles; + } + + void setName(String name) { + this.name = name; + } + + void setPassword(String password) { + this.password = password; + } + + void setRoles(List roles) { + this.roles = roles; + } +} diff --git a/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/dtopattern/api/UserDTO.java b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/dtopattern/api/UserDTO.java new file mode 100644 index 0000000000..c00de0077b --- /dev/null +++ b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/dtopattern/api/UserDTO.java @@ -0,0 +1,22 @@ +package com.baeldung.dtopattern.api; + +import java.util.List; + +public class UserDTO { + private String name; + private List roles; + + public UserDTO(String name, List roles) { + this.name = name; + this.roles = roles; + } + + public String getName() { + return name; + } + + public List getRoles() { + return roles; + } + +} diff --git a/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/dtopattern/api/UserIdDTO.java b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/dtopattern/api/UserIdDTO.java new file mode 100644 index 0000000000..33bd336a4f --- /dev/null +++ b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/dtopattern/api/UserIdDTO.java @@ -0,0 +1,14 @@ +package com.baeldung.dtopattern.api; + +public class UserIdDTO { + + private String id; + + public UserIdDTO(String id) { + this.id = id; + } + + public String getId() { + return id; + } +} diff --git a/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/dtopattern/domain/InMemoryRepository.java b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/dtopattern/domain/InMemoryRepository.java new file mode 100644 index 0000000000..e138b51328 --- /dev/null +++ b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/dtopattern/domain/InMemoryRepository.java @@ -0,0 +1,49 @@ +package com.baeldung.dtopattern.domain; + +import org.springframework.stereotype.Service; + +import java.util.*; + +@Service +class InMemoryRepository implements UserRepository, RoleRepository { + + private Map users = new LinkedHashMap<>(); + private Map roles = new LinkedHashMap<>(); + + @Override + public List getAll() { + return new ArrayList<>(users.values()); + } + + @Override + public void save(User user) { + user.setId(UUID.randomUUID().toString()); + users.put(user.getId(), user); + } + + @Override + public void save(Role role) { + role.setId(UUID.randomUUID().toString()); + roles.put(role.getId(), role); + } + + @Override + public Role getRoleById(String id) { + return roles.get(id); + } + + @Override + public Role getRoleByName(String name) { + return roles.values() + .stream() + .filter(role -> role.getName().equalsIgnoreCase(name)) + .findFirst() + .orElse(null); + } + + @Override + public void deleteAll() { + users.clear(); + roles.clear(); + } +} diff --git a/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/dtopattern/domain/Role.java b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/dtopattern/domain/Role.java new file mode 100644 index 0000000000..fa049421f8 --- /dev/null +++ b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/dtopattern/domain/Role.java @@ -0,0 +1,28 @@ +package com.baeldung.dtopattern.domain; + +import java.util.Objects; + +public class Role { + private String id; + private String name; + + public Role(String name) { + this.name = Objects.requireNonNull(name); + } + + public String getId() { + return id; + } + + void setId(String id) { + this.id = Objects.requireNonNull(id); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = Objects.requireNonNull(name); + } +} diff --git a/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/dtopattern/domain/RoleRepository.java b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/dtopattern/domain/RoleRepository.java new file mode 100644 index 0000000000..1e4383672b --- /dev/null +++ b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/dtopattern/domain/RoleRepository.java @@ -0,0 +1,7 @@ +package com.baeldung.dtopattern.domain; + +public interface RoleRepository { + Role getRoleById(String id); + Role getRoleByName(String name); + void save(Role role); +} diff --git a/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/dtopattern/domain/RoleService.java b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/dtopattern/domain/RoleService.java new file mode 100644 index 0000000000..f1d6b639cb --- /dev/null +++ b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/dtopattern/domain/RoleService.java @@ -0,0 +1,32 @@ +package com.baeldung.dtopattern.domain; + +import org.springframework.stereotype.Service; + +import java.util.Objects; + +@Service +public class RoleService { + + private RoleRepository repository; + + public RoleService(RoleRepository repository) { + this.repository = repository; + } + + public Role getOrCreate(String name) { + Role role = repository.getRoleByName(name); + + if (role == null) { + role = new Role(name); + repository.save(role); + } + + return role; + } + + public void save(Role role) { + Objects.requireNonNull(role); + repository.save(role); + } + +} diff --git a/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/dtopattern/domain/User.java b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/dtopattern/domain/User.java new file mode 100644 index 0000000000..fcc7629910 --- /dev/null +++ b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/dtopattern/domain/User.java @@ -0,0 +1,80 @@ +package com.baeldung.dtopattern.domain; + +import javax.crypto.*; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +public class User { + + private static SecretKeySpec KEY = initKey(); + + static SecretKeySpec initKey(){ + try { + SecretKey secretKey = KeyGenerator.getInstance("AES").generateKey(); + return new SecretKeySpec(secretKey.getEncoded(), "AES"); + } catch (NoSuchAlgorithmException ex) { + return null; + } + } + + private String id; + private String name; + private String password; + private List roles; + + public User(String name, String password, List roles) { + this.name = Objects.requireNonNull(name); + this.password = this.encrypt(password); + this.roles = Objects.requireNonNull(roles); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public void addRole(Role role) { + roles.add(role); + } + + public List getRoles() { + return Collections.unmodifiableList(roles); + } + + public String getId() { + return id; + } + + void setId(String id) { + this.id = id; + } + + String encrypt(String password) { + Objects.requireNonNull(password); + try { + Cipher cipher = Cipher.getInstance("AES"); + cipher.init(Cipher.ENCRYPT_MODE, KEY); + final byte[] encryptedBytes = cipher.doFinal(password.getBytes(StandardCharsets.UTF_8)); + return new String(encryptedBytes, StandardCharsets.UTF_8); + } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) { + // do nothing + return ""; + } + } +} diff --git a/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/dtopattern/domain/UserRepository.java b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/dtopattern/domain/UserRepository.java new file mode 100644 index 0000000000..fe62c6c70e --- /dev/null +++ b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/dtopattern/domain/UserRepository.java @@ -0,0 +1,9 @@ +package com.baeldung.dtopattern.domain; + +import java.util.List; + +public interface UserRepository { + List getAll(); + void save(User user); + void deleteAll(); +} diff --git a/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/dtopattern/domain/UserService.java b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/dtopattern/domain/UserService.java new file mode 100644 index 0000000000..ec65d61823 --- /dev/null +++ b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/dtopattern/domain/UserService.java @@ -0,0 +1,25 @@ +package com.baeldung.dtopattern.domain; + +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Objects; + +@Service +public class UserService { + + private UserRepository repository; + + public UserService(UserRepository repository) { + this.repository = repository; + } + + public List getAll() { + return repository.getAll(); + } + + public void save(User user) { + Objects.requireNonNull(user); + repository.save(user); + } +} diff --git a/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/mvc_mvp/mvc/MvcMainClass.java b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/mvc_mvp/mvc/MvcMainClass.java new file mode 100644 index 0000000000..9ccd4ee74c --- /dev/null +++ b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/mvc_mvp/mvc/MvcMainClass.java @@ -0,0 +1,24 @@ +package com.baeldung.mvc_mvp.mvc; + +public class MvcMainClass { + + public static void main(String[] args) { + + Product model = retrieveProductFromDatabase(); + ProductView view = new ProductView(); + model.setView(view); + model.showProduct(); + + ProductController controller = new ProductController(model); + controller.setName("SmartPhone"); + model.showProduct(); + } + + private static Product retrieveProductFromDatabase() { + Product product = new Product(); + product.setName("Mobile"); + product.setDescription("New Brand"); + product.setPrice(1000.0); + return product; + } +} diff --git a/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/mvc_mvp/mvc/Product.java b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/mvc_mvp/mvc/Product.java new file mode 100644 index 0000000000..5c44d4475a --- /dev/null +++ b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/mvc_mvp/mvc/Product.java @@ -0,0 +1,45 @@ +package com.baeldung.mvc_mvp.mvc; + +public class Product { + private String name; + private String description; + private Double price; + private ProductView view; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Double getPrice() { + return price; + } + + public void setPrice(Double price) { + this.price = price; + } + + public ProductView getView() { + return view; + } + + public void setView(ProductView view) { + this.view = view; + } + + public void showProduct() { + view.printProductDetails(name, description, price); + } + +} diff --git a/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/mvc_mvp/mvc/ProductController.java b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/mvc_mvp/mvc/ProductController.java new file mode 100644 index 0000000000..62cdb76f23 --- /dev/null +++ b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/mvc_mvp/mvc/ProductController.java @@ -0,0 +1,34 @@ +package com.baeldung.mvc_mvp.mvc; + +public class ProductController { + private final Product product; + + public ProductController(Product product) { + this.product = product; + } + + public String getName() { + return product.getName(); + } + + public void setName(String name) { + product.setName(name); + } + + public String getDescription() { + return product.getDescription(); + } + + public void setDescription(String description) { + product.setDescription(description); + } + + public Double getPrice() { + return product.getPrice(); + } + + public void setPrice(Double price) { + product.setPrice(price); + } + +} diff --git a/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/mvc_mvp/mvc/ProductView.java b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/mvc_mvp/mvc/ProductView.java new file mode 100644 index 0000000000..43f45b6df7 --- /dev/null +++ b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/mvc_mvp/mvc/ProductView.java @@ -0,0 +1,15 @@ +package com.baeldung.mvc_mvp.mvc; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ProductView { + private static Logger log = LoggerFactory.getLogger(ProductView.class); + + public void printProductDetails(String name, String description, Double price) { + log.info("Product details:"); + log.info("product Name: " + name); + log.info("product Description: " + description); + log.info("product price: " + price); + } +} diff --git a/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/mvc_mvp/mvp/MvpMainClass.java b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/mvc_mvp/mvp/MvpMainClass.java new file mode 100644 index 0000000000..7d12a0142c --- /dev/null +++ b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/mvc_mvp/mvp/MvpMainClass.java @@ -0,0 +1,22 @@ +package com.baeldung.mvc_mvp.mvp; + +public class MvpMainClass { + + public static void main(String[] args) { + + Product model = retrieveProductFromDatabase(); + ProductView view = new ProductView(); + ProductPresenter presenter = new ProductPresenter(model, view); + presenter.showProduct(); + presenter.setName("SmartPhone"); + presenter.showProduct(); + } + + private static Product retrieveProductFromDatabase() { + Product product = new Product(); + product.setName("Mobile"); + product.setDescription("New Brand"); + product.setPrice(1000.0); + return product; + } +} diff --git a/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/mvc_mvp/mvp/Product.java b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/mvc_mvp/mvp/Product.java new file mode 100644 index 0000000000..315e6e69b6 --- /dev/null +++ b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/mvc_mvp/mvp/Product.java @@ -0,0 +1,32 @@ +package com.baeldung.mvc_mvp.mvp; + +public class Product { + private String name; + private String description; + private Double price; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Double getPrice() { + return price; + } + + public void setPrice(Double price) { + this.price = price; + } + +} diff --git a/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/mvc_mvp/mvp/ProductPresenter.java b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/mvc_mvp/mvp/ProductPresenter.java new file mode 100644 index 0000000000..2b87bdbbb5 --- /dev/null +++ b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/mvc_mvp/mvp/ProductPresenter.java @@ -0,0 +1,40 @@ +package com.baeldung.mvc_mvp.mvp; + +public class ProductPresenter { + private final Product product; + private final ProductView view; + + public ProductPresenter(Product product, ProductView view) { + this.product = product; + this.view = view; + } + + public String getName() { + return product.getName(); + } + + public void setName(String name) { + product.setName(name); + } + + public String getDescription() { + return product.getDescription(); + } + + public void setDescription(String description) { + product.setDescription(description); + } + + public Double getProductPrice() { + return product.getPrice(); + } + + public void setPrice(Double price) { + product.setPrice(price); + } + + public void showProduct() { + view.printProductDetails(product.getName(), product.getDescription(), product.getPrice()); + } + +} diff --git a/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/mvc_mvp/mvp/ProductView.java b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/mvc_mvp/mvp/ProductView.java new file mode 100644 index 0000000000..27ccc6743d --- /dev/null +++ b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/mvc_mvp/mvp/ProductView.java @@ -0,0 +1,15 @@ +package com.baeldung.mvc_mvp.mvp; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ProductView { + private static Logger log = LoggerFactory.getLogger(ProductView.class); + + public void printProductDetails(String name, String description, Double price) { + log.info("Product details:"); + log.info("product Name: " + name); + log.info("product Description: " + description); + log.info("product price: " + price); + } +} diff --git a/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/repositoryvsdaopattern/Tweet.java b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/repositoryvsdaopattern/Tweet.java new file mode 100644 index 0000000000..13576f32f6 --- /dev/null +++ b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/repositoryvsdaopattern/Tweet.java @@ -0,0 +1,37 @@ +package com.baeldung.repositoryvsdaopattern; + +import java.util.Date; + +public class Tweet { + + private String email; + + private String tweetText; + + private Date dateCreated; + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getTweetText() { + return tweetText; + } + + public void setTweetText(String tweetText) { + this.tweetText = tweetText; + } + + public Date getDateCreated() { + return dateCreated; + } + + public void setDateCreated(Date dateCreated) { + this.dateCreated = dateCreated; + } + +} diff --git a/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/repositoryvsdaopattern/TweetDao.java b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/repositoryvsdaopattern/TweetDao.java new file mode 100644 index 0000000000..144c04e0ad --- /dev/null +++ b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/repositoryvsdaopattern/TweetDao.java @@ -0,0 +1,9 @@ +package com.baeldung.repositoryvsdaopattern; + +import java.util.List; + +public interface TweetDao { + + List fetchTweets(String email); + +} diff --git a/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/repositoryvsdaopattern/TweetDaoImpl.java b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/repositoryvsdaopattern/TweetDaoImpl.java new file mode 100644 index 0000000000..6e4e8a8985 --- /dev/null +++ b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/repositoryvsdaopattern/TweetDaoImpl.java @@ -0,0 +1,17 @@ +package com.baeldung.repositoryvsdaopattern; + +import java.util.ArrayList; +import java.util.List; + +public class TweetDaoImpl implements TweetDao { + + @Override + public List fetchTweets(String email) { + List tweets = new ArrayList(); + + //call Twitter API and prepare Tweet object + + return tweets; + } + +} diff --git a/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/repositoryvsdaopattern/User.java b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/repositoryvsdaopattern/User.java new file mode 100644 index 0000000000..8cd4fd0a00 --- /dev/null +++ b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/repositoryvsdaopattern/User.java @@ -0,0 +1,51 @@ +package com.baeldung.repositoryvsdaopattern; + +public class User { + + private Long id; + private String userName; + private String firstName; + private String lastName; + private String email; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + +} diff --git a/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/repositoryvsdaopattern/UserDao.java b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/repositoryvsdaopattern/UserDao.java new file mode 100644 index 0000000000..47fe2e3262 --- /dev/null +++ b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/repositoryvsdaopattern/UserDao.java @@ -0,0 +1,13 @@ +package com.baeldung.repositoryvsdaopattern; + +public interface UserDao { + + void create(User user); + + User read(Long id); + + void update(User user); + + void delete(String userName); + +} diff --git a/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/repositoryvsdaopattern/UserDaoImpl.java b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/repositoryvsdaopattern/UserDaoImpl.java new file mode 100644 index 0000000000..24ca04263b --- /dev/null +++ b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/repositoryvsdaopattern/UserDaoImpl.java @@ -0,0 +1,33 @@ +package com.baeldung.repositoryvsdaopattern; + +import javax.persistence.EntityManager; + +public class UserDaoImpl implements UserDao { + + private final EntityManager entityManager; + + public UserDaoImpl(EntityManager entityManager) { + this.entityManager = entityManager; + } + + @Override + public void create(User user) { + entityManager.persist(user); + } + + @Override + public User read(Long id) { + return entityManager.find(User.class, id); + } + + @Override + public void update(User user) { + + } + + @Override + public void delete(String userName) { + + } + +} diff --git a/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/repositoryvsdaopattern/UserRepository.java b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/repositoryvsdaopattern/UserRepository.java new file mode 100644 index 0000000000..1da384958d --- /dev/null +++ b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/repositoryvsdaopattern/UserRepository.java @@ -0,0 +1,21 @@ +package com.baeldung.repositoryvsdaopattern; + +import java.util.List; + +public interface UserRepository { + + User get(Long id); + + void add(User user); + + void update(User user); + + void remove(User user); + + User findByUserName(String userName); + + User findByEmail(String email); + + List fetchTweets(User user); + +} diff --git a/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/repositoryvsdaopattern/UserRepositoryImpl.java b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/repositoryvsdaopattern/UserRepositoryImpl.java new file mode 100644 index 0000000000..806b44e9d5 --- /dev/null +++ b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/repositoryvsdaopattern/UserRepositoryImpl.java @@ -0,0 +1,51 @@ +package com.baeldung.repositoryvsdaopattern; + +import java.util.List; + +public class UserRepositoryImpl implements UserRepository { + + private UserDaoImpl userDaoImpl; + private TweetDaoImpl tweetDaoImpl; + + @Override + public User get(Long id) { + UserSocialMedia user = (UserSocialMedia) userDaoImpl.read(id); + + List tweets = tweetDaoImpl.fetchTweets(user.getEmail()); + user.setTweets(tweets); + + return user; + } + + @Override + public void add(User user) { + userDaoImpl.create(user); + } + + @Override + public void remove(User user) { + + } + + @Override + public void update(User user) { + + } + + @Override + public List fetchTweets(User user) { + return tweetDaoImpl.fetchTweets(user.getEmail()); + + } + + @Override + public User findByUserName(String userName) { + return null; + } + + @Override + public User findByEmail(String email) { + return null; + } + +} diff --git a/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/repositoryvsdaopattern/UserSocialMedia.java b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/repositoryvsdaopattern/UserSocialMedia.java new file mode 100644 index 0000000000..bf729620af --- /dev/null +++ b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/repositoryvsdaopattern/UserSocialMedia.java @@ -0,0 +1,17 @@ +package com.baeldung.repositoryvsdaopattern; + +import java.util.List; + +public class UserSocialMedia extends User { + + private List tweets; + + public List getTweets() { + return tweets; + } + + public void setTweets(List tweets) { + this.tweets = tweets; + } + +} diff --git a/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/seda/apachecamel/WordCountRoute.java b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/seda/apachecamel/WordCountRoute.java new file mode 100644 index 0000000000..c84a755426 --- /dev/null +++ b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/seda/apachecamel/WordCountRoute.java @@ -0,0 +1,48 @@ +package com.baeldung.seda.apachecamel; + +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.apache.camel.Exchange; +import org.apache.camel.builder.ExpressionBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.processor.aggregate.AbstractListAggregationStrategy; + +public class WordCountRoute extends RouteBuilder { + + public static final String receiveTextUri = "seda:receiveText?concurrentConsumers=5"; + public static final String splitWordsUri = "seda:splitWords?concurrentConsumers=5"; + public static final String toLowerCaseUri = "seda:toLowerCase?concurrentConsumers=5"; + public static final String countWordsUri = "seda:countWords?concurrentConsumers=5"; + public static final String returnResponse = "mock:result"; + + @Override + public void configure() throws Exception { + + from(receiveTextUri).to(splitWordsUri); + + from(splitWordsUri).transform(ExpressionBuilder.bodyExpression(s -> s.toString() + .split(" "))) + .to(toLowerCaseUri); + + from(toLowerCaseUri).split(body(), new StringListAggregationStrategy()) + .transform(ExpressionBuilder.bodyExpression(body -> body.toString() + .toLowerCase())) + .end() + .to(countWordsUri); + + from(countWordsUri).transform(ExpressionBuilder.bodyExpression(List.class, body -> body.stream() + .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())))) + .to(returnResponse); + } +} + +class StringListAggregationStrategy extends AbstractListAggregationStrategy { + @Override + public String getValue(Exchange exchange) { + return exchange.getIn() + .getBody(String.class); + } + +} diff --git a/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/seda/springintegration/ChannelConfiguration.java b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/seda/springintegration/ChannelConfiguration.java new file mode 100644 index 0000000000..027ea6a83d --- /dev/null +++ b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/seda/springintegration/ChannelConfiguration.java @@ -0,0 +1,56 @@ +package com.baeldung.seda.springintegration; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.TaskExecutor; +import org.springframework.integration.dsl.MessageChannels; +import org.springframework.messaging.MessageChannel; + +@Configuration +public class ChannelConfiguration { + + private final TaskExecutor receiveTextChannelThreadPool; + private final TaskExecutor splitWordsChannelThreadPool; + private final TaskExecutor toLowerCaseChannelThreadPool; + private final TaskExecutor countWordsChannelThreadPool; + private final TaskExecutor returnResponseChannelThreadPool; + + public ChannelConfiguration(TaskExecutor receiveTextChannelThreadPool, TaskExecutor splitWordsChannelThreadPool, TaskExecutor toLowerCaseChannelThreadPool, TaskExecutor countWordsChannelThreadPool, TaskExecutor returnResponseChannelThreadPool) { + this.receiveTextChannelThreadPool = receiveTextChannelThreadPool; + this.splitWordsChannelThreadPool = splitWordsChannelThreadPool; + this.toLowerCaseChannelThreadPool = toLowerCaseChannelThreadPool; + this.countWordsChannelThreadPool = countWordsChannelThreadPool; + this.returnResponseChannelThreadPool = returnResponseChannelThreadPool; + } + + @Bean(name = "receiveTextChannel") + public MessageChannel getReceiveTextChannel() { + return MessageChannels.executor("receive-text", receiveTextChannelThreadPool) + .get(); + } + + @Bean(name = "splitWordsChannel") + public MessageChannel getSplitWordsChannel() { + return MessageChannels.executor("split-words", splitWordsChannelThreadPool) + .get(); + } + + @Bean(name = "toLowerCaseChannel") + public MessageChannel getToLowerCaseChannel() { + return MessageChannels.executor("to-lower-case", toLowerCaseChannelThreadPool) + .get(); + } + + @Bean(name = "countWordsChannel") + public MessageChannel getCountWordsChannel() { + return MessageChannels.executor("count-words", countWordsChannelThreadPool) + .get(); + } + + @Bean(name = "returnResponseChannel") + public MessageChannel getReturnResponseChannel() { + return MessageChannels.executor("return-response", returnResponseChannelThreadPool) + .get(); + } + +} diff --git a/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/seda/springintegration/IntegrationConfiguration.java b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/seda/springintegration/IntegrationConfiguration.java new file mode 100644 index 0000000000..7df4ee51c8 --- /dev/null +++ b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/seda/springintegration/IntegrationConfiguration.java @@ -0,0 +1,81 @@ +package com.baeldung.seda.springintegration; + +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.integration.aggregator.MessageGroupProcessor; +import org.springframework.integration.aggregator.ReleaseStrategy; +import org.springframework.integration.config.EnableIntegration; +import org.springframework.integration.dsl.IntegrationFlow; +import org.springframework.integration.dsl.IntegrationFlows; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.support.MessageBuilder; + +@Configuration +@EnableIntegration +public class IntegrationConfiguration { + + private final MessageChannel receiveTextChannel; + private final MessageChannel splitWordsChannel; + private final MessageChannel toLowerCaseChannel; + private final MessageChannel countWordsChannel; + private final MessageChannel returnResponseChannel; + + private final Function splitWordsFunction = sentence -> sentence.split(" "); + private final Function, Map> convertArrayListToCountMap = list -> list.stream() + .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); + private final Function toLowerCase = String::toLowerCase; + private final MessageGroupProcessor buildMessageWithListPayload = messageGroup -> MessageBuilder.withPayload(messageGroup.streamMessages() + .map(Message::getPayload) + .collect(Collectors.toList())) + .build(); + private final ReleaseStrategy listSizeReached = r -> r.size() == r.getSequenceSize(); + + public IntegrationConfiguration(MessageChannel receiveTextChannel, MessageChannel splitWordsChannel, MessageChannel toLowerCaseChannel, MessageChannel countWordsChannel, MessageChannel returnResponseChannel) { + this.receiveTextChannel = receiveTextChannel; + this.splitWordsChannel = splitWordsChannel; + this.toLowerCaseChannel = toLowerCaseChannel; + this.countWordsChannel = countWordsChannel; + this.returnResponseChannel = returnResponseChannel; + } + + @Bean + public IntegrationFlow receiveText() { + return IntegrationFlows.from(receiveTextChannel) + .channel(splitWordsChannel) + .get(); + } + + @Bean + public IntegrationFlow splitWords() { + return IntegrationFlows.from(splitWordsChannel) + .transform(splitWordsFunction) + .channel(toLowerCaseChannel) + .get(); + } + + @Bean + public IntegrationFlow toLowerCase() { + return IntegrationFlows.from(toLowerCaseChannel) + .split() + .transform(toLowerCase) + .aggregate(aggregatorSpec -> aggregatorSpec.releaseStrategy(listSizeReached) + .outputProcessor(buildMessageWithListPayload)) + .channel(countWordsChannel) + .get(); + } + + @Bean + public IntegrationFlow countWords() { + return IntegrationFlows.from(countWordsChannel) + .transform(convertArrayListToCountMap) + .channel(returnResponseChannel) + .get(); + } + +} diff --git a/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/seda/springintegration/TaskExecutorConfiguration.java b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/seda/springintegration/TaskExecutorConfiguration.java new file mode 100644 index 0000000000..0596d61eb1 --- /dev/null +++ b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/seda/springintegration/TaskExecutorConfiguration.java @@ -0,0 +1,61 @@ +package com.baeldung.seda.springintegration; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.TaskExecutor; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +@Configuration +public class TaskExecutorConfiguration { + + @Bean("receiveTextChannelThreadPool") + public TaskExecutor receiveTextChannelThreadPool() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(1); + executor.setMaxPoolSize(5); + executor.setThreadNamePrefix("receive-text-channel-thread-pool"); + executor.initialize(); + return executor; + } + + @Bean("splitWordsChannelThreadPool") + public TaskExecutor splitWordsChannelThreadPool() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(1); + executor.setMaxPoolSize(5); + executor.setThreadNamePrefix("split-words-channel-thread-pool"); + executor.initialize(); + return executor; + } + + @Bean("toLowerCaseChannelThreadPool") + public TaskExecutor toLowerCaseChannelThreadPool() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(1); + executor.setMaxPoolSize(5); + executor.setThreadNamePrefix("tto-lower-case-channel-thread-pool"); + executor.initialize(); + return executor; + } + + @Bean("countWordsChannelThreadPool") + public TaskExecutor countWordsChannelThreadPool() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(1); + executor.setMaxPoolSize(5); + executor.setThreadNamePrefix("count-words-channel-thread-pool"); + executor.initialize(); + return executor; + } + + @Bean("returnResponseChannelThreadPool") + public TaskExecutor returnResponseChannelThreadPool() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(1); + executor.setMaxPoolSize(5); + executor.setThreadNamePrefix("return-response-channel-thread-pool"); + executor.initialize(); + return executor; + } + +} diff --git a/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/seda/springintegration/TestGateway.java b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/seda/springintegration/TestGateway.java new file mode 100644 index 0000000000..7729477bb7 --- /dev/null +++ b/patterns-modules/design-patterns-architectural/src/main/java/com/baeldung/seda/springintegration/TestGateway.java @@ -0,0 +1,12 @@ +package com.baeldung.seda.springintegration; + +import java.util.Map; + +import org.springframework.integration.annotation.Gateway; +import org.springframework.integration.annotation.MessagingGateway; + +@MessagingGateway +public interface TestGateway { + @Gateway(requestChannel = "receiveTextChannel", replyChannel = "returnResponseChannel") + public Map countWords(String test); +} diff --git a/patterns-modules/design-patterns-architectural/src/test/java/com/baeldung/dtopattern/api/MapperUnitTest.java b/patterns-modules/design-patterns-architectural/src/test/java/com/baeldung/dtopattern/api/MapperUnitTest.java new file mode 100644 index 0000000000..97ccad1feb --- /dev/null +++ b/patterns-modules/design-patterns-architectural/src/test/java/com/baeldung/dtopattern/api/MapperUnitTest.java @@ -0,0 +1,56 @@ +package com.baeldung.dtopattern.api; + +import com.baeldung.dtopattern.domain.Role; +import com.baeldung.dtopattern.domain.User; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class MapperUnitTest { + + @Test + void toDto_shouldMapFromDomainToDTO() { + String name = "Test"; + String password = "test"; + Role admin = new Role("admin"); + List expectedRoles = Collections.singletonList("admin"); + + List roles = new ArrayList<>(); + roles.add(admin); + + User user = new User(name, password, roles); + Mapper mapper = new Mapper(); + + UserDTO dto = mapper.toDto(user); + + assertEquals(name, dto.getName()); + assertEquals(expectedRoles, dto.getRoles()); + } + + @Test + void toUser_shouldMapFromDTOToDomain() { + String name = "Test"; + String password = "test"; + String role = "admin"; + + UserCreationDTO dto = new UserCreationDTO(); + dto.setName(name); + dto.setPassword(password); + dto.setRoles(Collections.singletonList("admin")); + + User expectedUser = new User(name, password, new ArrayList<>()); + + Mapper mapper = new Mapper(); + + User user = mapper.toUser(dto); + + assertEquals(name, user.getName()); + assertEquals(expectedUser.getPassword(), user.getPassword()); + assertEquals(Collections.emptyList(), user.getRoles()); + } + +} \ No newline at end of file diff --git a/patterns-modules/design-patterns-architectural/src/test/java/com/baeldung/dtopattern/api/UserControllerIntegrationTest.java b/patterns-modules/design-patterns-architectural/src/test/java/com/baeldung/dtopattern/api/UserControllerIntegrationTest.java new file mode 100644 index 0000000000..13739f4c83 --- /dev/null +++ b/patterns-modules/design-patterns-architectural/src/test/java/com/baeldung/dtopattern/api/UserControllerIntegrationTest.java @@ -0,0 +1,80 @@ +package com.baeldung.dtopattern.api; + +import com.baeldung.dtopattern.domain.Role; +import com.baeldung.dtopattern.domain.RoleRepository; +import com.baeldung.dtopattern.domain.User; +import com.baeldung.dtopattern.domain.UserRepository; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.restassured.http.ContentType; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.web.server.LocalServerPort; + +import java.util.Collections; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.*; +import static org.springframework.http.HttpStatus.OK; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class UserControllerIntegrationTest { + + @Autowired + private TestRestTemplate restTemplate; + + @Autowired + private UserRepository userRepository; + + @Autowired + private RoleRepository roleRepository; + + @LocalServerPort + int port; + + @Autowired + private ObjectMapper objectMapper; + + @Test + void create_shouldReturnUseId() throws Exception { + UserCreationDTO request = new UserCreationDTO(); + request.setName("User 1"); + request.setPassword("Test@123456"); + request.setRoles(Collections.singletonList("admin")); + + given() + .contentType(ContentType.JSON) + .body(objectMapper.writeValueAsString(request)) + .when() + .port(port) + .post("/users") + .then() + .statusCode(OK.value()) + .body("id", notNullValue()); + } + + @Test + void getAll_shouldReturnUseDTO() { + + userRepository.deleteAll(); + + String roleName = "admin"; + Role admin = new Role(roleName); + roleRepository.save(admin); + + String name = "User 1"; + User user = new User(name, "Test@123456", Collections.singletonList(admin)); + userRepository.save(user); + + given() + .port(port) + .when() + .get("/users") + .then() + .statusCode(OK.value()) + .body("size()", is(1)) + .body("[0].name", equalTo(name)) + .body("[0].roles", hasItem(roleName)); + } +} diff --git a/patterns-modules/design-patterns-architectural/src/test/java/com/baeldung/dtopattern/domain/InMemoryRepositoryUnitTest.java b/patterns-modules/design-patterns-architectural/src/test/java/com/baeldung/dtopattern/domain/InMemoryRepositoryUnitTest.java new file mode 100644 index 0000000000..8b05e962fa --- /dev/null +++ b/patterns-modules/design-patterns-architectural/src/test/java/com/baeldung/dtopattern/domain/InMemoryRepositoryUnitTest.java @@ -0,0 +1,83 @@ +package com.baeldung.dtopattern.domain; + +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class InMemoryRepositoryUnitTest { + + @Test + void getAll_shouldReturnAllUsers() { + + String name = "Test"; + String password = "test123"; + List roles = new ArrayList<>(); + + User user = new User(name, password, roles); + List expectedUsers = Collections.singletonList(user); + + InMemoryRepository repository = new InMemoryRepository(); + repository.save(user); + + List users = repository.getAll(); + + assertEquals(expectedUsers, users); + } + + @Test + void save_whenSavingUser_shouldSetId() { + String name = "Test"; + String password = "test123"; + List roles = new ArrayList<>(); + + User user = new User(name, password, roles); + + InMemoryRepository repository = new InMemoryRepository(); + repository.save(user); + + assertNotNull(user.getId()); + } + + @Test + void save_whenSavingRole_shouldSetId() { + String name = "Test"; + Role role = new Role(name); + + InMemoryRepository repository = new InMemoryRepository(); + repository.save(role); + + assertNotNull(role.getId()); + } + + @Test + void getRoleById_shouldReturnRoleById() { + String name = "Test"; + Role expectedRole = new Role(name); + + InMemoryRepository repository = new InMemoryRepository(); + repository.save(expectedRole); + + Role role = repository.getRoleById(expectedRole.getId()); + + assertEquals(expectedRole, role); + } + + @Test + void getRoleByName_shouldReturnRoleByName() { + String name = "Test"; + Role expectedRole = new Role(name); + + InMemoryRepository repository = new InMemoryRepository(); + repository.save(expectedRole); + + Role role = repository.getRoleByName(name); + + assertEquals(expectedRole, role); + } + +} \ No newline at end of file diff --git a/patterns-modules/design-patterns-architectural/src/test/java/com/baeldung/dtopattern/domain/UserUnitTest.java b/patterns-modules/design-patterns-architectural/src/test/java/com/baeldung/dtopattern/domain/UserUnitTest.java new file mode 100644 index 0000000000..9ee0568a0d --- /dev/null +++ b/patterns-modules/design-patterns-architectural/src/test/java/com/baeldung/dtopattern/domain/UserUnitTest.java @@ -0,0 +1,38 @@ +package com.baeldung.dtopattern.domain; + +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.*; + +class UserUnitTest { + + + @Test + void whenUserIsCreated_shouldEncryptPassword() { + User user = new User("Test", "test", new ArrayList<>()); + + assertEquals(user.encrypt("test"), user.getPassword()); + assertNotEquals(user.encrypt("Test"), user.getPassword()); + } + + @Test + void whenUserIsCreated_shouldFailIfNameIsNull() { + assertThrows(NullPointerException.class, () -> + new User(null, "test", new ArrayList<>())); + } + + @Test + void whenUserIsCreated_shouldFailIfPasswordIsNull() { + assertThrows(NullPointerException.class, () -> + new User("Test", null, new ArrayList<>())); + } + + @Test + void whenUserIsCreated_shouldFailIfRolesIsNull() { + assertThrows(NullPointerException.class, () -> + new User("Test", "Test", null)); + } + +} diff --git a/patterns-modules/design-patterns-architectural/src/test/java/com/baeldung/seda/apachecamel/ApacheCamelSedaIntegrationTest.java b/patterns-modules/design-patterns-architectural/src/test/java/com/baeldung/seda/apachecamel/ApacheCamelSedaIntegrationTest.java new file mode 100644 index 0000000000..778165c356 --- /dev/null +++ b/patterns-modules/design-patterns-architectural/src/test/java/com/baeldung/seda/apachecamel/ApacheCamelSedaIntegrationTest.java @@ -0,0 +1,48 @@ +package com.baeldung.seda.apachecamel; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.camel.RoutesBuilder; +import org.apache.camel.test.junit5.CamelTestSupport; +import org.junit.jupiter.api.Test; + +import com.baeldung.seda.apachecamel.WordCountRoute; + +public class ApacheCamelSedaIntegrationTest extends CamelTestSupport { + + @Test + public void givenTextWithCapitalAndSmallCaseAndWithoutDuplicateWords_whenSendingTextToInputUri_thenWordCountReturnedAsMap() throws InterruptedException { + Map expected = new HashMap<>(); + expected.put("my", 1L); + expected.put("name", 1L); + expected.put("is", 1L); + expected.put("hesam", 1L); + getMockEndpoint(WordCountRoute.returnResponse).expectedBodiesReceived(expected); + template.sendBody(WordCountRoute.receiveTextUri, "My name is Hesam"); + + assertMockEndpointsSatisfied(); + } + + @Test + public void givenTextWithDuplicateWords_whenSendingTextToInputUri_thenWordCountReturnedAsMap() throws InterruptedException { + Map expected = new HashMap<>(); + expected.put("the", 3L); + expected.put("dog", 1L); + expected.put("chased", 1L); + expected.put("rabbit", 1L); + expected.put("into", 1L); + expected.put("jungle", 1L); + getMockEndpoint(WordCountRoute.returnResponse).expectedBodiesReceived(expected); + template.sendBody(WordCountRoute.receiveTextUri, "the dog chased the rabbit into the jungle"); + + assertMockEndpointsSatisfied(); + } + + @Override + protected RoutesBuilder createRouteBuilder() throws Exception { + RoutesBuilder wordCountRoute = new WordCountRoute(); + return wordCountRoute; + } + +} diff --git a/patterns-modules/design-patterns-architectural/src/test/java/com/baeldung/seda/springintegration/SpringIntegrationSedaIntegrationTest.java b/patterns-modules/design-patterns-architectural/src/test/java/com/baeldung/seda/springintegration/SpringIntegrationSedaIntegrationTest.java new file mode 100644 index 0000000000..0ca45aff95 --- /dev/null +++ b/patterns-modules/design-patterns-architectural/src/test/java/com/baeldung/seda/springintegration/SpringIntegrationSedaIntegrationTest.java @@ -0,0 +1,50 @@ +package com.baeldung.seda.springintegration; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.integration.annotation.IntegrationComponentScan; +import org.springframework.integration.config.EnableIntegration; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +@ExtendWith(SpringExtension.class) +@ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = { TaskExecutorConfiguration.class, ChannelConfiguration.class, IntegrationConfiguration.class }) +@EnableIntegration +@IntegrationComponentScan(basePackages = { "com.baeldung.seda.springintegration" }) +public class SpringIntegrationSedaIntegrationTest { + + @Autowired + TestGateway testGateway; + + @Test + void givenTextWithCapitalAndSmallCaseAndWithoutDuplicateWords_whenCallingCountWordOnGateway_thenWordCountReturnedAsMap() { + Map actual = testGateway.countWords("My name is Hesam"); + Map expected = new HashMap<>(); + expected.put("my", 1L); + expected.put("name", 1L); + expected.put("is", 1L); + expected.put("hesam", 1L); + + org.junit.Assert.assertEquals(expected, actual); + } + + @Test + void givenTextWithDuplicateWords_whenCallingCountWordOnGateway_thenWordCountReturnedAsMap() { + Map actual = testGateway.countWords("the dog chased the rabbit into the jungle"); + Map expected = new HashMap<>(); + expected.put("the", 3L); + expected.put("dog", 1L); + expected.put("chased", 1L); + expected.put("rabbit", 1L); + expected.put("into", 1L); + expected.put("jungle", 1L); + + org.junit.Assert.assertEquals(expected, actual); + } + +} diff --git a/patterns-modules/design-patterns-behavioral-2/README.md b/patterns-modules/design-patterns-behavioral-2/README.md new file mode 100644 index 0000000000..2afa7b9eb6 --- /dev/null +++ b/patterns-modules/design-patterns-behavioral-2/README.md @@ -0,0 +1,4 @@ +### Relevant Articles: +- [Memento Design Pattern in Java](https://www.baeldung.com/java-memento-design-pattern) +- [Difference Between Fluent Interface and Builder Pattern in Java](https://www.baeldung.com/java-fluent-interface-vs-builder-pattern) +- [Smart Batching in Java](https://www.baeldung.com/java-smart-batching) diff --git a/patterns-modules/design-patterns-behavioral-2/pom.xml b/patterns-modules/design-patterns-behavioral-2/pom.xml new file mode 100644 index 0000000000..e484e118eb --- /dev/null +++ b/patterns-modules/design-patterns-behavioral-2/pom.xml @@ -0,0 +1,17 @@ + + + 4.0.0 + design-patterns-behavioral-2 + 1.0 + design-patterns-behavioral-2 + jar + + + com.baeldung + patterns-modules + 1.0.0-SNAPSHOT + + + \ No newline at end of file diff --git a/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/fluentinterface/HtmlDocument.java b/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/fluentinterface/HtmlDocument.java new file mode 100644 index 0000000000..db0f988908 --- /dev/null +++ b/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/fluentinterface/HtmlDocument.java @@ -0,0 +1,46 @@ +package com.baeldung.fluentinterface; + +import static java.util.Arrays.stream; +import static java.util.stream.Collectors.joining; + +public class HtmlDocument { + private final String content; + + private HtmlDocument(String html) { + this.content = html; + } + + public HtmlDocument() { + this(""); + } + + public String html() { + return String.format("%s", content); + } + + public HtmlDocument header(String header) { + return new HtmlDocument(String.format("%s

%s

", content, header)); + } + + public HtmlDocument secondaryHeader(String header) { + return new HtmlDocument(String.format("%s

%s

", content, header)); + } + + public HtmlDocument paragraph(String paragraph) { + return new HtmlDocument(String.format("%s

%s

", content, paragraph)); + } + + public HtmlDocument horizontalLine() { + return new HtmlDocument(String.format("%s
", content)); + } + + public HtmlDocument orderedList(String... items) { + String listItems = stream(items).map(el -> String.format("
  • %s
  • ", el)).collect(joining()); + return new HtmlDocument(String.format("%s
      %s
    ", content, listItems)); + } + + public HtmlDocument unorderedList(String... items) { + String listItems = stream(items).map(el -> String.format("
  • %s
  • ", el)).collect(joining()); + return new HtmlDocument(String.format("%s
      %s
    ", content, listItems)); + } +} \ No newline at end of file diff --git a/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/fluentinterface/LargeHtmlDocument.java b/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/fluentinterface/LargeHtmlDocument.java new file mode 100644 index 0000000000..fff8c97b5b --- /dev/null +++ b/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/fluentinterface/LargeHtmlDocument.java @@ -0,0 +1,40 @@ +package com.baeldung.fluentinterface; + +import com.baeldung.fluentinterface.components.HtmlElement; +import com.baeldung.fluentinterface.components.HtmlHeader; +import com.baeldung.fluentinterface.components.HtmlList; + +import static java.lang.String.format; +import static java.util.Arrays.stream; +import static java.util.stream.Collectors.joining; + +public class LargeHtmlDocument { + private final String content; + + private LargeHtmlDocument(String html) { + this.content = html; + } + + public LargeHtmlDocument() { + this(""); + } + + public String html() { + return format("%s", content); + } + + public LargeHtmlDocument head(HtmlElement head) { + return new LargeHtmlDocument(format("%s %s", content, head.html())); + } + public LargeHtmlDocument body(HtmlElement body) { + return new LargeHtmlDocument(format("%s %s", content, body.html())); + } + public LargeHtmlDocument footer(HtmlElement footer) { + return new LargeHtmlDocument(format("%s
    %s
    ", content, footer.html())); + } + + private LargeHtmlDocument append(String html) { + return new LargeHtmlDocument(format("%s %s", content, html)); + } + +} \ No newline at end of file diff --git a/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/fluentinterface/User.java b/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/fluentinterface/User.java new file mode 100644 index 0000000000..1606369c26 --- /dev/null +++ b/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/fluentinterface/User.java @@ -0,0 +1,65 @@ +package com.baeldung.fluentinterface; + +public class User { + private String firstName; + private String lastName; + private String email; + private String username; + private Long id; + + public String name() { + return firstName + " " + lastName; + } + + public User(String firstName, String lastName, String email, String username, Long id) { + this.firstName = firstName; + this.lastName = lastName; + this.email = email; + this.username = username; + this.id = id; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private String firstName; + private String lastName; + private String email; + private String username; + private Long id; + + private Builder() { + } + + public Builder firstName(String firstName) { + this.firstName = firstName; + return this; + } + + public Builder lastName(String lastName) { + this.lastName = lastName; + return this; + } + + public Builder email(String email) { + this.email = email; + return this; + } + + public Builder username(String username) { + this.username = username; + return this; + } + + public Builder id(Long id) { + this.id = id; + return this; + } + + public User build() { + return new User(firstName, lastName, email, username, id); + } + } +} diff --git a/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/fluentinterface/components/HorizontalLine.java b/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/fluentinterface/components/HorizontalLine.java new file mode 100644 index 0000000000..66979b98f9 --- /dev/null +++ b/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/fluentinterface/components/HorizontalLine.java @@ -0,0 +1,8 @@ +package com.baeldung.fluentinterface.components; + +public class HorizontalLine implements HtmlElement { + @Override + public String html() { + return "
    "; + } +} diff --git a/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/fluentinterface/components/HtmlDiv.java b/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/fluentinterface/components/HtmlDiv.java new file mode 100644 index 0000000000..7625401444 --- /dev/null +++ b/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/fluentinterface/components/HtmlDiv.java @@ -0,0 +1,29 @@ +package com.baeldung.fluentinterface.components; + +public class HtmlDiv implements HtmlElement { + + private final String content; + + public HtmlDiv(String content) { + this.content = content; + } + + public HtmlDiv() { + this.content = ""; + } + + public HtmlDiv append(HtmlElement element) { + return new HtmlDiv(content + element.html()); + } + public HtmlDiv text(String text) { + return new HtmlDiv(content + text); + } + public HtmlDiv paragraph(String text) { + return new HtmlDiv(content + text); + } + + @Override + public String html() { + return String.format("
    %s
    ", content); + } +} diff --git a/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/fluentinterface/components/HtmlElement.java b/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/fluentinterface/components/HtmlElement.java new file mode 100644 index 0000000000..ff46dc0029 --- /dev/null +++ b/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/fluentinterface/components/HtmlElement.java @@ -0,0 +1,5 @@ +package com.baeldung.fluentinterface.components; + +public interface HtmlElement { + String html(); +} diff --git a/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/fluentinterface/components/HtmlHeader.java b/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/fluentinterface/components/HtmlHeader.java new file mode 100644 index 0000000000..12c400014d --- /dev/null +++ b/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/fluentinterface/components/HtmlHeader.java @@ -0,0 +1,34 @@ +package com.baeldung.fluentinterface.components; + +public class HtmlHeader implements HtmlElement { + + private final Type type; + private final String value; + + public HtmlHeader(Type type, String value) { + this.type = type; + this.value = value; + } + + @Override + public String html() { + return String.format("<%s>%s", type.tag(), value, type.tag()); + } + + public enum Type { + PRIMARY("h1"), + SECONDARY("h2"), + THIRD("h3"), + FOURTH("h4"); + + private final String tag; + + Type(String tag) { + this.tag = tag; + } + + public String tag() { + return tag; + } + } +} diff --git a/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/fluentinterface/components/HtmlList.java b/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/fluentinterface/components/HtmlList.java new file mode 100644 index 0000000000..030c9aaa5c --- /dev/null +++ b/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/fluentinterface/components/HtmlList.java @@ -0,0 +1,39 @@ +package com.baeldung.fluentinterface.components; + +import java.util.Arrays; +import java.util.List; + +import static java.lang.String.format; +import static java.util.stream.Collectors.joining; + +public class HtmlList implements HtmlElement { + + private final Type type; + private final List items; + + public HtmlList(Type type, String... items) { + this.type = type; + this.items = Arrays.asList(items); + } + + @Override + public String html() { + String listItems = items.stream().map(el -> format("
  • %s
  • ", el)).collect(joining()); + return String.format("<%s>%s", type.tag(), listItems, type.tag()); + } + + public enum Type { + ORDERED("ol"), + UNORDERED("ul"); + + private final String tag; + + Type(String tag) { + this.tag = tag; + } + + public String tag() { + return tag; + } + } +} diff --git a/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/fluentinterface/components/HtmlSpan.java b/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/fluentinterface/components/HtmlSpan.java new file mode 100644 index 0000000000..2bdf55eb72 --- /dev/null +++ b/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/fluentinterface/components/HtmlSpan.java @@ -0,0 +1,27 @@ +package com.baeldung.fluentinterface.components; + +public class HtmlSpan implements HtmlElement { + + private final String content; + + public HtmlSpan(String content) { + this.content = content; + } + + public HtmlSpan() { + this.content = ""; + } + + public HtmlSpan append(HtmlElement element) { + return new HtmlSpan(content + element.html()); + } + + public HtmlSpan paragraph(String text) { + return new HtmlSpan(content + text); + } + + @Override + public String html() { + return String.format("%s", content); + } +} diff --git a/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/smartbatching/BatchingApp.java b/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/smartbatching/BatchingApp.java new file mode 100644 index 0000000000..8f95d5cbee --- /dev/null +++ b/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/smartbatching/BatchingApp.java @@ -0,0 +1,72 @@ +package com.baeldung.smartbatching; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; + +/** + * @author KPentaris + * @date 07/06/2023 + * @project design-patterns-behavioral-2 + */ +public class BatchingApp { + + static void simpleProcessing() throws Exception { + final Path testPath = Paths.get("./testio.txt"); + testPath.toFile().createNewFile(); + ScheduledExecutorService executorService = Executors.newScheduledThreadPool(100); + Set futures = new HashSet<>(); + for (int i = 0; i < 50000; i++) { + futures.add(executorService.submit(() -> { + try { + Files.write(testPath, Collections.singleton(Thread.currentThread().getName()), StandardOpenOption.APPEND); + } catch (IOException e) { + e.printStackTrace(); + } + })); + } + long start = System.currentTimeMillis(); + for (Future future : futures) { + future.get(); + } + System.out.println("Time: " + (System.currentTimeMillis() - start)); + executorService.shutdown(); + } + + static void batchedProcessing() throws Exception { + final Path testPath = Paths.get("./testio.txt"); + testPath.toFile().createNewFile(); + SmartBatcher batcher = new SmartBatcher(10, strings -> { + List content = new ArrayList<>(strings); + content.add("-----Batch Operation-----"); + try { + Files.write(testPath, content, StandardOpenOption.APPEND); + } catch (IOException e) { + e.printStackTrace(); + } + }); + for (int i = 0; i < 50000; i++) { + batcher.submit(Thread.currentThread().getName() + "-1"); + } + long start = System.currentTimeMillis(); + while (!batcher.finished()) { + Thread.sleep(10); + } + System.out.println("Time: " + (System.currentTimeMillis() - start)); + } + + public static void main(String[] args) throws Exception { +// simpleProcessing(); + batchedProcessing(); + } +} diff --git a/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/smartbatching/MicroBatcher.java b/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/smartbatching/MicroBatcher.java new file mode 100644 index 0000000000..b6d171246a --- /dev/null +++ b/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/smartbatching/MicroBatcher.java @@ -0,0 +1,59 @@ +package com.baeldung.smartbatching; + +import java.util.ArrayList; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.function.Consumer; + +/** + * @author KPentaris + * @date 07/06/2023 + * @project design-patterns-behavioral-2 + */ +public class MicroBatcher { + Queue tasksQueue = new ConcurrentLinkedQueue<>(); + Thread batchThread; + int executionThreshold; + int timeoutThreshold; + boolean working = false; + + MicroBatcher(int executionThreshold, int timeoutThreshold, Consumer> executionLogic) { + batchThread = new Thread(batchHandling(executionLogic)); + batchThread.setDaemon(true); + batchThread.start(); + this.executionThreshold = executionThreshold; + this.timeoutThreshold = timeoutThreshold; + } + + void submit(String task) { + tasksQueue.add(task); + } + + Runnable batchHandling(Consumer> executionLogic) { + return () -> { + while (!batchThread.isInterrupted()) { + long startTime = System.currentTimeMillis(); + while (tasksQueue.size() < executionThreshold && (System.currentTimeMillis() - startTime) < timeoutThreshold) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + return; // exit the external loop + } + } + List tasks = new ArrayList<>(executionThreshold); + while (tasksQueue.size() > 0 && tasks.size() < executionThreshold) { + tasks.add(tasksQueue.poll()); + } + working = true; + executionLogic.accept(tasks); + working = false; + } + }; + } + + boolean finished() { + return tasksQueue.isEmpty() && !working; + } + +} diff --git a/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/smartbatching/SmartBatcher.java b/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/smartbatching/SmartBatcher.java new file mode 100644 index 0000000000..d4aab8e986 --- /dev/null +++ b/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/smartbatching/SmartBatcher.java @@ -0,0 +1,52 @@ +package com.baeldung.smartbatching; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.function.Consumer; + +/** + * @author KPentaris + * @date 07/06/2023 + * @project design-patterns-behavioral-2 + */ +public class SmartBatcher { + BlockingQueue tasksQueue = new LinkedBlockingQueue<>(); + Thread batchThread; + int executionThreshold; + boolean working = false; + + SmartBatcher(int executionThreshold, Consumer> executionLogic) { + batchThread = new Thread(batchHandling(executionLogic)); + batchThread.setDaemon(true); + batchThread.start(); + this.executionThreshold = executionThreshold; + } + + void submit(String task) { + tasksQueue.add(task); + } + + Runnable batchHandling(Consumer> executionLogic) { + return () -> { + while (!batchThread.isInterrupted()) { + List tasks = new ArrayList<>(executionThreshold); + while (tasksQueue.drainTo(tasks, executionThreshold) == 0) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + return; // exit the external loop + } + } + working = true; + executionLogic.accept(tasks); + working = false; + } + }; + } + + boolean finished() { + return tasksQueue.isEmpty() && !working; + } +} diff --git a/patterns-modules/design-patterns-behavioral-2/src/test/java/com/baeldung/fluentinterface/FluentInterfaceUnitTest.java b/patterns-modules/design-patterns-behavioral-2/src/test/java/com/baeldung/fluentinterface/FluentInterfaceUnitTest.java new file mode 100644 index 0000000000..bf5aadcf13 --- /dev/null +++ b/patterns-modules/design-patterns-behavioral-2/src/test/java/com/baeldung/fluentinterface/FluentInterfaceUnitTest.java @@ -0,0 +1,101 @@ +package com.baeldung.fluentinterface; + +import com.baeldung.fluentinterface.components.*; +import com.baeldung.fluentinterface.components.HtmlHeader.Type; +import org.junit.jupiter.api.Test; + +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static com.baeldung.fluentinterface.components.HtmlList.Type.ORDERED; +import static org.assertj.core.api.Assertions.assertThat; + +class FluentInterfaceUnitTest { + + @Test + void givenTenNumbers_thenStreamIsProcessedCorrectly() { + Stream numbers = Stream.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + Stream processedNumbers = numbers.distinct() + .filter(nr -> nr % 2 == 0) + .skip(1) + .limit(4) + .map(nr -> "#" + nr) + .peek(nr -> System.out.println(nr)); + String result = processedNumbers.collect(Collectors.joining(", ")); + + assertThat(result).isEqualTo("#2, #4, #6, #8"); + } + + @Test + void givenUserBuilder_thenCreateUserCorrectly() { + User.Builder userBuilder = User.builder(); + userBuilder = userBuilder + .firstName("John") + .lastName("Doe") + .email("jd@gmail.com") + .username("jd_2000") + .id(1234L); + + User user = userBuilder.build(); + + assertThat(user.name()).isEqualTo("John Doe"); + } + + @Test + void givenHtmlDocument_thenGenerateHtmlCorrectly() { + HtmlDocument document = new HtmlDocument() + .header("Principles of O.O.P.") + .paragraph("OOP in Java.") + .horizontalLine() + .paragraph("The main pillars of OOP are:") + .orderedList("Encapsulation", "Inheritance", "Abstraction", "Polymorphism"); + String html = document.html(); + + assertThat(html).isEqualToIgnoringWhitespace( + "" + + "

    Principles of O.O.P.

    " + + "

    OOP in Java.

    " + + "
    " + + "

    The main pillars of OOP are:

    " + + "
      " + + "
    1. Encapsulation
    2. " + + "
    3. Inheritance
    4. " + + "
    5. Abstraction
    6. " + + "
    7. Polymorphism
    8. " + + "
    " + + "" + ); + } + + @Test + void givenHtmlDocument_thenInstanceIsImmutable() { + HtmlDocument document = new HtmlDocument() + .header("Principles of O.O.P."); + HtmlDocument updatedDocument = document + .paragraph("OOP in Java."); + + assertThat(document).isNotEqualTo(updatedDocument); + } + + + @Test + void givenLargeHtmlDocument_thenGenerateHtmlCorrectly() { + String html = new LargeHtmlDocument() + .head(new HtmlHeader(Type.PRIMARY, "title")) + .body(new HtmlDiv() + .append(new HtmlSpan() + .paragraph("learning OOP from John Doe") + .append(new HorizontalLine()) + .paragraph("The pillars of OOP:") + ) + .append(new HtmlList(ORDERED, "Encapsulation", "Inheritance", "Abstraction", "Polymorphism")) + ) + .footer(new HtmlDiv() + .paragraph("trademark John Doe") + ) + .html(); + + String expectedHtml = "

    title

    learning OOP from John Doe
    The pillars of OOP:
    1. Encapsulation
    2. Inheritance
    3. Abstraction
    4. Polymorphism
    trademark John Doe
    "; + assertThat(html).isEqualToIgnoringWhitespace(expectedHtml); + } +} \ No newline at end of file diff --git a/patterns-modules/design-patterns-behavioral/pom.xml b/patterns-modules/design-patterns-behavioral/pom.xml new file mode 100644 index 0000000000..e2c8b7147b --- /dev/null +++ b/patterns-modules/design-patterns-behavioral/pom.xml @@ -0,0 +1,46 @@ + + + 4.0.0 + design-patterns-behavioral + 1.0 + design-patterns-behavioral + jar + + + com.baeldung + patterns-modules + 1.0.0-SNAPSHOT + + + + + org.jetbrains + annotations + ${intellij.annotations.version} + + + org.projectlombok + lombok + ${lombok.version} + provided + + + com.google.code.findbugs + annotations + ${findbugs.annotations.version} + + + org.apache.commons + commons-lang3 + ${commons-lang3.version} + + + + + 16.0.2 + 3.0.1 + + + \ No newline at end of file diff --git a/patterns-modules/design-patterns-behavioral/src/main/java/com/baeldung/nulls/UsingOptional.java b/patterns-modules/design-patterns-behavioral/src/main/java/com/baeldung/nulls/UsingOptional.java new file mode 100644 index 0000000000..03bed5fedf --- /dev/null +++ b/patterns-modules/design-patterns-behavioral/src/main/java/com/baeldung/nulls/UsingOptional.java @@ -0,0 +1,52 @@ +package com.baeldung.nulls; + +import java.util.List; +import java.util.Optional; + +import static java.util.Collections.emptyList; + +public class UsingOptional { + + public static final String DEFAULT_VALUE = "Default Value"; + + public Optional process(boolean processed) { + String response = doSomething(processed); + + return Optional.ofNullable(response); + } + + public String findFirst() { + return getList() + .stream() + .findFirst() + .orElse(DEFAULT_VALUE); + } + + + public Optional findOptionalFirst() { + return getList() + .stream() + .findFirst(); + } + + private List getList() { + return emptyList(); + } + + public Optional optionalListFirst() { + return getOptionalList() + .flatMap(list -> list.stream().findFirst()); + } + + private Optional> getOptionalList() { + return Optional.of(getList()); + } + + private String doSomething(boolean processed) { + if (processed) { + return "passed"; + } else { + return null; + } + } +} diff --git a/patterns-modules/design-patterns-behavioral/src/main/java/com/baeldung/templatemethod/model/StandardComputerBuilder.java b/patterns-modules/design-patterns-behavioral/src/main/java/com/baeldung/templatemethod/model/StandardComputerBuilder.java new file mode 100644 index 0000000000..f5b953c830 --- /dev/null +++ b/patterns-modules/design-patterns-behavioral/src/main/java/com/baeldung/templatemethod/model/StandardComputerBuilder.java @@ -0,0 +1,24 @@ +package com.baeldung.templatemethod.model; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class StandardComputerBuilder extends ComputerBuilder { + + @Override + public void addMotherboard() { + computerParts.put("Motherboard", "Standard Motherboard"); + } + + @Override + public void setupMotherboard() { + motherboardSetupStatus.add("Screwing the standard motherboard to the case."); + motherboardSetupStatus.add("Pluging in the power supply connectors."); + motherboardSetupStatus.forEach(step -> log.debug(step)); + } + + @Override + public void addProcessor() { + computerParts.put("Processor", "Standard Processor"); + } +} diff --git a/patterns-modules/design-patterns-behavioral/src/test/java/com/baeldung/nulls/UsingOptionalUnitTest.java b/patterns-modules/design-patterns-behavioral/src/test/java/com/baeldung/nulls/UsingOptionalUnitTest.java new file mode 100644 index 0000000000..576d2bacf3 --- /dev/null +++ b/patterns-modules/design-patterns-behavioral/src/test/java/com/baeldung/nulls/UsingOptionalUnitTest.java @@ -0,0 +1,54 @@ +package com.baeldung.nulls; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class UsingOptionalUnitTest { + + private UsingOptional dataObject; + + @BeforeEach + public void setup() { + dataObject = new UsingOptional(); + } + + @Test + void whenArgIsFalse_thenReturnEmptyResponse() { + Optional result = dataObject.process(false); + + assertFalse(result.isPresent()); + } + + @Test + void whenArgIsTrue_thenReturnValidResponse() { + Optional result = dataObject.process(true); + + assertTrue(result.isPresent()); + } + + @Test + void whenArgIsFalse_thenChainResponseAndThrowException() { + assertThrows(Exception.class, () -> dataObject.process(false).orElseThrow(Exception::new)); + } + + @Test() + void givenEmptyList_whenFindFirst_getDefaultValue() { + assertTrue(dataObject.findFirst().equalsIgnoreCase(UsingOptional.DEFAULT_VALUE)); + } + + @Test() + void givenEmptyList_whenFindOptionalFirst_returnsEmptyOptional() { + assertFalse(dataObject.findOptionalFirst().isPresent()); + } + + @Test() + void whenOptionalListFirst_returnsEmptyOptional() { + assertFalse(dataObject.optionalListFirst().isPresent()); + } +} \ No newline at end of file diff --git a/patterns-modules/design-patterns-behavioral/src/test/java/com/baeldung/state/StatePatternUnitTest.java b/patterns-modules/design-patterns-behavioral/src/test/java/com/baeldung/state/StatePatternUnitTest.java new file mode 100644 index 0000000000..98babf0ccd --- /dev/null +++ b/patterns-modules/design-patterns-behavioral/src/test/java/com/baeldung/state/StatePatternUnitTest.java @@ -0,0 +1,31 @@ +package com.baeldung.state; + +import com.baeldung.state.Package; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +public class StatePatternUnitTest { + + @Test + public void givenNewPackage_whenPackageReceived_thenStateReceived() { + Package pkg = new Package(); + + assertTrue(pkg.getState() instanceof OrderedState); + pkg.nextState(); + + assertTrue(pkg.getState() instanceof DeliveredState); + pkg.nextState(); + + assertTrue(pkg.getState() instanceof ReceivedState); + } + + @Test + public void givenDeliveredPackage_whenPrevState_thenStateOrdered() { + Package pkg = new Package(); + pkg.setState(new DeliveredState()); + pkg.previousState(); + + assertTrue(pkg.getState() instanceof OrderedState); + } +} diff --git a/patterns-modules/design-patterns-behavioral/src/test/resources/logback-test.xml b/patterns-modules/design-patterns-behavioral/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..499cc828a2 --- /dev/null +++ b/patterns-modules/design-patterns-behavioral/src/test/resources/logback-test.xml @@ -0,0 +1,12 @@ + + + + + %d{yyyy-MM-dd HH:mm:ss} | %-5p | [%thread] %logger{5}:%L - %msg%n + + + + + + + \ No newline at end of file diff --git a/patterns-modules/design-patterns-cloud/pom.xml b/patterns-modules/design-patterns-cloud/pom.xml new file mode 100644 index 0000000000..595e0cabcd --- /dev/null +++ b/patterns-modules/design-patterns-cloud/pom.xml @@ -0,0 +1,29 @@ + + + 4.0.0 + design-patterns-cloud + design-patterns-cloud + jar + + + com.baeldung + patterns-modules + 1.0.0-SNAPSHOT + + + + + io.github.resilience4j + resilience4j-retry + ${resilience4j-retry.version} + + + + + + 2.0.2 + + + \ No newline at end of file diff --git a/patterns-modules/design-patterns-cloud/src/test/java/com/baeldung/backoff/jitter/BackoffWithJitterUnitTest.java b/patterns-modules/design-patterns-cloud/src/test/java/com/baeldung/backoff/jitter/BackoffWithJitterUnitTest.java new file mode 100644 index 0000000000..5bd6b0fa23 --- /dev/null +++ b/patterns-modules/design-patterns-cloud/src/test/java/com/baeldung/backoff/jitter/BackoffWithJitterUnitTest.java @@ -0,0 +1,103 @@ +package com.baeldung.backoff.jitter; + +import io.github.resilience4j.core.IntervalFunction; +import io.github.resilience4j.retry.Retry; +import io.github.resilience4j.retry.RetryConfig; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.function.Function; + +import static com.baeldung.backoff.jitter.BackoffWithJitterUnitTest.RetryProperties.*; +import static io.github.resilience4j.core.IntervalFunction.ofExponentialBackoff; +import static io.github.resilience4j.core.IntervalFunction.ofExponentialRandomBackoff; +import static java.util.Collections.nCopies; +import static java.util.concurrent.Executors.newFixedThreadPool; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +public class BackoffWithJitterUnitTest { + + static Logger log = LoggerFactory.getLogger(BackoffWithJitterUnitTest.class); + + interface PingPongService { + + String call(String ping) throws PingPongServiceException; + } + + class PingPongServiceException extends RuntimeException { + + public PingPongServiceException(String reason) { + super(reason); + } + } + + private PingPongService service; + private static final int NUM_CONCURRENT_CLIENTS = 8; + + @Before + public void setUp() { + service = mock(PingPongService.class); + } + + @Test + public void whenRetryExponentialBackoff_thenRetriedConfiguredNoOfTimes() { + IntervalFunction intervalFn = ofExponentialBackoff(INITIAL_INTERVAL, MULTIPLIER); + Function pingPongFn = getRetryablePingPongFn(intervalFn); + + when(service.call(anyString())).thenThrow(PingPongServiceException.class); + try { + pingPongFn.apply("Hello"); + } catch (PingPongServiceException e) { + verify(service, times(MAX_RETRIES)).call(anyString()); + } + } + + @Test + public void whenRetryExponentialBackoffWithoutJitter_thenThunderingHerdProblemOccurs() throws InterruptedException { + IntervalFunction intervalFn = ofExponentialBackoff(INITIAL_INTERVAL, MULTIPLIER); + test(intervalFn); + } + + @Test + public void whenRetryExponentialBackoffWithJitter_thenRetriesAreSpread() throws InterruptedException { + IntervalFunction intervalFn = ofExponentialRandomBackoff(INITIAL_INTERVAL, MULTIPLIER, RANDOMIZATION_FACTOR); + test(intervalFn); + } + + private void test(IntervalFunction intervalFn) throws InterruptedException { + Function pingPongFn = getRetryablePingPongFn(intervalFn); + ExecutorService executors = newFixedThreadPool(NUM_CONCURRENT_CLIENTS); + List> tasks = nCopies(NUM_CONCURRENT_CLIENTS, () -> pingPongFn.apply("Hello")); + + when(service.call(anyString())).thenThrow(PingPongServiceException.class); + + executors.invokeAll(tasks); + } + + private Function getRetryablePingPongFn(IntervalFunction intervalFn) { + RetryConfig retryConfig = RetryConfig.custom() + .maxAttempts(MAX_RETRIES) + .intervalFunction(intervalFn) + .retryExceptions(PingPongServiceException.class) + .build(); + Retry retry = Retry.of("pingpong", retryConfig); + return Retry.decorateFunction(retry, ping -> { + log.info("Invoked at {}", LocalDateTime.now()); + return service.call(ping); + }); + } + + static class RetryProperties { + static final Long INITIAL_INTERVAL = 1000L; + static final Double MULTIPLIER = 2.0D; + static final Double RANDOMIZATION_FACTOR = 0.6D; + static final Integer MAX_RETRIES = 4; + } +} diff --git a/patterns-modules/design-patterns-creational-2/README.md b/patterns-modules/design-patterns-creational-2/README.md new file mode 100644 index 0000000000..dc5b2a1861 --- /dev/null +++ b/patterns-modules/design-patterns-creational-2/README.md @@ -0,0 +1,2 @@ +## Relevant Articles +- [The Factory Design Pattern in Java](https://www.baeldung.com/java-factory-pattern) diff --git a/patterns-modules/design-patterns-creational-2/pom.xml b/patterns-modules/design-patterns-creational-2/pom.xml new file mode 100644 index 0000000000..fe79052a99 --- /dev/null +++ b/patterns-modules/design-patterns-creational-2/pom.xml @@ -0,0 +1,15 @@ + + 4.0.0 + design-patterns-creational-2 + design-patterns-creational-2 + jar + + + patterns-modules + com.baeldung + 1.0.0-SNAPSHOT + + + \ No newline at end of file diff --git a/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/abstract_factory/Corporation.java b/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/abstract_factory/Corporation.java new file mode 100644 index 0000000000..a75e65e4fe --- /dev/null +++ b/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/abstract_factory/Corporation.java @@ -0,0 +1,8 @@ +package com.baeldung.factory_pattern.abstract_factory; + +public abstract class Corporation { + + public abstract MotorVehicle createMotorVehicle(); + + public abstract ElectricVehicle createElectricVehicle(); +} \ No newline at end of file diff --git a/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/abstract_factory/ElectricVehicle.java b/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/abstract_factory/ElectricVehicle.java new file mode 100644 index 0000000000..8de25d65ca --- /dev/null +++ b/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/abstract_factory/ElectricVehicle.java @@ -0,0 +1,5 @@ +package com.baeldung.factory_pattern.abstract_factory; + +public interface ElectricVehicle { + void build(); +} \ No newline at end of file diff --git a/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/abstract_factory/FutureVehicleCorporation.java b/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/abstract_factory/FutureVehicleCorporation.java new file mode 100644 index 0000000000..b99ab3d2d7 --- /dev/null +++ b/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/abstract_factory/FutureVehicleCorporation.java @@ -0,0 +1,13 @@ +package com.baeldung.factory_pattern.abstract_factory; + +public class FutureVehicleCorporation extends Corporation { + @Override + public MotorVehicle createMotorVehicle() { + return new FutureVehicleMotorcycle(); + } + + @Override + public ElectricVehicle createElectricVehicle() { + return new FutureVehicleElectricCar(); + } +} \ No newline at end of file diff --git a/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/abstract_factory/FutureVehicleElectricCar.java b/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/abstract_factory/FutureVehicleElectricCar.java new file mode 100644 index 0000000000..436bbc9801 --- /dev/null +++ b/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/abstract_factory/FutureVehicleElectricCar.java @@ -0,0 +1,8 @@ +package com.baeldung.factory_pattern.abstract_factory; + +public class FutureVehicleElectricCar implements ElectricVehicle { + @Override + public void build() { + System.out.println("Future Vehicle Electric Car"); + } +} \ No newline at end of file diff --git a/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/abstract_factory/FutureVehicleMotorcycle.java b/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/abstract_factory/FutureVehicleMotorcycle.java new file mode 100644 index 0000000000..c8896256d5 --- /dev/null +++ b/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/abstract_factory/FutureVehicleMotorcycle.java @@ -0,0 +1,8 @@ +package com.baeldung.factory_pattern.abstract_factory; + +public class FutureVehicleMotorcycle implements MotorVehicle { + @Override + public void build() { + System.out.println("Future Vehicle Motorcycle"); + } +} \ No newline at end of file diff --git a/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/abstract_factory/MotorVehicle.java b/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/abstract_factory/MotorVehicle.java new file mode 100644 index 0000000000..f71d5636e2 --- /dev/null +++ b/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/abstract_factory/MotorVehicle.java @@ -0,0 +1,5 @@ +package com.baeldung.factory_pattern.abstract_factory; + +public interface MotorVehicle { + void build(); +} \ No newline at end of file diff --git a/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/abstract_factory/NextGenCorporation.java b/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/abstract_factory/NextGenCorporation.java new file mode 100644 index 0000000000..3cb965cf93 --- /dev/null +++ b/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/abstract_factory/NextGenCorporation.java @@ -0,0 +1,13 @@ +package com.baeldung.factory_pattern.abstract_factory; + +public class NextGenCorporation extends Corporation { + @Override + public MotorVehicle createMotorVehicle() { + return new NextGenMotorcycle(); + } + + @Override + public ElectricVehicle createElectricVehicle() { + return new NextGenElectricCar(); + } +} \ No newline at end of file diff --git a/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/abstract_factory/NextGenElectricCar.java b/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/abstract_factory/NextGenElectricCar.java new file mode 100644 index 0000000000..716d4060be --- /dev/null +++ b/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/abstract_factory/NextGenElectricCar.java @@ -0,0 +1,8 @@ +package com.baeldung.factory_pattern.abstract_factory; + +public class NextGenElectricCar implements ElectricVehicle { + @Override + public void build() { + System.out.println("NextGen Electric Car"); + } +} \ No newline at end of file diff --git a/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/abstract_factory/NextGenMotorcycle.java b/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/abstract_factory/NextGenMotorcycle.java new file mode 100644 index 0000000000..c475503616 --- /dev/null +++ b/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/abstract_factory/NextGenMotorcycle.java @@ -0,0 +1,8 @@ +package com.baeldung.factory_pattern.abstract_factory; + +public class NextGenMotorcycle implements MotorVehicle { + @Override + public void build() { + System.out.println("NextGen Motorcycle"); + } +} \ No newline at end of file diff --git a/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/method/Car.java b/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/method/Car.java new file mode 100644 index 0000000000..0c7b0d9eef --- /dev/null +++ b/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/method/Car.java @@ -0,0 +1,8 @@ +package com.baeldung.factory_pattern.method; + +public class Car implements MotorVehicle { + @Override + public void build() { + System.out.println("Build Car"); + } +} \ No newline at end of file diff --git a/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/method/CarFactory.java b/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/method/CarFactory.java new file mode 100644 index 0000000000..2cd78e0394 --- /dev/null +++ b/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/method/CarFactory.java @@ -0,0 +1,8 @@ +package com.baeldung.factory_pattern.method; + +public class CarFactory extends MotorVehicleFactory { + @Override + protected MotorVehicle createMotorVehicle() { + return new Car(); + } +} \ No newline at end of file diff --git a/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/method/MotorVehicle.java b/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/method/MotorVehicle.java new file mode 100644 index 0000000000..a9517e887a --- /dev/null +++ b/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/method/MotorVehicle.java @@ -0,0 +1,5 @@ +package com.baeldung.factory_pattern.method; + +public interface MotorVehicle { + void build(); +} \ No newline at end of file diff --git a/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/method/MotorVehicleFactory.java b/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/method/MotorVehicleFactory.java new file mode 100644 index 0000000000..0b719ff19f --- /dev/null +++ b/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/method/MotorVehicleFactory.java @@ -0,0 +1,11 @@ +package com.baeldung.factory_pattern.method; + +public abstract class MotorVehicleFactory { + public MotorVehicle create() { + MotorVehicle vehicle = createMotorVehicle(); + vehicle.build(); + return vehicle; + } + + protected abstract MotorVehicle createMotorVehicle(); +} \ No newline at end of file diff --git a/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/method/Motorcycle.java b/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/method/Motorcycle.java new file mode 100644 index 0000000000..be3e736a06 --- /dev/null +++ b/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/method/Motorcycle.java @@ -0,0 +1,8 @@ +package com.baeldung.factory_pattern.method; + +public class Motorcycle implements MotorVehicle { + @Override + public void build() { + System.out.println("Build Motorcycle"); + } +} \ No newline at end of file diff --git a/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/method/MotorcycleFactory.java b/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/method/MotorcycleFactory.java new file mode 100644 index 0000000000..10acef7bce --- /dev/null +++ b/patterns-modules/design-patterns-creational-2/src/main/java/com/baeldung/factory_pattern/method/MotorcycleFactory.java @@ -0,0 +1,8 @@ +package com.baeldung.factory_pattern.method; + +public class MotorcycleFactory extends MotorVehicleFactory { + @Override + protected MotorVehicle createMotorVehicle() { + return new Motorcycle(); + } +} diff --git a/patterns-modules/design-patterns-creational-2/src/test/java/com/baeldung/factory_pattern/abstract_factory/AbstractFactoryUnitTest.java b/patterns-modules/design-patterns-creational-2/src/test/java/com/baeldung/factory_pattern/abstract_factory/AbstractFactoryUnitTest.java new file mode 100644 index 0000000000..54f3fe4011 --- /dev/null +++ b/patterns-modules/design-patterns-creational-2/src/test/java/com/baeldung/factory_pattern/abstract_factory/AbstractFactoryUnitTest.java @@ -0,0 +1,39 @@ +package com.baeldung.factory_pattern.abstract_factory; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +class AbstractFactoryUnitTest { + @Test + public void givenFutureVehicleFactory_whenCreateMotorVehicle_thenInstanceOf() { + Corporation corporation = new FutureVehicleCorporation(); + MotorVehicle motorVehicle = corporation.createMotorVehicle(); + assertThat(motorVehicle).isInstanceOf(MotorVehicle.class); + assertThat(motorVehicle).isInstanceOf(FutureVehicleMotorcycle.class); + } + + @Test + public void givenFutureVehicleFactory_whenCreateElectricVehicle_thenInstanceOf() { + Corporation corporation = new FutureVehicleCorporation(); + ElectricVehicle electricVehicle = corporation.createElectricVehicle(); + assertThat(electricVehicle).isInstanceOf(ElectricVehicle.class); + assertThat(electricVehicle).isInstanceOf(FutureVehicleElectricCar.class); + } + + @Test + public void givenNextGenFactory_whenCreateMotorVehicle_thenInstanceOf() { + Corporation corporation = new NextGenCorporation(); + MotorVehicle motorVehicle = corporation.createMotorVehicle(); + assertThat(motorVehicle).isInstanceOf(MotorVehicle.class); + assertThat(motorVehicle).isInstanceOf(NextGenMotorcycle.class); + } + + @Test + public void givenNextGenFactory_whenCreateElectricVehicle_thenInstanceOf() { + Corporation corporation = new NextGenCorporation(); + ElectricVehicle electricVehicle = corporation.createElectricVehicle(); + assertThat(electricVehicle).isInstanceOf(ElectricVehicle.class); + assertThat(electricVehicle).isInstanceOf(NextGenElectricCar.class); + } +} \ No newline at end of file diff --git a/patterns-modules/design-patterns-creational-2/src/test/java/com/baeldung/factory_pattern/method/FactoryMethodUnitTest.java b/patterns-modules/design-patterns-creational-2/src/test/java/com/baeldung/factory_pattern/method/FactoryMethodUnitTest.java new file mode 100644 index 0000000000..9fd77ef139 --- /dev/null +++ b/patterns-modules/design-patterns-creational-2/src/test/java/com/baeldung/factory_pattern/method/FactoryMethodUnitTest.java @@ -0,0 +1,23 @@ +package com.baeldung.factory_pattern.method; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +class FactoryMethodUnitTest { + @Test + public void givenCarFactory_whenCreateMotorVehicle_thenInstanceOf() { + MotorVehicleFactory factory = new CarFactory(); + MotorVehicle car = factory.createMotorVehicle(); + assertThat(car).isInstanceOf(MotorVehicle.class); + assertThat(car).isInstanceOf(Car.class); + } + + @Test + public void givenMotorcycleFactory_whenCreateMotorVehicle_thenInstanceOf() { + MotorVehicleFactory factory = new MotorcycleFactory(); + MotorVehicle motorcycle = factory.createMotorVehicle(); + assertThat(motorcycle).isInstanceOf(MotorVehicle.class); + assertThat(motorcycle).isInstanceOf(Motorcycle.class); + } +} \ No newline at end of file diff --git a/patterns-modules/design-patterns-creational/README.md b/patterns-modules/design-patterns-creational/README.md new file mode 100644 index 0000000000..b599955846 --- /dev/null +++ b/patterns-modules/design-patterns-creational/README.md @@ -0,0 +1,11 @@ +### Relevant Articles: +- [Singletons in Java](https://www.baeldung.com/java-singleton) +- [Introduction to Creational Design Patterns](https://www.baeldung.com/creational-design-patterns) +- [Abstract Factory Pattern in Java](https://www.baeldung.com/java-abstract-factory-pattern) +- [Flyweight Pattern in Java](https://www.baeldung.com/java-flyweight) +- [Double-Checked Locking with Singleton](https://www.baeldung.com/java-singleton-double-checked-locking) +- [Java Constructors vs Static Factory Methods](https://www.baeldung.com/java-constructors-vs-static-factory-methods) +- [Automatic Generation of the Builder Pattern with FreeBuilder](https://www.baeldung.com/java-builder-pattern-freebuilder) +- [How to Replace Many if Statements in Java](https://www.baeldung.com/java-replace-if-statements) +- [Prototype Pattern in Java](https://www.baeldung.com/java-pattern-prototype) +- [Implementing Factory Pattern With Generics in Java](https://www.baeldung.com/java-factory-pattern-generics) diff --git a/patterns-modules/design-patterns-creational/pom.xml b/patterns-modules/design-patterns-creational/pom.xml new file mode 100644 index 0000000000..8945b83d23 --- /dev/null +++ b/patterns-modules/design-patterns-creational/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + design-patterns-creational + 1.0 + design-patterns-creational + jar + + + com.baeldung + patterns-modules + 1.0.0-SNAPSHOT + + + + + org.inferred + freebuilder + ${freebuilder.version} + true + + + com.google.code.findbugs + jsr305 + ${javax.annotations.version} + + + + + 2.4.1 + 3.0.2 + + + \ No newline at end of file diff --git a/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/constructorsstaticfactorymethods/entities/User.java b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/constructorsstaticfactorymethods/entities/User.java new file mode 100644 index 0000000000..a602d1c55d --- /dev/null +++ b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/constructorsstaticfactorymethods/entities/User.java @@ -0,0 +1,56 @@ +package com.baeldung.constructorsstaticfactorymethods.entities; + +import java.time.LocalTime; +import java.util.logging.ConsoleHandler; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.logging.SimpleFormatter; + +public class User { + + private static volatile User instance = null; + private static final Logger LOGGER = Logger.getLogger(User.class.getName()); + private final String name; + private final String email; + private final String country; + + public static User createWithDefaultCountry(String name, String email) { + return new User(name, email, "Argentina"); + } + + public static User createWithLoggedInstantiationTime(String name, String email, String country) { + LOGGER.log(Level.INFO, "Creating User instance at : {0}", LocalTime.now()); + + return new User(name, email, country); + } + + public static User getSingletonInstance(String name, String email, String country) { + if (instance == null) { + synchronized (User.class) { + if (instance == null) { + instance = new User(name, email, country); + } + } + } + return instance; + + } + + private User(String name, String email, String country) { + this.name = name; + this.email = email; + this.country = country; + } + + public String getName() { + return name; + } + + public String getEmail() { + return email; + } + + public String getCountry() { + return country; + } +} diff --git a/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/creational/abstractfactory2/AbstractFactoryRunner.java b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/creational/abstractfactory2/AbstractFactoryRunner.java new file mode 100644 index 0000000000..59aa3f8040 --- /dev/null +++ b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/creational/abstractfactory2/AbstractFactoryRunner.java @@ -0,0 +1,8 @@ +package com.baeldung.creational.abstractfactory2; + +public class AbstractFactoryRunner { + + public static void main(String[] args) { + new AnimalAbstractFactory().createAnimal(AnimalType.LAND); + } +} diff --git a/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/creational/abstractfactory2/Animal.java b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/creational/abstractfactory2/Animal.java new file mode 100644 index 0000000000..97b444c3e0 --- /dev/null +++ b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/creational/abstractfactory2/Animal.java @@ -0,0 +1,13 @@ +package com.baeldung.creational.abstractfactory2; + +public abstract class Animal { + AnimalType type; + AnimalEra era; + String name; + Animal(AnimalType type, AnimalEra era, String name) { + this.type = type; + this.era = era; + this.name = name; + } + abstract void create(); +} diff --git a/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/creational/abstractfactory2/AnimalAbstractFactory.java b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/creational/abstractfactory2/AnimalAbstractFactory.java new file mode 100644 index 0000000000..6d09e1661a --- /dev/null +++ b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/creational/abstractfactory2/AnimalAbstractFactory.java @@ -0,0 +1,22 @@ +package com.baeldung.creational.abstractfactory2; + +public class AnimalAbstractFactory { + + Animal animal; + Animal createAnimal(AnimalType type) { + AnimalEra era = getFromConfiguration(); + switch (era) { + case MESOZOIC: + animal = new MesozoicAnimalFactory().createAnimal(type); + break; + case CENOZOIC: + animal = new CenozoicAnimalFactory().createAnimal(type); + break; + } + return null; + } + + AnimalEra getFromConfiguration() { + return AnimalEra.MESOZOIC; + } +} diff --git a/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/creational/abstractfactory2/AnimalEra.java b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/creational/abstractfactory2/AnimalEra.java new file mode 100644 index 0000000000..0ee4deda65 --- /dev/null +++ b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/creational/abstractfactory2/AnimalEra.java @@ -0,0 +1,6 @@ +package com.baeldung.creational.abstractfactory2; + +public enum AnimalEra { + MESOZOIC, + CENOZOIC +} \ No newline at end of file diff --git a/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/creational/abstractfactory2/AnimalType.java b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/creational/abstractfactory2/AnimalType.java new file mode 100644 index 0000000000..be2f5e2361 --- /dev/null +++ b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/creational/abstractfactory2/AnimalType.java @@ -0,0 +1,6 @@ +package com.baeldung.creational.abstractfactory2; + +public enum AnimalType { + LAND, + SKY +} \ No newline at end of file diff --git a/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/creational/abstractfactory2/CenozoicAnimalFactory.java b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/creational/abstractfactory2/CenozoicAnimalFactory.java new file mode 100644 index 0000000000..cf28acdd25 --- /dev/null +++ b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/creational/abstractfactory2/CenozoicAnimalFactory.java @@ -0,0 +1,23 @@ +package com.baeldung.creational.abstractfactory2; + +public class CenozoicAnimalFactory implements EraAnimalFactory{ + @Override + public LandAnimal makeLandAnimal() { + return new LandAnimal(AnimalEra.CENOZOIC, "Mammoth"); + } + + @Override + public SkyAnimal makeSkyAnimal() { + return new SkyAnimal(AnimalEra.CENOZOIC, "Terror bird"); + } + + Animal createAnimal(AnimalType type) { + switch (type) { + case LAND: + return makeLandAnimal(); + case SKY: + return makeSkyAnimal(); + } + return null; + } +} diff --git a/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/creational/abstractfactory2/EraAnimalFactory.java b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/creational/abstractfactory2/EraAnimalFactory.java new file mode 100644 index 0000000000..30c69b4598 --- /dev/null +++ b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/creational/abstractfactory2/EraAnimalFactory.java @@ -0,0 +1,7 @@ +package com.baeldung.creational.abstractfactory2; + +public interface EraAnimalFactory { + + LandAnimal makeLandAnimal(); + SkyAnimal makeSkyAnimal(); +} diff --git a/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/creational/abstractfactory2/LandAnimal.java b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/creational/abstractfactory2/LandAnimal.java new file mode 100644 index 0000000000..963cd96969 --- /dev/null +++ b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/creational/abstractfactory2/LandAnimal.java @@ -0,0 +1,13 @@ +package com.baeldung.creational.abstractfactory2; + +public class LandAnimal extends Animal{ + + LandAnimal(AnimalEra era, String name) { + super(AnimalType.LAND, era, name); + create(); + } + @Override + void create() { + System.out.println("Creating a " + type + " animal: " + name); + } +} diff --git a/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/creational/abstractfactory2/MesozoicAnimalFactory.java b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/creational/abstractfactory2/MesozoicAnimalFactory.java new file mode 100644 index 0000000000..dd56669242 --- /dev/null +++ b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/creational/abstractfactory2/MesozoicAnimalFactory.java @@ -0,0 +1,24 @@ +package com.baeldung.creational.abstractfactory2; + +public class MesozoicAnimalFactory implements EraAnimalFactory{ + @Override + public LandAnimal makeLandAnimal() { + return new LandAnimal(AnimalEra.MESOZOIC, "Tyrannosaurus Rex"); + } + + @Override + public SkyAnimal makeSkyAnimal() { + return new SkyAnimal(AnimalEra.MESOZOIC, "Pterodactylus"); + } + + Animal createAnimal(AnimalType type) { + switch (type) { + case LAND: + return makeLandAnimal(); + case SKY: + return makeSkyAnimal(); + } + return null; + } + +} diff --git a/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/creational/abstractfactory2/SkyAnimal.java b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/creational/abstractfactory2/SkyAnimal.java new file mode 100644 index 0000000000..ce27236d7c --- /dev/null +++ b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/creational/abstractfactory2/SkyAnimal.java @@ -0,0 +1,13 @@ +package com.baeldung.creational.abstractfactory2; + +public class SkyAnimal extends Animal{ + + SkyAnimal(AnimalEra era, String name) { + super(AnimalType.SKY, era, name); + create(); + } + @Override + void create() { + System.out.println("Creating a " + type + " animal: " + name); + } +} diff --git a/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/factorygeneric/DateNotifier.java b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/factorygeneric/DateNotifier.java new file mode 100644 index 0000000000..b48c432a32 --- /dev/null +++ b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/factorygeneric/DateNotifier.java @@ -0,0 +1,11 @@ +package com.baeldung.factorygeneric; + +import java.util.Date; + +public class DateNotifier implements Notifier { + + @Override + public void notify(Date date) { + System.out.println("Notifying: " + date); + } +} diff --git a/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/factorygeneric/Main.java b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/factorygeneric/Main.java new file mode 100644 index 0000000000..0b3da9013d --- /dev/null +++ b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/factorygeneric/Main.java @@ -0,0 +1,14 @@ +package com.baeldung.factorygeneric; + +import java.util.Date; + +class Main { + public static void main(String[] args) { + NotifierFactory factory = new NotifierFactory(); + Notifier stringNotifier = factory.getNotifier(String.class); + Notifier dateNotifier = factory.getNotifier(Date.class); + + stringNotifier.notify("Hello world!"); + dateNotifier.notify(new Date()); + } +} diff --git a/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/factorygeneric/Notifier.java b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/factorygeneric/Notifier.java new file mode 100644 index 0000000000..bfda24bc15 --- /dev/null +++ b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/factorygeneric/Notifier.java @@ -0,0 +1,6 @@ +package com.baeldung.factorygeneric; + +public interface Notifier { + + void notify(T obj); +} diff --git a/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/factorygeneric/NotifierFactory.java b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/factorygeneric/NotifierFactory.java new file mode 100644 index 0000000000..ff1091d18f --- /dev/null +++ b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/factorygeneric/NotifierFactory.java @@ -0,0 +1,17 @@ +package com.baeldung.factorygeneric; + +import java.util.Date; + +public class NotifierFactory { + + public Notifier getNotifier(Class c) { + if (c == String.class) { + return Record.STRING.make(); + } + if (c == Date.class) { + return Record.DATE.make(); + } + return null; + } + +} diff --git a/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/factorygeneric/Record.java b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/factorygeneric/Record.java new file mode 100644 index 0000000000..1dbe94f2e7 --- /dev/null +++ b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/factorygeneric/Record.java @@ -0,0 +1,20 @@ +package com.baeldung.factorygeneric; + +import java.util.Date; + +public enum Record { + STRING { + @Override + public Notifier make() { + return new StringNotifier(); + } + }, + DATE { + @Override + public Notifier make() { + return new DateNotifier(); + } + }; + + public abstract Notifier make(); +} diff --git a/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/factorygeneric/StringNotifier.java b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/factorygeneric/StringNotifier.java new file mode 100644 index 0000000000..576085f267 --- /dev/null +++ b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/factorygeneric/StringNotifier.java @@ -0,0 +1,9 @@ +package com.baeldung.factorygeneric; + +public class StringNotifier implements Notifier { + + @Override + public void notify(String str) { + System.out.println("Notifying: " + str); + } +} diff --git a/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/prototype/PineTree.java b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/prototype/PineTree.java new file mode 100644 index 0000000000..03f0ebfe7f --- /dev/null +++ b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/prototype/PineTree.java @@ -0,0 +1,23 @@ +package com.baeldung.prototype; + +public class PineTree extends Tree { + + private String type; + + public PineTree(double mass, double height) { + super(mass, height); + this.type = "Pine"; + } + + public String getType() { + return type; + } + + @Override + public Tree copy() { + PineTree pineTreeClone = new PineTree(this.getMass(), this.getHeight()); + pineTreeClone.setPosition(this.getPosition()); + return pineTreeClone; + } + +} diff --git a/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/prototype/PlasticTree.java b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/prototype/PlasticTree.java new file mode 100644 index 0000000000..aea805dd47 --- /dev/null +++ b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/prototype/PlasticTree.java @@ -0,0 +1,23 @@ +package com.baeldung.prototype; + +public class PlasticTree extends Tree { + + private String name; + + public PlasticTree(double mass, double height) { + super(mass, height); + this.name = "PlasticTree"; + } + + public String getName() { + return name; + } + + @Override + public Tree copy() { + PlasticTree plasticTreeClone = new PlasticTree(this.getMass(), this.getHeight()); + plasticTreeClone.setPosition(this.getPosition()); + return plasticTreeClone; + } + +} diff --git a/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/prototype/Position.java b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/prototype/Position.java new file mode 100644 index 0000000000..dd7694feb6 --- /dev/null +++ b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/prototype/Position.java @@ -0,0 +1,51 @@ +package com.baeldung.prototype; + +public final class Position { + + private final int x; + private final int y; + + public Position(int x, int y) { + this.x = x; + this.y = y; + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + x; + result = prime * result + y; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Position other = (Position) obj; + if (x != other.x) + return false; + if (y != other.y) + return false; + return true; + } + + @Override + public String toString() { + return "Position [x=" + x + ", y=" + y + "]"; + } + +} diff --git a/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/prototype/Tree.java b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/prototype/Tree.java new file mode 100644 index 0000000000..dffb573057 --- /dev/null +++ b/patterns-modules/design-patterns-creational/src/main/java/com/baeldung/prototype/Tree.java @@ -0,0 +1,44 @@ +package com.baeldung.prototype; + +public abstract class Tree { + + private double mass; + private double height; + private Position position; + + public Tree(double mass, double height) { + this.mass = mass; + this.height = height; + } + + public void setMass(double mass) { + this.mass = mass; + } + + public void setHeight(double height) { + this.height = height; + } + + public void setPosition(Position position) { + this.position = position; + } + + public double getMass() { + return mass; + } + + public double getHeight() { + return height; + } + + public Position getPosition() { + return position; + } + + @Override + public String toString() { + return "Tree [mass=" + mass + ", height=" + height + ", position=" + position + "]"; + } + + public abstract Tree copy(); +} diff --git a/patterns-modules/design-patterns-creational/src/test/java/com/baeldung/factorygeneric/FactoryGenericUnitTest.java b/patterns-modules/design-patterns-creational/src/test/java/com/baeldung/factorygeneric/FactoryGenericUnitTest.java new file mode 100644 index 0000000000..6fabfc22dc --- /dev/null +++ b/patterns-modules/design-patterns-creational/src/test/java/com/baeldung/factorygeneric/FactoryGenericUnitTest.java @@ -0,0 +1,43 @@ +package com.baeldung.factorygeneric; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Date; + +// Unit test for the NotifierFactory class. +public class FactoryGenericUnitTest { + + // Checks that when the factory is asked to provide a string notifier, the object is an actual string notifier. + @Test + public void givenStringNotifier_whenEquals_thenTrue() { + NotifierFactory factory = new NotifierFactory(); + Notifier stringNotifier = factory.getNotifier(String.class); + + assertNotNull(stringNotifier, "The object returned by the factory is null!"); + assertTrue(stringNotifier instanceof Notifier, "The object returned by the factory is not a notifier!"); + } + + // Checks that when the factory is asked to provide a date notifier, the object is an actual date notifier. + @Test + public void givenDateNotifier_whenEquals_thenTrue() { + NotifierFactory factory = new NotifierFactory(); + Notifier dateNotifier = factory.getNotifier(Date.class); + + assertNotNull(dateNotifier, "The object returned by the factory is null!"); + assertTrue(dateNotifier instanceof Notifier, "The object returned by the factory is not a notifier!"); + } + + // Checks that when the factory is asked to provide both a date notifier and a string notifier, the objects returned are different. + @Test + public void givenDateNotifierAndStringNotifier_whenEquals_thenFalse() { + NotifierFactory factory = new NotifierFactory(); + Notifier stringNotifier = factory.getNotifier(String.class); + Notifier dateNotifier = factory.getNotifier(Date.class); + + assertNotNull(stringNotifier, "The object returned by the factory is null!"); + assertNotNull(dateNotifier, "The object returned by the factory is null!"); + assertNotEquals(stringNotifier, dateNotifier, "The string notifier and date notifier objects returned by the factory are actually the same!"); + } + +} diff --git a/patterns-modules/design-patterns-creational/src/test/java/com/baeldung/prototype/TreePrototypeUnitTest.java b/patterns-modules/design-patterns-creational/src/test/java/com/baeldung/prototype/TreePrototypeUnitTest.java new file mode 100644 index 0000000000..1fb1fbfea6 --- /dev/null +++ b/patterns-modules/design-patterns-creational/src/test/java/com/baeldung/prototype/TreePrototypeUnitTest.java @@ -0,0 +1,68 @@ +package com.baeldung.prototype; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import java.util.List; +import static java.util.stream.Collectors.toList; + +import org.junit.jupiter.api.Test; + +public class TreePrototypeUnitTest { + + @Test + public void givenAPlasticTreePrototypeWhenClonedThenCreateA_Clone() { + double mass = 10.0; + double height = 3.7; + Position position = new Position(3, 7); + Position otherPosition = new Position(4, 8); + + PlasticTree plasticTree = new PlasticTree(mass, height); + plasticTree.setPosition(position); + PlasticTree anotherPlasticTree = (PlasticTree) plasticTree.copy(); + anotherPlasticTree.setPosition(otherPosition); + + assertEquals(position, plasticTree.getPosition()); + assertEquals(otherPosition, anotherPlasticTree.getPosition()); + + } + + @Test + public void givenAPineTreePrototypeWhenClonedThenCreateA_Clone() { + double mass = 10.0; + double height = 3.7; + Position position = new Position(3, 7); + Position otherPosition = new Position(4, 8); + + PineTree pineTree = new PineTree(mass, height); + pineTree.setPosition(position); + PineTree anotherPineTree = (PineTree) pineTree.copy(); + anotherPineTree.setPosition(otherPosition); + + assertEquals(position, pineTree.getPosition()); + assertEquals(otherPosition, anotherPineTree.getPosition()); + } + + @Test + public void givenA_ListOfTreesWhenClonedThenCreateListOfClones() { + double mass = 10.0; + double height = 3.7; + Position position = new Position(3, 7); + Position otherPosition = new Position(4, 8); + + PlasticTree plasticTree = new PlasticTree(mass, height); + plasticTree.setPosition(position); + PineTree pineTree = new PineTree(mass, height); + pineTree.setPosition(otherPosition); + + List trees = Arrays.asList(plasticTree, pineTree); + + List treeClones = trees.stream().map(Tree::copy).collect(toList()); + + Tree plasticTreeClone = treeClones.get(0); + + assertEquals(mass, plasticTreeClone.getMass()); + assertEquals(height, plasticTreeClone.getHeight()); + assertEquals(position, plasticTreeClone.getPosition()); + } +} diff --git a/patterns-modules/design-patterns-functional/pom.xml b/patterns-modules/design-patterns-functional/pom.xml new file mode 100644 index 0000000000..f86d2e8cd7 --- /dev/null +++ b/patterns-modules/design-patterns-functional/pom.xml @@ -0,0 +1,17 @@ + + + 4.0.0 + design-patterns-functional + 1.0 + design-patterns-functional + jar + + + com.baeldung + patterns-modules + 1.0.0-SNAPSHOT + + + \ No newline at end of file diff --git a/patterns-modules/design-patterns-singleton/README.md b/patterns-modules/design-patterns-singleton/README.md new file mode 100644 index 0000000000..edec116b93 --- /dev/null +++ b/patterns-modules/design-patterns-singleton/README.md @@ -0,0 +1,2 @@ +### Relevant Articles: +- [How to Serialize a Singleton in Java](https://www.baeldung.com/java-serialize-singleton) diff --git a/patterns-modules/design-patterns-singleton/pom.xml b/patterns-modules/design-patterns-singleton/pom.xml new file mode 100644 index 0000000000..362f7f83c1 --- /dev/null +++ b/patterns-modules/design-patterns-singleton/pom.xml @@ -0,0 +1,17 @@ + + + 4.0.0 + design-patterns-singleton + 1.0 + design-patterns-singleton + jar + + + com.baeldung + patterns-modules + 1.0.0-SNAPSHOT + + + \ No newline at end of file diff --git a/patterns-modules/design-patterns-singleton/src/main/java/com/baeldung/serializable_singleton/EnumSingleton.java b/patterns-modules/design-patterns-singleton/src/main/java/com/baeldung/serializable_singleton/EnumSingleton.java new file mode 100644 index 0000000000..4259211934 --- /dev/null +++ b/patterns-modules/design-patterns-singleton/src/main/java/com/baeldung/serializable_singleton/EnumSingleton.java @@ -0,0 +1,20 @@ +package com.baeldung.serializable_singleton; + +public enum EnumSingleton { + + INSTANCE("State Zero"); + + private String state; + + private EnumSingleton(String state) { + this.state = state; + } + + public static EnumSingleton getInstance() { + return INSTANCE; + } + + public String getState() { return this.state; } + + public void setState(String state) { this.state = state; } +} \ No newline at end of file diff --git a/patterns-modules/design-patterns-singleton/src/main/java/com/baeldung/serializable_singleton/Singleton.java b/patterns-modules/design-patterns-singleton/src/main/java/com/baeldung/serializable_singleton/Singleton.java new file mode 100644 index 0000000000..6605d5dcf7 --- /dev/null +++ b/patterns-modules/design-patterns-singleton/src/main/java/com/baeldung/serializable_singleton/Singleton.java @@ -0,0 +1,28 @@ +package com.baeldung.serializable_singleton; + +import java.io.Serializable; + +public class Singleton implements Serializable { + + private static Singleton INSTANCE; + private String state = "State Zero"; + + private Singleton() { + } + + public static Singleton getInstance() { + if(INSTANCE == null) { + INSTANCE = new Singleton(); + } + + return INSTANCE; + } + + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } +} \ No newline at end of file diff --git a/patterns-modules/design-patterns-singleton/src/test/java/com/baeldung/serializable_singleton/EnumSingletonUnitTest.java b/patterns-modules/design-patterns-singleton/src/test/java/com/baeldung/serializable_singleton/EnumSingletonUnitTest.java new file mode 100644 index 0000000000..7fdcb20850 --- /dev/null +++ b/patterns-modules/design-patterns-singleton/src/test/java/com/baeldung/serializable_singleton/EnumSingletonUnitTest.java @@ -0,0 +1,79 @@ +package com.baeldung.serializable_singleton; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +// Unit test for the EnumSingleton class. +public class EnumSingletonUnitTest { + + private static final String ENUM_SINGLETON_TEST_TXT = "enum_singleton_test.txt"; + + // Checks that when an EnumSingleton instance is serialized + // and then deserialized, its state is preserved. + @Test + public void givenEnumSingleton_whenSerializedAndDeserialized_thenStatePreserved() { + EnumSingleton es1 = EnumSingleton.getInstance(); + + es1.setState("State One"); + + try (FileOutputStream fos = new FileOutputStream(ENUM_SINGLETON_TEST_TXT); + ObjectOutputStream oos = new ObjectOutputStream(fos); + FileInputStream fis = new FileInputStream(ENUM_SINGLETON_TEST_TXT); + ObjectInputStream ois = new ObjectInputStream(fis)) { + + // Serializing. + oos.writeObject(es1); + + // Deserializing. + EnumSingleton es2 = (EnumSingleton) ois.readObject(); + + // Checking if the state is preserved. + assertEquals(es1.getState(), es2.getState()); + + } catch (Exception e) { + System.out.println(e); + } + } + + // Checking that when an EnumSingleton instance is serialized + // and then deserialized, then there is still one instance + // of the EnumSingleton class in memory. + @Test + public void givenEnumSingleton_whenSerializedAndDeserialized_thenOneInstance() { + EnumSingleton es1 = EnumSingleton.getInstance(); + + try (FileOutputStream fos = new FileOutputStream(ENUM_SINGLETON_TEST_TXT); + ObjectOutputStream oos = new ObjectOutputStream(fos); + FileInputStream fis = new FileInputStream(ENUM_SINGLETON_TEST_TXT); + ObjectInputStream ois = new ObjectInputStream(fis)) { + + // Serializing. + oos.writeObject(es1); + + // Deserializing. + EnumSingleton es2 = (EnumSingleton) ois.readObject(); + + // Checking if es1 and es2 are pointing to + // the same instance in memory. + assertEquals(es1, es2); + + } catch (Exception e) { + System.out.println(e); + } + } + + @AfterAll + public static void cleanUp() { + final File removeFile = new File(ENUM_SINGLETON_TEST_TXT); + if (removeFile.exists()) { + removeFile.deleteOnExit(); + } + } +} diff --git a/patterns-modules/design-patterns-singleton/src/test/java/com/baeldung/serializable_singleton/SingletonUnitTest.java b/patterns-modules/design-patterns-singleton/src/test/java/com/baeldung/serializable_singleton/SingletonUnitTest.java new file mode 100644 index 0000000000..a46288cc8f --- /dev/null +++ b/patterns-modules/design-patterns-singleton/src/test/java/com/baeldung/serializable_singleton/SingletonUnitTest.java @@ -0,0 +1,78 @@ +package com.baeldung.serializable_singleton; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +// Unit test for the Singleton class. +public class SingletonUnitTest { + + private static final String SINGLETON_TEST_TXT = "singleton_test.txt"; + + // Checks that when a Singleton instance is serialized + // and then deserialized, its state is preserved. + @Test + public void givenSingleton_whenSerializedAndDeserialized_thenStatePreserved() { + Singleton s1 = Singleton.getInstance(); + + s1.setState("State One"); + + try (FileOutputStream fos = new FileOutputStream(SINGLETON_TEST_TXT); + ObjectOutputStream oos = new ObjectOutputStream(fos); + FileInputStream fis = new FileInputStream(SINGLETON_TEST_TXT); + ObjectInputStream ois = new ObjectInputStream(fis)) { + + // Serializing. + oos.writeObject(s1); + + // Deserializing. + Singleton s2 = (Singleton) ois.readObject(); + + // Checking if the state is preserved. + assertEquals(s1.getState(), s2.getState()); + + } catch (Exception e) { + System.out.println(e); + } + } + + // Checking that when a Singleton instance is serialized + // and then deserialized, then there are two instances of + // the Singleton class. + @Test + public void givenSingleton_whenSerializedAndDeserialized_thenTwoInstances() { + Singleton s1 = Singleton.getInstance(); + + try (FileOutputStream fos = new FileOutputStream(SINGLETON_TEST_TXT); + ObjectOutputStream oos = new ObjectOutputStream(fos); + FileInputStream fis = new FileInputStream(SINGLETON_TEST_TXT); + ObjectInputStream ois = new ObjectInputStream(fis)) { + + // Serializing. + oos.writeObject(s1); + + // Deserializing. + Singleton s2 = (Singleton) ois.readObject(); + + // Checking if s1 and s2 are not the same instance. + assertNotEquals(s1, s2); + + } catch (Exception e) { + System.out.println(e); + } + } + + @AfterAll + public static void cleanUp() { + final File removeFile = new File(SINGLETON_TEST_TXT); + if (removeFile.exists()) { + removeFile.deleteOnExit(); + } + } +} diff --git a/patterns-modules/design-patterns-structural/README.md b/patterns-modules/design-patterns-structural/README.md new file mode 100644 index 0000000000..7a9c7acf4d --- /dev/null +++ b/patterns-modules/design-patterns-structural/README.md @@ -0,0 +1,9 @@ +### Relevant Articles: +- [Facade Design Pattern in Java](https://www.baeldung.com/java-facade-pattern) +- [Proxy, Decorator, Adapter and Bridge Patterns](https://www.baeldung.com/java-structural-design-patterns) +- [Composite Design Pattern in Java](https://www.baeldung.com/java-composite-pattern) +- [The Decorator Pattern in Java](https://www.baeldung.com/java-decorator-pattern) +- [The Adapter Pattern in Java](https://www.baeldung.com/java-adapter-pattern) +- [The Proxy Pattern in Java](https://www.baeldung.com/java-proxy-pattern) +- [The Bridge Pattern in Java](https://www.baeldung.com/java-bridge-pattern) +- [Pipeline Design Pattern in Java](https://www.baeldung.com/java-pipeline-design-pattern) diff --git a/patterns-modules/design-patterns-structural/pom.xml b/patterns-modules/design-patterns-structural/pom.xml new file mode 100644 index 0000000000..e9b7194b73 --- /dev/null +++ b/patterns-modules/design-patterns-structural/pom.xml @@ -0,0 +1,25 @@ + + + 4.0.0 + design-patterns-structural + 1.0 + design-patterns-structural + jar + + + com.baeldung + patterns-modules + 1.0.0-SNAPSHOT + + + + + log4j + log4j + ${log4j.version} + + + + \ No newline at end of file diff --git a/patterns-modules/design-patterns-structural/src/main/java/com/baeldung/adapter/IteratorAdapter.java b/patterns-modules/design-patterns-structural/src/main/java/com/baeldung/adapter/IteratorAdapter.java new file mode 100644 index 0000000000..0abf333068 --- /dev/null +++ b/patterns-modules/design-patterns-structural/src/main/java/com/baeldung/adapter/IteratorAdapter.java @@ -0,0 +1,24 @@ +package com.baeldung.adapter; + +import java.util.Enumeration; +import java.util.Iterator; + +public class IteratorAdapter implements Iterator { + + private Enumeration enumeration; + + public IteratorAdapter(Enumeration enumeration) { + this.enumeration = enumeration; + } + + @Override + public boolean hasNext() { + return enumeration.hasMoreElements(); + } + + @Override + public E next() { + return enumeration.nextElement(); + } + +} diff --git a/patterns-modules/design-patterns-structural/src/main/java/com/baeldung/adapter/StringTokenizerIteratorAdapter.java b/patterns-modules/design-patterns-structural/src/main/java/com/baeldung/adapter/StringTokenizerIteratorAdapter.java new file mode 100644 index 0000000000..36c17b2609 --- /dev/null +++ b/patterns-modules/design-patterns-structural/src/main/java/com/baeldung/adapter/StringTokenizerIteratorAdapter.java @@ -0,0 +1,29 @@ +package com.baeldung.adapter; + +import java.util.Iterator; +import java.util.StringTokenizer; + +public class StringTokenizerIteratorAdapter extends StringTokenizer implements Iterator { + + public StringTokenizerIteratorAdapter(final String str, final String delim, final boolean returnDelims) { + super(str, delim, returnDelims); + } + + public StringTokenizerIteratorAdapter(final String str, final String delim) { + super(str, delim); + } + + public StringTokenizerIteratorAdapter(final String str) { + super(str); + } + + @Override + public boolean hasNext() { + return hasMoreTokens(); + } + + @Override + public String next() { + return nextToken(); + } +} diff --git a/patterns-modules/design-patterns-structural/src/main/java/com/baeldung/pipeline/immutable/Pipe.java b/patterns-modules/design-patterns-structural/src/main/java/com/baeldung/pipeline/immutable/Pipe.java new file mode 100644 index 0000000000..6fdb4c71c3 --- /dev/null +++ b/patterns-modules/design-patterns-structural/src/main/java/com/baeldung/pipeline/immutable/Pipe.java @@ -0,0 +1,5 @@ +package com.baeldung.pipeline.immutable; + +public interface Pipe { + OUT process(IN input); +} diff --git a/patterns-modules/design-patterns-structural/src/main/java/com/baeldung/pipeline/immutable/Pipeline.java b/patterns-modules/design-patterns-structural/src/main/java/com/baeldung/pipeline/immutable/Pipeline.java new file mode 100644 index 0000000000..db46a26d7e --- /dev/null +++ b/patterns-modules/design-patterns-structural/src/main/java/com/baeldung/pipeline/immutable/Pipeline.java @@ -0,0 +1,37 @@ +package com.baeldung.pipeline.immutable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; + +public class Pipeline { + + private Collection> pipes; + + private Pipeline(Pipe pipe) { + pipes = Collections.singletonList(pipe); + } + + private Pipeline(Collection> pipes) { + this.pipes = new ArrayList<>(pipes); + } + + public static Pipeline of(Pipe pipe) { + return new Pipeline<>(pipe); + } + + + public Pipeline withNextPipe(Pipe pipe) { + final ArrayList> newPipes = new ArrayList<>(pipes); + newPipes.add(pipe); + return new Pipeline<>(newPipes); + } + + public OUT process(IN input) { + Object output = input; + for (final Pipe pipe : pipes) { + output = pipe.process(output); + } + return (OUT) output; + } +} diff --git a/patterns-modules/design-patterns-structural/src/main/java/com/baeldung/pipeline/pipes/Pipe.java b/patterns-modules/design-patterns-structural/src/main/java/com/baeldung/pipeline/pipes/Pipe.java new file mode 100644 index 0000000000..93f005fdd2 --- /dev/null +++ b/patterns-modules/design-patterns-structural/src/main/java/com/baeldung/pipeline/pipes/Pipe.java @@ -0,0 +1,9 @@ +package com.baeldung.pipeline.pipes; + +public interface Pipe { + OUT process(IN input); + + default Pipe add(Pipe pipe) { + return input -> pipe.process(process(input)); + } +} diff --git a/patterns-modules/design-patterns-structural/src/main/resources/log4jstructuraldp.properties b/patterns-modules/design-patterns-structural/src/main/resources/log4jstructuraldp.properties new file mode 100644 index 0000000000..5bc2bfe4b9 --- /dev/null +++ b/patterns-modules/design-patterns-structural/src/main/resources/log4jstructuraldp.properties @@ -0,0 +1,9 @@ + +# Root logger +log4j.rootLogger=INFO, file, stdout + +# Write to console +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.Target=System.out +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n \ No newline at end of file diff --git a/patterns-modules/design-patterns-structural/src/test/java/com/baeldung/adapter/IteratorAdapterUnitTest.java b/patterns-modules/design-patterns-structural/src/test/java/com/baeldung/adapter/IteratorAdapterUnitTest.java new file mode 100644 index 0000000000..19de6c92ba --- /dev/null +++ b/patterns-modules/design-patterns-structural/src/test/java/com/baeldung/adapter/IteratorAdapterUnitTest.java @@ -0,0 +1,34 @@ +package com.baeldung.adapter; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.StringTokenizer; +import org.junit.jupiter.api.Test; + +class IteratorAdapterUnitTest { + + public static final String STRING_DATA = "Welcome to baeldung.com"; + private final StringTokenizer tokenizer = new StringTokenizer(STRING_DATA); + private final Iterator tokenizerAdapter = new StringTokenizerIteratorAdapter(STRING_DATA); + private final List expectedTokensForString = Arrays.asList("Welcome", "to", "baeldung.com"); + + @Test + void givenTokenizer_thenAdapterProducesCorrectResult() { + List actualTokens = new ArrayList<>(); + new IteratorAdapter(tokenizer).forEachRemaining(s -> actualTokens.add((String) s)); + assertEquals(expectedTokensForString, actualTokens); + } + + @Test + void givenTokenizerAdapter_thenIteratorProducesCorrectResult() { + List actualTokens = new ArrayList<>(); + tokenizerAdapter.forEachRemaining(actualTokens::add); + assertEquals(expectedTokensForString, actualTokens); + } + + +} \ No newline at end of file diff --git a/patterns-modules/design-patterns-structural/src/test/java/com/baeldung/pipeline/BiFunctionPipelineUnitTest.java b/patterns-modules/design-patterns-structural/src/test/java/com/baeldung/pipeline/BiFunctionPipelineUnitTest.java new file mode 100644 index 0000000000..5f094ad6e8 --- /dev/null +++ b/patterns-modules/design-patterns-structural/src/test/java/com/baeldung/pipeline/BiFunctionPipelineUnitTest.java @@ -0,0 +1,22 @@ +package com.baeldung.pipeline; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.function.BiFunction; +import java.util.function.Function; +import org.junit.jupiter.api.Test; + +class BiFunctionPipelineUnitTest { + + @Test + void whenCombiningFunctionAndBiFunctions_andInitializingPipeline_thenResultIsCorrect() { + BiFunction add = Integer::sum; + BiFunction mul = (a, b) -> a * b; + Function toString = Object::toString; + BiFunction pipeline = add.andThen(a -> mul.apply(a, 2)) + .andThen(toString); + String result = pipeline.apply(1, 2); + String expected = "6"; + assertEquals(expected, result); + } +} \ No newline at end of file diff --git a/patterns-modules/design-patterns-structural/src/test/java/com/baeldung/pipeline/FunctionPipelineUnitTest.java b/patterns-modules/design-patterns-structural/src/test/java/com/baeldung/pipeline/FunctionPipelineUnitTest.java new file mode 100644 index 0000000000..71bc14a5eb --- /dev/null +++ b/patterns-modules/design-patterns-structural/src/test/java/com/baeldung/pipeline/FunctionPipelineUnitTest.java @@ -0,0 +1,21 @@ +package com.baeldung.pipeline; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.function.Function; +import org.junit.jupiter.api.Test; + +class FunctionPipelineUnitTest { + + @Test + void whenCombiningThreeFunctions_andInitializingPipeline_thenResultIsCorrect() { + Function square = s -> s * s; + Function half = s -> s / 2; + Function toString = Object::toString; + Function pipeline = square.andThen(half) + .andThen(toString); + String result = pipeline.apply(5); + String expected = "12"; + assertEquals(expected, result); + } +} \ No newline at end of file diff --git a/patterns-modules/design-patterns-structural/src/test/java/com/baeldung/pipeline/PipeUnitTest.java b/patterns-modules/design-patterns-structural/src/test/java/com/baeldung/pipeline/PipeUnitTest.java new file mode 100644 index 0000000000..6a3a988f89 --- /dev/null +++ b/patterns-modules/design-patterns-structural/src/test/java/com/baeldung/pipeline/PipeUnitTest.java @@ -0,0 +1,20 @@ +package com.baeldung.pipeline; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.baeldung.pipeline.pipes.Pipe; +import org.junit.jupiter.api.Test; + +class PipeUnitTest { + + @Test + void whenCombiningThreePipes_andInitializingPipeline_thenResultIsCorrect() { + Pipe square = s -> s * s; + Pipe half = s -> s / 2; + Pipe toString = Object::toString; + Pipe pipeline = square.add(half).add(toString); + String result = pipeline.process(5); + String expected = "12"; + assertThat(result).isEqualTo(expected); + } +} \ No newline at end of file diff --git a/patterns-modules/design-patterns-structural/src/test/java/com/baeldung/pipeline/PipelineUnitTest.java b/patterns-modules/design-patterns-structural/src/test/java/com/baeldung/pipeline/PipelineUnitTest.java new file mode 100644 index 0000000000..2cbafc6213 --- /dev/null +++ b/patterns-modules/design-patterns-structural/src/test/java/com/baeldung/pipeline/PipelineUnitTest.java @@ -0,0 +1,24 @@ +package com.baeldung.pipeline; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.baeldung.pipeline.immutable.Pipe; +import com.baeldung.pipeline.immutable.Pipeline; +import org.junit.jupiter.api.Test; + +class PipelineUnitTest { + + @Test + void whenCombiningThreePipes_andInitializingPipeline_thenResultIsCorrect() { + Pipe square = s -> s * s; + Pipe half = s -> s / 2; + Pipe toString = Object::toString; + Pipeline squarePipeline = Pipeline.of(square); + Pipeline squareAndHalfPipeline = squarePipeline.withNextPipe(half); + Pipeline squareHalfAndStringPipeline = squareAndHalfPipeline.withNextPipe(toString); + + String result = squareHalfAndStringPipeline.process(5); + String expected = "12"; + assertThat(result).isEqualTo(expected); + } +} \ No newline at end of file diff --git a/patterns-modules/dip/pom.xml b/patterns-modules/dip/pom.xml new file mode 100644 index 0000000000..a216c4fb12 --- /dev/null +++ b/patterns-modules/dip/pom.xml @@ -0,0 +1,17 @@ + + + 4.0.0 + com.baeldung.dip + dip + 1.0-SNAPSHOT + dip + + + com.baeldung + patterns-modules + 1.0.0-SNAPSHOT + + + \ No newline at end of file diff --git a/patterns-modules/enterprise-patterns/README.md b/patterns-modules/enterprise-patterns/README.md new file mode 100644 index 0000000000..61d27f5c44 --- /dev/null +++ b/patterns-modules/enterprise-patterns/README.md @@ -0,0 +1,33 @@ +# Wire Tap Pattern + +The application shows you how to use a Wire Tap to monitor, debug or troubleshoot messages flowing through the system, without permanently consuming them off, or making any changes to the expected message in the output channel. + +This example shows how to implement this with a simple Apache Camel application using Spring Boot and Apache ActiveMq. +For convenience, we are using in-memory activeMq. + + + +### Configuring and using the Connection Factory + +1. Create CamelContext. +2. Connect to embedded (or remote) ActiveMQ JMS broker. +3. Add JMS queue to CamelContext. +4. Load file orders (xml/csv) from src/data into the JMS queue. +5. Based on the extension of the incoming file message, route to the respective queues. +6. Test that the destination route is working. +7. Audit the received file (order) from the wire tap queue. + +### How to run the example: + + mvn spring-boot:run + + +The Wire Tap processor, by default, makes a shallow copy of the Camel Exchange instance. The copy of the exchange is sent to the endpoint specified in the wireTap statement. The body of the wire tapped message contains the same object as that in the original message which means any change to the internal state of that object during the wire tap route may also end up changing the main message’s body. + +To solve this, we need to create a deep copy of the object before passing it to the wire tap destination. Wire Tap EIP provides us with a mechanism to perform a “deep” copy of the message, by implementing the org.apache.camel.Processor class. This needs to be be called using onPrepare statement right after wireTap. +For more details, check out the AmqApplicationUnitTest.class. + +### Relevant Articles: + +- [Wire Tap Enterprise Integration Pattern](https://www.baeldung.com/wiretap-pattern) + diff --git a/patterns-modules/enterprise-patterns/pom.xml b/patterns-modules/enterprise-patterns/pom.xml new file mode 100644 index 0000000000..0e9edbff67 --- /dev/null +++ b/patterns-modules/enterprise-patterns/pom.xml @@ -0,0 +1,72 @@ + + + 4.0.0 + enterprise-patterns + pom + + + com.baeldung + patterns-modules + 1.0.0-SNAPSHOT + + + + + + org.apache.camel.springboot + camel-spring-boot-dependencies + ${camel.version} + pom + import + + + org.apache.logging.log4j + log4j-bom + ${log4j2.version} + import + pom + + + + + + + org.apache.camel.springboot + camel-spring-boot-starter + + + org.apache.camel.springboot + camel-activemq-starter + + + + org.springframework.boot + spring-boot-starter-test + ${spring-boot.version} + test + + + org.apache.camel + camel-test-spring-junit5 + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + 3.7.4 + 2.7.8 + 2.17.1 + + + \ No newline at end of file diff --git a/patterns-modules/enterprise-patterns/src/data/.camel/msg1.xml b/patterns-modules/enterprise-patterns/src/data/.camel/msg1.xml new file mode 100644 index 0000000000..0a6fa64552 --- /dev/null +++ b/patterns-modules/enterprise-patterns/src/data/.camel/msg1.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/patterns-modules/enterprise-patterns/src/data/msg.xml b/patterns-modules/enterprise-patterns/src/data/msg.xml new file mode 100644 index 0000000000..0a6fa64552 --- /dev/null +++ b/patterns-modules/enterprise-patterns/src/data/msg.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/patterns-modules/enterprise-patterns/src/main/java/com/baeldung/AmqApplication.java b/patterns-modules/enterprise-patterns/src/main/java/com/baeldung/AmqApplication.java new file mode 100644 index 0000000000..eacef86d75 --- /dev/null +++ b/patterns-modules/enterprise-patterns/src/main/java/com/baeldung/AmqApplication.java @@ -0,0 +1,70 @@ +package com.baeldung; + +import org.apache.activemq.ActiveMQConnectionFactory; +import org.apache.camel.CamelContext; +import org.apache.camel.ProducerTemplate; +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.jms.JmsComponent; +import org.apache.camel.impl.DefaultCamelContext; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class AmqApplication { + + public static void main(String[] args) throws Exception { + SpringApplication.run(AmqApplication.class, args); + + try (CamelContext context = new DefaultCamelContext()) { + ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory( + "vm://localhost?broker.persistent=false"); + connectionFactory.setTrustAllPackages(true); + context.addComponent("direct", JmsComponent.jmsComponentAutoAcknowledge(connectionFactory)); + addRoute(context); + + try (ProducerTemplate template = context.createProducerTemplate()) { + context.start(); + + MyPayload payload = new MyPayload("One"); + template.sendBody("direct:source", payload); + Thread.sleep(10000); + } finally { + context.stop(); + } + } + } + + private static void addRoute(CamelContext context) throws Exception { + context.addRoutes(newExchangeRoute()); + } + + static RoutesBuilder traditionalWireTapRoute() { + return new RouteBuilder() { + public void configure() { + + from("direct:source").log("Main route: Send '${body}' to tap router").wireTap("direct:tap").delay(1000) + .log("Main route: Add 'two' to '${body}'").bean(MyBean.class, "addTwo").to("direct:destination") + .log("Main route: Output '${body}'"); + + from("direct:tap").log("Tap Wire route: received '${body}'") + .log("Tap Wire route: Add 'three' to '${body}'").bean(MyBean.class, "addThree") + .log("Tap Wire route: Output '${body}'"); + + from("direct:destination").log("Output at destination: '${body}'"); + } + }; + } + + static RoutesBuilder newExchangeRoute() throws Exception { + return new RouteBuilder() { + public void configure() throws Exception { + + from("direct:source").wireTap("direct:tap").onPrepare(new MyPayloadClonePrepare()).end().delay(1000); + + from("direct:tap").bean(MyBean.class, "addThree"); + } + }; + } + +} diff --git a/patterns-modules/enterprise-patterns/src/main/java/com/baeldung/MyBean.java b/patterns-modules/enterprise-patterns/src/main/java/com/baeldung/MyBean.java new file mode 100644 index 0000000000..62c45725b0 --- /dev/null +++ b/patterns-modules/enterprise-patterns/src/main/java/com/baeldung/MyBean.java @@ -0,0 +1,14 @@ +package com.baeldung; + +public class MyBean { + + public MyPayload addTwo(MyPayload body) { + body.setValue(body.getValue() + " and two"); + return body; + } + + public MyPayload addThree(MyPayload body) { + body.setValue(body.getValue() + " and three"); + return body; + } +} diff --git a/patterns-modules/enterprise-patterns/src/main/java/com/baeldung/MyPayload.java b/patterns-modules/enterprise-patterns/src/main/java/com/baeldung/MyPayload.java new file mode 100644 index 0000000000..84d6a22733 --- /dev/null +++ b/patterns-modules/enterprise-patterns/src/main/java/com/baeldung/MyPayload.java @@ -0,0 +1,31 @@ +package com.baeldung; + +import java.io.Serializable; + +public class MyPayload implements Serializable { + + private static final long serialVersionUID = 1L; + private String value; + + public MyPayload(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public String toString() { + return value; + } + + public MyPayload deepClone() { + MyPayload myPayload = new MyPayload(value); + return myPayload; + } + +} diff --git a/patterns-modules/enterprise-patterns/src/main/java/com/baeldung/MyPayloadClonePrepare.java b/patterns-modules/enterprise-patterns/src/main/java/com/baeldung/MyPayloadClonePrepare.java new file mode 100644 index 0000000000..938194bc1a --- /dev/null +++ b/patterns-modules/enterprise-patterns/src/main/java/com/baeldung/MyPayloadClonePrepare.java @@ -0,0 +1,15 @@ +package com.baeldung; + +import java.util.Date; + +import org.apache.camel.Exchange; +import org.apache.camel.Processor; + +public class MyPayloadClonePrepare implements Processor { + + public void process(Exchange exchange) throws Exception { + MyPayload myPayload = exchange.getIn().getBody(MyPayload.class); + exchange.getIn().setBody(myPayload.deepClone()); + exchange.getIn().setHeader("date", new Date()); + } +} diff --git a/patterns-modules/enterprise-patterns/src/main/resources/application.properties b/patterns-modules/enterprise-patterns/src/main/resources/application.properties new file mode 100644 index 0000000000..a74f494fa5 --- /dev/null +++ b/patterns-modules/enterprise-patterns/src/main/resources/application.properties @@ -0,0 +1,9 @@ +# to keep the JVM running +camel.springboot.main-run-controller = true + +#configure the URL of the remote ActiveMQ broker +#camel.component.activemq.broker-url=tcp://localhost:61616 +#spring.activemq.broker-url=tcp://localhost:61616 + +spring.activemq.in-memory=true +spring.activemq.pool.enabled=false \ No newline at end of file diff --git a/patterns-modules/enterprise-patterns/src/main/resources/log4j.properties b/patterns-modules/enterprise-patterns/src/main/resources/log4j.properties new file mode 100644 index 0000000000..f62f1da96f --- /dev/null +++ b/patterns-modules/enterprise-patterns/src/main/resources/log4j.properties @@ -0,0 +1,16 @@ +# Root logger option +log4j.rootLogger=INFO, file, console + +log4j.logger.com.javarticles=INFO, file + +# Direct log messages to a log file +log4j.appender.file=org.apache.log4j.FileAppender +log4j.appender.file.File=javarticles.log +log4j.appender.file.layout=org.apache.log4j.PatternLayout +log4j.appender.file.layout.ConversionPattern=%d | %p | %F %L | %m%n + +# Direct log messages to stdout +log4j.appender.console=org.apache.log4j.ConsoleAppender +log4j.appender.console.Target=System.out +log4j.appender.console.layout=org.apache.log4j.PatternLayout +log4j.appender.console.layout.ConversionPattern=%d{HH:mm}| %p | %F %L | %m%n \ No newline at end of file diff --git a/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-10-1-1-1 b/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-10-1-1-1 new file mode 100644 index 0000000000..25b70e3283 --- /dev/null +++ b/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-10-1-1-1 @@ -0,0 +1 @@ +Test Message: 8 \ No newline at end of file diff --git a/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-11-1-1-1 b/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-11-1-1-1 new file mode 100644 index 0000000000..3948af158c --- /dev/null +++ b/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-11-1-1-1 @@ -0,0 +1 @@ +Test Message: 9 \ No newline at end of file diff --git a/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-2-1-1-1 b/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-2-1-1-1 new file mode 100644 index 0000000000..262fbf6de5 --- /dev/null +++ b/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-2-1-1-1 @@ -0,0 +1 @@ +Test Message: 0 \ No newline at end of file diff --git a/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-3-1-1-1 b/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-3-1-1-1 new file mode 100644 index 0000000000..64a5c33134 --- /dev/null +++ b/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-3-1-1-1 @@ -0,0 +1 @@ +Test Message: 1 \ No newline at end of file diff --git a/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-4-1-1-1 b/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-4-1-1-1 new file mode 100644 index 0000000000..de22e70d38 --- /dev/null +++ b/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-4-1-1-1 @@ -0,0 +1 @@ +Test Message: 2 \ No newline at end of file diff --git a/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-5-1-1-1 b/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-5-1-1-1 new file mode 100644 index 0000000000..d400e1afeb --- /dev/null +++ b/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-5-1-1-1 @@ -0,0 +1 @@ +Test Message: 3 \ No newline at end of file diff --git a/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-6-1-1-1 b/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-6-1-1-1 new file mode 100644 index 0000000000..660c960fcb --- /dev/null +++ b/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-6-1-1-1 @@ -0,0 +1 @@ +Test Message: 4 \ No newline at end of file diff --git a/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-7-1-1-1 b/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-7-1-1-1 new file mode 100644 index 0000000000..c97f21578a --- /dev/null +++ b/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-7-1-1-1 @@ -0,0 +1 @@ +Test Message: 5 \ No newline at end of file diff --git a/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-8-1-1-1 b/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-8-1-1-1 new file mode 100644 index 0000000000..be38d662ff --- /dev/null +++ b/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-8-1-1-1 @@ -0,0 +1 @@ +Test Message: 6 \ No newline at end of file diff --git a/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-9-1-1-1 b/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-9-1-1-1 new file mode 100644 index 0000000000..518e4227fc --- /dev/null +++ b/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-34209-1621429668568-4-9-1-1-1 @@ -0,0 +1 @@ +Test Message: 7 \ No newline at end of file diff --git a/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-10-1-1-1 b/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-10-1-1-1 new file mode 100644 index 0000000000..25b70e3283 --- /dev/null +++ b/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-10-1-1-1 @@ -0,0 +1 @@ +Test Message: 8 \ No newline at end of file diff --git a/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-11-1-1-1 b/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-11-1-1-1 new file mode 100644 index 0000000000..3948af158c --- /dev/null +++ b/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-11-1-1-1 @@ -0,0 +1 @@ +Test Message: 9 \ No newline at end of file diff --git a/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-2-1-1-1 b/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-2-1-1-1 new file mode 100644 index 0000000000..262fbf6de5 --- /dev/null +++ b/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-2-1-1-1 @@ -0,0 +1 @@ +Test Message: 0 \ No newline at end of file diff --git a/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-3-1-1-1 b/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-3-1-1-1 new file mode 100644 index 0000000000..64a5c33134 --- /dev/null +++ b/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-3-1-1-1 @@ -0,0 +1 @@ +Test Message: 1 \ No newline at end of file diff --git a/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-4-1-1-1 b/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-4-1-1-1 new file mode 100644 index 0000000000..de22e70d38 --- /dev/null +++ b/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-4-1-1-1 @@ -0,0 +1 @@ +Test Message: 2 \ No newline at end of file diff --git a/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-5-1-1-1 b/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-5-1-1-1 new file mode 100644 index 0000000000..d400e1afeb --- /dev/null +++ b/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-5-1-1-1 @@ -0,0 +1 @@ +Test Message: 3 \ No newline at end of file diff --git a/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-6-1-1-1 b/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-6-1-1-1 new file mode 100644 index 0000000000..660c960fcb --- /dev/null +++ b/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-6-1-1-1 @@ -0,0 +1 @@ +Test Message: 4 \ No newline at end of file diff --git a/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-7-1-1-1 b/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-7-1-1-1 new file mode 100644 index 0000000000..c97f21578a --- /dev/null +++ b/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-7-1-1-1 @@ -0,0 +1 @@ +Test Message: 5 \ No newline at end of file diff --git a/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-8-1-1-1 b/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-8-1-1-1 new file mode 100644 index 0000000000..be38d662ff --- /dev/null +++ b/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-8-1-1-1 @@ -0,0 +1 @@ +Test Message: 6 \ No newline at end of file diff --git a/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-9-1-1-1 b/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-9-1-1-1 new file mode 100644 index 0000000000..518e4227fc --- /dev/null +++ b/patterns-modules/enterprise-patterns/test/ID-PRINHYLTPDL1209-46717-1621429562728-4-9-1-1-1 @@ -0,0 +1 @@ +Test Message: 7 \ No newline at end of file diff --git a/patterns-modules/front-controller/pom.xml b/patterns-modules/front-controller/pom.xml new file mode 100644 index 0000000000..567f3fb793 --- /dev/null +++ b/patterns-modules/front-controller/pom.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + front-controller + front-controller + war + + + com.baeldung + patterns-modules + 1.0.0-SNAPSHOT + + + + + javax.servlet + javax.servlet-api + ${javax.servlet-api.version} + + + + + + + org.apache.maven.plugins + maven-war-plugin + ${maven-war-plugin.version} + + + org.eclipse.jetty + jetty-maven-plugin + ${jetty-maven-plugin.version} + + + /front-controller + + + + + + + + 3.3.2 + 11.0.13 + + + + \ No newline at end of file diff --git a/patterns-modules/idd/README.md b/patterns-modules/idd/README.md new file mode 100644 index 0000000000..22fe277f0b --- /dev/null +++ b/patterns-modules/idd/README.md @@ -0,0 +1,2 @@ +### Relevant Articles: +- [Introduction to Interface Driven Development (IDD)](https://www.baeldung.com/java-idd) diff --git a/patterns-modules/idd/pom.xml b/patterns-modules/idd/pom.xml new file mode 100644 index 0000000000..5e20a30317 --- /dev/null +++ b/patterns-modules/idd/pom.xml @@ -0,0 +1,17 @@ + + + 4.0.0 + idd + 1.0 + idd + jar + + + com.baeldung + patterns-modules + 1.0.0-SNAPSHOT + + + \ No newline at end of file diff --git a/patterns-modules/idd/src/main/java/com/baeldung/idd/CreateHelpRequestDTO.java b/patterns-modules/idd/src/main/java/com/baeldung/idd/CreateHelpRequestDTO.java new file mode 100644 index 0000000000..87084474db --- /dev/null +++ b/patterns-modules/idd/src/main/java/com/baeldung/idd/CreateHelpRequestDTO.java @@ -0,0 +1,15 @@ +package com.baeldung.idd; + +public class CreateHelpRequestDTO { + + private final HelpRequestStatus status; + + public CreateHelpRequestDTO(HelpRequestStatus status) { + this.status = status; + } + + public HelpRequestStatus getStatus() { + return status; + } + +} diff --git a/patterns-modules/idd/src/main/java/com/baeldung/idd/HelpRequestDTO.java b/patterns-modules/idd/src/main/java/com/baeldung/idd/HelpRequestDTO.java new file mode 100644 index 0000000000..2ad0c5f100 --- /dev/null +++ b/patterns-modules/idd/src/main/java/com/baeldung/idd/HelpRequestDTO.java @@ -0,0 +1,13 @@ +package com.baeldung.idd; + +public class HelpRequestDTO { + private HelpRequestStatus status; + + public HelpRequestStatus getStatus() { + return status; + } + + public HelpRequestDTO(HelpRequestStatus status) { + this.status = status; + } +} diff --git a/patterns-modules/idd/src/main/java/com/baeldung/idd/HelpRequestService.java b/patterns-modules/idd/src/main/java/com/baeldung/idd/HelpRequestService.java new file mode 100644 index 0000000000..02c0b2531c --- /dev/null +++ b/patterns-modules/idd/src/main/java/com/baeldung/idd/HelpRequestService.java @@ -0,0 +1,11 @@ +package com.baeldung.idd; + +import java.util.List; + +public interface HelpRequestService { + HelpRequestDTO createHelpRequest(CreateHelpRequestDTO createHelpRequestDTO); + + List findAllByStatus(HelpRequestStatus status); + + HelpRequestDTO updateHelpRequest(UpdateHelpRequestDTO updateHelpRequestDTO); +} diff --git a/patterns-modules/idd/src/main/java/com/baeldung/idd/HelpRequestServiceImpl.java b/patterns-modules/idd/src/main/java/com/baeldung/idd/HelpRequestServiceImpl.java new file mode 100644 index 0000000000..8ffed62078 --- /dev/null +++ b/patterns-modules/idd/src/main/java/com/baeldung/idd/HelpRequestServiceImpl.java @@ -0,0 +1,24 @@ +package com.baeldung.idd; + +import java.util.List; + +public class HelpRequestServiceImpl implements HelpRequestService { + + @Override + public HelpRequestDTO createHelpRequest(CreateHelpRequestDTO createHelpRequestDTO) { + // here goes the implementation + return new HelpRequestDTO(createHelpRequestDTO.getStatus()); + } + + @Override + public List findAllByStatus(HelpRequestStatus status) { + // here goes the implementation + return List.of(new HelpRequestDTO(status), new HelpRequestDTO(status)); + } + + @Override + public HelpRequestDTO updateHelpRequest(UpdateHelpRequestDTO updateHelpRequestDTO) { + // here goes the implementation + return new HelpRequestDTO(updateHelpRequestDTO.getStatus()); + } +} diff --git a/patterns-modules/idd/src/main/java/com/baeldung/idd/HelpRequestStatus.java b/patterns-modules/idd/src/main/java/com/baeldung/idd/HelpRequestStatus.java new file mode 100644 index 0000000000..adb2fb5cc3 --- /dev/null +++ b/patterns-modules/idd/src/main/java/com/baeldung/idd/HelpRequestStatus.java @@ -0,0 +1,6 @@ +package com.baeldung.idd; + +public enum HelpRequestStatus { + OPEN, IN_PROGRESS, CLOSED +} + diff --git a/patterns-modules/idd/src/main/java/com/baeldung/idd/UpdateHelpRequestDTO.java b/patterns-modules/idd/src/main/java/com/baeldung/idd/UpdateHelpRequestDTO.java new file mode 100644 index 0000000000..04a46e3d27 --- /dev/null +++ b/patterns-modules/idd/src/main/java/com/baeldung/idd/UpdateHelpRequestDTO.java @@ -0,0 +1,14 @@ +package com.baeldung.idd; + +public class UpdateHelpRequestDTO { + + private final HelpRequestStatus status; + + public UpdateHelpRequestDTO(HelpRequestStatus status) { + this.status = status; + } + + public HelpRequestStatus getStatus() { + return status; + } +} diff --git a/patterns-modules/idd/src/test/java/com/baeldung/idd/HelpRequestServiceUnitTest.java b/patterns-modules/idd/src/test/java/com/baeldung/idd/HelpRequestServiceUnitTest.java new file mode 100644 index 0000000000..52cbcaba57 --- /dev/null +++ b/patterns-modules/idd/src/test/java/com/baeldung/idd/HelpRequestServiceUnitTest.java @@ -0,0 +1,47 @@ +package com.baeldung.idd; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +public class HelpRequestServiceUnitTest { + + HelpRequestService testHelpRequestService = new HelpRequestService() { + @Override + public HelpRequestDTO createHelpRequest(CreateHelpRequestDTO createHelpRequestDTO) { + return new HelpRequestDTO(HelpRequestStatus.OPEN); + } + + @Override + public List findAllByStatus(HelpRequestStatus status) { + return List.of(new HelpRequestDTO(HelpRequestStatus.OPEN)); + } + + @Override + public HelpRequestDTO updateHelpRequest(UpdateHelpRequestDTO updateHelpRequestDTO) { + return new HelpRequestDTO(HelpRequestStatus.OPEN); + } + }; + + @Test + public void givenHelpRequest_whenCreateHelpRequest_thenHelpRequestIsCreated() { + // given + CreateHelpRequestDTO createHelpRequestDTO = new CreateHelpRequestDTO(HelpRequestStatus.OPEN); + + // when + HelpRequestDTO helpRequestDTO = testHelpRequestService.createHelpRequest(createHelpRequestDTO); + + // then + Assertions.assertThat(helpRequestDTO).isNotNull(); + Assertions.assertThat(helpRequestDTO.getStatus()).isEqualTo(HelpRequestStatus.OPEN); + } + + @Test + public void givenHelpRequestList_whenFindAllByStatus_shouldContainOnlyStatus() { + HelpRequestService helpRequestService = new HelpRequestServiceImpl(); + List allByStatusOpen = helpRequestService.findAllByStatus(HelpRequestStatus.OPEN); + Assertions.assertThat(allByStatusOpen).extracting(HelpRequestDTO::getStatus).containsOnly(HelpRequestStatus.OPEN); + } + +} diff --git a/patterns-modules/intercepting-filter/pom.xml b/patterns-modules/intercepting-filter/pom.xml new file mode 100644 index 0000000000..60246e7838 --- /dev/null +++ b/patterns-modules/intercepting-filter/pom.xml @@ -0,0 +1,55 @@ + + + 4.0.0 + intercepting-filter + intercepting-filter + war + + + com.baeldung + patterns-modules + 1.0.0-SNAPSHOT + + + + + javax.servlet + javax.servlet-api + ${javax.servlet-api.version} + + + org.slf4j + slf4j-api + ${org.slf4j.version} + + + + + + + org.apache.maven.plugins + maven-war-plugin + ${maven-war-plugin.version} + + false + + + + org.eclipse.jetty + jetty-maven-plugin + + + / + + + + + + + + 3.3.2 + + + \ No newline at end of file diff --git a/patterns-modules/pom.xml b/patterns-modules/pom.xml new file mode 100644 index 0000000000..add2b0475b --- /dev/null +++ b/patterns-modules/pom.xml @@ -0,0 +1,37 @@ + + + 4.0.0 + patterns-modules + patterns-modules + pom + + + com.baeldung + parent-modules + 1.0.0-SNAPSHOT + + + + design-patterns-architectural + design-patterns-behavioral + design-patterns-behavioral-2 + design-patterns-cloud + design-patterns-creational + design-patterns-creational-2 + design-patterns-functional + design-patterns-singleton + design-patterns-structural + dip + cqrs-es + front-controller + intercepting-filter + solid + clean-architecture + enterprise-patterns + coupling + idd + + + diff --git a/patterns-modules/solid/README.md b/patterns-modules/solid/README.md new file mode 100644 index 0000000000..3ef73bc42f --- /dev/null +++ b/patterns-modules/solid/README.md @@ -0,0 +1,7 @@ +### Relevant Articles: + +- [A Solid Guide to SOLID Principles](https://www.baeldung.com/solid-principles) +- [Single Responsibility Principle in Java](https://www.baeldung.com/java-single-responsibility-principle) +- [Open/Closed Principle in Java](https://www.baeldung.com/java-open-closed-principle) +- [Interface Segregation Principle in Java](https://www.baeldung.com/java-interface-segregation) +- [Liskov Substitution Principle in Java](https://www.baeldung.com/java-liskov-substitution-principle) diff --git a/patterns-modules/solid/pom.xml b/patterns-modules/solid/pom.xml new file mode 100644 index 0000000000..0e4921c566 --- /dev/null +++ b/patterns-modules/solid/pom.xml @@ -0,0 +1,16 @@ + + + 4.0.0 + solid + 1.0-SNAPSHOT + solid + + + com.baeldung + patterns-modules + 1.0.0-SNAPSHOT + + + \ No newline at end of file diff --git a/patterns-modules/solid/src/main/java/com/baeldung/i/fixed/Bank.java b/patterns-modules/solid/src/main/java/com/baeldung/i/fixed/Bank.java new file mode 100644 index 0000000000..7d1c93e83f --- /dev/null +++ b/patterns-modules/solid/src/main/java/com/baeldung/i/fixed/Bank.java @@ -0,0 +1,5 @@ +package com.baeldung.i.fixed; + +public interface Bank extends Payment { + void initiatePayments(); +} diff --git a/patterns-modules/solid/src/main/java/com/baeldung/i/fixed/BankPayment.java b/patterns-modules/solid/src/main/java/com/baeldung/i/fixed/BankPayment.java new file mode 100644 index 0000000000..f077a6772b --- /dev/null +++ b/patterns-modules/solid/src/main/java/com/baeldung/i/fixed/BankPayment.java @@ -0,0 +1,21 @@ +package com.baeldung.i.fixed; + +import java.util.List; + +public class BankPayment implements Bank { + + @Override + public void initiatePayments() { + + } + + @Override + public Object status() { + return null; + } + + @Override + public List getPayments() { + return null; + } +} diff --git a/patterns-modules/solid/src/main/java/com/baeldung/i/fixed/Loan.java b/patterns-modules/solid/src/main/java/com/baeldung/i/fixed/Loan.java new file mode 100644 index 0000000000..74141113db --- /dev/null +++ b/patterns-modules/solid/src/main/java/com/baeldung/i/fixed/Loan.java @@ -0,0 +1,6 @@ +package com.baeldung.i.fixed; + +public interface Loan extends Payment { + void intiateLoanSettlement(); + void initiateRePayment(); +} diff --git a/patterns-modules/solid/src/main/java/com/baeldung/i/fixed/LoanPayment.java b/patterns-modules/solid/src/main/java/com/baeldung/i/fixed/LoanPayment.java new file mode 100644 index 0000000000..b6c32d7730 --- /dev/null +++ b/patterns-modules/solid/src/main/java/com/baeldung/i/fixed/LoanPayment.java @@ -0,0 +1,26 @@ +package com.baeldung.i.fixed; + +import java.util.List; + +public class LoanPayment implements Loan { + + @Override + public void intiateLoanSettlement() { + + } + + @Override + public void initiateRePayment() { + + } + + @Override + public Object status() { + return null; + } + + @Override + public List getPayments() { + return null; + } +} diff --git a/patterns-modules/solid/src/main/java/com/baeldung/i/fixed/Payment.java b/patterns-modules/solid/src/main/java/com/baeldung/i/fixed/Payment.java new file mode 100644 index 0000000000..620c944944 --- /dev/null +++ b/patterns-modules/solid/src/main/java/com/baeldung/i/fixed/Payment.java @@ -0,0 +1,8 @@ +package com.baeldung.i.fixed; + +import java.util.List; + +public interface Payment { + Object status(); + List getPayments(); +} diff --git a/patterns-modules/solid/src/main/java/com/baeldung/i/polluted/BankPayment.java b/patterns-modules/solid/src/main/java/com/baeldung/i/polluted/BankPayment.java new file mode 100644 index 0000000000..c8f667c25f --- /dev/null +++ b/patterns-modules/solid/src/main/java/com/baeldung/i/polluted/BankPayment.java @@ -0,0 +1,32 @@ +package com.baeldung.i.polluted; + +import java.util.Collections; +import java.util.List; + +public class BankPayment implements Payment { + + @Override + public void initiatePayments() { + + } + + @Override + public Object status() { + return null; + } + + @Override + public List getPayments() { + return Collections.emptyList(); + } + + @Override + public void intiateLoanSettlement() { + throw new UnsupportedOperationException("This is not a loan payment"); + } + + @Override + public void initiateRePayment() { + throw new UnsupportedOperationException("This is not a loan payment"); + } +} diff --git a/patterns-modules/solid/src/main/java/com/baeldung/i/polluted/LoanPayment.java b/patterns-modules/solid/src/main/java/com/baeldung/i/polluted/LoanPayment.java new file mode 100644 index 0000000000..8535d7ad0a --- /dev/null +++ b/patterns-modules/solid/src/main/java/com/baeldung/i/polluted/LoanPayment.java @@ -0,0 +1,31 @@ +package com.baeldung.i.polluted; + +import java.util.List; + +public class LoanPayment implements Payment { + + @Override + public void initiatePayments() { + + } + + @Override + public Object status() { + return null; + } + + @Override + public List getPayments() { + return null; + } + + @Override + public void intiateLoanSettlement() { + + } + + @Override + public void initiateRePayment() { + + } +} diff --git a/patterns-modules/solid/src/main/java/com/baeldung/i/polluted/Payment.java b/patterns-modules/solid/src/main/java/com/baeldung/i/polluted/Payment.java new file mode 100644 index 0000000000..987b4cee33 --- /dev/null +++ b/patterns-modules/solid/src/main/java/com/baeldung/i/polluted/Payment.java @@ -0,0 +1,13 @@ +package com.baeldung.i.polluted; + +import java.util.List; + +public interface Payment { + void initiatePayments(); + Object status(); + List getPayments(); + + //Loan related methods + void intiateLoanSettlement(); + void initiateRePayment(); +} diff --git a/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/Account.java b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/Account.java new file mode 100644 index 0000000000..3a8260924b --- /dev/null +++ b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/Account.java @@ -0,0 +1,17 @@ +package com.baeldung.l.advanced; + +import java.math.BigDecimal; + +public abstract class Account { + protected abstract void deposit(BigDecimal amount); + + /** + * Reduces the account balance by the specified amount + * provided given amount > 0 and account meets minimum available + * balance criteria. + * + * @param amount + */ + protected abstract void withdraw(BigDecimal amount); + +} diff --git a/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/BankingAppWithdrawalService.java b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/BankingAppWithdrawalService.java new file mode 100644 index 0000000000..6689b1dc8c --- /dev/null +++ b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/BankingAppWithdrawalService.java @@ -0,0 +1,15 @@ +package com.baeldung.l.advanced; + +import java.math.BigDecimal; + +public class BankingAppWithdrawalService { + private Account account; + + public BankingAppWithdrawalService(Account account) { + this.account = account; + } + + public void withdraw(BigDecimal amount) { + account.withdraw(amount); + } +} diff --git a/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/Bar.java b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/Bar.java new file mode 100644 index 0000000000..8f421cebaa --- /dev/null +++ b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/Bar.java @@ -0,0 +1,27 @@ +package com.baeldung.l.advanced; + +public class Bar extends Foo { + + @Override + // precondition: 0 < num <= 10 + public void doStuff(int num) { + if (num <= 0 || num > 10) { + throw new IllegalArgumentException("Input out of range 1-10"); + } + // some logic here... + } + + @Override + // precondition: 0 < num <= 3 + public void doOtherStuff(int num) { + if (num <= 0 || num > 3) { + throw new IllegalArgumentException("Input out of range 1-3"); + } + // some logic here... + } + + @Override + public Integer generateNumber() { + return new Integer(10); + } +} diff --git a/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/Car.java b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/Car.java new file mode 100644 index 0000000000..48688d9a45 --- /dev/null +++ b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/Car.java @@ -0,0 +1,26 @@ +package com.baeldung.l.advanced; + +public abstract class Car { + protected int limit; + + // invariant: speed < limit; + protected int speed; + + // Allowed to be set once at the time of creation. + // Value can only increment thereafter. + // Value cannot be reset. + protected int mileage; + + public Car(int mileage) { + this.mileage = mileage; + } + + protected abstract void turnOnEngine(); + + // postcondition: speed < limit + protected abstract void accelerate(); + + // postcondition: speed must reduce + protected abstract void brake(); + +} diff --git a/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/CurrentAccount.java b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/CurrentAccount.java new file mode 100644 index 0000000000..7187582126 --- /dev/null +++ b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/CurrentAccount.java @@ -0,0 +1,15 @@ +package com.baeldung.l.advanced; + +import java.math.BigDecimal; + +public class CurrentAccount extends Account { + @Override + protected void deposit(BigDecimal amount) { + // Deposit into CurrentAccount + } + + @Override + protected void withdraw(BigDecimal amount) { + // Withdraw from CurrentAccount + } +} diff --git a/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/ElectricCar.java b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/ElectricCar.java new file mode 100644 index 0000000000..ca78af633f --- /dev/null +++ b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/ElectricCar.java @@ -0,0 +1,23 @@ +package com.baeldung.l.advanced; + +public class ElectricCar extends Car { + + public ElectricCar(int mileage) { + super(mileage); + } + + @Override + protected void turnOnEngine() { + throw new AssertionError("I am an Electric Car. I don't have an engine!"); + } + + @Override + protected void accelerate() { + // this acceleration is crazy! + } + + @Override + protected void brake() { + // Apply ElectricCar brake + } +} diff --git a/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/FilePurgingJob.java b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/FilePurgingJob.java new file mode 100644 index 0000000000..57334696b0 --- /dev/null +++ b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/FilePurgingJob.java @@ -0,0 +1,18 @@ +package com.baeldung.l.advanced; + +import java.io.IOException; + +public class FilePurgingJob { + private FileSystem fileSystem; + + public FilePurgingJob(FileSystem fileSystem) { + this.fileSystem = fileSystem; + } + + public void purgeOldestFile(String path) throws IOException { + if (!(fileSystem instanceof ReadOnlyFileSystem)) { + // code to detect oldest file + fileSystem.deleteFile(path); + } + } +} diff --git a/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/FileSystem.java b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/FileSystem.java new file mode 100644 index 0000000000..00a8fb8ec7 --- /dev/null +++ b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/FileSystem.java @@ -0,0 +1,10 @@ +package com.baeldung.l.advanced; + +import java.io.File; +import java.io.IOException; + +public interface FileSystem { + File[] listFiles(String path); + + void deleteFile(String path) throws IOException; +} diff --git a/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/FixedTermDepositAccount.java b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/FixedTermDepositAccount.java new file mode 100644 index 0000000000..50dd4f3d17 --- /dev/null +++ b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/FixedTermDepositAccount.java @@ -0,0 +1,15 @@ +package com.baeldung.l.advanced; + +import java.math.BigDecimal; + +public class FixedTermDepositAccount extends Account { + @Override + protected void deposit(BigDecimal amount) { + // Deposit into this account + } + + @Override + protected void withdraw(BigDecimal amount) { + throw new UnsupportedOperationException("Withdrawals are not supported by FixedTermDepositAccount!!"); + } +} diff --git a/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/Foo.java b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/Foo.java new file mode 100644 index 0000000000..f61577c18f --- /dev/null +++ b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/Foo.java @@ -0,0 +1,22 @@ +package com.baeldung.l.advanced; + +public abstract class Foo { + + // precondition: 0 < num <=5 + public void doStuff(int num) { + if (num <= 0 || num > 5) { + throw new IllegalArgumentException("Input out of range 1-5"); + } + // some logic here... + } + + // precondition: 0 < num <=5 + public void doOtherStuff(int num) { + if (num <= 0 || num > 5) { + throw new IllegalArgumentException("Input out of range 1-5"); + } + // some logic here... + } + + public abstract Number generateNumber(); +} diff --git a/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/HybridCar.java b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/HybridCar.java new file mode 100644 index 0000000000..ea643a7419 --- /dev/null +++ b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/HybridCar.java @@ -0,0 +1,28 @@ +package com.baeldung.l.advanced; + +public class HybridCar extends Car { + // invariant: charge >= 0; + private int charge; + + public HybridCar(int mileage) { + super(mileage); + } + + @Override + protected void turnOnEngine() { + // Start HybridCar + } + + @Override + // postcondition: speed < limit + protected void accelerate() { + // Accelerate HybridCar speed < limit + } + + @Override + // postcondition: speed must reduce + // postcondition: charge must increase + protected void brake() { + // Apply HybridCar brake + } +} diff --git a/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/MotorCar.java b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/MotorCar.java new file mode 100644 index 0000000000..68a54966cc --- /dev/null +++ b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/MotorCar.java @@ -0,0 +1,25 @@ +package com.baeldung.l.advanced; + +public class MotorCar extends Car { + + public MotorCar(int mileage) { + super(mileage); + } + + @Override + protected void turnOnEngine() { + // Start MotorCar + } + + @Override + // postcondition: speed < limit + protected void accelerate() { + // Accelerate MotorCar + } + + @Override + // postcondition: speed must reduce + protected void brake() { + // Apply MotorCar brake + } +} diff --git a/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/ReadOnlyFileSystem.java b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/ReadOnlyFileSystem.java new file mode 100644 index 0000000000..6ba3d9770d --- /dev/null +++ b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/ReadOnlyFileSystem.java @@ -0,0 +1,16 @@ +package com.baeldung.l.advanced; + +import java.io.File; +import java.io.IOException; + +public class ReadOnlyFileSystem implements FileSystem { + public File[] listFiles(String path) { + // code to list files + return new File[0]; + } + + public void deleteFile(String path) throws IOException { + // Do nothing. + // deleteFile operation is not supported on a read-only file system + } +} diff --git a/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/SavingsAccount.java b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/SavingsAccount.java new file mode 100644 index 0000000000..8dbb5eb4d4 --- /dev/null +++ b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/SavingsAccount.java @@ -0,0 +1,15 @@ +package com.baeldung.l.advanced; + +import java.math.BigDecimal; + +public class SavingsAccount extends Account { + @Override + protected void deposit(BigDecimal amount) { + // Deposit into SavingsAccount + } + + @Override + protected void withdraw(BigDecimal amount) { + // Withdraw from SavingsAccount + } +} diff --git a/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/ToyCar.java b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/ToyCar.java new file mode 100644 index 0000000000..a41694deef --- /dev/null +++ b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/ToyCar.java @@ -0,0 +1,24 @@ +package com.baeldung.l.advanced; + +public class ToyCar extends Car { + + public ToyCar(int mileage) { + super(mileage); + } + + protected void turnOnEngine() { + + } + + protected void accelerate() { + + } + + protected void brake() { + + } + + public void reset() { + mileage = 0; + } +} diff --git a/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/refactored/Account.java b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/refactored/Account.java new file mode 100644 index 0000000000..cb950df164 --- /dev/null +++ b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/refactored/Account.java @@ -0,0 +1,7 @@ +package com.baeldung.l.advanced.refactored; + +import java.math.BigDecimal; + +public abstract class Account { + protected abstract void deposit(BigDecimal amount); +} diff --git a/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/refactored/BankingAppWithdrawalService.java b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/refactored/BankingAppWithdrawalService.java new file mode 100644 index 0000000000..cbb375dd52 --- /dev/null +++ b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/refactored/BankingAppWithdrawalService.java @@ -0,0 +1,17 @@ +package com.baeldung.l.advanced.refactored; + +import com.baeldung.l.advanced.Account; + +import java.math.BigDecimal; + +public class BankingAppWithdrawalService { + private WithdrawableAccount withdrawableAccount; + + public BankingAppWithdrawalService(WithdrawableAccount withdrawableAccount) { + this.withdrawableAccount = withdrawableAccount; + } + + public void withdraw(BigDecimal amount) { + withdrawableAccount.withdraw(amount); + } +} diff --git a/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/refactored/CurrentAccount.java b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/refactored/CurrentAccount.java new file mode 100644 index 0000000000..5b5b9efe04 --- /dev/null +++ b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/refactored/CurrentAccount.java @@ -0,0 +1,13 @@ +package com.baeldung.l.advanced.refactored; + +import java.math.BigDecimal; + +public class CurrentAccount extends WithdrawableAccount { + protected void deposit(BigDecimal amount) { + // Deposit into CurrentAccount + } + + protected void withdraw(BigDecimal amount) { + // Withdraw from CurrentAccount + } +} diff --git a/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/refactored/FixedTermDepositAccount.java b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/refactored/FixedTermDepositAccount.java new file mode 100644 index 0000000000..14461b85a3 --- /dev/null +++ b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/refactored/FixedTermDepositAccount.java @@ -0,0 +1,9 @@ +package com.baeldung.l.advanced.refactored; + +import java.math.BigDecimal; + +public class FixedTermDepositAccount extends Account { + protected void deposit(BigDecimal amount) { + // Deposit into this account + } +} diff --git a/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/refactored/SavingsAccount.java b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/refactored/SavingsAccount.java new file mode 100644 index 0000000000..45c50079bf --- /dev/null +++ b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/refactored/SavingsAccount.java @@ -0,0 +1,13 @@ +package com.baeldung.l.advanced.refactored; + +import java.math.BigDecimal; + +public class SavingsAccount extends WithdrawableAccount { + protected void deposit(BigDecimal amount) { + // Deposit into SavingsAccount + } + + protected void withdraw(BigDecimal amount) { + // Withdraw from SavingsAccount + } +} diff --git a/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/refactored/WithdrawableAccount.java b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/refactored/WithdrawableAccount.java new file mode 100644 index 0000000000..b3c7606cb7 --- /dev/null +++ b/patterns-modules/solid/src/main/java/com/baeldung/l/advanced/refactored/WithdrawableAccount.java @@ -0,0 +1,14 @@ +package com.baeldung.l.advanced.refactored; + +import java.math.BigDecimal; + +public abstract class WithdrawableAccount extends Account { + /** + * Reduces the account balance by the specified amount + * provided given amount > 0 and account meets minimum available + * balance criteria. + * + * @param amount + */ + protected abstract void withdraw(BigDecimal amount); +} diff --git a/patterns-modules/solid/src/main/java/com/baeldung/o/AbstractCalculatorOperation.java b/patterns-modules/solid/src/main/java/com/baeldung/o/AbstractCalculatorOperation.java new file mode 100644 index 0000000000..798d5e2d95 --- /dev/null +++ b/patterns-modules/solid/src/main/java/com/baeldung/o/AbstractCalculatorOperation.java @@ -0,0 +1,7 @@ +package com.baeldung.o; + +public abstract class AbstractCalculatorOperation { + + abstract void perform(); + +} diff --git a/patterns-modules/solid/src/main/java/com/baeldung/o/Addition.java b/patterns-modules/solid/src/main/java/com/baeldung/o/Addition.java new file mode 100644 index 0000000000..1ad99aad4b --- /dev/null +++ b/patterns-modules/solid/src/main/java/com/baeldung/o/Addition.java @@ -0,0 +1,41 @@ +package com.baeldung.o; + +public class Addition implements CalculatorOperation { + private double left; + private double right; + private double result = 0.0; + + public Addition(double left, double right) { + this.left = left; + this.right = right; + } + + public double getLeft() { + return left; + } + + public void setLeft(double left) { + this.left = left; + } + + public double getRight() { + return right; + } + + public void setRight(double right) { + this.right = right; + } + + public double getResult() { + return result; + } + + public void setResult(double result) { + this.result = result; + } + + @Override + public void perform() { + result = left + right; + } +} diff --git a/patterns-modules/solid/src/main/java/com/baeldung/o/Calculator.java b/patterns-modules/solid/src/main/java/com/baeldung/o/Calculator.java new file mode 100644 index 0000000000..11ace8eb20 --- /dev/null +++ b/patterns-modules/solid/src/main/java/com/baeldung/o/Calculator.java @@ -0,0 +1,14 @@ +package com.baeldung.o; + +import java.security.InvalidParameterException; + +public class Calculator { + + public void calculate(CalculatorOperation operation) { + if (operation == null) { + throw new InvalidParameterException("Can not perform operation"); + } + + operation.perform(); + } +} diff --git a/patterns-modules/solid/src/main/java/com/baeldung/o/CalculatorOperation.java b/patterns-modules/solid/src/main/java/com/baeldung/o/CalculatorOperation.java new file mode 100644 index 0000000000..46648e25d8 --- /dev/null +++ b/patterns-modules/solid/src/main/java/com/baeldung/o/CalculatorOperation.java @@ -0,0 +1,7 @@ +package com.baeldung.o; + +public interface CalculatorOperation { + + void perform(); + +} diff --git a/patterns-modules/solid/src/main/java/com/baeldung/o/Division.java b/patterns-modules/solid/src/main/java/com/baeldung/o/Division.java new file mode 100644 index 0000000000..fbb8a1f523 --- /dev/null +++ b/patterns-modules/solid/src/main/java/com/baeldung/o/Division.java @@ -0,0 +1,43 @@ +package com.baeldung.o; + +public class Division implements CalculatorOperation { + private double left; + private double right; + private double result = 0.0; + + public Division(double left, double right) { + this.left = left; + this.right = right; + } + + public double getLeft() { + return left; + } + + public void setLeft(double left) { + this.left = left; + } + + public double getRight() { + return right; + } + + public void setRight(double right) { + this.right = right; + } + + public double getResult() { + return result; + } + + public void setResult(double result) { + this.result = result; + } + + @Override + public void perform() { + if (right != 0) { + result = left / right; + } + } +} diff --git a/patterns-modules/solid/src/main/java/com/baeldung/o/Subtraction.java b/patterns-modules/solid/src/main/java/com/baeldung/o/Subtraction.java new file mode 100644 index 0000000000..5827d33568 --- /dev/null +++ b/patterns-modules/solid/src/main/java/com/baeldung/o/Subtraction.java @@ -0,0 +1,41 @@ +package com.baeldung.o; + +public class Subtraction implements CalculatorOperation { + private double left; + private double right; + private double result = 0.0; + + public Subtraction(double left, double right) { + this.left = left; + this.right = right; + } + + public double getLeft() { + return left; + } + + public void setLeft(double left) { + this.left = left; + } + + public double getRight() { + return right; + } + + public void setRight(double right) { + this.right = right; + } + + public double getResult() { + return result; + } + + public void setResult(double result) { + this.result = result; + } + + @Override + public void perform() { + result = left - right; + } +} diff --git a/patterns-modules/solid/src/main/java/com/baeldung/s/Book.java b/patterns-modules/solid/src/main/java/com/baeldung/s/Book.java new file mode 100644 index 0000000000..8e26c88655 --- /dev/null +++ b/patterns-modules/solid/src/main/java/com/baeldung/s/Book.java @@ -0,0 +1,20 @@ +package com.baeldung.s; + +public class Book { + + private String name; + private String author; + private String text; + + //constructor, getters and setters + + //methods that directly relate to the book properties + public String replaceWordInText(String word, String replacementWord){ + return text.replaceAll(word, replacementWord); + } + + public boolean isWordInText(String word){ + return text.contains(word); + } + +} diff --git a/patterns-modules/solid/src/main/java/com/baeldung/s/TextManipulator.java b/patterns-modules/solid/src/main/java/com/baeldung/s/TextManipulator.java new file mode 100644 index 0000000000..d084a78815 --- /dev/null +++ b/patterns-modules/solid/src/main/java/com/baeldung/s/TextManipulator.java @@ -0,0 +1,37 @@ +package com.baeldung.s; + +public class TextManipulator { + private String text; + + public TextManipulator(String text) { + this.text = text; + } + + public String getText() { + return text; + } + + public void appendText(String newText) { + text = text.concat(newText); + } + + public String findWordAndReplace(String word, String replacementWord) { + if (text.contains(word)) { + text = text.replace(word, replacementWord); + } + return text; + } + + public String findWordAndDelete(String word) { + if (text.contains(word)) { + text = text.replace(word, ""); + } + return text; + } + + /* + * Bad practice when implementing SRP principle, not in the scope of this class + public void printText() { + System.out.println(textManipulator.getText()); + }*/ +} diff --git a/patterns-modules/solid/src/main/java/com/baeldung/s/TextPrinter.java b/patterns-modules/solid/src/main/java/com/baeldung/s/TextPrinter.java new file mode 100644 index 0000000000..d6a413e7ac --- /dev/null +++ b/patterns-modules/solid/src/main/java/com/baeldung/s/TextPrinter.java @@ -0,0 +1,23 @@ +package com.baeldung.s; + +import java.util.Arrays; + +public class TextPrinter { + TextManipulator textManipulator; + + public TextPrinter(TextManipulator textManipulator) { + this.textManipulator = textManipulator; + } + + public void printText() { + System.out.println(textManipulator.getText()); + } + + public void printOutEachWordOfText() { + System.out.println(Arrays.toString(textManipulator.getText().split(" "))); + } + + public void printRangeOfCharacters(int startingIndex, int endIndex) { + System.out.println(textManipulator.getText().substring(startingIndex, endIndex)); + } +} diff --git a/patterns-modules/solid/src/main/test/com/baeldung/o/CalculatorUnitTest.java b/patterns-modules/solid/src/main/test/com/baeldung/o/CalculatorUnitTest.java new file mode 100644 index 0000000000..3ae894e963 --- /dev/null +++ b/patterns-modules/solid/src/main/test/com/baeldung/o/CalculatorUnitTest.java @@ -0,0 +1,44 @@ +package com.baeldung.o; + +import static org.junit.Assert.assertEquals; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class CalculatorUnitTest { + + private static final double RIGHT = 10.0; + private static final double LEFT = 20.0; + private static final double SUM = 30.0; + private static final double SUBTRACTION_RESULT = 10.0; + private static final double DIVISION_RESULT = 2.0; + + private Calculator calculator; + + @BeforeEach + public void setUp() { + calculator = new Calculator(); + } + + @Test + public void whenAddTwoNumber_returnSum() { + Addition addition = new Addition(RIGHT, LEFT); + calculator.calculate(addition); + assertEquals(SUM, addition.getResult(), 0.0); + } + + @Test + public void whenSutractTwoNumber_returnCorrectResult() { + Subtraction subtraction = new Subtraction(LEFT, RIGHT); + calculator.calculate(subtraction); + assertEquals(SUBTRACTION_RESULT, subtraction.getResult(), 0.0); + } + + @Test + public void whenDivideTwoNumber_returnCorrectResult() { + Division division = new Division(LEFT, RIGHT); + calculator.calculate(division); + assertEquals(DIVISION_RESULT, division.getResult(), 0.0); + } + +}