From fd38b05869ea5bbdfe31b36170b47dd75d161399 Mon Sep 17 00:00:00 2001 From: anirban das Date: Mon, 15 Mar 2021 03:38:27 +0530 Subject: [PATCH] added base docker compose file, fixed cache eviction trigger in polling service, fixed compilation errosm in polling service, made vehicle entity type consistent for redis persistence to be shared acrioss two different projects, removed throwing exception from scheduled tasks, fixed cron timings for polling service for consistent results, implemented logic to determine vehicle staleness, moved functional interface implementations to init method of refresh service in polling service, implemented correct setting of expiration time for redis keys cleanup service of polling service, implemented end to end flow of vehicle service to get all vehicles and their details by their vin --- docker-compose.yml | 0 polling-service/Dockerfile | 5 + polling-service/pom.xml | 24 ++++ .../polling/PollingServiceApplication.java | 2 + .../cleanup/service/VehicleStaleness.java | 10 ++ .../impl/Car2GoVehicleCleanupServiceImpl.java | 46 +++++--- .../polling/exception/PollingErrorCode.java | 8 ++ .../exception/PollingServiceException.java | 20 ++++ .../filter/RestClientErrorHandler.java | 6 +- .../filter/ScheduledServiceErrorHandler.java | 23 ++++ .../sharenow/polling/model/dto/ErrorDTO.java | 2 +- .../polling/model/dto/PositionDTO.java | 2 +- .../polling/model/dto/VehicleDTO.java | 2 +- .../polling/model/entity/PositionEntity.java | 2 +- .../polling/model/entity/VehicleEntity.java | 6 +- .../VehicleRefreshConfiguration.java | 3 - .../converter/PositionDTO2EntityConveter.java | 4 +- .../converter/VehicleDTO2EntityConveter.java | 4 +- .../polling/refresh/service/VehicleInput.java | 3 +- .../refresh/service/VehicleOutput.java | 2 +- .../refresh/service/VehicleProcessor.java | 4 +- .../refresh/service/VehicleService.java | 2 +- .../impl/Car2GoVehicleRefreshServiceImpl.java | 101 ++++++++-------- .../polling/repository/VehicleRepository.java | 4 +- .../src/main/resources/application.properties | 11 +- .../polling/VehicleRefreshServiceTests.java | 33 ++++++ .../PositionDTO2EntityConversionTests.java | 34 ++++++ .../polling/refresh/VehicleInputTests.java | 33 ++++++ scripts/seeding/seed_geojson_dump.py | 0 vehicle-service/Dockerfile | 5 + vehicle-service/pom.xml | 71 +++++++++++ .../vehicle/VehicleServiceApplication.java | 15 +++ .../VehicleServiceConfiguration.java | 30 +++++ .../vehicle/controller/VehicleController.java | 47 ++++++++ .../converter/PositionEntity2VOConverter.java | 12 ++ .../VehicleEntity2DetailedVOConverter.java | 12 ++ .../converter/VehicleEntity2VOConverter.java | 12 ++ .../vehicle/filter/RestErrorHandler.java | 35 ++++++ .../vehicle/model/entity/PositionEntity.java | 19 +++ .../vehicle/model/entity/VehicleEntity.java | 42 +++++++ .../vehicle/model/error/VehicleErrorCode.java | 23 ++++ .../model/error/VehicleServiceException.java | 33 ++++++ .../sharenow/vehicle/model/vo/ErrorVO.java | 17 +++ .../sharenow/vehicle/model/vo/PositionVO.java | 13 ++ .../vehicle/model/vo/VehicleDetailsVO.java | 18 +++ .../sharenow/vehicle/model/vo/VehicleVO.java | 15 +++ .../vehicle/repository/VehicleRepository.java | 14 +++ .../vehicle/service/VehicleService.java | 17 +++ .../service/impl/VehicleServiceImpl.java | 111 ++++++++++++++++++ .../src/main/resources/application.properties | 5 + .../src/main/resources/messages.properties | 2 + 51 files changed, 866 insertions(+), 98 deletions(-) create mode 100644 docker-compose.yml create mode 100644 polling-service/Dockerfile create mode 100644 polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/cleanup/service/VehicleStaleness.java create mode 100644 polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/exception/PollingErrorCode.java create mode 100644 polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/filter/ScheduledServiceErrorHandler.java create mode 100644 polling-service/src/test/java/com/teenthofabud/codingchallenge/sharenow/polling/VehicleRefreshServiceTests.java create mode 100644 polling-service/src/test/java/com/teenthofabud/codingchallenge/sharenow/polling/refresh/PositionDTO2EntityConversionTests.java create mode 100644 polling-service/src/test/java/com/teenthofabud/codingchallenge/sharenow/polling/refresh/VehicleInputTests.java create mode 100644 scripts/seeding/seed_geojson_dump.py create mode 100644 vehicle-service/Dockerfile create mode 100644 vehicle-service/pom.xml create mode 100644 vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/VehicleServiceApplication.java create mode 100644 vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/configuration/VehicleServiceConfiguration.java create mode 100644 vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/controller/VehicleController.java create mode 100644 vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/converter/PositionEntity2VOConverter.java create mode 100644 vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/converter/VehicleEntity2DetailedVOConverter.java create mode 100644 vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/converter/VehicleEntity2VOConverter.java create mode 100644 vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/filter/RestErrorHandler.java create mode 100644 vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/model/entity/PositionEntity.java create mode 100644 vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/model/entity/VehicleEntity.java create mode 100644 vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/model/error/VehicleErrorCode.java create mode 100644 vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/model/error/VehicleServiceException.java create mode 100644 vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/model/vo/ErrorVO.java create mode 100644 vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/model/vo/PositionVO.java create mode 100644 vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/model/vo/VehicleDetailsVO.java create mode 100644 vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/model/vo/VehicleVO.java create mode 100644 vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/repository/VehicleRepository.java create mode 100644 vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/service/VehicleService.java create mode 100644 vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/service/impl/VehicleServiceImpl.java create mode 100644 vehicle-service/src/main/resources/application.properties create mode 100644 vehicle-service/src/main/resources/messages.properties diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e69de29 diff --git a/polling-service/Dockerfile b/polling-service/Dockerfile new file mode 100644 index 0000000..3504e63 --- /dev/null +++ b/polling-service/Dockerfile @@ -0,0 +1,5 @@ +FROM adoptopenjdk:11-jre-hotspot-focal +MAINTAINER Anirban Das +RUN mkdip ~p /opt/sharenow-coding-challenge +COPY target/polling-app.jar /opt/sharenow-coding-challenge/polling-app.jar +CMD ["java","-jar","/opt/sharenow-coding-challenge/polling-app.jar"] \ No newline at end of file diff --git a/polling-service/pom.xml b/polling-service/pom.xml index 060ed0d..debd03a 100644 --- a/polling-service/pom.xml +++ b/polling-service/pom.xml @@ -21,6 +21,29 @@ polling-app + + + com.spotify + dockerfile-maven-plugin + 1.4.13 + + + default + + build + push + + + + + teenthofabud/sharenow-coding-challenge-${project.artifactId} + ${project.version} + + target/${project.build.finalName}.jar + + + + @@ -44,4 +67,5 @@ + \ No newline at end of file diff --git a/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/PollingServiceApplication.java b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/PollingServiceApplication.java index 410d747..9678fd7 100644 --- a/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/PollingServiceApplication.java +++ b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/PollingServiceApplication.java @@ -3,11 +3,13 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; +import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication @EnableEurekaClient @EnableScheduling +@EnableRedisRepositories public class PollingServiceApplication { public static void main(String[] args) { diff --git a/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/cleanup/service/VehicleStaleness.java b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/cleanup/service/VehicleStaleness.java new file mode 100644 index 0000000..0e5f691 --- /dev/null +++ b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/cleanup/service/VehicleStaleness.java @@ -0,0 +1,10 @@ +package com.teenthofabud.codingchallenge.sharenow.polling.cleanup.service; + +import com.teenthofabud.codingchallenge.sharenow.polling.model.entity.VehicleEntity; + +@FunctionalInterface +public interface VehicleStaleness { + + public boolean isVehicleStale(VehicleEntity entity); + +} diff --git a/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/cleanup/service/impl/Car2GoVehicleCleanupServiceImpl.java b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/cleanup/service/impl/Car2GoVehicleCleanupServiceImpl.java index 5d73cb9..c9a3b66 100644 --- a/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/cleanup/service/impl/Car2GoVehicleCleanupServiceImpl.java +++ b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/cleanup/service/impl/Car2GoVehicleCleanupServiceImpl.java @@ -2,9 +2,11 @@ import com.teenthofabud.codingchallenge.sharenow.polling.PollingMonitor; import com.teenthofabud.codingchallenge.sharenow.polling.cleanup.service.VehicleCleanupService; -import com.teenthofabud.codingchallenge.sharenow.polling.refresh.model.entity.VehicleEntity; -import com.teenthofabud.codingchallenge.sharenow.polling.refresh.repository.VehicleRepository; +import com.teenthofabud.codingchallenge.sharenow.polling.cleanup.service.VehicleStaleness; +import com.teenthofabud.codingchallenge.sharenow.polling.model.entity.VehicleEntity; +import com.teenthofabud.codingchallenge.sharenow.polling.repository.VehicleRepository; import org.redisson.api.RKeys; +import org.redisson.api.RMapCache; import org.redisson.api.RedissonClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -13,6 +15,7 @@ import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; +import javax.annotation.PostConstruct; import java.util.concurrent.TimeUnit; @@ -21,13 +24,11 @@ public class Car2GoVehicleCleanupServiceImpl implements VehicleCleanupService { private static final Logger LOGGER = LoggerFactory.getLogger(Car2GoVehicleCleanupServiceImpl.class); - private static final long MIN_TTL_FOR_EVICTION = 1l; - @Value("${ps.cleanup.staleness.interval:60}") private int stalenessIntervalInSeconds; - @Value("${ps.cleanup.stale.ttl:1}") - private long ttlSeconds; + @Value("${ps.cleanup.ttl.eviction:1}") + private long ttlEviction; @Autowired private RedissonClient redisson; @@ -38,6 +39,22 @@ public class Car2GoVehicleCleanupServiceImpl implements VehicleCleanupService { @Autowired private PollingMonitor monitor; + private VehicleStaleness staleDetector; + + @PostConstruct + private void init() { + this.staleDetector = (ve) -> { + boolean status = false; + if(ve != null) { + long now = System.currentTimeMillis(); + long lastSeen = ve.getUpdatedAt().getTime(); + long elapsed = now - lastSeen; + status = (elapsed/1000l) > stalenessIntervalInSeconds; + } + return status; + }; + } + @Override @Scheduled(cron = "${ps.cleanup.cron.expression:*/90 * * * *}") @@ -48,10 +65,10 @@ public void clearStaleVehiclesForConfiguredCity() { int count = 0; LOGGER.info("Starting transaction for evicting stale vehicles by least recently updated policy"); for(VehicleEntity entity : itr) { - if(isVehicleStale(entity)) { - String vehicleId = String.valueOf(entity.getId()); - keys.expire(vehicleId, MIN_TTL_FOR_EVICTION, TimeUnit.MILLISECONDS); - LOGGER.info("Preparing vehicle with id {} for eviction by assigning the lowest ttl", vehicleId); + if(this.staleDetector.isVehicleStale(entity)) { + String vehicleKey = entity.getCacheKey(); + keys.expire(vehicleKey, ttlEviction, TimeUnit.SECONDS); + LOGGER.info("Preparing vehicle with id {} for eviction by assigning the lowest ttl", entity.getId()); count++; } } @@ -59,13 +76,4 @@ public void clearStaleVehiclesForConfiguredCity() { } } - private boolean isVehicleStale(VehicleEntity ve) { - boolean status = false; - long now = System.currentTimeMillis(); - long lastSeen = ve.getUpdatedAt().getTime(); - long elapsed = now - lastSeen; - status = (elapsed/1000l) > stalenessIntervalInSeconds; - return status; - } - } diff --git a/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/exception/PollingErrorCode.java b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/exception/PollingErrorCode.java new file mode 100644 index 0000000..2555b42 --- /dev/null +++ b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/exception/PollingErrorCode.java @@ -0,0 +1,8 @@ +package com.teenthofabud.codingchallenge.sharenow.polling.exception; + +public enum PollingErrorCode { + + REST_ERROR, + SCHEDULED_ERROR + +} diff --git a/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/exception/PollingServiceException.java b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/exception/PollingServiceException.java index 5fa4a45..14ee6c3 100644 --- a/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/exception/PollingServiceException.java +++ b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/exception/PollingServiceException.java @@ -1,4 +1,24 @@ package com.teenthofabud.codingchallenge.sharenow.polling.exception; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter public class PollingServiceException extends RuntimeException { + + private PollingErrorCode errorCode; + private String message; + + public PollingServiceException(String message) { + super(message); + this.message = message; + } + + public PollingServiceException(String message, PollingErrorCode errorCode) { + super(message); + this.message = message; + this.errorCode = errorCode; + } + } diff --git a/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/filter/RestClientErrorHandler.java b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/filter/RestClientErrorHandler.java index 9f28d9f..708add1 100644 --- a/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/filter/RestClientErrorHandler.java +++ b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/filter/RestClientErrorHandler.java @@ -1,7 +1,9 @@ package com.teenthofabud.codingchallenge.sharenow.polling.filter; import com.fasterxml.jackson.databind.ObjectMapper; -import com.teenthofabud.codingchallenge.sharenow.polling.refresh.model.dto.ErrorDTO; +import com.teenthofabud.codingchallenge.sharenow.polling.exception.PollingErrorCode; +import com.teenthofabud.codingchallenge.sharenow.polling.exception.PollingServiceException; +import com.teenthofabud.codingchallenge.sharenow.polling.model.dto.ErrorDTO; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; @@ -36,6 +38,7 @@ public void handleError(ClientHttpResponse clientHttpResponse) throws IOExceptio .series() == HttpStatus.Series.SERVER_ERROR) { ErrorDTO err = om.readValue(clientHttpResponse.getBody(), ErrorDTO.class); LOGGER.error("Unexpected server error: [{}={}]", clientHttpResponse.getRawStatusCode(), err.toString()); + throw new PollingServiceException(err.toString(), PollingErrorCode.REST_ERROR); } else if (clientHttpResponse.getStatusCode() .series() == HttpStatus.Series.CLIENT_ERROR) { ErrorDTO err = om.readValue(clientHttpResponse.getBody(), ErrorDTO.class); @@ -44,6 +47,7 @@ public void handleError(ClientHttpResponse clientHttpResponse) throws IOExceptio } else { LOGGER.error("Unexpected client error: [{}={}]", clientHttpResponse.getRawStatusCode(), err.toString()); } + throw new PollingServiceException(err.toString(), PollingErrorCode.REST_ERROR); } } } diff --git a/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/filter/ScheduledServiceErrorHandler.java b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/filter/ScheduledServiceErrorHandler.java new file mode 100644 index 0000000..2f8c51f --- /dev/null +++ b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/filter/ScheduledServiceErrorHandler.java @@ -0,0 +1,23 @@ +package com.teenthofabud.codingchallenge.sharenow.polling.filter; + +import com.teenthofabud.codingchallenge.sharenow.polling.exception.PollingErrorCode; +import com.teenthofabud.codingchallenge.sharenow.polling.exception.PollingServiceException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.task.TaskSchedulerCustomizer; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; + +@Configuration +public class ScheduledServiceErrorHandler implements TaskSchedulerCustomizer { + + private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledServiceErrorHandler.class); + + @Override + public void customize(ThreadPoolTaskScheduler taskScheduler) { + taskScheduler.setErrorHandler(t -> { + LOGGER.error("Error executing scheduled task: {}", t.getMessage()); + t.printStackTrace(); + }); + } +} diff --git a/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/model/dto/ErrorDTO.java b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/model/dto/ErrorDTO.java index 3930098..6e40619 100644 --- a/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/model/dto/ErrorDTO.java +++ b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/model/dto/ErrorDTO.java @@ -1,4 +1,4 @@ -package com.teenthofabud.codingchallenge.sharenow.polling.refresh.model.dto; +package com.teenthofabud.codingchallenge.sharenow.polling.model.dto; import lombok.Getter; diff --git a/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/model/dto/PositionDTO.java b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/model/dto/PositionDTO.java index 1ae8374..edbbfa8 100644 --- a/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/model/dto/PositionDTO.java +++ b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/model/dto/PositionDTO.java @@ -1,4 +1,4 @@ -package com.teenthofabud.codingchallenge.sharenow.polling.refresh.model.dto; +package com.teenthofabud.codingchallenge.sharenow.polling.model.dto; import lombok.Getter; import lombok.Setter; diff --git a/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/model/dto/VehicleDTO.java b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/model/dto/VehicleDTO.java index c5bcf98..ac9d9db 100644 --- a/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/model/dto/VehicleDTO.java +++ b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/model/dto/VehicleDTO.java @@ -1,4 +1,4 @@ -package com.teenthofabud.codingchallenge.sharenow.polling.refresh.model.dto; +package com.teenthofabud.codingchallenge.sharenow.polling.model.dto; import lombok.Getter; import lombok.Setter; diff --git a/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/model/entity/PositionEntity.java b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/model/entity/PositionEntity.java index 60a7893..531514a 100644 --- a/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/model/entity/PositionEntity.java +++ b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/model/entity/PositionEntity.java @@ -1,4 +1,4 @@ -package com.teenthofabud.codingchallenge.sharenow.polling.refresh.model.entity; +package com.teenthofabud.codingchallenge.sharenow.polling.model.entity; import lombok.Getter; import lombok.Setter; diff --git a/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/model/entity/VehicleEntity.java b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/model/entity/VehicleEntity.java index 8572b22..8ca6b3d 100644 --- a/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/model/entity/VehicleEntity.java +++ b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/model/entity/VehicleEntity.java @@ -1,10 +1,12 @@ -package com.teenthofabud.codingchallenge.sharenow.polling.refresh.model.entity; +package com.teenthofabud.codingchallenge.sharenow.polling.model.entity; import lombok.Getter; import lombok.Setter; import lombok.ToString; import org.springframework.data.annotation.Id; +import org.springframework.data.annotation.TypeAlias; import org.springframework.data.redis.core.RedisHash; +import org.springframework.data.redis.core.index.Indexed; import java.io.Serializable; import java.util.Date; @@ -13,11 +15,13 @@ @Setter @ToString @RedisHash("Vehicle") +@TypeAlias("Vehicle") public class VehicleEntity implements Serializable { @Id private int id; private int locationId; + @Indexed private String vin; private String numberPlate; private PositionEntity position; diff --git a/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/refresh/configuration/VehicleRefreshConfiguration.java b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/refresh/configuration/VehicleRefreshConfiguration.java index 21a1492..4666908 100644 --- a/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/refresh/configuration/VehicleRefreshConfiguration.java +++ b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/refresh/configuration/VehicleRefreshConfiguration.java @@ -1,15 +1,12 @@ package com.teenthofabud.codingchallenge.sharenow.polling.refresh.configuration; -import io.lettuce.core.RedisClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; import org.springframework.stereotype.Component; @Component -@EnableRedisRepositories public class VehicleRefreshConfiguration { @Autowired diff --git a/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/refresh/converter/PositionDTO2EntityConveter.java b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/refresh/converter/PositionDTO2EntityConveter.java index c49b636..29faf39 100644 --- a/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/refresh/converter/PositionDTO2EntityConveter.java +++ b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/refresh/converter/PositionDTO2EntityConveter.java @@ -1,7 +1,7 @@ package com.teenthofabud.codingchallenge.sharenow.polling.refresh.converter; -import com.teenthofabud.codingchallenge.sharenow.polling.refresh.model.dto.PositionDTO; -import com.teenthofabud.codingchallenge.sharenow.polling.refresh.model.entity.PositionEntity; +import com.teenthofabud.codingchallenge.sharenow.polling.model.dto.PositionDTO; +import com.teenthofabud.codingchallenge.sharenow.polling.model.entity.PositionEntity; import org.springframework.core.convert.converter.Converter; @FunctionalInterface diff --git a/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/refresh/converter/VehicleDTO2EntityConveter.java b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/refresh/converter/VehicleDTO2EntityConveter.java index 76d5c09..4076f51 100644 --- a/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/refresh/converter/VehicleDTO2EntityConveter.java +++ b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/refresh/converter/VehicleDTO2EntityConveter.java @@ -1,7 +1,7 @@ package com.teenthofabud.codingchallenge.sharenow.polling.refresh.converter; -import com.teenthofabud.codingchallenge.sharenow.polling.refresh.model.dto.VehicleDTO; -import com.teenthofabud.codingchallenge.sharenow.polling.refresh.model.entity.VehicleEntity; +import com.teenthofabud.codingchallenge.sharenow.polling.model.dto.VehicleDTO; +import com.teenthofabud.codingchallenge.sharenow.polling.model.entity.VehicleEntity; import org.springframework.core.convert.converter.Converter; @FunctionalInterface diff --git a/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/refresh/service/VehicleInput.java b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/refresh/service/VehicleInput.java index 8a90e85..2359e84 100644 --- a/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/refresh/service/VehicleInput.java +++ b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/refresh/service/VehicleInput.java @@ -1,6 +1,7 @@ package com.teenthofabud.codingchallenge.sharenow.polling.refresh.service; -import com.teenthofabud.codingchallenge.sharenow.polling.refresh.model.dto.VehicleDTO; + +import com.teenthofabud.codingchallenge.sharenow.polling.model.dto.VehicleDTO; import java.util.List; diff --git a/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/refresh/service/VehicleOutput.java b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/refresh/service/VehicleOutput.java index f04d2de..3156b5d 100644 --- a/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/refresh/service/VehicleOutput.java +++ b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/refresh/service/VehicleOutput.java @@ -1,6 +1,6 @@ package com.teenthofabud.codingchallenge.sharenow.polling.refresh.service; -import com.teenthofabud.codingchallenge.sharenow.polling.refresh.model.entity.VehicleEntity; +import com.teenthofabud.codingchallenge.sharenow.polling.model.entity.VehicleEntity; import java.util.List; diff --git a/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/refresh/service/VehicleProcessor.java b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/refresh/service/VehicleProcessor.java index 1fe7c4e..a7a9cb1 100644 --- a/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/refresh/service/VehicleProcessor.java +++ b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/refresh/service/VehicleProcessor.java @@ -1,7 +1,7 @@ package com.teenthofabud.codingchallenge.sharenow.polling.refresh.service; -import com.teenthofabud.codingchallenge.sharenow.polling.refresh.model.dto.VehicleDTO; -import com.teenthofabud.codingchallenge.sharenow.polling.refresh.model.entity.VehicleEntity; +import com.teenthofabud.codingchallenge.sharenow.polling.model.dto.VehicleDTO; +import com.teenthofabud.codingchallenge.sharenow.polling.model.entity.VehicleEntity; import java.util.List; diff --git a/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/refresh/service/VehicleService.java b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/refresh/service/VehicleService.java index 51d18a4..41391ee 100644 --- a/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/refresh/service/VehicleService.java +++ b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/refresh/service/VehicleService.java @@ -1,6 +1,6 @@ package com.teenthofabud.codingchallenge.sharenow.polling.refresh.service; -import com.teenthofabud.codingchallenge.sharenow.polling.refresh.model.dto.VehicleDTO; +import com.teenthofabud.codingchallenge.sharenow.polling.model.dto.VehicleDTO; import org.springframework.stereotype.Service; import java.util.List; diff --git a/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/refresh/service/impl/Car2GoVehicleRefreshServiceImpl.java b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/refresh/service/impl/Car2GoVehicleRefreshServiceImpl.java index d357de5..a94d523 100644 --- a/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/refresh/service/impl/Car2GoVehicleRefreshServiceImpl.java +++ b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/refresh/service/impl/Car2GoVehicleRefreshServiceImpl.java @@ -2,17 +2,17 @@ import com.teenthofabud.codingchallenge.sharenow.polling.PollingMonitor; +import com.teenthofabud.codingchallenge.sharenow.polling.model.dto.VehicleDTO; +import com.teenthofabud.codingchallenge.sharenow.polling.model.entity.PositionEntity; +import com.teenthofabud.codingchallenge.sharenow.polling.model.entity.VehicleEntity; import com.teenthofabud.codingchallenge.sharenow.polling.refresh.converter.PositionDTO2EntityConveter; import com.teenthofabud.codingchallenge.sharenow.polling.refresh.converter.VehicleDTO2EntityConveter; import com.teenthofabud.codingchallenge.sharenow.polling.filter.RestClientErrorHandler; -import com.teenthofabud.codingchallenge.sharenow.polling.refresh.model.dto.VehicleDTO; -import com.teenthofabud.codingchallenge.sharenow.polling.refresh.model.entity.PositionEntity; -import com.teenthofabud.codingchallenge.sharenow.polling.refresh.model.entity.VehicleEntity; import com.teenthofabud.codingchallenge.sharenow.polling.refresh.service.VehicleInput; import com.teenthofabud.codingchallenge.sharenow.polling.refresh.service.VehicleOutput; import com.teenthofabud.codingchallenge.sharenow.polling.refresh.service.VehicleProcessor; -import com.teenthofabud.codingchallenge.sharenow.polling.refresh.repository.VehicleRepository; import com.teenthofabud.codingchallenge.sharenow.polling.refresh.service.VehicleRefreshService; +import com.teenthofabud.codingchallenge.sharenow.polling.repository.VehicleRepository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -40,6 +40,16 @@ public class Car2GoVehicleRefreshServiceImpl implements VehicleRefreshService { private RestTemplate car2goClient; + private PositionDTO2EntityConveter positionDto2Entity; + + private VehicleDTO2EntityConveter vehicleDto2Entity; + + private VehicleInput input; + + private VehicleProcessor processor; + + private VehicleOutput output; + private int counter; @Value("${ps.refresh.search.location:Stuttgart}") @@ -70,25 +80,7 @@ private void init() { this.car2goClient = new RestTemplate(); this.car2goClient.setErrorHandler(this.errorHandler); this.counter = 0; - } - - - @Override - @Scheduled(cron = "${ps.refresh.cron.expression:*/5 * * * *}") - public void collectLiveVehiclesForConfiguredCity() { - synchronized (monitor) { - VehicleInput vi = vehicleInput(); - List vehicleDTOs = vi.readVehicles(REQUEST_URL); - VehicleProcessor vp = vehicleProcessor(); - List vehicleEntities = vp.processVehicles(vehicleDTOs); - VehicleOutput vo = vehicleOutput(); - boolean status = vo.writeVehicles(vehicleEntities); - LOGGER.info("Vehicle refresh status: {}", status); - } - } - - private VehicleInput vehicleInput() { - VehicleInput vi = (sourceUrl) -> { + this.input = (sourceUrl) -> { List dtoList = new ArrayList<>(); String variableName = "locationName"; Map uriVariables = Collections.singletonMap(variableName, searchLocation); @@ -99,42 +91,34 @@ private VehicleInput vehicleInput() { LOGGER.info("Queried vehicle count: {}", dtoList.size()); return dtoList; }; - return vi; - } - - private VehicleProcessor vehicleProcessor() { - VehicleProcessor vp = (dtoList) -> { - PositionDTO2EntityConveter positionDto2Entity = (dto) -> { - PositionEntity entity = new PositionEntity(); - entity.setLatitude(dto.getLatitude()); - entity.setLongitude(dto.getLongitude()); - return entity; - }; - VehicleDTO2EntityConveter vehicleDto2Entity = (dto) -> { - VehicleEntity entity = new VehicleEntity(); - entity.setFuel(dto.getFuel()); - entity.setId(dto.getId()); - entity.setLocationId(dto.getLocationId()); - entity.setModel(dto.getModel()); - entity.setNumberPlate(dto.getNumberPlate()); - entity.setVin(dto.getVin()); - entity.setPosition(positionDto2Entity.convert(dto.getPosition())); - entity.setUpdatedAt(new Date()); - return entity; - }; + this.positionDto2Entity = (dto) -> { + PositionEntity entity = new PositionEntity(); + entity.setLatitude(dto.getLatitude()); + entity.setLongitude(dto.getLongitude()); + return entity; + }; + this.vehicleDto2Entity = (dto) -> { + VehicleEntity entity = new VehicleEntity(); + entity.setFuel(dto.getFuel()); + entity.setId(dto.getId()); + entity.setLocationId(dto.getLocationId()); + entity.setModel(dto.getModel()); + entity.setNumberPlate(dto.getNumberPlate()); + entity.setVin(dto.getVin()); + entity.setPosition(this.positionDto2Entity.convert(dto.getPosition())); + entity.setUpdatedAt(new Date()); + return entity; + }; + this.processor = (dtoList) -> { List entityList = new ArrayList<>(); for(VehicleDTO dto : dtoList) { - VehicleEntity entity = vehicleDto2Entity.convert(dto); + VehicleEntity entity = this.vehicleDto2Entity.convert(dto); entityList.add(entity); } LOGGER.info("Processed vehicle count: {}", entityList.size()); return entityList; }; - return vp; - } - - private VehicleOutput vehicleOutput() { - VehicleOutput vo = (entityList) -> { + this.output = (entityList) -> { int count = 0; for(VehicleEntity entity : entityList) { if(this.repository.save(entity) != null) { @@ -144,7 +128,18 @@ private VehicleOutput vehicleOutput() { LOGGER.info("Updated vehicle count: {}", count); return count == entityList.size(); }; - return vo; + } + + + @Override + @Scheduled(cron = "${ps.refresh.cron.expression:*/5 * * * *}") + public void collectLiveVehiclesForConfiguredCity() { + synchronized (monitor) { + List vehicleDTOs = this.input.readVehicles(REQUEST_URL); + List vehicleEntities = this.processor.processVehicles(vehicleDTOs); + boolean status = this.output.writeVehicles(vehicleEntities); + LOGGER.info("Vehicle refresh status: {}", status); + } } } diff --git a/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/repository/VehicleRepository.java b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/repository/VehicleRepository.java index 56b94dd..64457e9 100644 --- a/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/repository/VehicleRepository.java +++ b/polling-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/polling/repository/VehicleRepository.java @@ -1,6 +1,6 @@ -package com.teenthofabud.codingchallenge.sharenow.polling.refresh.repository; +package com.teenthofabud.codingchallenge.sharenow.polling.repository; -import com.teenthofabud.codingchallenge.sharenow.polling.refresh.model.entity.VehicleEntity; +import com.teenthofabud.codingchallenge.sharenow.polling.model.entity.VehicleEntity; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Repository; diff --git a/polling-service/src/main/resources/application.properties b/polling-service/src/main/resources/application.properties index 061dfd2..4d9e58f 100644 --- a/polling-service/src/main/resources/application.properties +++ b/polling-service/src/main/resources/application.properties @@ -1,15 +1,14 @@ spring.application.name=polling-service -server.port=18080 +server.port=17080 spring.redis.host=localhost spring.redis.port=6379 ps.refresh.search.location=Stuttgart -ps.refresh.cron.expression=*/2 * * * * * -ps.cleanup.cron.expression=*/20 * * * * * -ps.cleanup.staleness.interval=10 -ps.cleanup.stale.ttl=1 -ps.refresh.bucket.size=5 +ps.refresh.cron.expression=*/20 * * * * * +ps.cleanup.cron.expression=*/90 * * * * * +ps.cleanup.staleness.interval=20 +pps.cleanup.ttl.eviction=3 car2go.base.url=http://192.168.0.119:3000 car2go.vehicles.by.location.uri=/vehicles/{locationName} \ No newline at end of file diff --git a/polling-service/src/test/java/com/teenthofabud/codingchallenge/sharenow/polling/VehicleRefreshServiceTests.java b/polling-service/src/test/java/com/teenthofabud/codingchallenge/sharenow/polling/VehicleRefreshServiceTests.java new file mode 100644 index 0000000..b6fde13 --- /dev/null +++ b/polling-service/src/test/java/com/teenthofabud/codingchallenge/sharenow/polling/VehicleRefreshServiceTests.java @@ -0,0 +1,33 @@ +package com.teenthofabud.codingchallenge.sharenow.polling; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.web.client.RestTemplate; + +@SpringBootTest +public class VehicleRefreshServiceTests { + + @MockBean + private RestTemplate restClient; + + @BeforeEach + public void init() { + this.restClient = new RestTemplate(); + } + + @Test + @DisplayName("Test vehicle search for correct location should respond a vehicle list") + public void testQueryingVehiclesAPI_ForCorrectLocation_ShouldReturnList_WithHttpStatus200() { + + } + + @Test + @DisplayName("Test vehicle search for incorrect location should respond with an error") + public void testQueryingVehiclesAPI_ForIncorrectLocation_ShouldReturnError_WithHttpStatus404() { + + } + +} diff --git a/polling-service/src/test/java/com/teenthofabud/codingchallenge/sharenow/polling/refresh/PositionDTO2EntityConversionTests.java b/polling-service/src/test/java/com/teenthofabud/codingchallenge/sharenow/polling/refresh/PositionDTO2EntityConversionTests.java new file mode 100644 index 0000000..a021bab --- /dev/null +++ b/polling-service/src/test/java/com/teenthofabud/codingchallenge/sharenow/polling/refresh/PositionDTO2EntityConversionTests.java @@ -0,0 +1,34 @@ +package com.teenthofabud.codingchallenge.sharenow.polling.refresh; + +import com.teenthofabud.codingchallenge.sharenow.polling.model.dto.PositionDTO; +import com.teenthofabud.codingchallenge.sharenow.polling.model.entity.PositionEntity; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +public class PositionDTO2EntityConversionTests { + + private static PositionDTO dto; + + private static PositionEntity entity; + + @BeforeAll + private static void init() { + dto = new PositionDTO(); + dto.setLatitude(1.1d); + dto.setLongitude(9.9d); + + entity = new PositionEntity(); + entity.setLatitude(1.1d); + entity.setLongitude(9.9d); + } + + @Test + @DisplayName("Test PositionDTO to PositionEntity Conversion") + public void testConversion_OfPositionDTO_ToPositionEntity() { + + } + +} diff --git a/polling-service/src/test/java/com/teenthofabud/codingchallenge/sharenow/polling/refresh/VehicleInputTests.java b/polling-service/src/test/java/com/teenthofabud/codingchallenge/sharenow/polling/refresh/VehicleInputTests.java new file mode 100644 index 0000000..1261818 --- /dev/null +++ b/polling-service/src/test/java/com/teenthofabud/codingchallenge/sharenow/polling/refresh/VehicleInputTests.java @@ -0,0 +1,33 @@ +package com.teenthofabud.codingchallenge.sharenow.polling.refresh; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.web.client.RestTemplate; + +@SpringBootTest +public class VehicleInputTests { + + @MockBean + private RestTemplate restClient; + + @BeforeEach + public void init() { + this.restClient = new RestTemplate(); + } + + @Test + @DisplayName("Test vehicle search for correct location should respond vehicle list") + public void testQueryingVehiclesAPI_ForCorrectLocation_ShouldReturnList_WithHttpStatus200() { + + } + + @Test + @DisplayName("Test vehicle search for incorrect location should respond with an error") + public void testQueryingVehiclesAPI_ForIncorrectLocation_ShouldReturnError_WithHttpStatus404() { + + } + +} diff --git a/scripts/seeding/seed_geojson_dump.py b/scripts/seeding/seed_geojson_dump.py new file mode 100644 index 0000000..e69de29 diff --git a/vehicle-service/Dockerfile b/vehicle-service/Dockerfile new file mode 100644 index 0000000..93c351d --- /dev/null +++ b/vehicle-service/Dockerfile @@ -0,0 +1,5 @@ +FROM adoptopenjdk:11-jre-hotspot-focal +MAINTAINER Anirban Das +RUN mkdip ~p /opt/sharenow-coding-challenge +COPY target/vehicle-app.jar /opt/sharenow-coding-challenge/vehicle-app.jar +CMD ["java","-jar","/opt/sharenow-coding-challenge/vehicle-app.jar"] \ No newline at end of file diff --git a/vehicle-service/pom.xml b/vehicle-service/pom.xml new file mode 100644 index 0000000..966bf97 --- /dev/null +++ b/vehicle-service/pom.xml @@ -0,0 +1,71 @@ + + + 4.0.0 + + com.teenthofabud.codingchallenge.sharenow + sharenow-coding-challenge + 1.0-SNAPSHOT + + + vehicle-service + 1.0.0-SNAPSHOT + jar + vehicle-service + + + 11 + + + + vehicle-app + + + com.spotify + dockerfile-maven-plugin + 1.4.13 + + + default + + build + push + + + + + teenthofabud/sharenow-coding-challenge-${project.artifactId} + ${project.version} + + target/${project.build.finalName}.jar + + + + + + + + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-client + + + org.springframework.boot + spring-boot-starter-data-redis + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + org.springframework.cloud + spring-cloud-starter-sleuth + + + org.springframework.cloud + spring-cloud-sleuth-zipkin + + + + \ No newline at end of file diff --git a/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/VehicleServiceApplication.java b/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/VehicleServiceApplication.java new file mode 100644 index 0000000..46e4339 --- /dev/null +++ b/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/VehicleServiceApplication.java @@ -0,0 +1,15 @@ +package com.teenthofabud.codingchallenge.sharenow.vehicle; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.netflix.eureka.EnableEurekaClient; + +@SpringBootApplication +@EnableEurekaClient +public class VehicleServiceApplication { + + public static void main(String[] args) { + SpringApplication.run(VehicleServiceApplication.class, args); + } + +} diff --git a/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/configuration/VehicleServiceConfiguration.java b/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/configuration/VehicleServiceConfiguration.java new file mode 100644 index 0000000..ffc02ac --- /dev/null +++ b/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/configuration/VehicleServiceConfiguration.java @@ -0,0 +1,30 @@ +package com.teenthofabud.codingchallenge.sharenow.vehicle.configuration; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.ResourceBundleMessageSource; +import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; +import org.springframework.web.servlet.LocaleResolver; +import org.springframework.web.servlet.i18n.SessionLocaleResolver; + +import java.util.Locale; + +@Configuration +@EnableRedisRepositories +public class VehicleServiceConfiguration { + + @Bean + public LocaleResolver localeResolver() { + SessionLocaleResolver localeResolver = new SessionLocaleResolver(); + localeResolver.setDefaultLocale(Locale.US); + return localeResolver; + } + + @Bean + public ResourceBundleMessageSource bundleMessageSource() { + ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); + messageSource.setBasename("messages"); + return messageSource; + } + +} diff --git a/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/controller/VehicleController.java b/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/controller/VehicleController.java new file mode 100644 index 0000000..47bfc51 --- /dev/null +++ b/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/controller/VehicleController.java @@ -0,0 +1,47 @@ +package com.teenthofabud.codingchallenge.sharenow.vehicle.controller; + +import com.teenthofabud.codingchallenge.sharenow.vehicle.model.error.VehicleErrorCode; +import com.teenthofabud.codingchallenge.sharenow.vehicle.model.error.VehicleServiceException; +import com.teenthofabud.codingchallenge.sharenow.vehicle.model.vo.VehicleDetailsVO; +import com.teenthofabud.codingchallenge.sharenow.vehicle.model.vo.VehicleVO; +import com.teenthofabud.codingchallenge.sharenow.vehicle.service.VehicleService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequestMapping("vehicle") +public class VehicleController { + + private static final Logger LOGGER = LoggerFactory.getLogger(VehicleController.class); + + @Autowired + private VehicleService service; + + @GetMapping("vin/{vin}") + public ResponseEntity getVehicleByVin(@PathVariable String vin) throws VehicleServiceException{ + if(StringUtils.hasText(vin)) { + VehicleDetailsVO vo = this.service.retrieveVehicleDetailsByVin(vin); + ResponseEntity response = ResponseEntity.ok(vo); + return response; + } else { + throw new VehicleServiceException("", VehicleErrorCode.INVALID_PARAMETER, new Object[] {"vin"}); + } + } + + @GetMapping + public ResponseEntity getAllVehicles() throws VehicleServiceException{ + List voList = this.service.retrieveAllVehicles(); + ResponseEntity> response = ResponseEntity.ok(voList); + return response; + } + +} diff --git a/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/converter/PositionEntity2VOConverter.java b/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/converter/PositionEntity2VOConverter.java new file mode 100644 index 0000000..381b0f3 --- /dev/null +++ b/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/converter/PositionEntity2VOConverter.java @@ -0,0 +1,12 @@ +package com.teenthofabud.codingchallenge.sharenow.vehicle.converter; + +import com.teenthofabud.codingchallenge.sharenow.vehicle.model.entity.PositionEntity; +import com.teenthofabud.codingchallenge.sharenow.vehicle.model.vo.PositionVO; +import org.springframework.core.convert.converter.Converter; + +@FunctionalInterface +public interface PositionEntity2VOConverter extends Converter { + + public PositionVO convert(PositionEntity entity); + +} diff --git a/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/converter/VehicleEntity2DetailedVOConverter.java b/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/converter/VehicleEntity2DetailedVOConverter.java new file mode 100644 index 0000000..1ede1be --- /dev/null +++ b/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/converter/VehicleEntity2DetailedVOConverter.java @@ -0,0 +1,12 @@ +package com.teenthofabud.codingchallenge.sharenow.vehicle.converter; + +import com.teenthofabud.codingchallenge.sharenow.vehicle.model.entity.VehicleEntity; +import com.teenthofabud.codingchallenge.sharenow.vehicle.model.vo.VehicleDetailsVO; +import org.springframework.core.convert.converter.Converter; + +@FunctionalInterface +public interface VehicleEntity2DetailedVOConverter extends Converter { + + public VehicleDetailsVO convert(VehicleEntity entity); + +} diff --git a/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/converter/VehicleEntity2VOConverter.java b/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/converter/VehicleEntity2VOConverter.java new file mode 100644 index 0000000..48a2003 --- /dev/null +++ b/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/converter/VehicleEntity2VOConverter.java @@ -0,0 +1,12 @@ +package com.teenthofabud.codingchallenge.sharenow.vehicle.converter; + +import com.teenthofabud.codingchallenge.sharenow.vehicle.model.entity.VehicleEntity; +import com.teenthofabud.codingchallenge.sharenow.vehicle.model.vo.VehicleVO; +import org.springframework.core.convert.converter.Converter; + +@FunctionalInterface +public interface VehicleEntity2VOConverter extends Converter { + + public VehicleVO convert(VehicleEntity entity); + +} diff --git a/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/filter/RestErrorHandler.java b/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/filter/RestErrorHandler.java new file mode 100644 index 0000000..a98fee2 --- /dev/null +++ b/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/filter/RestErrorHandler.java @@ -0,0 +1,35 @@ +package com.teenthofabud.codingchallenge.sharenow.vehicle.filter; + +import com.teenthofabud.codingchallenge.sharenow.vehicle.model.error.VehicleServiceException; +import com.teenthofabud.codingchallenge.sharenow.vehicle.model.vo.ErrorVO; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.MessageSource; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +import java.util.Locale; + +@ControllerAdvice +public class RestErrorHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(RestErrorHandler.class); + + @Autowired + private MessageSource messageSource; + + @ExceptionHandler(VehicleServiceException.class) + public ResponseEntity handleVehicleServiceException(VehicleServiceException vsex) { + LOGGER.error("Error encountered: {}", vsex); + ErrorVO vo = new ErrorVO(); + String msg = messageSource.getMessage(vsex.getError().getErrorCode(), null, Locale.US); + msg = String.format(msg, vsex.getParams()); + vo.setErrorCode(vsex.getError().getErrorCode()); + vo.setErrorMessage(msg); + ResponseEntity response = ResponseEntity.status(vsex.getError().getStatusCode()).body(vo); + return response; + } + +} diff --git a/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/model/entity/PositionEntity.java b/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/model/entity/PositionEntity.java new file mode 100644 index 0000000..3f30267 --- /dev/null +++ b/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/model/entity/PositionEntity.java @@ -0,0 +1,19 @@ +package com.teenthofabud.codingchallenge.sharenow.vehicle.model.entity; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +import java.io.Serializable; + +@Getter +@Setter +@ToString +public class PositionEntity implements Serializable { + + @ToString.Include + private double latitude; + @ToString.Include + private double longitude; + +} diff --git a/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/model/entity/VehicleEntity.java b/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/model/entity/VehicleEntity.java new file mode 100644 index 0000000..1df2f22 --- /dev/null +++ b/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/model/entity/VehicleEntity.java @@ -0,0 +1,42 @@ +package com.teenthofabud.codingchallenge.sharenow.vehicle.model.entity; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import org.springframework.data.annotation.Id; +import org.springframework.data.annotation.TypeAlias; +import org.springframework.data.redis.core.RedisHash; +import org.springframework.data.redis.core.index.Indexed; + +import java.io.Serializable; +import java.util.Date; + +@Getter +@Setter +@ToString +@RedisHash("Vehicle") +@TypeAlias("Vehicle") +@NoArgsConstructor +public class VehicleEntity implements Serializable { + + @Id + private int id; + private int locationId; + @Indexed + private String vin; + private String numberPlate; + private PositionEntity position; + private float fuel; + private String model; + private Date updatedAt; + + public String getCacheKey() { + return "Vehicle:" + id; + } + + public VehicleEntity(String vin) { + this.vin = vin; + } + +} diff --git a/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/model/error/VehicleErrorCode.java b/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/model/error/VehicleErrorCode.java new file mode 100644 index 0000000..32e9c18 --- /dev/null +++ b/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/model/error/VehicleErrorCode.java @@ -0,0 +1,23 @@ +package com.teenthofabud.codingchallenge.sharenow.vehicle.model.error; + +import lombok.Getter; +import lombok.ToString; + +@Getter +@ToString +public enum VehicleErrorCode { + + NOT_FOUND("SNCC-VS-001", 404), + INVALID_PARAMETER("SNCC-VS-002", 400); + @ToString.Include + private int statusCode; + @ToString.Include + private String errorCode; + + private VehicleErrorCode(String errorCode, int statusCode) { + this.errorCode = errorCode; + this.statusCode = statusCode; + } + + +} diff --git a/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/model/error/VehicleServiceException.java b/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/model/error/VehicleServiceException.java new file mode 100644 index 0000000..808c832 --- /dev/null +++ b/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/model/error/VehicleServiceException.java @@ -0,0 +1,33 @@ +package com.teenthofabud.codingchallenge.sharenow.vehicle.model.error; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class VehicleServiceException extends Exception { + + private VehicleErrorCode error; + private String message; + private Object[] params; + + public VehicleServiceException(String message) { + super(message); + this.message = message; + } + + public VehicleServiceException(String message, Object[] params) { + super(message); + this.message = message; + this.params = params; + } + + public VehicleServiceException(String message, VehicleErrorCode error, Object[] params) { + super(message); + this.message = message; + this.error = error; + this.params = params; + } + + +} diff --git a/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/model/vo/ErrorVO.java b/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/model/vo/ErrorVO.java new file mode 100644 index 0000000..1ed4713 --- /dev/null +++ b/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/model/vo/ErrorVO.java @@ -0,0 +1,17 @@ +package com.teenthofabud.codingchallenge.sharenow.vehicle.model.vo; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class ErrorVO { + + private String errorCode; + private String errorMessage; + +} diff --git a/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/model/vo/PositionVO.java b/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/model/vo/PositionVO.java new file mode 100644 index 0000000..25e1adc --- /dev/null +++ b/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/model/vo/PositionVO.java @@ -0,0 +1,13 @@ +package com.teenthofabud.codingchallenge.sharenow.vehicle.model.vo; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class PositionVO { + + private double latitude; + private double longitude; + +} diff --git a/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/model/vo/VehicleDetailsVO.java b/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/model/vo/VehicleDetailsVO.java new file mode 100644 index 0000000..774e57f --- /dev/null +++ b/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/model/vo/VehicleDetailsVO.java @@ -0,0 +1,18 @@ +package com.teenthofabud.codingchallenge.sharenow.vehicle.model.vo; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class VehicleDetailsVO { + + private int id; + private int locationId; + private String vin; + private String numberPlate; + private PositionVO position; + private float fuel; + private String model; + +} diff --git a/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/model/vo/VehicleVO.java b/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/model/vo/VehicleVO.java new file mode 100644 index 0000000..3c827a4 --- /dev/null +++ b/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/model/vo/VehicleVO.java @@ -0,0 +1,15 @@ +package com.teenthofabud.codingchallenge.sharenow.vehicle.model.vo; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class VehicleVO { + + private int id; + private String vin; + private String numberPlate; + private int locationId; + +} diff --git a/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/repository/VehicleRepository.java b/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/repository/VehicleRepository.java new file mode 100644 index 0000000..e1088c8 --- /dev/null +++ b/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/repository/VehicleRepository.java @@ -0,0 +1,14 @@ +package com.teenthofabud.codingchallenge.sharenow.vehicle.repository; + +import com.teenthofabud.codingchallenge.sharenow.vehicle.model.entity.VehicleEntity; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface VehicleRepository extends CrudRepository { + + public List findByVin(String vin); + +} diff --git a/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/service/VehicleService.java b/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/service/VehicleService.java new file mode 100644 index 0000000..97335ad --- /dev/null +++ b/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/service/VehicleService.java @@ -0,0 +1,17 @@ +package com.teenthofabud.codingchallenge.sharenow.vehicle.service; + +import com.teenthofabud.codingchallenge.sharenow.vehicle.model.error.VehicleServiceException; +import com.teenthofabud.codingchallenge.sharenow.vehicle.model.vo.VehicleDetailsVO; +import com.teenthofabud.codingchallenge.sharenow.vehicle.model.vo.VehicleVO; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public interface VehicleService { + + public List retrieveAllVehicles(); + + public VehicleDetailsVO retrieveVehicleDetailsByVin(String vin) throws VehicleServiceException; + +} diff --git a/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/service/impl/VehicleServiceImpl.java b/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/service/impl/VehicleServiceImpl.java new file mode 100644 index 0000000..557fb5b --- /dev/null +++ b/vehicle-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/vehicle/service/impl/VehicleServiceImpl.java @@ -0,0 +1,111 @@ +package com.teenthofabud.codingchallenge.sharenow.vehicle.service.impl; + +import com.teenthofabud.codingchallenge.sharenow.vehicle.converter.PositionEntity2VOConverter; +import com.teenthofabud.codingchallenge.sharenow.vehicle.converter.VehicleEntity2DetailedVOConverter; +import com.teenthofabud.codingchallenge.sharenow.vehicle.converter.VehicleEntity2VOConverter; +import com.teenthofabud.codingchallenge.sharenow.vehicle.model.entity.VehicleEntity; +import com.teenthofabud.codingchallenge.sharenow.vehicle.model.error.VehicleErrorCode; +import com.teenthofabud.codingchallenge.sharenow.vehicle.model.error.VehicleServiceException; +import com.teenthofabud.codingchallenge.sharenow.vehicle.model.vo.PositionVO; +import com.teenthofabud.codingchallenge.sharenow.vehicle.model.vo.VehicleDetailsVO; +import com.teenthofabud.codingchallenge.sharenow.vehicle.model.vo.VehicleVO; +import com.teenthofabud.codingchallenge.sharenow.vehicle.repository.VehicleRepository; +import com.teenthofabud.codingchallenge.sharenow.vehicle.service.VehicleService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import javax.annotation.PostConstruct; +import java.util.*; + +@Component +public class VehicleServiceImpl implements VehicleService { + + private static final Logger LOGGER = LoggerFactory.getLogger(VehicleServiceImpl.class); + + @Autowired + private VehicleRepository repository; + + private PositionEntity2VOConverter positionConverter; + + private VehicleEntity2VOConverter simpleVOConverter; + + private VehicleEntity2DetailedVOConverter complexVOConverter; + + private Comparator cmpVehicleByVin; + + @PostConstruct + private void init() { + this.positionConverter = (entity) -> { + PositionVO vo = new PositionVO(); + if(entity != null) { + vo.setLatitude(entity.getLatitude()); + vo.setLongitude(entity.getLongitude()); + } + return vo; + }; + this.simpleVOConverter = (entity) -> { + VehicleVO vo = new VehicleVO(); + if(entity != null) { + vo.setId(entity.getId()); + vo.setNumberPlate(entity.getNumberPlate()); + vo.setVin(entity.getVin()); + vo.setLocationId(entity.getLocationId()); + } + return vo; + }; + this.complexVOConverter = (entity) -> { + VehicleDetailsVO vo = new VehicleDetailsVO(); + if(entity != null) { + vo.setId(entity.getId()); + vo.setNumberPlate(entity.getNumberPlate()); + vo.setVin(entity.getVin()); + vo.setFuel(entity.getFuel()); + vo.setLocationId(entity.getLocationId()); + vo.setModel(entity.getModel()); + vo.setPosition(positionConverter.convert(entity.getPosition())); + } + return vo; + }; + this.cmpVehicleByVin = (v1, v2) -> { + return v1.getVin().compareTo(v2.getVin()); + }; + } + + @Override + public List retrieveAllVehicles() { + List vehicleVOList = new ArrayList<>(); + Iterable entityItr = this.repository.findAll(); + for(VehicleEntity entity : entityItr) { + VehicleVO vo = this.simpleVOConverter.convert(entity); + vehicleVOList.add(vo); + } + return vehicleVOList; + } + + @Override + public VehicleDetailsVO retrieveVehicleDetailsByVin(String vin) throws VehicleServiceException { + if(StringUtils.hasText(vin)) { + Iterable entityItr = this.repository.findAll(); + List entityList = new ArrayList<>(); + for(VehicleEntity entity : entityItr) { + entityList.add(entity); + } + Collections.sort(entityList, cmpVehicleByVin); + int idx = Collections.binarySearch(entityList, new VehicleEntity(vin), cmpVehicleByVin); + //List entityList = this.repository.findByVin(vin); + //if(entityList != null && entityList.size() == 1) + if(idx >= 0) { + VehicleEntity entity = entityList.get(idx); + VehicleDetailsVO vo = this.complexVOConverter.convert(entity); + return vo; + } else { + throw new VehicleServiceException("", VehicleErrorCode.NOT_FOUND, new Object[] {"vin", vin}); + } + } else { + throw new VehicleServiceException("", VehicleErrorCode.INVALID_PARAMETER, new Object[] {"vin"}); + } + } +} diff --git a/vehicle-service/src/main/resources/application.properties b/vehicle-service/src/main/resources/application.properties new file mode 100644 index 0000000..f93544d --- /dev/null +++ b/vehicle-service/src/main/resources/application.properties @@ -0,0 +1,5 @@ +spring.application.name=vehicle-service +server.port=18080 + +spring.redis.host=localhost +spring.redis.port=6379 \ No newline at end of file diff --git a/vehicle-service/src/main/resources/messages.properties b/vehicle-service/src/main/resources/messages.properties new file mode 100644 index 0000000..96249d7 --- /dev/null +++ b/vehicle-service/src/main/resources/messages.properties @@ -0,0 +1,2 @@ +SNCC-VS-001=%s: %s not available +SNCC-VS-002=%s is not valid \ No newline at end of file