diff --git a/.gitignore b/.gitignore
index 0eb99f1..d954bce 100644
--- a/.gitignore
+++ b/.gitignore
@@ -110,6 +110,8 @@ cmake-build-*/
# File-based project format
*.iws
+*.iml
+*.ipr
# IntelliJ
out/
diff --git a/ANSWERS.md b/ANSWERS.md
index 9cd3f0d..9b5130e 100644
--- a/ANSWERS.md
+++ b/ANSWERS.md
@@ -2,10 +2,96 @@
## A - The entities
+There will be 5 entities in the class diagram to represent the ford-example.xml. However
+I added a 6th entity called CarBrand that will allow any company such as Ford, Chevy, Audi, BMW,
+etc to post XML data to the REST service we will create. Here are the entities:
+
+### 1. CarBrand
+CarBrand will contain the name of a vendor and the catalogue. CarBrand will have a One-to-One
+mapping with Catalogue. CarBrand will allow us to search for a Model list based on brand name.
+
+### 2. Catalogue
+Catalogue will contain a list of Model. Catalogue will have a unidirectional One-to-Many relationship with Model
+
+### 3. Model
+Model will contain an Engine, Wheels, SubModel. Model will have a One-to-One relationship with Engine, Wheels and SubModel.
+
+### 4. Engine
+Engine will contain 2 fields, power and type. Engine will have a One-to-One relationship with
+Model.
+
+### 5. SubModels
+SubModel will contain a list of Models. SubModel will have a One-to-One relationship with the
+Model that contains it. SubModel has a One-to-Many relationship with the Model list it contains.
+
+### 6. Wheels
+Wheels will contain 2 fields, size and type. Wheels will have a One-to-One relationship with
+Model.
+
+
## B - Ingest the data
+#### 1. To read XML file from ingester I used the XStream library which I find very useful and easy to use to read
+serialize XML strings to POJOs. I also created DTOs (Data Transfer Objects) to match every entity. The reason
+for introducing DTO is simple. We don't want to expose entities to our RestController, instead
+our RestController will only know about DTOs.
+#### 2. List of DTOs:
+CarBrandDto, CalalogueDto, ModelDto, SubModelDto, EngineDto and WheelsDto
+Repositories were implemented to persist all entities.
+#### 3. The list of repositories are:
+CarBrandRepository, CalalogueRepository, ModelRepository, EngineRepository, WheelsRepository
+and SubModelRepository
+#### 4. Two helper classed ConverterHelper and XStreamHelper were implemented. ConverterHelper to help with converting between entities and DTOs XStreamHelper to help with reading and writing XML.
+#### 5. Added an exception CarBrandNotFoundException that will be thrown if the ingester finds that a brand already exists in the database. A brand name is unique and thus a check has to be done to make sure a row does not exist in the database.
+
+
## C - Expose data with a RESTful API
+#### 1. Add a REST Controller CarBrandController to get model by ID and get models by brand
+#### 2. Added and interface ICarBrandService and its implementation service CarBrandService.
+#### 3. Added postman_get.model_by_id.png and postman_get.models_by_brand.png to display JSON response from
+the 2 endpoints.
+#### 4. Added a helper class JacksonHelper to pretty print JSON in the logs
+#### 5. Endpoints exposed: GET /cars/{modelId} and GET /cars/byBrand/{name}
+
+
## D - Adding images
-## E - Improvements
\ No newline at end of file
+#### 1 To add images for any model I would expose an upload REST controller to consume a modeId and a MultiPart file. A service carBrandService could then find the model based on a modelId and process
+the Multipart files and store the contents in byte format on the model entity so the image can be stored in a database or preferable
+in a document store such as AWS S3.
+
+#### 2. Add LOB to Model entity class:
+
+@Lob
+@Column(name="model_image")
+private byte[] modelImage;
+
+#### 3. Create an REST ImageController
+
+@GetMapping(value = "cars/upload/model/{id}" , consumes="Multipart/formdata")
+public String uploadModelImage(@PathVariable Long modelId, @RequestParam("image") MultipartFile multipartFile) throws IOException {
+String fileName = StringUtils.cleanPath(multipartFile.getOriginalFilename());
+ModelDto modelDto = carBrandService.getModel(modelId);
+modelDto.setFileName(fileName);
+modelDto.setModelImage(multipartFile);
+return new ResponseEntity<>(carBrandService.saveModelImage(modelDto), HttpStatus.OK);
+}
+
+#### 4. CarBrandService would process multipart file and extract contents as bytes and persist using ModelRepository
+
+#### 5. If a user request an endpoint from part C then I would get send the model JSON response and REDIRECT to a download image endpoint to send the image file to the clients machine.
+
+## E - Improvements
+
+#### 1. Creating the CarBrand entity is an improvement because it allow us to store catalogues for any vendor as well as allow us to query by brand.
+
+#### 2. Converting the ingester task to consume JSON instead of XML would improve performance for large files. It would also mean the file size would be smaller for JSON vs XML and that would matter if we intend to also store the files we get from vendors in a document store such as AWS S3.
+
+#### 3. Engine and Wheels entities could be embedded into the Model entity using @Embedded and this would simplify the amount of code we have to write.
+
+#### 4. CarBrand entity can be elimminate as well if we add a brandName filed or attribute on the Catalogue entity. This would simplify the code.
+
+#### 5. Writing unit and integration test for the IngesterTask and the CarBrandService and making sure they scale under load would improve the service.
+
+#### 6. Running the code through Sonar and CheckStyle would help eliminate code smells before the code gets to QA/Prod etc
\ No newline at end of file
diff --git a/cars/.gitignore b/cars/.gitignore
index 153c933..d954bce 100644
--- a/cars/.gitignore
+++ b/cars/.gitignore
@@ -1,29 +1,267 @@
-HELP.md
-/target/
-!.mvn/wrapper/maven-wrapper.jar
+### Eclipse ###
+.metadata
+bin/
+tmp/
+*.tmp
+*.bak
+*.swp
+*~.nib
+local.properties
+.settings/
+.loadpath
+.recommenders
-### STS ###
-.apt_generated
-.classpath
+# External tool builders
+.externalToolBuilders/
+
+# Locally stored "Eclipse launch configurations"
+*.launch
+
+# PyDev specific (Python IDE for Eclipse)
+*.pydevproject
+
+# CDT-specific (C/C++ Development Tooling)
+.cproject
+
+# CDT- autotools
+.autotools
+
+# Java annotation processor (APT)
.factorypath
-.project
-.settings
+
+# PDT-specific (PHP Development Tools)
+.buildpath
+
+# sbteclipse plugin
+.target
+
+# Tern plugin
+.tern-project
+
+# TeXlipse plugin
+.texlipse
+
+# STS (Spring Tool Suite)
.springBeans
-.sts4-cache
-### IntelliJ IDEA ###
-.idea
+# Code Recommenders
+.recommenders/
+
+# Annotation Processing
+.apt_generated/
+
+# Scala IDE specific (Scala & Java development for Eclipse)
+.cache-main
+.scala_dependencies
+.worksheet
+
+### Eclipse Patch ###
+# Eclipse Core
+.project
+
+# JDT-specific (Eclipse Java Development Tools)
+.classpath
+
+# Annotation Processing
+.apt_generated
+
+.sts4-cache/
+
+### Intellij ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
*.iws
*.iml
*.ipr
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+# JetBrains templates
+**___jb_tmp___
+
+### Intellij Patch ###
+# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
+
+# *.iml
+# modules.xml
+# .idea/misc.xml
+# *.ipr
+
+# Sonarlint plugin
+.idea/sonarlint
+
+### Java ###
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+### Linux ###
+*~
+
+# temporary files which can be created if a process still has a handle open of a deleted file
+.fuse_hidden*
+
+# KDE directory preferences
+.directory
+
+# Linux trash folder which might appear on any partition or disk
+.Trash-*
+
+# .nfs files are created when an open file is removed but is still being accessed
+.nfs*
+
+### Maven ###
+target/
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+pom.xml.next
+release.properties
+dependency-reduced-pom.xml
+buildNumber.properties
+.mvn/timing.properties
+.mvn/wrapper/maven-wrapper.jar
+
### NetBeans ###
-/nbproject/private/
-/nbbuild/
-/dist/
-/nbdist/
-/.nb-gradle/
-/build/
-
-### VS Code ###
-.vscode/
+**/nbproject/private/
+**/nbproject/Makefile-*.mk
+**/nbproject/Package-*.bash
+build/
+nbbuild/
+dist/
+nbdist/
+.nb-gradle/
+
+### OSX ###
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+### Windows ###
+# Windows thumbnail cache files
+Thumbs.db
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
diff --git a/cars/pom.xml b/cars/pom.xml
index 43b1e4c..e38d7a0 100644
--- a/cars/pom.xml
+++ b/cars/pom.xml
@@ -17,6 +17,16 @@
1.8
+ 3.9
+ 4.4
+ 1.4.9
+ 5.3.2
+ 5.2.12.Final
+ 6.0.7.Final
+ 3.0.0
+ 1.0.0.Final
+ 1.0.2
+ 2.9.9
@@ -32,7 +42,11 @@
org.springframework.boot
spring-boot-starter-hateoas
-
+
+ javax.persistence
+ javax.persistence-api
+ 2.2
+
com.h2database
h2
@@ -43,10 +57,87 @@
lombok
true
-
+
+ org.apache.commons
+ commons-lang3
+ ${commons.lang3.version}
+
+
+ org.apache.commons
+ commons-collections4
+ ${commons.collections4.version}
+
+
+ org.hibernate
+ hibernate-entitymanager
+ ${hibernate.version}
+
+
+ org.hibernate
+ hibernate-jpamodelgen
+ ${hibernate.version}
+
+
+ org.hibernate.validator
+ hibernate-validator
+ ${hibernate.validator.version}
+
+
+ org.hibernate.javax.persistence
+ hibernate-jpa-2.1-api
+ ${hibernate.jpa.api.version}
+
+
+ javax.persistence
+ persistence-api
+ ${javax.persistence.version}
+
+
+ com.thoughtworks.xstream
+ xstream
+ ${xstream.version}
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+ ${jackson.version}
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ ${jackson.version}
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-hibernate5
+ ${jackson.version}
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jdk8
+ ${jackson.version}
+
org.springframework.boot
spring-boot-starter-test
+
+
+ junit
+ junit
+
+
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ ${junit-jupiter}
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ ${junit-jupiter}
test
@@ -56,8 +147,19 @@
org.springframework.boot
spring-boot-maven-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+
+ maven-surefire-plugin
+ 2.22.0
-
diff --git a/cars/src/main/java/com/mooveit/cars/controller/CarBrandController.java b/cars/src/main/java/com/mooveit/cars/controller/CarBrandController.java
new file mode 100644
index 0000000..1526a55
--- /dev/null
+++ b/cars/src/main/java/com/mooveit/cars/controller/CarBrandController.java
@@ -0,0 +1,41 @@
+package com.mooveit.cars.controller;
+
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.mooveit.cars.dto.ModelDto;
+import com.mooveit.cars.service.ICarBrandService;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@RestController
+public class CarBrandController {
+
+ private final ICarBrandService carModelService;
+
+ @Autowired
+ public CarBrandController(ICarBrandService carModelService) {
+ this.carModelService = carModelService;
+ }
+
+ @GetMapping(value = "/cars/{modelId}", produces = MediaType.APPLICATION_JSON_VALUE)
+ ResponseEntity getModel(@PathVariable Long modelId) {
+ log.debug("@@@@@ Entering 'REST::getModel' method with modelId param -> {}", modelId);
+ return new ResponseEntity<>(carModelService.getModel(modelId), HttpStatus.OK);
+ }
+
+ @GetMapping(value = "/cars/byBrand/{name}", produces = MediaType.APPLICATION_JSON_VALUE)
+ ResponseEntity>getModelByBrand(@PathVariable String name) {
+ log.debug("@@@@@ Entering 'REST::getModelByBrand' method with name param -> {}", name);
+ return new ResponseEntity<>(carModelService.getModelByBrand(name), HttpStatus.OK);
+ }
+
+}
diff --git a/cars/src/main/java/com/mooveit/cars/domain/CarBrand.java b/cars/src/main/java/com/mooveit/cars/domain/CarBrand.java
new file mode 100644
index 0000000..bc51ffe
--- /dev/null
+++ b/cars/src/main/java/com/mooveit/cars/domain/CarBrand.java
@@ -0,0 +1,45 @@
+package com.mooveit.cars.domain;
+
+import java.io.Serializable;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+
+import lombok.Data;
+
+@Data
+@Entity
+@Table(name = "car_brand")
+public class CarBrand implements Serializable {
+ private static final long serialVersionUID = 5719261850801444936L;
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private Long id;
+
+ @Column(name = "name", unique = true, nullable = false)
+ private String name;
+
+ @OneToOne(cascade = CascadeType.ALL)
+ @JoinColumn(name = "fk_catalogue_id")
+ private Catalogue catalogue;
+
+ public CarBrand() {
+ }
+
+ public CarBrand(String name, Catalogue catalogue) {
+ this.name = name;
+ this.catalogue = catalogue;
+ }
+
+ @Override
+ public String toString() {
+ return "CarBrand{" + "id=" + id + ", name='" + name + '\'' + ", catalogue=" + catalogue + '}';
+ }
+}
diff --git a/cars/src/main/java/com/mooveit/cars/domain/Catalogue.java b/cars/src/main/java/com/mooveit/cars/domain/Catalogue.java
new file mode 100644
index 0000000..05b7dff
--- /dev/null
+++ b/cars/src/main/java/com/mooveit/cars/domain/Catalogue.java
@@ -0,0 +1,53 @@
+package com.mooveit.cars.domain;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+
+import lombok.Data;
+
+@Data
+@Entity
+@Table(name = "catalogue")
+public class Catalogue implements Serializable {
+ private static final long serialVersionUID = -4692794045742673878L;
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private Long id;
+
+ @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
+ @JoinColumn(name = "fr_catalogue_id", referencedColumnName = "id")
+ private List models = new ArrayList<>();
+
+ public Catalogue() {
+ }
+
+ public Catalogue(List models) {
+ if (Objects.isNull(models)) {
+ this.models = new ArrayList<>();
+ } else {
+ this.models = models;
+ }
+ }
+
+ public void addModel(Model model) {
+ this.models.add(model);
+ }
+
+ @Override
+ public String toString() {
+ return "Catalogue{" + "models=" + models + '}';
+ }
+
+}
diff --git a/cars/src/main/java/com/mooveit/cars/domain/Engine.java b/cars/src/main/java/com/mooveit/cars/domain/Engine.java
new file mode 100644
index 0000000..f3a0a14
--- /dev/null
+++ b/cars/src/main/java/com/mooveit/cars/domain/Engine.java
@@ -0,0 +1,43 @@
+package com.mooveit.cars.domain;
+
+import java.io.Serializable;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.validation.constraints.NotNull;
+
+import lombok.Data;
+
+@Data //generate getters/setters automatically
+@Entity
+@Table(name = "engines")
+public class Engine implements Serializable {
+ private static final long serialVersionUID = 214035518018246381L;
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private Long id;
+ @NotNull
+ @Column(name = "power")
+ private String power;
+ @NotNull
+ @Column(name = "type")
+ private String type;
+
+ public Engine() {
+ // Hibernate requires a no-arg constructor
+ }
+
+ public Engine(String power, String type) {
+ this.power = power;
+ this.type = type;
+ }
+
+ @Override
+ public String toString() {
+ return "Engine{" + "power='" + power + '\'' + ", type='" + type + '\'' + '}';
+ }
+}
diff --git a/cars/src/main/java/com/mooveit/cars/domain/Model.java b/cars/src/main/java/com/mooveit/cars/domain/Model.java
new file mode 100644
index 0000000..212b3bc
--- /dev/null
+++ b/cars/src/main/java/com/mooveit/cars/domain/Model.java
@@ -0,0 +1,58 @@
+package com.mooveit.cars.domain;
+
+import java.io.Serializable;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+
+import lombok.Data;
+
+@Data
+@Entity
+@Table(name = "models")
+public class Model implements Serializable {
+ private static final long serialVersionUID = 4478693618673332244L;
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private Long id;
+ @Column(name = "name")
+ private String name;
+ @Column(name = "from_year")
+ private String from;
+ @Column(name = "to_year")
+ private String to;
+ @Column(name = "type")
+ private String type;
+ @Column(name = "line")
+ private String line;
+ @OneToOne(cascade = CascadeType.ALL)
+ @JoinColumn(name = "fk_engine_id")
+ private Engine engine;
+ @OneToOne(cascade = CascadeType.ALL)
+ @JoinColumn(name = "fk_wheels_id")
+ private Wheels wheels;
+ @ManyToOne
+ @JoinColumn(name = "fr_sub_models_id")
+ private SubModels subModels;
+
+ public Model() {
+ // Hibernate requires a no-arg constructor
+ }
+
+ public Model(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String toString() {
+ return "Model{" + "id=" + id + ", name='" + name + '\'' + ", from='" + from + '\'' + ", to='" + to + '\'' + ", type='" + type + '\'' + ", line='" + line + '\'' + ", engine=" + engine + ", wheel=" + wheels + ", subModels=" + subModels + '}';
+ }
+}
diff --git a/cars/src/main/java/com/mooveit/cars/domain/SubModels.java b/cars/src/main/java/com/mooveit/cars/domain/SubModels.java
new file mode 100644
index 0000000..40d62c9
--- /dev/null
+++ b/cars/src/main/java/com/mooveit/cars/domain/SubModels.java
@@ -0,0 +1,45 @@
+package com.mooveit.cars.domain;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+
+import lombok.Data;
+
+@Data
+@Entity
+@Table(name = "sub_models")
+public class SubModels implements Serializable {
+ private static final long serialVersionUID = 6805234243952876176L;
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private Long id;
+
+ @OneToMany(mappedBy = "subModels", cascade = CascadeType.ALL)
+ private List models = new ArrayList<>();
+
+ public SubModels() {
+ }
+
+ public SubModels(List models) {
+ if (Objects.isNull(models)) {
+ this.models = new ArrayList<>();
+ } else {
+ this.models = models;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "SubModels{" + "models=" + models + '}';
+ }
+}
diff --git a/cars/src/main/java/com/mooveit/cars/domain/Wheels.java b/cars/src/main/java/com/mooveit/cars/domain/Wheels.java
new file mode 100644
index 0000000..8f9222c
--- /dev/null
+++ b/cars/src/main/java/com/mooveit/cars/domain/Wheels.java
@@ -0,0 +1,43 @@
+package com.mooveit.cars.domain;
+
+import java.io.Serializable;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.validation.constraints.NotNull;
+
+import lombok.Data;
+
+@Data //generate getters/setters automatically
+@Entity
+@Table(name = "wheels")
+public class Wheels implements Serializable {
+ private static final long serialVersionUID = -5667475827211512777L;
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private Long id;
+ @NotNull
+ @Column(name = "size")
+ private String size;
+ @NotNull
+ @Column(name = "type")
+ private String type;
+
+ public Wheels() {
+ // Hibernate requires a no-arg constructor
+ }
+
+ public Wheels(String size, String type) {
+ this.size = size;
+ this.type = type;
+ }
+
+ @Override
+ public String toString() {
+ return "Wheel{" + "size='" + size + '\'' + ", type='" + type + '\'' + '}';
+ }
+}
diff --git a/cars/src/main/java/com/mooveit/cars/dto/CarBrandDto.java b/cars/src/main/java/com/mooveit/cars/dto/CarBrandDto.java
new file mode 100644
index 0000000..272e3b6
--- /dev/null
+++ b/cars/src/main/java/com/mooveit/cars/dto/CarBrandDto.java
@@ -0,0 +1,26 @@
+package com.mooveit.cars.dto;
+
+import java.io.Serializable;
+
+import lombok.Data;
+
+@Data
+public class CarBrandDto implements Serializable {
+ private static final long serialVersionUID = 370012978057179686L;
+ private Long id;
+ private String name;
+ private CatalogueDto catalogueDto;
+
+ public CarBrandDto() {
+ }
+
+ public CarBrandDto(String name, CatalogueDto catalogueDto) {
+ this.name = name;
+ this.catalogueDto = catalogueDto;
+ }
+
+ @Override
+ public String toString() {
+ return "CarBrandDto{" + "id=" + id + ", name='" + name + '\'' + ", catalogueDto=" + catalogueDto + '}';
+ }
+}
diff --git a/cars/src/main/java/com/mooveit/cars/dto/CatalogueDto.java b/cars/src/main/java/com/mooveit/cars/dto/CatalogueDto.java
new file mode 100644
index 0000000..6cd2af7
--- /dev/null
+++ b/cars/src/main/java/com/mooveit/cars/dto/CatalogueDto.java
@@ -0,0 +1,33 @@
+package com.mooveit.cars.dto;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import com.thoughtworks.xstream.annotations.XStreamImplicit;
+import com.thoughtworks.xstream.annotations.XStreamOmitField;
+
+import lombok.Data;
+
+@Data
+@XStreamAlias("CATALOGUE")
+public class CatalogueDto implements Serializable {
+ private static final long serialVersionUID = 8286966866687089758L;
+ @XStreamOmitField
+ private Long id;
+ @XStreamImplicit(itemFieldName = "MODEL")
+ private List modelsDtos = new ArrayList<>();
+
+ public CatalogueDto() {
+ }
+
+ public CatalogueDto(List modelsDtos) {
+ this.modelsDtos = modelsDtos;
+ }
+
+ @Override
+ public String toString() {
+ return "CatalogueDto{" + "id=" + id + ", modelsDtos=" + modelsDtos + '}';
+ }
+}
diff --git a/cars/src/main/java/com/mooveit/cars/dto/EngineDto.java b/cars/src/main/java/com/mooveit/cars/dto/EngineDto.java
new file mode 100644
index 0000000..dcffc8e
--- /dev/null
+++ b/cars/src/main/java/com/mooveit/cars/dto/EngineDto.java
@@ -0,0 +1,34 @@
+package com.mooveit.cars.dto;
+
+import java.io.Serializable;
+
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
+import com.thoughtworks.xstream.annotations.XStreamOmitField;
+
+import lombok.Data;
+
+@Data
+@XStreamAlias("ENGINE")
+public class EngineDto implements Serializable {
+ private static final long serialVersionUID = -4400283264470260421L;
+ @XStreamOmitField
+ private Long id;
+ @XStreamAsAttribute
+ private String power;
+ @XStreamAsAttribute
+ private String type;
+
+ public EngineDto() {
+ }
+
+ public EngineDto(String power, String type) {
+ this.power = power;
+ this.type = type;
+ }
+
+ @Override
+ public String toString() {
+ return "EngineDto{" + "id=" + id + ", power='" + power + '\'' + ", type='" + type + '\'' + '}';
+ }
+}
diff --git a/cars/src/main/java/com/mooveit/cars/dto/ModelDto.java b/cars/src/main/java/com/mooveit/cars/dto/ModelDto.java
new file mode 100644
index 0000000..4e2308e
--- /dev/null
+++ b/cars/src/main/java/com/mooveit/cars/dto/ModelDto.java
@@ -0,0 +1,41 @@
+package com.mooveit.cars.dto;
+
+import java.io.Serializable;
+
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
+import com.thoughtworks.xstream.annotations.XStreamOmitField;
+
+import lombok.Data;
+
+@Data
+@XStreamAlias("MODEL")
+public class ModelDto implements Serializable {
+ private static final long serialVersionUID = -7702483387617011359L;
+ @XStreamOmitField
+ private Long id;
+ @XStreamAsAttribute
+ private String name;
+ @XStreamAsAttribute
+ private String from;
+ @XStreamAsAttribute
+ private String to;
+ @XStreamAsAttribute
+ private String type;
+ @XStreamAsAttribute
+ private String line;
+ @XStreamAlias("ENGINE")
+ private EngineDto engineDto;
+ @XStreamAlias("WHEELS")
+ private WheelsDto wheelsDto;
+ @XStreamAlias("SUBMODELS")
+ private SubModelsDto subModelsDto;
+
+ public ModelDto() {
+ }
+
+ @Override
+ public String toString() {
+ return "ModelDto{" + "id=" + id + ", name='" + name + '\'' + ", from='" + from + '\'' + ", to='" + to + '\'' + ", type='" + type + '\'' + ", line='" + line + '\'' + ", engineDto=" + engineDto + ", wheelsDto=" + wheelsDto + ", subModelsDto=" + subModelsDto + '}';
+ }
+}
diff --git a/cars/src/main/java/com/mooveit/cars/dto/SubModelsDto.java b/cars/src/main/java/com/mooveit/cars/dto/SubModelsDto.java
new file mode 100644
index 0000000..76aae23
--- /dev/null
+++ b/cars/src/main/java/com/mooveit/cars/dto/SubModelsDto.java
@@ -0,0 +1,33 @@
+package com.mooveit.cars.dto;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import com.thoughtworks.xstream.annotations.XStreamImplicit;
+import com.thoughtworks.xstream.annotations.XStreamOmitField;
+
+import lombok.Data;
+
+@Data
+@XStreamAlias("SUBMODELS")
+public class SubModelsDto implements Serializable {
+ private static final long serialVersionUID = -6491157741337175370L;
+ @XStreamOmitField
+ private Long id;
+ @XStreamImplicit(itemFieldName = "MODEL")
+ private List modelsDtos = new ArrayList<>();
+
+ public SubModelsDto() {
+ }
+
+ public SubModelsDto(List modelsDtos) {
+ this.modelsDtos = modelsDtos;
+ }
+
+ @Override
+ public String toString() {
+ return "SubModelsDto{" + "id=" + id + ", modelsDtos=" + modelsDtos + '}';
+ }
+}
diff --git a/cars/src/main/java/com/mooveit/cars/dto/WheelsDto.java b/cars/src/main/java/com/mooveit/cars/dto/WheelsDto.java
new file mode 100644
index 0000000..9240904
--- /dev/null
+++ b/cars/src/main/java/com/mooveit/cars/dto/WheelsDto.java
@@ -0,0 +1,34 @@
+package com.mooveit.cars.dto;
+
+import java.io.Serializable;
+
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
+import com.thoughtworks.xstream.annotations.XStreamOmitField;
+
+import lombok.Data;
+
+@Data
+@XStreamAlias("WHEELS")
+public class WheelsDto implements Serializable {
+ private static final long serialVersionUID = 3599198042007085074L;
+ @XStreamOmitField
+ private Long id;
+ @XStreamAsAttribute
+ private String size;
+ @XStreamAsAttribute
+ private String type;
+
+ public WheelsDto() {
+ }
+
+ public WheelsDto(String size, String type) {
+ this.size = size;
+ this.type = type;
+ }
+
+ @Override
+ public String toString() {
+ return "WheelsDto{" + "id=" + id + ", size='" + size + '\'' + ", type='" + type + '\'' + '}';
+ }
+}
diff --git a/cars/src/main/java/com/mooveit/cars/exceptions/CarBrandAlreadyExistsException.java b/cars/src/main/java/com/mooveit/cars/exceptions/CarBrandAlreadyExistsException.java
new file mode 100644
index 0000000..998ad32
--- /dev/null
+++ b/cars/src/main/java/com/mooveit/cars/exceptions/CarBrandAlreadyExistsException.java
@@ -0,0 +1,8 @@
+package com.mooveit.cars.exceptions;
+
+public class CarBrandAlreadyExistsException extends RuntimeException {
+
+ public CarBrandAlreadyExistsException(String name) {
+ super(String.format("Car brand with name '%s' already exists!", name));
+ }
+}
diff --git a/cars/src/main/java/com/mooveit/cars/exceptions/CarBrandNotFoundException.java b/cars/src/main/java/com/mooveit/cars/exceptions/CarBrandNotFoundException.java
new file mode 100644
index 0000000..0dcbc79
--- /dev/null
+++ b/cars/src/main/java/com/mooveit/cars/exceptions/CarBrandNotFoundException.java
@@ -0,0 +1,8 @@
+package com.mooveit.cars.exceptions;
+
+public class CarBrandNotFoundException extends RuntimeException {
+
+ public CarBrandNotFoundException(String name) {
+ super(String.format("Car brand with name '%s' not found!", name));
+ }
+}
diff --git a/cars/src/main/java/com/mooveit/cars/exceptions/ModelNotFoundException.java b/cars/src/main/java/com/mooveit/cars/exceptions/ModelNotFoundException.java
new file mode 100644
index 0000000..fc7dad6
--- /dev/null
+++ b/cars/src/main/java/com/mooveit/cars/exceptions/ModelNotFoundException.java
@@ -0,0 +1,8 @@
+package com.mooveit.cars.exceptions;
+
+public class ModelNotFoundException extends RuntimeException {
+
+ public ModelNotFoundException(Long id) {
+ super(String.format("Model with ID '%d' not found!", id));
+ }
+}
diff --git a/cars/src/main/java/com/mooveit/cars/helper/ConverterHelper.java b/cars/src/main/java/com/mooveit/cars/helper/ConverterHelper.java
new file mode 100644
index 0000000..622eeed
--- /dev/null
+++ b/cars/src/main/java/com/mooveit/cars/helper/ConverterHelper.java
@@ -0,0 +1,167 @@
+package com.mooveit.cars.helper;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import org.apache.commons.collections4.CollectionUtils;
+
+import com.mooveit.cars.domain.CarBrand;
+import com.mooveit.cars.domain.Catalogue;
+import com.mooveit.cars.domain.Engine;
+import com.mooveit.cars.domain.Model;
+import com.mooveit.cars.domain.SubModels;
+import com.mooveit.cars.domain.Wheels;
+import com.mooveit.cars.dto.CarBrandDto;
+import com.mooveit.cars.dto.CatalogueDto;
+import com.mooveit.cars.dto.EngineDto;
+import com.mooveit.cars.dto.ModelDto;
+import com.mooveit.cars.dto.SubModelsDto;
+import com.mooveit.cars.dto.WheelsDto;
+
+public final class ConverterHelper {
+ private ConverterHelper() {}
+
+ //carBrand
+ public static CarBrandDto toCarBrandDto(CarBrand carBrand) {
+ if(Objects.nonNull(carBrand.getCatalogue())) {
+ return new CarBrandDto(carBrand.getName(), ConverterHelper.toCatalogueDto(carBrand.getCatalogue()));
+ }
+ return null;
+ }
+
+ public static CarBrand fromCarBrandDto(CarBrandDto dto) {
+ if(Objects.nonNull(dto.getCatalogueDto())) {
+ return new CarBrand(dto.getName(), ConverterHelper.fromCatalogueDto(dto.getCatalogueDto()));
+ }
+ return null;
+ }
+
+ //catalogue
+ public static CatalogueDto toCatalogueDto(Catalogue catalogue) {
+ List modelDtos = null;
+ if(CollectionUtils.isNotEmpty(catalogue.getModels())) {
+ modelDtos = catalogue.getModels().stream().map(ConverterHelper::toModelDto).collect(Collectors.toList());
+ }
+ CatalogueDto dto = new CatalogueDto(Objects.nonNull(modelDtos) ? modelDtos : new ArrayList<>());
+ dto.setId(catalogue.getId());
+ return dto;
+ }
+
+ public static Catalogue fromCatalogueDto(CatalogueDto dto) {
+ List models = new ArrayList<>();
+ if(CollectionUtils.isNotEmpty(dto.getModelsDtos())) {
+ models = dto.getModelsDtos().stream().map(ConverterHelper::fromModelDto).collect(Collectors.toList());
+ }
+ Catalogue catalogue = new Catalogue(models);
+ catalogue.setId(dto.getId());
+ return catalogue;
+ }
+
+ //model
+ public static ModelDto toModelDto(Model model) {
+ ModelDto dto = new ModelDto();
+ dto.setId(model.getId());
+ dto.setFrom(model.getFrom());
+ dto.setTo(model.getTo());
+ dto.setName(model.getName());
+ dto.setLine(model.getLine());
+ dto.setType(model.getType());
+ if(Objects.nonNull(model.getEngine())) {
+ dto.setEngineDto(ConverterHelper.toEngineDto(model.getEngine()));
+ }
+ if(Objects.nonNull(model.getWheels())) {
+ dto.setWheelsDto(ConverterHelper.toWheelsDto(model.getWheels()));
+ }
+ if(Objects.nonNull(model.getSubModels())) {
+ dto.setSubModelsDto(ConverterHelper.toSubModelsDto(model.getSubModels()));
+ }
+ return dto;
+ }
+
+ public static ModelDto toSubModelsModelDto(Model model) {
+ ModelDto dto = new ModelDto();
+ dto.setId(model.getId());
+ dto.setFrom(model.getFrom());
+ dto.setTo(model.getTo());
+ dto.setName(model.getName());
+ dto.setLine(model.getLine());
+ dto.setType(model.getType());
+ if(Objects.nonNull(model.getEngine())) {
+ dto.setEngineDto(ConverterHelper.toEngineDto(model.getEngine()));
+ }
+ if(Objects.nonNull(model.getWheels())) {
+ dto.setWheelsDto(ConverterHelper.toWheelsDto(model.getWheels()));
+ }
+ return dto;
+ }
+
+ public static Model fromModelDto(ModelDto dto) {
+ Model model = new Model();
+ model.setId(dto.getId());
+ model.setFrom(dto.getFrom());
+ model.setTo(dto.getTo());
+ model.setName(dto.getName());
+ model.setLine(dto.getLine());
+ model.setType(dto.getType());
+ if(Objects.nonNull(dto.getEngineDto())) {
+ model.setEngine(ConverterHelper.fromEngineDto(dto.getEngineDto()));
+ }
+ if(Objects.nonNull(dto.getWheelsDto())) {
+ model.setWheels(ConverterHelper.fromWheelsDto(dto.getWheelsDto()));
+ }
+ if(Objects.nonNull(dto.getSubModelsDto())) {
+ model.setSubModels(ConverterHelper.fromSubModelsDto(dto.getSubModelsDto()));
+ }
+ return model;
+ }
+
+ //engine
+ public static EngineDto toEngineDto(Engine engine) {
+ EngineDto dto = new EngineDto(engine.getPower(), engine.getType());
+ dto.setId(engine.getId());
+ return dto;
+ }
+
+ public static Engine fromEngineDto(EngineDto dto) {
+ Engine engine = new Engine(dto.getPower(), dto.getType());
+ engine.setId(dto.getId());
+ return engine;
+ }
+
+ //wheels
+ public static WheelsDto toWheelsDto(Wheels wheels) {
+ WheelsDto dto = new WheelsDto(wheels.getSize(), wheels.getType());
+ dto.setId(wheels.getId());
+ return dto;
+ }
+
+ public static Wheels fromWheelsDto(WheelsDto dto) {
+ Wheels wheels = new Wheels(dto.getSize(), dto.getType());
+ wheels.setId(dto.getId());
+ return wheels;
+ }
+
+ //subModels
+ public static SubModelsDto toSubModelsDto(SubModels subModels) {
+ List modelDtos = new ArrayList<>();
+ if(CollectionUtils.isNotEmpty(subModels.getModels())) {
+ modelDtos = subModels.getModels().stream().map(ConverterHelper::toSubModelsModelDto).collect(Collectors.toList());
+ }
+ SubModelsDto dto = new SubModelsDto(modelDtos);
+ dto.setId(subModels.getId());
+ return dto;
+ }
+
+
+ public static SubModels fromSubModelsDto(SubModelsDto dto) {
+ List models = new ArrayList<>();
+ if(CollectionUtils.isNotEmpty(dto.getModelsDtos())) {
+ models = dto.getModelsDtos().stream().map(ConverterHelper::fromModelDto).collect(Collectors.toList());
+ }
+ SubModels subModels = new SubModels(models);
+ subModels.setId(dto.getId());
+ return subModels;
+ }
+}
diff --git a/cars/src/main/java/com/mooveit/cars/helper/JacksonHelper.java b/cars/src/main/java/com/mooveit/cars/helper/JacksonHelper.java
new file mode 100644
index 0000000..538bbcf
--- /dev/null
+++ b/cars/src/main/java/com/mooveit/cars/helper/JacksonHelper.java
@@ -0,0 +1,47 @@
+package com.mooveit.cars.helper;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.MapperFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+
+public class JacksonHelper {
+
+ public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ public static final ObjectMapper OBJECT_NON_NULL_MAPPER = new ObjectMapper()
+ .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
+ .configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true)
+ .configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true)
+ .enable(SerializationFeature.INDENT_OUTPUT)
+ .setSerializationInclusion(JsonInclude.Include.NON_NULL);
+
+ private JacksonHelper() {
+ }
+
+ public static T fromString(String string, Class clazz) throws IOException {
+ return OBJECT_MAPPER.readerFor(clazz).readValue(string);
+ }
+
+ public static String toString(Object value) {
+ try {
+ return OBJECT_MAPPER.writer().writeValueAsString(value);
+ } catch (JsonProcessingException e) {
+ e.printStackTrace();
+ }
+ return "";
+ }
+
+ public static String prettyPrintString(Object value) {
+ try {
+ return OBJECT_NON_NULL_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(value);
+ } catch (JsonProcessingException e) {
+ e.printStackTrace();
+ }
+ return "";
+ }
+
+}
diff --git a/cars/src/main/java/com/mooveit/cars/helper/XStreamHelper.java b/cars/src/main/java/com/mooveit/cars/helper/XStreamHelper.java
new file mode 100644
index 0000000..a45e924
--- /dev/null
+++ b/cars/src/main/java/com/mooveit/cars/helper/XStreamHelper.java
@@ -0,0 +1,43 @@
+package com.mooveit.cars.helper;
+
+import java.io.StringWriter;
+
+import com.mooveit.cars.dto.CatalogueDto;
+import com.mooveit.cars.dto.EngineDto;
+import com.mooveit.cars.dto.ModelDto;
+import com.mooveit.cars.dto.SubModelsDto;
+import com.mooveit.cars.dto.WheelsDto;
+import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
+import com.thoughtworks.xstream.io.xml.StaxDriver;
+
+public final class XStreamHelper {
+ private XStreamHelper() {}
+
+ public static String toXML(CatalogueDto catalogueDto) {
+ final XStream xstream = getXStream();
+ xstream.setMode(XStream.ID_REFERENCES);
+
+ StringWriter writer = new StringWriter();
+ xstream.marshal(catalogueDto, new PrettyPrintWriter(writer));
+ return writer.toString();
+ }
+
+ private static XStream getXStream() {
+ final XStream xstream = new XStream(new StaxDriver());
+ xstream.autodetectAnnotations(true);
+ xstream.processAnnotations(CatalogueDto.class);
+ xstream.processAnnotations(ModelDto.class);
+ xstream.processAnnotations(EngineDto.class);
+ xstream.processAnnotations(WheelsDto.class);
+ xstream.processAnnotations(SubModelsDto.class);
+ return xstream;
+ }
+
+ public static CatalogueDto xmlToDto(String xmlInput) {
+ final XStream xstream = getXStream();
+ xstream.setMode(XStream.ID_REFERENCES);
+ return (CatalogueDto) xstream.fromXML(xmlInput);
+ }
+
+}
diff --git a/cars/src/main/java/com/mooveit/cars/repositories/CarBrandRepository.java b/cars/src/main/java/com/mooveit/cars/repositories/CarBrandRepository.java
new file mode 100644
index 0000000..7be3f65
--- /dev/null
+++ b/cars/src/main/java/com/mooveit/cars/repositories/CarBrandRepository.java
@@ -0,0 +1,13 @@
+package com.mooveit.cars.repositories;
+
+import java.util.Optional;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import com.mooveit.cars.domain.CarBrand;
+
+@Repository
+public interface CarBrandRepository extends JpaRepository {
+ Optional getCarBrandByName(String name);
+}
diff --git a/cars/src/main/java/com/mooveit/cars/repositories/CatalogueRepository.java b/cars/src/main/java/com/mooveit/cars/repositories/CatalogueRepository.java
new file mode 100644
index 0000000..12184ec
--- /dev/null
+++ b/cars/src/main/java/com/mooveit/cars/repositories/CatalogueRepository.java
@@ -0,0 +1,10 @@
+package com.mooveit.cars.repositories;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import com.mooveit.cars.domain.Catalogue;
+
+@Repository
+public interface CatalogueRepository extends JpaRepository {
+}
diff --git a/cars/src/main/java/com/mooveit/cars/repositories/ModelRepository.java b/cars/src/main/java/com/mooveit/cars/repositories/ModelRepository.java
new file mode 100644
index 0000000..c1fc33b
--- /dev/null
+++ b/cars/src/main/java/com/mooveit/cars/repositories/ModelRepository.java
@@ -0,0 +1,10 @@
+package com.mooveit.cars.repositories;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import com.mooveit.cars.domain.Model;
+
+@Repository
+public interface ModelRepository extends JpaRepository {
+}
diff --git a/cars/src/main/java/com/mooveit/cars/repositories/SubModelsRepository.java b/cars/src/main/java/com/mooveit/cars/repositories/SubModelsRepository.java
new file mode 100644
index 0000000..b4e09b9
--- /dev/null
+++ b/cars/src/main/java/com/mooveit/cars/repositories/SubModelsRepository.java
@@ -0,0 +1,10 @@
+package com.mooveit.cars.repositories;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import com.mooveit.cars.domain.SubModels;
+
+@Repository
+public interface SubModelsRepository extends JpaRepository {
+}
diff --git a/cars/src/main/java/com/mooveit/cars/service/CarBrandMService.java b/cars/src/main/java/com/mooveit/cars/service/CarBrandMService.java
new file mode 100644
index 0000000..1155b06
--- /dev/null
+++ b/cars/src/main/java/com/mooveit/cars/service/CarBrandMService.java
@@ -0,0 +1,114 @@
+package com.mooveit.cars.service;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.mooveit.cars.domain.CarBrand;
+import com.mooveit.cars.domain.Catalogue;
+import com.mooveit.cars.domain.Model;
+import com.mooveit.cars.domain.SubModels;
+import com.mooveit.cars.dto.CarBrandDto;
+import com.mooveit.cars.dto.CatalogueDto;
+import com.mooveit.cars.dto.ModelDto;
+import com.mooveit.cars.exceptions.CarBrandNotFoundException;
+import com.mooveit.cars.exceptions.ModelNotFoundException;
+import com.mooveit.cars.helper.ConverterHelper;
+import com.mooveit.cars.helper.JacksonHelper;
+import com.mooveit.cars.helper.XStreamHelper;
+import com.mooveit.cars.repositories.CarBrandRepository;
+import com.mooveit.cars.repositories.ModelRepository;
+import com.mooveit.cars.repositories.SubModelsRepository;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Service
+public class CarBrandMService implements ICarBrandService {
+ private static final String DEFAULT_VENDOR = "Ford";
+
+ private final CarBrandRepository carBrandRepository;
+ private final ModelRepository modelRepository;
+ private final SubModelsRepository subModelsRepository;
+
+ @Autowired
+ public CarBrandMService(CarBrandRepository carBrandRepository, ModelRepository modelRepository, SubModelsRepository subModelsRepository) {
+ this.carBrandRepository = carBrandRepository;
+ this.modelRepository = modelRepository;
+ this.subModelsRepository = subModelsRepository;
+ }
+
+ @Override
+ @Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
+ public ModelDto getModel(Long modelId) {
+ log.debug("Entering 'getModel' method with modelId param -> {}", modelId);
+ Model model = modelRepository.findById(modelId).orElseThrow(() -> new ModelNotFoundException(modelId));
+ return ConverterHelper.toModelDto(model);
+ }
+
+ @Override
+ @Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
+ public List getModelByBrand(String name) {
+ log.debug("Entering 'getModelByBrand' method with name param -> {}", name);
+ CarBrand carBrand = carBrandRepository.getCarBrandByName(name).orElseThrow(() -> new CarBrandNotFoundException(name));
+ CarBrandDto carBrandDto = ConverterHelper.toCarBrandDto(carBrand);
+ return Objects.requireNonNull(carBrandDto).getCatalogueDto().getModelsDtos();
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
+ public CarBrandDto saveCarBrand(String xmlString) {
+ log.debug("@@@@@ Entering 'saveCarBrand' method with xmlString param -> {}", xmlString);
+ CatalogueDto catalogueDto = XStreamHelper.xmlToDto(xmlString);
+ catalogueDto.getModelsDtos().forEach(dto -> log.debug("\nmodelDto ->\n{}", dto));
+ Catalogue catalogue = ConverterHelper.fromCatalogueDto(catalogueDto);
+ log.debug("@@@@@ catalogueDto:\n{}\n", JacksonHelper.prettyPrintString(catalogueDto));
+ log.debug("@@@@@ catalogue:\n{}\n", JacksonHelper.prettyPrintString(catalogue));
+
+ List copyCatalogueModels = new ArrayList<>(catalogue.getModels());
+ catalogue.setModels(new ArrayList<>());
+
+ //save each model entity before saving catalogue
+ copyCatalogueModels.forEach(model -> {
+ SubModels subModels = model.getSubModels();
+ List copyModels = new ArrayList<>(subModels.getModels());
+ subModels.setModels(new ArrayList<>());
+ //save each model entity before saving subModel
+ copyModels.forEach(model2 -> {
+ model2.setSubModels(null);
+ model2 = modelRepository.save(model2);
+ log.debug("@@@@@ saved subModel::model:\n{}\n", JacksonHelper.prettyPrintString(model2));
+ subModels.getModels().add(model2);
+ });
+
+ SubModels subModels1 = subModelsRepository.save(subModels);
+ log.debug("@@@@@ saved subModel:\n{}\n", JacksonHelper.prettyPrintString(subModels1));
+
+ model.setSubModels(subModels);
+
+ Model model2 = modelRepository.save(model);
+ log.debug("@@@@@ saved model2:\n{}\n", JacksonHelper.prettyPrintString(model2));
+
+ catalogue.addModel(model);
+ });
+
+ CarBrand carBrand = new CarBrand(DEFAULT_VENDOR, catalogue);
+ CarBrand carBrand2 = carBrandRepository.save(carBrand);
+ log.debug("@@@@@ saved carBrand:\n{}\n", JacksonHelper.prettyPrintString(carBrand2));
+ CarBrandDto carBrandDto = ConverterHelper.toCarBrandDto(carBrand2);
+ log.debug("@@@@@ carBrand Dto:\n{}\n", JacksonHelper.prettyPrintString(carBrandDto));
+ return carBrandDto;
+ }
+
+ @Override
+ public boolean isCarBrandWithNameExists(String name) {
+ log.debug("@@@@@ Entering 'isCarBrandWithNameExists' method with name param -> {}", name);
+ return carBrandRepository.getCarBrandByName(name).isPresent();
+ }
+
+}
diff --git a/cars/src/main/java/com/mooveit/cars/service/ICarBrandService.java b/cars/src/main/java/com/mooveit/cars/service/ICarBrandService.java
new file mode 100644
index 0000000..197b37a
--- /dev/null
+++ b/cars/src/main/java/com/mooveit/cars/service/ICarBrandService.java
@@ -0,0 +1,14 @@
+package com.mooveit.cars.service;
+
+import java.util.List;
+
+import com.mooveit.cars.dto.CarBrandDto;
+import com.mooveit.cars.dto.ModelDto;
+
+public interface ICarBrandService {
+ ModelDto getModel(Long modelId);
+ List getModelByBrand(String name);
+ CarBrandDto saveCarBrand(String xmlString);
+ boolean isCarBrandWithNameExists(String name);
+
+}
diff --git a/cars/src/main/java/com/mooveit/cars/tasks/FordIngesterTask.java b/cars/src/main/java/com/mooveit/cars/tasks/FordIngesterTask.java
index a04f791..e92f3cd 100644
--- a/cars/src/main/java/com/mooveit/cars/tasks/FordIngesterTask.java
+++ b/cars/src/main/java/com/mooveit/cars/tasks/FordIngesterTask.java
@@ -1,15 +1,51 @@
package com.mooveit.cars.tasks;
-import lombok.extern.slf4j.Slf4j;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
+import org.springframework.util.FileCopyUtils;
+
+import com.mooveit.cars.exceptions.CarBrandAlreadyExistsException;
+import com.mooveit.cars.service.ICarBrandService;
+
+import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
public class FordIngesterTask {
+ private static final String DEFAULT_VENDOR = "Ford";
+ private final ICarBrandService carBrandService;
+
+ @Autowired
+ public FordIngesterTask(ICarBrandService carBrandService) {
+ this.carBrandService = carBrandService;
+ }
+
+ @Scheduled(cron = "${cars.ford.ingester.runCron}")
+ public void ingestFile() {
+ log.debug("Ingesting XML file started!");
+ if(carBrandService.isCarBrandWithNameExists(DEFAULT_VENDOR)) {
+ log.error("Car brand already exists in the database!");
+ throw new CarBrandAlreadyExistsException(DEFAULT_VENDOR);
+ }
+ String xmlString = getFileContent();
+ log.debug("\nxmlString ->\n{}", xmlString);
+ carBrandService.saveCarBrand(xmlString);
+ log.debug("\nIngesting XML file done!");
+ }
- @Scheduled(cron = "${cars.ford.ingester.runCron}")
- public void ingestFile() {
- log.warn("Not implemented yet.");
- }
+ private String getFileContent() {
+ try {
+ Resource resource = new ClassPathResource("ford-example.xml");
+ return new String(FileCopyUtils.copyToByteArray(resource.getInputStream()), StandardCharsets.UTF_8);
+ } catch (IOException e) {
+ log.error("IOException", e);
+ }
+ return "";
+ }
}
diff --git a/cars/src/main/resources/EnitiyClassDiagram.png b/cars/src/main/resources/EnitiyClassDiagram.png
new file mode 100644
index 0000000..6f2181e
Binary files /dev/null and b/cars/src/main/resources/EnitiyClassDiagram.png differ
diff --git a/cars/src/main/resources/application.yml b/cars/src/main/resources/application.yml
index 436f591..013ca9d 100644
--- a/cars/src/main/resources/application.yml
+++ b/cars/src/main/resources/application.yml
@@ -1,7 +1,7 @@
cars:
ford:
ingester:
- runCron: '0 * * ? * *' #each minute
+ runCron: '0/15 * * * * ?' #triggered every 15 secs
spring:
datasource:
@@ -11,4 +11,15 @@ spring:
password:
jpa:
database-platform: 'org.hibernate.dialect.H2Dialect'
- h2.console.enabled: true
+ show-sql: true
+ properties:
+ hibernate:
+ format_sql: true
+ hibernate:
+ ddl-auto: create-drop
+ h2:
+ console:
+ enabled: true
+ path: /h2
+ settings:
+ trace: true
diff --git a/cars/src/main/resources/logback.xml b/cars/src/main/resources/logback.xml
new file mode 100644
index 0000000..70a040d
--- /dev/null
+++ b/cars/src/main/resources/logback.xml
@@ -0,0 +1,17 @@
+
+
+
+ %magenta(%d{yyyy-MM-dd HH:mm:ss.SSS zzz}) %cyan([%thread]) %highlight(%-5level) %green(%logger{36}) - %msg%n
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cars/src/main/resources/postman_get_model_by_id.png b/cars/src/main/resources/postman_get_model_by_id.png
new file mode 100644
index 0000000..5e32f43
Binary files /dev/null and b/cars/src/main/resources/postman_get_model_by_id.png differ
diff --git a/cars/src/main/resources/postman_get_models_by_brand.png b/cars/src/main/resources/postman_get_models_by_brand.png
new file mode 100644
index 0000000..bfee450
Binary files /dev/null and b/cars/src/main/resources/postman_get_models_by_brand.png differ
diff --git a/cars/src/test/java/com/mooveit/cars/CarsApplicationTests.java b/cars/src/test/java/com/mooveit/cars/CarsApplicationTests.java
index 426b96d..a84b14c 100644
--- a/cars/src/test/java/com/mooveit/cars/CarsApplicationTests.java
+++ b/cars/src/test/java/com/mooveit/cars/CarsApplicationTests.java
@@ -1,11 +1,11 @@
package com.mooveit.cars;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
-@RunWith(SpringRunner.class)
+@ExtendWith(SpringExtension.class)
@SpringBootTest
public class CarsApplicationTests {
diff --git a/cars/src/test/resources/logback.xml b/cars/src/test/resources/logback.xml
new file mode 100644
index 0000000..6ca91ad
--- /dev/null
+++ b/cars/src/test/resources/logback.xml
@@ -0,0 +1,18 @@
+
+
+
+ %magenta(%d{yyyy-MM-dd HH:mm:ss.SSS zzz}) %cyan([%thread]) %highlight(%-5level) %green(%logger{36}) - %msg%n
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file