diff --git a/mapstruct-2/pom.xml b/mapstruct-2/pom.xml index 451074fc6ece..d96e4e81b309 100644 --- a/mapstruct-2/pom.xml +++ b/mapstruct-2/pom.xml @@ -54,13 +54,15 @@ ${lombok.mapstruct.binding.version} + 17 + 17 - 1.6.0.Beta2 + 1.6.3 0.2.0 diff --git a/mapstruct-2/src/main/java/com/baeldung/list/CarMapper.java b/mapstruct-2/src/main/java/com/baeldung/list/CarMapper.java new file mode 100644 index 000000000000..5838e52e7b22 --- /dev/null +++ b/mapstruct-2/src/main/java/com/baeldung/list/CarMapper.java @@ -0,0 +1,28 @@ +package com.baeldung.list; + +import java.util.Arrays; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +import com.baeldung.list.entity.Car; +import com.baeldung.list.entity.CarDto; +import com.baeldung.list.entity.ManufacturingPlantDto; + +@Mapper(imports = { Arrays.class, ManufacturingPlantDto.class }) +public interface CarMapper { + CarMapper INSTANCE = Mappers.getMapper(CarMapper.class); + + @Mapping(target = "numberOfSeats", source = "seats") + @Mapping( + target = "manufacturingPlantDtos", + expression = """ + java(Arrays.asList( + new ManufacturingPlantDto(car.getPlant1(), car.getPlant1Loc()), + new ManufacturingPlantDto(car.getPlant2(), car.getPlant2Loc()) + )) + """ + ) + CarDto carToCarDto(Car car); +} diff --git a/mapstruct-2/src/main/java/com/baeldung/list/CarMapperDecorator.java b/mapstruct-2/src/main/java/com/baeldung/list/CarMapperDecorator.java new file mode 100644 index 000000000000..b6ed4b416024 --- /dev/null +++ b/mapstruct-2/src/main/java/com/baeldung/list/CarMapperDecorator.java @@ -0,0 +1,38 @@ +package com.baeldung.list; + +import java.util.Arrays; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.baeldung.list.entity.Car; +import com.baeldung.list.entity.CarDto; +import com.baeldung.list.entity.ManufacturingPlantDto; + +public abstract class CarMapperDecorator implements CustomCarMapper { + private final Logger logger = LoggerFactory.getLogger(CarMapperDecorator.class); + private CustomCarMapper delegate; + + public CarMapperDecorator(CustomCarMapper delegate) { + this.delegate = delegate; + } + + @Override + public CarDto carToCarDto(Car car) { + logger.info("calling Mapper decorator"); + CarDto carDto = delegate.carToCarDto(car); + + carDto.setManufacturingPlantDtos(getManufacturingPlantDtos(car)); + + return carDto; + } + + private List getManufacturingPlantDtos(Car car) { + // some custom logic or transformation which may require calls to other services + return Arrays.asList( + new ManufacturingPlantDto(car.getPlant1(), car.getPlant1Loc()), + new ManufacturingPlantDto(car.getPlant2(), car.getPlant2Loc()) + ); + } +} diff --git a/mapstruct-2/src/main/java/com/baeldung/list/CustomCarMapper.java b/mapstruct-2/src/main/java/com/baeldung/list/CustomCarMapper.java new file mode 100644 index 000000000000..a1e294a66e86 --- /dev/null +++ b/mapstruct-2/src/main/java/com/baeldung/list/CustomCarMapper.java @@ -0,0 +1,18 @@ +package com.baeldung.list; + +import org.mapstruct.DecoratedWith; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +import com.baeldung.list.entity.Car; +import com.baeldung.list.entity.CarDto; + +@Mapper +@DecoratedWith(CarMapperDecorator.class) +public interface CustomCarMapper { + CustomCarMapper INSTANCE = Mappers.getMapper(CustomCarMapper.class); + + @Mapping(source = "seats", target = "numberOfSeats") + CarDto carToCarDto(Car car); +} diff --git a/mapstruct-2/src/main/java/com/baeldung/list/entity/Car.java b/mapstruct-2/src/main/java/com/baeldung/list/entity/Car.java new file mode 100644 index 000000000000..1d6b90404bab --- /dev/null +++ b/mapstruct-2/src/main/java/com/baeldung/list/entity/Car.java @@ -0,0 +1,72 @@ +package com.baeldung.list.entity; + +public class Car { + private String make; + private String model; + private int year; + private int seats; + + private String plant1; + private String plant1Loc; + private String plant2; + private String plant2Loc; + + public Car(String make, String model, int year, int seats) { + this.make = make; + this.model = model; + this.year = year; + this.seats = seats; + } + + public String getPlant2() { + return plant2; + } + + public void setPlant2(String plant2) { + this.plant2 = plant2; + } + + public String getPlant2Loc() { + return plant2Loc; + } + + public void setPlant2Loc(String plant2Loc) { + this.plant2Loc = plant2Loc; + } + + public String getPlant1Loc() { + return plant1Loc; + } + + public void setPlant1Loc(String plant1Loc) { + this.plant1Loc = plant1Loc; + } + + public String getPlant1() { + return plant1; + } + + public void setPlant1(String plant1) { + this.plant1 = plant1; + } + + public int getSeats() { + return seats; + } + + public void setSeats(int seats) { + this.seats = seats; + } + + public String getMake() { + return make; + } + + public String getModel() { + return model; + } + + public int getYear() { + return year; + } +} diff --git a/mapstruct-2/src/main/java/com/baeldung/list/entity/CarDto.java b/mapstruct-2/src/main/java/com/baeldung/list/entity/CarDto.java new file mode 100644 index 000000000000..1073f940326c --- /dev/null +++ b/mapstruct-2/src/main/java/com/baeldung/list/entity/CarDto.java @@ -0,0 +1,60 @@ +package com.baeldung.list.entity; + +import java.util.List; + +public class CarDto { + private String make; + private String model; + private int year; + private int numberOfSeats; + + private List manufacturingPlantDtos; + + public CarDto(String make, String model, int year, int numberOfSeats) { + this.make = make; + this.model = model; + this.year = year; + this.numberOfSeats = numberOfSeats; + } + + public void setMake(String make) { + this.make = make; + } + + public void setModel(String model) { + this.model = model; + } + + public void setYear(int year) { + this.year = year; + } + + public List getManufacturingPlantDtos() { + return manufacturingPlantDtos; + } + + public void setManufacturingPlantDtos(List manufacturingPlantDtos) { + this.manufacturingPlantDtos = manufacturingPlantDtos; + } + + + public int getNumberOfSeats() { + return numberOfSeats; + } + + public void setNumberOfSeats(int numberOfSeats) { + this.numberOfSeats = numberOfSeats; + } + + public String getMake() { + return make; + } + + public String getModel() { + return model; + } + + public int getYear() { + return year; + } +} diff --git a/mapstruct-2/src/main/java/com/baeldung/list/entity/ManufacturingPlantDto.java b/mapstruct-2/src/main/java/com/baeldung/list/entity/ManufacturingPlantDto.java new file mode 100644 index 000000000000..192c5a2abe83 --- /dev/null +++ b/mapstruct-2/src/main/java/com/baeldung/list/entity/ManufacturingPlantDto.java @@ -0,0 +1,27 @@ +package com.baeldung.list.entity; + +public class ManufacturingPlantDto { + private String plantName; + private String location; + + public ManufacturingPlantDto(String plantName, String location) { + this.plantName = plantName; + this.location = location; + } + + public String getPlantName() { + return plantName; + } + + public void setPlantName(String plantName) { + this.plantName = plantName; + } + + public String getLocation() { + return location; + } + + public void setLocation(String location) { + this.location = location; + } +} diff --git a/mapstruct-2/src/main/resources/puml/object_to_list_cld.puml b/mapstruct-2/src/main/resources/puml/object_to_list_cld.puml new file mode 100644 index 000000000000..787311d73c2e --- /dev/null +++ b/mapstruct-2/src/main/resources/puml/object_to_list_cld.puml @@ -0,0 +1,69 @@ +@startuml +'https://plantuml.com/class-diagram +hide empty attributes +skinparam Handwritten false +skinparam ClassBorderColor black +skinparam BackgroundColor #fffce8/#f8f9fa +skinparam class { + ArrowColor SeaGreen + BackgroundColor #fffce8 +} + +class Car { + -make: String + -model: String + -year: int + -seats: int + -plant1: String + -plant2: String + -plant1Loc: String + -plant2Loc: String + __ + +Car(make: String, model: String, year: int, seats: int) + __ + Standard getter and setter methods +} + +class CarDto { + -make: String + -model: String + -year: int + -numberOfseats: int + -manufacturingPlantDtos:List + __ + +CarDto(make: String, model: String, year: int, numberOfSeats: int) + __ + Standard getter and setter methods +} + + + +class ManufacturingPlantDto { + -plantName: String + -location: String + __ + +ManufacturingPlantDto(plantName: String, location: String) + __ + Standard getter and setter methods +} + +note left of Car + Source object +end note + +note left of CarDto + Target object derived from Car +end note + +note left of ManufacturingPlantDto::plantName + possible values mapped to + Car#plant1 and Car#plant2 +end note +note left of ManufacturingPlantDto::location + possible values mapped to + Car#plant1Loc and Car#plant2Loc +end note + +CarDto -down-> ManufacturingPlantDto: 1\n* +Car .down. CarDto: 1\n1 +@enduml \ No newline at end of file diff --git a/mapstruct-2/src/test/java/com/baeldung/list/SourcePropsToListElementsUnitTest.java b/mapstruct-2/src/test/java/com/baeldung/list/SourcePropsToListElementsUnitTest.java new file mode 100644 index 000000000000..07fac31fba60 --- /dev/null +++ b/mapstruct-2/src/test/java/com/baeldung/list/SourcePropsToListElementsUnitTest.java @@ -0,0 +1,76 @@ +package com.baeldung.list; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import com.baeldung.list.entity.Car; +import com.baeldung.list.entity.CarDto; +import com.baeldung.list.entity.ManufacturingPlantDto; + +public class SourcePropsToListElementsUnitTest { + @Test + void whenUseMappingExpression_thenConvertCarToCarDto() { + Car car = createCarObject(); + + CarDto carDto = CarMapper.INSTANCE.carToCarDto(car); + + assertEquals("Morris", carDto.getMake()); + assertEquals("Mini", carDto.getModel()); + assertEquals(1969, carDto.getYear()); + assertEquals(4, carDto.getNumberOfSeats()); + + validateTargetList(carDto.getManufacturingPlantDtos()); + } + + @Test + void whenUsingDecorator_thenConvertCarToCarDto() { + Car car = createCarObject(); + + CarDto carDto = CustomCarMapper.INSTANCE.carToCarDto(car); + + assertEquals("Morris", carDto.getMake()); + assertEquals("Mini", carDto.getModel()); + assertEquals(1969, carDto.getYear()); + assertEquals(4, carDto.getNumberOfSeats()); + + validateTargetList(carDto.getManufacturingPlantDtos()); + } + + @Test + void whenUsingQualifiedByName_thenConvertCarToCarDto() { + Car car = createCarObject(); + + CarDto carDto = QualifiedByNameCarMapper.INSTANCE.carToCarDto(car); + + assertEquals("Morris", carDto.getMake()); + assertEquals("Mini", carDto.getModel()); + assertEquals(1969, carDto.getYear()); + assertEquals(4, carDto.getNumberOfSeats()); + + validateTargetList(carDto.getManufacturingPlantDtos()); + } + + + private Car createCarObject() { + Car car = new Car("Morris", "Mini", 1969, 4); + car.setPlant1("Oxford"); + car.setPlant1Loc("United Kingdom"); + car.setPlant2("Swinden"); + car.setPlant2Loc("United Kingdom"); + return car; + } + + private void validateTargetList(List manufacturingPlantDtos) { + assertEquals(2, manufacturingPlantDtos.size()); + manufacturingPlantDtos.forEach(plant -> { + switch (plant.getPlantName()) { + case "Oxford", "Swinden" -> assertEquals("United Kingdom", plant.getLocation()); + default -> fail("Unexpected plant name: " + plant.getPlantName()); + } + }); + } +} diff --git a/mapstruct/pom.xml b/mapstruct/pom.xml index 79ecba8ac305..81c59ab6ce16 100644 --- a/mapstruct/pom.xml +++ b/mapstruct/pom.xml @@ -73,7 +73,7 @@ - 1.6.0.Beta2 + 1.6.3 4.3.4.RELEASE 0.2.0 diff --git a/spring-boot-modules/pom.xml b/spring-boot-modules/pom.xml index a010cb62485d..a7772ee0cd5f 100644 --- a/spring-boot-modules/pom.xml +++ b/spring-boot-modules/pom.xml @@ -71,6 +71,7 @@ spring-boot-nashorn spring-boot-parent spring-boot-performance + spring-boot-pkl spring-boot-property-exp spring-boot-request-params spring-boot-runtime diff --git a/spring-boot-modules/spring-boot-pkl/pom.xml b/spring-boot-modules/spring-boot-pkl/pom.xml new file mode 100644 index 000000000000..4ef1de1c5d0e --- /dev/null +++ b/spring-boot-modules/spring-boot-pkl/pom.xml @@ -0,0 +1,130 @@ + + + 4.0.0 + + com.baeldung.spring-boot-modules + spring-boot-modules + 1.0.0-SNAPSHOT + + spring-boot-pkl + Spring Boot Integration with pkl-lang + spring-boot-pkl + 1.0-SNAPSHOT + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-test + test + + + org.pkl-lang + pkl-spring + ${pickle-spring.version} + + + + + + + + org.codehaus.mojo + exec-maven-plugin + ${exec-maven-plugin.version} + + + org.pkl-lang + pkl-tools + ${pkl-tools.version} + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${surefire.version} + + + gitpassword + jirapassword + + + + + org.codehaus.mojo + exec-maven-plugin + + + gen-pkl-java-bind + generate-sources + + java + + + org.pkl.codegen.java.Main + false + + ${pkl-tools-filepath}/pkl-tools-0.27.2.jar + + + ${pkl-template-filepath}/ToolIntegrationTemplate.pkl + -o + ${project.build.directory}/generated-sources + --generate-spring-boot + --generate-getters + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + ${build-helper-maven-plugin.version} + + + add-source + generate-sources + + add-source + + + + target/generated-sources + + + + + + + + + + 21 + 21 + UTF-8 + + 0.16.0 + 3.3.0 + 0.27.2 + 3.5.0 + 3.4.0 + + ${settings.localRepository}/org/pkl-lang/pkl-tools/0.27.2 + ${project.basedir}/src/main/resources + + + \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-pkl/src/main/java/com/baeldung/spring/pkl/GitHubService.java b/spring-boot-modules/spring-boot-pkl/src/main/java/com/baeldung/spring/pkl/GitHubService.java new file mode 100644 index 000000000000..f4de19177ddc --- /dev/null +++ b/spring-boot-modules/spring-boot-pkl/src/main/java/com/baeldung/spring/pkl/GitHubService.java @@ -0,0 +1,17 @@ +package com.baeldung.spring.pkl; + +public class GitHubService { + private final ToolIntegrationProperties.Connection gitConnection; + + public GitHubService(ToolIntegrationProperties.Connection connection) { + this.gitConnection = connection; + } + + public String readIssues() { + return "Reading issues from GitHub URL " + gitConnection.getUrl(); + } + + public ToolIntegrationProperties.Connection getGitConnection() { + return gitConnection; + } +} diff --git a/spring-boot-modules/spring-boot-pkl/src/main/java/com/baeldung/spring/pkl/JiraService.java b/spring-boot-modules/spring-boot-pkl/src/main/java/com/baeldung/spring/pkl/JiraService.java new file mode 100644 index 000000000000..681eb6bfc79d --- /dev/null +++ b/spring-boot-modules/spring-boot-pkl/src/main/java/com/baeldung/spring/pkl/JiraService.java @@ -0,0 +1,17 @@ +package com.baeldung.spring.pkl; + +public class JiraService { + private final ToolIntegrationProperties.Connection jiraConnection; + + public JiraService(ToolIntegrationProperties.Connection connection) { + this.jiraConnection = connection; + } + + public String readIssues() { + return "Reading issues from Jira URL " + jiraConnection.getUrl(); + } + + public ToolIntegrationProperties.Connection getJiraConnection() { + return jiraConnection; + } +} diff --git a/spring-boot-modules/spring-boot-pkl/src/main/java/com/baeldung/spring/pkl/SpringPklDemoApplication.java b/spring-boot-modules/spring-boot-pkl/src/main/java/com/baeldung/spring/pkl/SpringPklDemoApplication.java new file mode 100644 index 000000000000..c97fe66f8058 --- /dev/null +++ b/spring-boot-modules/spring-boot-pkl/src/main/java/com/baeldung/spring/pkl/SpringPklDemoApplication.java @@ -0,0 +1,14 @@ +package com.baeldung.spring.pkl; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; + +@SpringBootApplication +@ConfigurationPropertiesScan +public class SpringPklDemoApplication { + public static void main(String[] args) { + SpringApplication.run(SpringPklDemoApplication.class, args); + } + +} diff --git a/spring-boot-modules/spring-boot-pkl/src/main/java/com/baeldung/spring/pkl/ToolConfiguration.java b/spring-boot-modules/spring-boot-pkl/src/main/java/com/baeldung/spring/pkl/ToolConfiguration.java new file mode 100644 index 000000000000..e8a97cb944e0 --- /dev/null +++ b/spring-boot-modules/spring-boot-pkl/src/main/java/com/baeldung/spring/pkl/ToolConfiguration.java @@ -0,0 +1,17 @@ +package com.baeldung.spring.pkl; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ToolConfiguration { + @Bean + public GitHubService getGitHubService(ToolIntegrationProperties toolIntegration) { + return new GitHubService(toolIntegration.getGitConnection()); + } + + @Bean + public JiraService getJiraService(ToolIntegrationProperties toolIntegration) { + return new JiraService(toolIntegration.getJiraConnection()); + } +} diff --git a/spring-boot-modules/spring-boot-pkl/src/main/resources/ToolIntegrationTemplate.pkl b/spring-boot-modules/spring-boot-pkl/src/main/resources/ToolIntegrationTemplate.pkl new file mode 100644 index 000000000000..01b16ec77268 --- /dev/null +++ b/spring-boot-modules/spring-boot-pkl/src/main/resources/ToolIntegrationTemplate.pkl @@ -0,0 +1,15 @@ +module com.baeldung.spring.pkl.ToolIntegrationProperties + +class Connection { + url:String + name:String + credential:Credential +} + +class Credential { + user:String + password:String +} + +gitConnection: Connection +jiraConnection: Connection \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-pkl/src/main/resources/application.pkl b/spring-boot-modules/spring-boot-pkl/src/main/resources/application.pkl new file mode 100644 index 000000000000..2d6e7dfe6801 --- /dev/null +++ b/spring-boot-modules/spring-boot-pkl/src/main/resources/application.pkl @@ -0,0 +1,17 @@ +amends "ToolIntegrationTemplate.pkl" +gitConnection { + url = "https://api.github.com" + name = "GitHub" + credential { + user = "gituser" + password = read("env:GITHUB_PASSWORD") + } +} +jiraConnection { + url = "https://jira.atlassian.com" + name = "Jira" + credential { + user = "jirauser" + password = read("env:JIRA_PASSWORD") + } +} \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-pkl/src/test/java/com/baeldung/spring/pkl/SpringPklUnitTest.java b/spring-boot-modules/spring-boot-pkl/src/test/java/com/baeldung/spring/pkl/SpringPklUnitTest.java new file mode 100644 index 000000000000..b7840285999e --- /dev/null +++ b/spring-boot-modules/spring-boot-pkl/src/test/java/com/baeldung/spring/pkl/SpringPklUnitTest.java @@ -0,0 +1,49 @@ +package com.baeldung.spring.pkl; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +public class SpringPklUnitTest { + Logger logger = LoggerFactory.getLogger(SpringPklUnitTest.class); + + @Autowired + private GitHubService gitHubService; + @Autowired + private JiraService jiraService; + + @Test + public void whenJiraConfigsDefined_thenLoadFromApplicationPklFile() { + ToolIntegrationProperties.Connection jiraConnection = jiraService.getJiraConnection(); + ToolIntegrationProperties.Credential jiraCredential = jiraConnection.getCredential(); + + assertAll( + () -> assertEquals("Jira", jiraConnection.getName()), + () -> assertEquals("https://jira.atlassian.com", jiraConnection.getUrl()), + () -> assertEquals("jirauser", jiraCredential.getUser()), + () -> assertEquals("jirapassword", jiraCredential.getPassword()), + () -> assertEquals("Reading issues from Jira URL https://jira.atlassian.com", + jiraService.readIssues()) + ); + } + + @Test + public void whenGitHubConfigsDefined_thenLoadFromApplicationPklFile() { + ToolIntegrationProperties.Connection gitHubConnection = gitHubService.getGitConnection(); + ToolIntegrationProperties.Credential gitHubCredential = gitHubConnection.getCredential(); + + assertAll( + () -> assertEquals("GitHub", gitHubConnection.getName()), + () -> assertEquals("https://api.github.com", gitHubConnection.getUrl()), + () -> assertEquals("gituser", gitHubCredential.getUser()), + () -> assertEquals("gitpassword", gitHubCredential.getPassword()), + () -> assertEquals("Reading issues from GitHub URL https://api.github.com", gitHubService.readIssues()) + ); + } +}