diff --git a/README.md b/README.md index 4e5d5ec..e151135 100644 --- a/README.md +++ b/README.md @@ -22,16 +22,19 @@ This is sample 'Motor depot' web application. - - **Data Access:** - [Spring JDBC](https://docs.spring.io/spring-framework/docs/5.3.x/reference/html/data-access.html#jdbc) spring +- [Spring Data MongoDB](https://spring.io/projects/spring-data-mongodb) + spring - **Build System:** [Maven](https://maven.apache.org/) - **Control System:** [Git](https://git-scm.com/) git - **License:** [Apache license, version 2.0](http://www.apache.org/licenses/LICENSE-2.0) - **Automated Testing:** - [JUnit5](https://junit.org/junit5/) - - [Mockito](http://site.mockito.org/) + - [Mockito](http://site.mockito.org/) - **Log:** [Log4j 2](https://logging.apache.org/log4j/2.x/) - **Database:** - - [H2](http://www.h2database.com/html/main.html) + - [H2](http://www.h2database.com/html/main.html) - [PostgreSQL](https://www.postgresql.org/) postgresql + - [MongoDB](https://www.mongodb.com/) - **API documentation generation:** - [Swagger UI](https://swagger.io/tools/swagger-ui/) - **Code generation:** diff --git a/dao-api/src/main/java/com/epam/brest/dao_api/ModelSpecificationDao.java b/dao-api/src/main/java/com/epam/brest/dao_api/ModelSpecificationDao.java new file mode 100644 index 0000000..c983899 --- /dev/null +++ b/dao-api/src/main/java/com/epam/brest/dao_api/ModelSpecificationDao.java @@ -0,0 +1,15 @@ +package com.epam.brest.dao_api; + +import com.epam.brest.model.ModelSpecification; + +public interface ModelSpecificationDao { + + /** + * Getting car's model specification by model name. + * + * @param carModel String; + * @return ModelSpecification. + */ + + ModelSpecification getModelSpecificationByCarModel(String carModel); +} diff --git a/dao-jdbc/pom.xml b/dao-jdbc/pom.xml index 521ea6e..84a3953 100644 --- a/dao-jdbc/pom.xml +++ b/dao-jdbc/pom.xml @@ -45,6 +45,12 @@ org.springframework.boot spring-boot-starter-jdbc + + + org.springframework.boot + spring-boot-starter-logging + + @@ -59,14 +65,10 @@ org.mockito mockito-junit-jupiter - - - org.apache.logging.log4j - log4j-api - + - org.apache.logging.log4j - log4j-core + org.springframework.boot + spring-boot-starter-log4j2 diff --git a/dao-jdbc/src/main/java/com/epam/brest/dao/ModelSpecificationDaoImpl.java b/dao-jdbc/src/main/java/com/epam/brest/dao/ModelSpecificationDaoImpl.java new file mode 100644 index 0000000..8ea4b50 --- /dev/null +++ b/dao-jdbc/src/main/java/com/epam/brest/dao/ModelSpecificationDaoImpl.java @@ -0,0 +1,53 @@ +package com.epam.brest.dao; + +import com.epam.brest.dao_api.ModelSpecificationDao; +import com.epam.brest.model.ModelSpecification; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.jdbc.core.BeanPropertyRowMapper; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.core.namedparam.SqlParameterSource; +import org.springframework.stereotype.Repository; + +import static com.epam.brest.dao.Queries.FIND_MODEL_SPECIFICATION_BY_CAR_MODEL; + +@Repository +public class ModelSpecificationDaoImpl implements ModelSpecificationDao { + + public static final Logger LOG = LogManager.getLogger(ModelSpecificationDaoImpl.class); + + /** + * Field namedParameterJdbcTemplate. + */ + + private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; + + /** + * @Constructor + * + * @param namedParameterJdbcTemplate NamedParameterJdbcTemplate. + */ + + public ModelSpecificationDaoImpl(NamedParameterJdbcTemplate namedParameterJdbcTemplate) { + this.namedParameterJdbcTemplate = namedParameterJdbcTemplate; + } + + /** + * Getting car's model specification by model name. + * + * @param carModel String; + * @return instance of ModelSpecification. + */ + + @Override + public ModelSpecification getModelSpecificationByCarModel(String carModel) { + LOG.info("Method getModelSpecificationByCarModel() of class {} started", + getClass().getName()); + + SqlParameterSource sqlParameterSource = + new MapSqlParameterSource("modelName", carModel); + return namedParameterJdbcTemplate.queryForObject(FIND_MODEL_SPECIFICATION_BY_CAR_MODEL, + sqlParameterSource, BeanPropertyRowMapper.newInstance(ModelSpecification.class)); + } +} diff --git a/dao-jdbc/src/main/java/com/epam/brest/dao/Queries.java b/dao-jdbc/src/main/java/com/epam/brest/dao/Queries.java index 70e21d8..294553f 100644 --- a/dao-jdbc/src/main/java/com/epam/brest/dao/Queries.java +++ b/dao-jdbc/src/main/java/com/epam/brest/dao/Queries.java @@ -126,4 +126,14 @@ public class Queries { public static final String CARS_LIST_ASSIGN_TO_DRIVER = "SELECT * FROM car c WHERE c.driver_id = :driverId"; + + /** + * Field constant FIND_MODEL_SPECIFICATION_BY_CAR_MODEL. + */ + + public static final String FIND_MODEL_SPECIFICATION_BY_CAR_MODEL = + "SELECT ms.model_id AS modelId, ms.model_name AS modelName, ms.description AS description," + + " ms.max_speed AS maxSpeed, ms.carrying_capacity AS carryingCapacity" + + " FROM model_specifications AS ms WHERE ms.model_name=:modelName"; + } diff --git a/dao-jdbc/src/main/resources/log4j2.properties b/dao-jdbc/src/main/resources/log4j2.properties index 9757bbc..4d6ec2f 100644 --- a/dao-jdbc/src/main/resources/log4j2.properties +++ b/dao-jdbc/src/main/resources/log4j2.properties @@ -1,12 +1,50 @@ -status=warn -name=PropertiesConfig -appenders=console +status=warn, rolling, console +name=PropertiesConfig, RollingFileLogConfigDemo +appenders=console, rolling + appender.console.type=Console appender.console.name=STDOUT appender.console.layout.type=PatternLayout appender.console.layout.pattern=%d{HH:mm:ss} %-5p %-20.20C{1} %m%n log.test_app.name=com.epam.brest.dao -log.test_app.level=debug +log.test_app.level=info log.test_app.appenderRef.stdout.ref=STDOUT -rootLogger.level=debug rootLogger.appenderRef.stdout.ref=STDOUT + +# Log files location +property.basePath =../ + +# RollingFileAppender name, pattern, path and rollover policy +appender.rolling.type = RollingFile +appender.rolling.name = fileLogger +appender.rolling.fileName=${basePath}/cacheLogs.log +appender.rolling.filePattern= ${basePath}/cache.log.gz +#appender.rolling.filePattern= ${basePath}/app_%d{yyyyMMdd}.log.gz +appender.rolling.layout.type = PatternLayout +appender.rolling.layout.pattern = %d{yyyy-MM-dd HH:mm:ss.SSS} %level [%t] [%l] - %msg%n +appender.rolling.policies.type = Policies +appender.rolling.filter.threshold.type = ThresholdFilter +appender.rolling.filter.threshold.level = warn + +# Configure root logger +rootLogger.level = info +rootLogger.appenderRef.rolling.ref = fileLogger + +# RollingFileAppender rotation policy +#appender.rolling.policies.size.type = SizeBasedTriggeringPolicy +#appender.rolling.policies.size.size = 10MB +#appender.rolling.policies.time.type = TimeBasedTriggeringPolicy +#appender.rolling.policies.time.interval = 1 +#appender.rolling.policies.time.modulate = true +#appender.rolling.strategy.type = DefaultRolloverStrategy +#appender.rolling.strategy.delete.type = Delete +#appender.rolling.strategy.delete.basePath = ${basePath} +#appender.rolling.strategy.delete.maxDepth = 10 +#appender.rolling.strategy.delete.ifLastModified.type = IfLastModified + +# Delete all files older than 30 days +#appender.rolling.strategy.delete.ifLastModified.age = 30d + + + + diff --git a/dao-jdbc/src/test/java/com/epam/brest/dao/ModelSpecificationDaoImplTest.java b/dao-jdbc/src/test/java/com/epam/brest/dao/ModelSpecificationDaoImplTest.java new file mode 100644 index 0000000..f8ece63 --- /dev/null +++ b/dao-jdbc/src/test/java/com/epam/brest/dao/ModelSpecificationDaoImplTest.java @@ -0,0 +1,73 @@ +package com.epam.brest.dao; + +import com.epam.brest.model.ModelSpecification; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.*; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.jdbc.core.BeanPropertyRowMapper; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.core.namedparam.SqlParameterSource; + +import static com.epam.brest.dao.Queries.FIND_MODEL_SPECIFICATION_BY_CAR_MODEL; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class ModelSpecificationDaoImplTest { + + public static final Logger LOG = LogManager.getLogger(ModelSpecificationDaoImplTest.class); + + @InjectMocks + private ModelSpecificationDaoImpl modelSpecificationDao; + + @Mock + private NamedParameterJdbcTemplate namedParameterJdbcTemplate; + + @Captor + private ArgumentCaptor sqlParameterSourceArgumentCaptor; + + @Captor + private ArgumentCaptor> beanPropertyRowMapperArgumentCaptor; + + private ModelSpecification modelSpecification; + + + @BeforeEach + void setUp() { + modelSpecification = new ModelSpecification( + "NISSAN", "Passenger car: made in Japan", 200, 1870); + } + + @Test + void getModelSpecificationByCarModel() { + LOG.info("Method getModelSpecificationByCarModel() of class {} started", + getClass().getName()); + assertNotNull(namedParameterJdbcTemplate); + + lenient().when(namedParameterJdbcTemplate.queryForObject(anyString(), + ArgumentMatchers.any(), + any(BeanPropertyRowMapper.class))).thenReturn(modelSpecification); + + ModelSpecification modelSpecificationDst = modelSpecificationDao.getModelSpecificationByCarModel(modelSpecification.getModelName()); + + verify(namedParameterJdbcTemplate).queryForObject(eq(FIND_MODEL_SPECIFICATION_BY_CAR_MODEL), + sqlParameterSourceArgumentCaptor.capture(), beanPropertyRowMapperArgumentCaptor.capture()); + + BeanPropertyRowMapper rowMapper = beanPropertyRowMapperArgumentCaptor.getValue(); + SqlParameterSource sqlParameterSource = sqlParameterSourceArgumentCaptor.getValue(); + + assertNotNull(modelSpecificationDst); + assertNotNull(rowMapper); + assertNotNull(sqlParameterSource); + assertEquals(modelSpecificationDst, modelSpecification); + LOG.info("ModelSpecification was received after getModelSpecificationByCarModel() {} equals modelSpecification till it {}", + modelSpecificationDst, modelSpecification); + } +} \ No newline at end of file diff --git a/model/src/main/java/com/epam/brest/model/ModelSpecification.java b/model/src/main/java/com/epam/brest/model/ModelSpecification.java new file mode 100644 index 0000000..29bdec5 --- /dev/null +++ b/model/src/main/java/com/epam/brest/model/ModelSpecification.java @@ -0,0 +1,154 @@ +package com.epam.brest.model; + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.Objects; + +@Schema(name = "model-specification", description = "Specification of car model") +public class ModelSpecification { + + /** + * @serialField modelId Integer. + */ + + @Schema(hidden = true) + private Integer modelId; + + /** + * @serialField modelName String. + */ + + @Schema(name = "modelName", description = "Car's model name", example = "NISSAN") + private String modelName; + + /** + * @serialField description String. + */ + + @Schema(name = "description", description = "Model's description", + example = "Passenger car: made in Japan") + private String description; + + /** + * @serialField maxSpeed Integer. + */ + + @Schema(name = "max-speed", description = "Model's max speed", example = "200") + private Integer maxSpeed; + + /** + * @serialField carryingCapacity Integer. + */ + + @Schema(name = "carrying-capacity", description = "Model's max carrying capacity", + example = "12000") + private Integer carryingCapacity; + + /** + * @Constructor without parameters. + */ + + public ModelSpecification() { + } + + /** + * @Constructor + * + * @param modelId Integer. + * @param modelName String. + * @param description String. + * @param maxSpeed Integer. + * @param carryingCapacity Integer. + */ + + public ModelSpecification(final Integer modelId, final String modelName, + final String description, final Integer maxSpeed, + final Integer carryingCapacity) { + this.modelId = modelId; + this.modelName = modelName; + this.description = description; + this.maxSpeed = maxSpeed; + this.carryingCapacity = carryingCapacity; + } + + /** + * @Constructor + * + * @param modelName String. + * @param description String. + * @param maxSpeed Integer. + * @param carryingCapacity Integer. + */ + + public ModelSpecification(final String modelName, final String description, + final Integer maxSpeed, final Integer carryingCapacity) { + this.modelName = modelName; + this.description = description; + this.maxSpeed = maxSpeed; + this.carryingCapacity = carryingCapacity; + } + + public Integer getModelId() { + return modelId; + } + + public void setModelId(Integer modelId) { + this.modelId = modelId; + } + + public String getModelName() { + return modelName; + } + + public void setModelName(String modelName) { + this.modelName = modelName; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Integer getMaxSpeed() { + return maxSpeed; + } + + public void setMaxSpeed(Integer maxSpeed) { + this.maxSpeed = maxSpeed; + } + + public Integer getCarryingCapacity() { + return carryingCapacity; + } + + public void setCarryingCapacity(Integer carryingCapacity) { + this.carryingCapacity = carryingCapacity; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ModelSpecification that = (ModelSpecification) o; + return Objects.equals(modelId, that.modelId) && Objects.equals(modelName, that.modelName) && Objects.equals(description, that.description) && Objects.equals(maxSpeed, that.maxSpeed) && Objects.equals(carryingCapacity, that.carryingCapacity); + } + + @Override + public int hashCode() { + return Objects.hash(modelId, modelName, description, maxSpeed, carryingCapacity); + } + + @Override + public String toString() { + return "ModelSpecification{" + + "modelId=" + modelId + + ", modelName='" + modelName + '\'' + + ", description='" + description + '\'' + + ", maxSpeed=" + maxSpeed + + ", carryingCapacity=" + carryingCapacity + + '}'; + } +} diff --git a/mongodb-from-postgresql/pom.xml b/mongodb-from-postgresql/pom.xml index 1fa3d26..2ced78b 100644 --- a/mongodb-from-postgresql/pom.xml +++ b/mongodb-from-postgresql/pom.xml @@ -25,6 +25,12 @@ org.springframework.boot spring-boot-starter-data-mongodb + + + org.springframework.boot + spring-boot-starter-logging + + diff --git a/pom.xml b/pom.xml index b8f7d17..1969451 100644 --- a/pom.xml +++ b/pom.xml @@ -134,6 +134,17 @@ org.springframework.boot spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + + ${spring-boot.version} + + + org.springframework.boot + spring-boot-starter-log4j2 ${spring-boot.version} @@ -170,17 +181,6 @@ ${hamcrest.version} test - - - org.apache.logging.log4j - log4j-api - ${log4j2.version} - - - org.apache.logging.log4j - log4j-core - ${log4j2.version} - diff --git a/rest-app/pom.xml b/rest-app/pom.xml index 4043976..42867b4 100644 --- a/rest-app/pom.xml +++ b/rest-app/pom.xml @@ -33,6 +33,11 @@ org.springframework.boot spring-boot-starter-validation + + com.google.guava + guava + 31.1-jre + org.springframework.boot diff --git a/rest-app/src/main/java/com/epam/brest/rest/config/SpringFoxConfig.java b/rest-app/src/main/java/com/epam/brest/rest/config/SpringFoxConfig.java index 18249a3..333663b 100644 --- a/rest-app/src/main/java/com/epam/brest/rest/config/SpringFoxConfig.java +++ b/rest-app/src/main/java/com/epam/brest/rest/config/SpringFoxConfig.java @@ -16,8 +16,9 @@ public class SpringFoxConfig { /** + * @Bean configure swagger openApi. + * * @return Docket class. - * @bean configure swagger openApi. */ @Bean diff --git a/rest-app/src/main/java/com/epam/brest/rest/controller/DriverController.java b/rest-app/src/main/java/com/epam/brest/rest/controller/DriverController.java index ec8d9fa..20fb7ac 100644 --- a/rest-app/src/main/java/com/epam/brest/rest/controller/DriverController.java +++ b/rest-app/src/main/java/com/epam/brest/rest/controller/DriverController.java @@ -51,7 +51,7 @@ public DriverController( @Operation(summary = "Allows to get list of all drivers") @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Driver's list was provide", + @ApiResponse(responseCode = "200", description = "Driver's list was provided", content = {@Content(mediaType = "application/json", array = @ArraySchema(schema = @Schema(implementation = Driver.class)))}), @ApiResponse(responseCode = "404", description = diff --git a/rest-app/src/main/java/com/epam/brest/rest/controller/ModelSpecificationController.java b/rest-app/src/main/java/com/epam/brest/rest/controller/ModelSpecificationController.java new file mode 100644 index 0000000..5242ec6 --- /dev/null +++ b/rest-app/src/main/java/com/epam/brest/rest/controller/ModelSpecificationController.java @@ -0,0 +1,83 @@ +package com.epam.brest.rest.controller; + +import com.epam.brest.model.ModelSpecification; +import com.epam.brest.rest.config.CacheModelSpecificationWithGuavaConfig; +import com.epam.brest.service_api.ModelSpecificationService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@CrossOrigin +@Tag(name = "model-specification-controller", description = "Allows to get description of model specification") +public class ModelSpecificationController { + + public static final Logger LOG = LogManager.getLogger(ModelSpecificationController.class); + + /** + * @Field modelSpecificationService ModelSpecificationService. + */ + + private final ModelSpecificationService modelSpecificationService; + + /** + * @Field cacheModelSpecification CacheModelSpecificationWithGuavaConfig. + */ + + private final CacheModelSpecificationWithGuavaConfig cacheModelSpecification; + + /** + * @param modelSpecificationService ModelSpecificationService. + * @param cacheModelSpecification CacheModelSpecificationWithGuavaConfig. + * @Constructor + */ + + public ModelSpecificationController( + final ModelSpecificationService modelSpecificationService, + final CacheModelSpecificationWithGuavaConfig cacheModelSpecification) { + this.modelSpecificationService = modelSpecificationService; + this.cacheModelSpecification = cacheModelSpecification; + + } + + @Operation(summary = "Allows to get car's specification by its model") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Specification was provided", + content = {@Content(mediaType = "application/json", + array = @ArraySchema(schema = @Schema(implementation = ModelSpecification.class)))}), + @ApiResponse(responseCode = "404", description = + "Trying to get a non-existent model", + content = @Content), + @ApiResponse(responseCode = "500", description = + "Something is wrong! We'll sort this out soon.", + content = @Content)}) + @GetMapping(value = "/model_info/{model}") + public ResponseEntity getModelSpecificationByCarModel( + @PathVariable("model") @Parameter(description = "Car's model", + example = "URAL") final String carModel) { + + LOG.info("Method getModelSpecificationByCarModel() with car's model {} started of class {}", + carModel, getClass().getName()); + + + ModelSpecification modelSpecification = + modelSpecificationService.getModelSpecificationByCarModel(carModel); + + cacheModelSpecification.cacheRun(carModel); + + return new ResponseEntity<>(modelSpecification, HttpStatus.OK); + } +} diff --git a/rest-app/src/test/java/com/epam/brest/rest/controller/ModelSpecificationControllerIT.java b/rest-app/src/test/java/com/epam/brest/rest/controller/ModelSpecificationControllerIT.java new file mode 100644 index 0000000..ae591a9 --- /dev/null +++ b/rest-app/src/test/java/com/epam/brest/rest/controller/ModelSpecificationControllerIT.java @@ -0,0 +1,98 @@ +package com.epam.brest.rest.controller; + +import com.epam.brest.model.Driver; +import com.epam.brest.model.ModelSpecification; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Profile; +import org.springframework.http.MediaType; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultHandlers; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +import static com.epam.brest.model.constant.DriverConstants.DRIVER_NAME_SIZE; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@ExtendWith(SpringExtension.class) +@AutoConfigureMockMvc +@Transactional +@Profile("dev, test") +class ModelSpecificationControllerIT { + + public static final Logger LOG = LogManager.getLogger(ModelSpecificationControllerIT.class); + + public static final String MODEL_ENDPOINT = "/model_info"; + + @Autowired + private ModelSpecificationController modelSpecificationController; + + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + MockMvcModelSpecificationService modelSpecificationService = new MockMvcModelSpecificationService(); + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(modelSpecificationController) + .setMessageConverters(new MappingJackson2HttpMessageConverter()) +// .setControllerAdvice(customExceptionHandler) + .alwaysDo(MockMvcResultHandlers.print()) + .build(); + } + + @Test + void getModelSpecificationByCarModelTest() throws Exception { + LOG.info("Method getModelSpecificationByCarModelTest() started of class {}", getClass().getName()); + + // I should make CRUD operations for this test!!!!!!!! +// //given +// ModelSpecification specificationSrc = new ModelSpecification( +// "URAL", "Passenger car: made in Japan", 200, 1870); +// assertNotNull(specificationSrc); +//// Integer id = driverService.saveDriver(driver); +// String carModel = specificationSrc.getModelName(); +// assertNotNull(carModel); +// // when +// ModelSpecification specificationDst = modelSpecificationService.getSpecificationByModelName(carModel); +// // then +// assertNotNull(specificationDst); +// assertEquals(specificationDst.getModelName(), carModel); +// assertEquals(specificationSrc.getModelName(), specificationDst.getModelName()); + } + + class MockMvcModelSpecificationService { + + public ModelSpecification getSpecificationByModelName(final String carModel) throws Exception { + LOG.info("Method getSpecificationByModelName() with car's model: {} started of class {}", carModel, getClass().getName()); + + MockHttpServletResponse response = mockMvc.perform( + MockMvcRequestBuilders.get(MODEL_ENDPOINT + "/" + carModel) + .accept(MediaType.APPLICATION_JSON) + ).andExpect(status().isOk()) + .andReturn().getResponse(); + assertNotNull(response); + return objectMapper.readValue(response.getContentAsString(), ModelSpecification.class); + } + } +} \ No newline at end of file diff --git a/service-api/src/main/java/com/epam/brest/service_api/ModelSpecificationService.java b/service-api/src/main/java/com/epam/brest/service_api/ModelSpecificationService.java new file mode 100644 index 0000000..305c0e9 --- /dev/null +++ b/service-api/src/main/java/com/epam/brest/service_api/ModelSpecificationService.java @@ -0,0 +1,15 @@ +package com.epam.brest.service_api; + +import com.epam.brest.model.ModelSpecification; + +public interface ModelSpecificationService { + + /** + * Getting car's model specification by model name. + * + * @param carModel String; + * @return ModelSpecification. + */ + + ModelSpecification getModelSpecificationByCarModel(final String carModel); +} diff --git a/service-rest/src/main/java/com/epam/brest/service_rest/service/ModelSpecificationServiceRest.java b/service-rest/src/main/java/com/epam/brest/service_rest/service/ModelSpecificationServiceRest.java new file mode 100644 index 0000000..788009f --- /dev/null +++ b/service-rest/src/main/java/com/epam/brest/service_rest/service/ModelSpecificationServiceRest.java @@ -0,0 +1,69 @@ +package com.epam.brest.service_rest.service; + +import com.epam.brest.model.Car; +import com.epam.brest.model.ModelSpecification; +import com.epam.brest.service_api.ModelSpecificationService; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.*; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import java.util.List; + +@Service +public class ModelSpecificationServiceRest implements ModelSpecificationService { + + public static final Logger LOG = LogManager.getLogger( + DriverServiceRest.class); + + /** + * Field url String. + */ + + private String url; + + /** + * Field restTemplate RestTemplate. + */ + + private RestTemplate restTemplate; + + /** + * Constructor without parameters. + */ + + public ModelSpecificationServiceRest() { + } + + /** + * @Constructor + * + * @param url String. + * @param restTemplate RestTemplate. + */ + + public ModelSpecificationServiceRest(final String url, + final RestTemplate restTemplate) { + this.url = url; + this.restTemplate = restTemplate; + } + + @Override + public ModelSpecification getModelSpecificationByCarModel(final String carModel) { + LOG.info("Method getModelSpecificationByCarModel() with parameter {} started {}", + carModel, getClass().getName()); +// ParameterizedTypeReference typeReference = +// new ParameterizedTypeReference<>() {}; +// +// ResponseEntity responseEntity = restTemplate +// .exchange(url + "/" + carModel, HttpMethod.GET, null, typeReference); + HttpHeaders headers = new HttpHeaders(); + headers.setAccept(List.of(MediaType.APPLICATION_JSON)); + HttpEntity entity = new HttpEntity<>(carModel, headers); + ResponseEntity responseEntity = restTemplate.exchange( + url + "/" + carModel, HttpMethod.GET, entity, ModelSpecification.class); + return responseEntity.getBody(); + } +} diff --git a/service-rest/src/test/java/com/epam/brest/service_rest/service/ModelSpecificationServiceRestTest.java b/service-rest/src/test/java/com/epam/brest/service_rest/service/ModelSpecificationServiceRestTest.java new file mode 100644 index 0000000..e300b01 --- /dev/null +++ b/service-rest/src/test/java/com/epam/brest/service_rest/service/ModelSpecificationServiceRestTest.java @@ -0,0 +1,80 @@ +package com.epam.brest.service_rest.service; + +import com.epam.brest.model.ModelSpecification; +import com.epam.brest.service_rest.service.config.ServiceRestTestConfig; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Import; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.client.ExpectedCount; +import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.web.client.RestTemplate; + +import java.net.URI; +import java.net.URISyntaxException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.method; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus; + +@ExtendWith(SpringExtension.class) +@Import({ServiceRestTestConfig.class}) +class ModelSpecificationServiceRestTest { + + public static final Logger LOG = LogManager.getLogger( + ModelSpecificationServiceRestTest.class); + + public static final String MODEL_INFO_URL = "http://localhost:8088/model_info"; + + @Autowired + private RestTemplate restTemplate; + + private ModelSpecificationServiceRest modelSpecificationServiceRest; + + private MockRestServiceServer mockRestServiceServer; + + private ObjectMapper objectMapper; + + @BeforeEach + void setUp() { + modelSpecificationServiceRest = new ModelSpecificationServiceRest(MODEL_INFO_URL, restTemplate); + mockRestServiceServer = MockRestServiceServer.createServer(restTemplate); + objectMapper = new ObjectMapper().findAndRegisterModules(); + } + + @Test + void getModelSpecificationByCarModel() throws URISyntaxException, JsonProcessingException { + LOG.info("Method getModelSpecificationByCarModel() started {}", + getClass().getName()); + // given + String carModel = "NISSAN"; + ModelSpecification modelSpecification = new ModelSpecification( + 1, carModel, "Passenger car: made in Japan", + 210, 1740); + + mockRestServiceServer.expect(ExpectedCount.once(), requestTo(new URI(MODEL_INFO_URL + "/" + modelSpecification.getModelName()))) + .andExpect(method(HttpMethod.GET)) + .andRespond(withStatus(HttpStatus.OK) + .contentType(MediaType.APPLICATION_JSON) + .body(objectMapper.writeValueAsString(modelSpecification)) + ); + // when + ModelSpecification modelSpecificationDst = modelSpecificationServiceRest.getModelSpecificationByCarModel(carModel); + // then + mockRestServiceServer.verify(); + assertNotNull(modelSpecificationDst); + assertEquals(modelSpecificationDst.getModelName(), carModel); + assertEquals(modelSpecificationDst.getModelId(), modelSpecification.getModelId()); + } +} \ No newline at end of file diff --git a/service/src/main/java/com/epam/brest/service/impl/ModelSpecificationServiceImpl.java b/service/src/main/java/com/epam/brest/service/impl/ModelSpecificationServiceImpl.java new file mode 100644 index 0000000..23a3d56 --- /dev/null +++ b/service/src/main/java/com/epam/brest/service/impl/ModelSpecificationServiceImpl.java @@ -0,0 +1,32 @@ +package com.epam.brest.service.impl; + +import com.epam.brest.dao_api.ModelSpecificationDao; +import com.epam.brest.model.ModelSpecification; +import com.epam.brest.service_api.ModelSpecificationService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class ModelSpecificationServiceImpl implements ModelSpecificationService { + + /** + * @Field modelSpecificationDao ModelSpecificationDao. + */ + + private final ModelSpecificationDao modelSpecificationDao; + + /** + * @Constructor + * @param modelSpecificationDao ModelSpecificationDao. + */ + + public ModelSpecificationServiceImpl(ModelSpecificationDao modelSpecificationDao) { + this.modelSpecificationDao = modelSpecificationDao; + } + + @Transactional(readOnly = true) + @Override + public ModelSpecification getModelSpecificationByCarModel(String carModel) { + return modelSpecificationDao.getModelSpecificationByCarModel(carModel); + } +} diff --git a/service/src/test/java/com/epam/brest/service/impl/ModelSpecificationServiceImplTest.java b/service/src/test/java/com/epam/brest/service/impl/ModelSpecificationServiceImplTest.java new file mode 100644 index 0000000..6b8a33e --- /dev/null +++ b/service/src/test/java/com/epam/brest/service/impl/ModelSpecificationServiceImplTest.java @@ -0,0 +1,56 @@ +package com.epam.brest.service.impl; + +import com.epam.brest.dao_api.ModelSpecificationDao; +import com.epam.brest.model.ModelSpecification; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class ModelSpecificationServiceImplTest { + + private static final Logger LOG = LogManager.getLogger(ModelSpecificationServiceImplTest.class); + + @InjectMocks + private ModelSpecificationServiceImpl modelSpecificationService; + + @Mock + private ModelSpecificationDao modelSpecificationDao; + + private ModelSpecification modelSpecification; + + @BeforeEach + void setUp() { + modelSpecification = new ModelSpecification( + "NISSAN", "Passenger car: made in Japan", 200, 1870); + } + + @Test + void getModelSpecificationByCarModel() { + LOG.info("Method getModelSpecificationByCarModel() of class {} started", getClass().getName()); + + lenient().when(modelSpecificationDao.getModelSpecificationByCarModel(anyString())) + .thenReturn(modelSpecification); + + ModelSpecification modelSpecificationDst = modelSpecificationService + .getModelSpecificationByCarModel(modelSpecification.getModelName()); + + verify(modelSpecificationDao, times(1)) + .getModelSpecificationByCarModel(eq(modelSpecification.getModelName())); + + assertNotNull(modelSpecificationDst); + assertEquals(modelSpecification, modelSpecificationDst); + LOG.info("ModelSpecification was received after getModelSpecificationByCarModel() {} equals modelSpecification till it {}", + modelSpecificationDst, modelSpecification); + } +} \ No newline at end of file diff --git a/test-db/src/main/resources/create-tables.sql b/test-db/src/main/resources/create-tables.sql index b46dd7d..274eac2 100644 --- a/test-db/src/main/resources/create-tables.sql +++ b/test-db/src/main/resources/create-tables.sql @@ -1,5 +1,6 @@ DROP TABLE IF EXISTS car; DROP TABLE IF EXISTS driver; +DROP TABLE IF EXISTS model_specifications; CREATE TABLE driver ( driver_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, @@ -13,4 +14,12 @@ CREATE TABLE car ( model VARCHAR(20) NOT NULL, driver_id INT NOT NULL, CONSTRAINT car_driver_fk FOREIGN KEY (driver_id) REFERENCES driver(driver_id) -); \ No newline at end of file +); + +CREATE TABLE model_specifications ( + model_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + model_name VARCHAR(20) NOT NULL UNIQUE , + description VARCHAR(255) NOT NULL , + max_speed INT NOT NULL , + carrying_capacity INT NOT NULL +) \ No newline at end of file diff --git a/test-db/src/main/resources/init-tables.sql b/test-db/src/main/resources/init-tables.sql index 7ab72ef..172c4b1 100644 --- a/test-db/src/main/resources/init-tables.sql +++ b/test-db/src/main/resources/init-tables.sql @@ -6,3 +6,8 @@ INSERT INTO `car` (`car_id`, `model`, `driver_id`) VALUES (1, 'GAZ', 1), (3, 'LADA', 3), (4, 'GIGA', 1), (5, 'URAL', 3); +INSERT INTO `model_specifications` (`model_id`, `model_name`, `description`, `max_speed`, `carrying_capacity`) VALUES (1, 'GAZ', 'Truck: made in Russia', 90, 7500), + (2, 'ZIL', 'Truck: made in Russia', 80, 8500), + (3, 'LADA', 'Passenger car: made in Russia', 180, 1450), + (4, 'GIGA', 'Passenger car: made in Russia', 150, 1200), + (5, 'URAL', 'Truck: made in Russia', 80, 10400); diff --git a/web-app/src/main/java/com/epam/brest/controller/ModelSpecificationController.java b/web-app/src/main/java/com/epam/brest/controller/ModelSpecificationController.java new file mode 100644 index 0000000..8ae2492 --- /dev/null +++ b/web-app/src/main/java/com/epam/brest/controller/ModelSpecificationController.java @@ -0,0 +1,52 @@ +package com.epam.brest.controller; + +import com.epam.brest.service_api.ModelSpecificationService; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; + +@Controller +public class ModelSpecificationController { + + public static final Logger LOG = LogManager.getLogger( + ModelSpecificationController.class); + + /** + * @Field modelSpecificationService ModelSpecificationService. + */ + + private final ModelSpecificationService modelSpecificationService; + + /** + * @Constructor + * @param modelSpecificationService ModelSpecificationService + */ + + public ModelSpecificationController( + final ModelSpecificationService modelSpecificationService) { + this.modelSpecificationService = modelSpecificationService; + } + + /** + * Get specification of car by it name. + * + * @param carModel String. + * @param model Model. + * @return view String. + */ + + @GetMapping("/model_info/{model}") + public String getModelSpecificationByCarName( + @PathVariable("model") final String carModel, final Model model) { + LOG.info("Method getModelSpecificationByCarName() started in class {}", + getClass().getName()); + + model.addAttribute("specification", + modelSpecificationService.getModelSpecificationByCarModel(carModel)); + + return "cars/cars"; + } +} diff --git a/web-app/src/main/java/com/epam/brest/controller/config/WebMvcConfig.java b/web-app/src/main/java/com/epam/brest/controller/config/WebMvcConfig.java index 6bf74b5..a89b838 100644 --- a/web-app/src/main/java/com/epam/brest/controller/config/WebMvcConfig.java +++ b/web-app/src/main/java/com/epam/brest/controller/config/WebMvcConfig.java @@ -3,9 +3,11 @@ import com.epam.brest.mongodb_postgresql.service.DriverDtoMongodbService; import com.epam.brest.service_api.CarService; import com.epam.brest.service_api.DriverService; +import com.epam.brest.service_api.ModelSpecificationService; import com.epam.brest.service_api.dto.DriverDtoService; import com.epam.brest.service_rest.service.CarServiceRest; import com.epam.brest.service_rest.service.DriverServiceRest; +import com.epam.brest.service_rest.service.ModelSpecificationServiceRest; import com.epam.brest.service_rest.service.dto.DriverDtoMongodbServiceRest; import com.epam.brest.service_rest.service.dto.DriverDtoServiceRest; import org.springframework.beans.factory.annotation.Value; @@ -98,4 +100,16 @@ DriverDtoMongodbService driverDtoMongodbService() { String url = String.format("%s://%s:%d/mongo", protocol, host, port); return new DriverDtoMongodbServiceRest(url, restTemplate()); } + + /** + * ModelSpecificationService's bean. + * + * @return modelSpecificationService ModelSpecificationService. + */ + + @Bean + ModelSpecificationService modelSpecificationService() { + String url = String.format("%s://%s:%d/model_info", protocol, host, port); + return new ModelSpecificationServiceRest(url, restTemplate()); + } } diff --git a/web-app/src/main/resources/templates/cars/cars.html b/web-app/src/main/resources/templates/cars/cars.html index c078698..813d0fa 100644 --- a/web-app/src/main/resources/templates/cars/cars.html +++ b/web-app/src/main/resources/templates/cars/cars.html @@ -66,12 +66,28 @@ Unique number car Model Unique number driver + Edit and delete car - - + + - + + + + + + + + + + + +  Delete @@ -101,6 +117,25 @@

© 2021

+ + + @@ -125,6 +160,15 @@ th:src="@{/js/bootstrap.bundle.min.js}"> +