sourceToOmit);
+
+
+ /**
+ * Return the name of the {@link Brand} that will be constructed
+ * @return String with the name of {@link Brand}
+ */
+ String getBrandName();
+
+}
diff --git a/cars/src/main/java/com/mooveit/cars/ingestion/FordBrandBuilder.java b/cars/src/main/java/com/mooveit/cars/ingestion/FordBrandBuilder.java
new file mode 100644
index 0000000..b7d9ad2
--- /dev/null
+++ b/cars/src/main/java/com/mooveit/cars/ingestion/FordBrandBuilder.java
@@ -0,0 +1,260 @@
+package com.mooveit.cars.ingestion;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import org.dom4j.Document;
+import org.dom4j.DocumentException;
+import org.dom4j.Element;
+import org.dom4j.io.SAXReader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.mooveit.cars.domain.Brand;
+import com.mooveit.cars.domain.Engine;
+import com.mooveit.cars.domain.EngineType;
+import com.mooveit.cars.domain.Modification;
+import com.mooveit.cars.domain.Specification;
+import com.mooveit.cars.domain.Wheel;
+
+/**
+ * Ford brand builder builds a specific {@link Brand} per each file found on the
+ * base folder.
+ *
+ * Sources are used to filter these files based on certain criteria, as for
+ * examples old, temporary or already processed files.
+ *
+ * The .xml format follow the next structure :
+ *
+ *
+ *{@code
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * ...
+ *
+ *}
+ *
+ */
+public class FordBrandBuilder implements BrandBuilder {
+
+ private static final Logger log = LoggerFactory.getLogger(FordBrandBuilder.class);
+
+ /**
+ * Folder to scan
+ */
+ private String scanDirectory = ".";
+
+ /**
+ * Scan folder on classloader or file system
+ */
+ private boolean scanClassloader = true;
+
+ public FordBrandBuilder() {
+ super();
+ }
+
+ public FordBrandBuilder(String scanDirectory, boolean scanClassloader) {
+ super();
+ this.scanDirectory = scanDirectory;
+ this.scanClassloader = scanClassloader;
+ }
+
+ /**
+ * Brand name
+ */
+ @Override
+ public String getBrandName() {
+ return "Ford";
+ }
+
+ /**
+ * Build a {@link Brand} per each `.xml` file found in the specific
+ * `fordFilesDir` folder.
+ */
+ @Override
+ public Map createBrands(Set sourceToOmit) {
+
+ Map brands = new HashMap();
+ try {
+
+ List files = this.getSourceXMLFiles(sourceToOmit);
+ for (File file : files) {
+ InputStream is = new FileInputStream(file);
+ brands.put(file.toString(), this.createBrandFromXml(is));
+ }
+
+ } catch (Exception e) {
+ String errorMessage = String.format(
+ "There was a problem gathering files from directory %s with pattern ford-*.xml",
+ this.scanDirectory);
+ log.error(errorMessage);
+ throw new IngestionException(errorMessage, e);
+ }
+
+ return brands;
+
+ }
+
+ protected List getSourceXMLFiles(Set sourceToOmit) {
+
+ String filePattern = String.format("^ford-(.+).xml$", File.separator);
+ try {
+ // Scan directory
+ Path basePath = null;
+ if (this.scanClassloader) {
+ basePath = Paths.get(getClass().getClassLoader().getResource(this.scanDirectory).toURI());
+ } else {
+ basePath = Paths.get(this.scanDirectory);
+ }
+
+ // Filter all ford-*.xml files with given format not in
+ // `sourceToOmit`
+ Pattern fordXmlFilePattern = Pattern.compile(filePattern);
+ return Files.list(basePath).filter(p -> p.toFile().isFile())
+ .filter(p -> fordXmlFilePattern.matcher(p.getFileName().toString()).matches())
+ .filter(p -> !sourceToOmit.contains(p.toAbsolutePath().toString())).map(p -> p.toFile())
+ .collect(Collectors.toList());
+ } catch (Exception e) {
+ String errorMessage = String.format(
+ "There was a probles scanning files from directory %s with pattern `%s`", this.scanDirectory,
+ filePattern);
+ log.error(errorMessage);
+ throw new IngestionException(errorMessage, e);
+ }
+ }
+
+ /**
+ * Build a {@link Brand} based on a XML which path is given by parameter.
+ *
+ * @param path
+ * Path of the XML file to be parsed
+ * @return {@link Brand} built based on XML file given as argument
+ */
+ protected Brand createBrandFromXml(InputStream is) {
+
+ try {
+
+ log.debug(String.format("Processing Ford catalog"));
+
+ SAXReader reader = new SAXReader();
+ Document document = reader.read(is);
+
+ // Create Ford Brand
+ Brand brand = new Brand("Ford");
+
+ log.debug("Start processing CATALOG element ...");
+ Element catalog = document.getRootElement();
+ if (catalog.getName().equals("CATALOG")) {
+ throw new Error("Root element name must be `CATALOG`");
+ }
+
+ log.debug("Start processing CATALOG/MODEL list ...");
+ Iterator iterator = catalog.elementIterator("MODEL");
+ while (iterator.hasNext()) {
+ Specification spec = this.buildSpecification(brand, (Element) iterator.next());
+ brand.addSpecification(spec);
+ }
+ log.debug(String.format("Specifications : %d created", brand.getSpecifications().count()));
+
+ return brand;
+
+ } catch (DocumentException e) {
+ String errorMessage = String.format("Failed reading source InputStream");
+ log.error(errorMessage);
+ throw new IngestionException(errorMessage, e);
+ }
+
+ }
+
+ /**
+ * Based on a `MODEL` element, create a {@link Specification} and all their
+ * {@link Modification}.
+ */
+ private Specification buildSpecification(Brand brand, Element element) {
+
+ String name = element.attributeValue("name");
+ String type = element.attributeValue("type");
+ Integer from = attributeIntValue(element, "from");
+ Integer to = attributeIntValue(element, "to");
+ Engine engine = this.buildEngine(element.element("ENGINE"));
+ Wheel wheel = this.buildWheel(element.element("WHEELS"));
+
+ Specification spec = new Specification(brand, name, from, to, type, engine, wheel);
+ log.debug(String.format("Specification '%s' was created", spec.getName()));
+
+ Element submodels = element.element("SUBMODELS");
+ if (submodels != null) {
+ Iterator iterator = submodels.elementIterator("MODEL");
+ while (iterator.hasNext()) {
+ Modification modif = this.buildModification((Element) iterator.next());
+ spec.addModification(modif);
+ }
+ }
+ log.debug(String.format("Modifications : %d created", spec.getModifications().size()));
+
+ return spec;
+ }
+
+ private Modification buildModification(Element element) {
+ if (element == null) {
+ return null;
+ }
+
+ String name = element.attributeValue("name");
+ String line = element.attributeValue("line");
+ Integer from = attributeIntValue(element, "from");
+ Integer to = attributeIntValue(element, "to");
+ Engine engine = this.buildEngine(element.element("ENGINE"));
+ Wheel wheel = this.buildWheel(element.element("WHEELS"));
+
+ return new Modification(name, from, to, line, engine, wheel);
+ }
+
+ private Wheel buildWheel(Element element) {
+ if (element == null) {
+ return null;
+ }
+ String size = element.attributeValue("size");
+ String type = element.attributeValue("type");
+ return new Wheel(size, type);
+ }
+
+ private Engine buildEngine(Element element) {
+ if (element == null) {
+ return null;
+ }
+ Integer size = attributeIntValue(element, "power");
+ EngineType type = element.attributeValue("type") == null ? null
+ : EngineType.valueOf(element.attributeValue("type"));
+ return new Engine(size, type);
+ }
+
+ private Integer attributeIntValue(Element element, String attrName) {
+ return element.attributeValue(attrName) != null ? Integer.valueOf(element.attributeValue(attrName)) : null;
+ }
+
+}
diff --git a/cars/src/main/java/com/mooveit/cars/ingestion/IngestStrategy.java b/cars/src/main/java/com/mooveit/cars/ingestion/IngestStrategy.java
new file mode 100644
index 0000000..215a7b2
--- /dev/null
+++ b/cars/src/main/java/com/mooveit/cars/ingestion/IngestStrategy.java
@@ -0,0 +1,13 @@
+package com.mooveit.cars.ingestion;
+
+import com.mooveit.cars.domain.Brand;
+import com.mooveit.cars.domain.Ingestion;
+
+/**
+ * Strategy to ingest a new {@link Brand}
+ */
+public interface IngestStrategy {
+
+ Ingestion ingest(String source, Brand brandToIngest);
+
+}
\ No newline at end of file
diff --git a/cars/src/main/java/com/mooveit/cars/ingestion/IngestionException.java b/cars/src/main/java/com/mooveit/cars/ingestion/IngestionException.java
new file mode 100644
index 0000000..35cfae4
--- /dev/null
+++ b/cars/src/main/java/com/mooveit/cars/ingestion/IngestionException.java
@@ -0,0 +1,15 @@
+package com.mooveit.cars.ingestion;
+
+public class IngestionException extends RuntimeException {
+
+ private static final long serialVersionUID = 612343670266273279L;
+
+ public IngestionException() {
+ super();
+ }
+
+ public IngestionException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/cars/src/main/java/com/mooveit/cars/ingestion/MergeBrandsIngestStrategy.java b/cars/src/main/java/com/mooveit/cars/ingestion/MergeBrandsIngestStrategy.java
new file mode 100644
index 0000000..93a6791
--- /dev/null
+++ b/cars/src/main/java/com/mooveit/cars/ingestion/MergeBrandsIngestStrategy.java
@@ -0,0 +1,53 @@
+package com.mooveit.cars.ingestion;
+
+import java.util.Date;
+import java.util.Optional;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.mooveit.cars.domain.Brand;
+import com.mooveit.cars.domain.Ingestion;
+import com.mooveit.cars.domain.Specification;
+import com.mooveit.cars.repositories.BrandRepository;
+
+/**
+ * This strategy takes a transient {@link Brand} and:
+ *
+ * - If the {@link Brand} doesn't exists into the repository, save it as it is
+ *
- If the {@link Brand} exists, for each {@link Specification}:
+ *
- If {@link Specification} doesn't exists, add it to the {@link Brand}
+ *
- If {@link Specification} exists, this will be replaced for the new one
+ *
+ */
+@Service
+public class MergeBrandsIngestStrategy implements IngestStrategy {
+
+ @Autowired
+ private BrandRepository brandRepo;
+
+ /*
+ * @see com.mooveit.cars.ingestion.IngestStrategy#ingest(java.lang.String,
+ * com.mooveit.cars.domain.Brand)
+ */
+ @Override
+ public Ingestion ingest(String source, Brand brandToIngest) {
+
+ Optional persistentBrand = brandRepo.findByName(brandToIngest.getName());
+
+ if (!persistentBrand.isPresent()) {
+ // If brand didn't exists, persist complete new brand
+ brandToIngest = brandRepo.save(brandToIngest);
+ Long totalSpecs = brandToIngest.getSpecifications().count();
+ return new Ingestion(brandToIngest, new Date(), source, totalSpecs, totalSpecs);
+ } else {
+
+ // TODO : If brand exists, persist new Specifications and update
+ // existent ones.
+
+ }
+ return new Ingestion();
+
+ }
+
+}
diff --git a/cars/src/main/java/com/mooveit/cars/repositories/AbstractSpecRepository.java b/cars/src/main/java/com/mooveit/cars/repositories/AbstractSpecRepository.java
new file mode 100644
index 0000000..634b3c2
--- /dev/null
+++ b/cars/src/main/java/com/mooveit/cars/repositories/AbstractSpecRepository.java
@@ -0,0 +1,11 @@
+package com.mooveit.cars.repositories;
+
+import org.springframework.data.repository.PagingAndSortingRepository;
+import org.springframework.stereotype.Repository;
+
+import com.mooveit.cars.domain.AbstractSpec;
+
+@Repository
+public interface AbstractSpecRepository extends PagingAndSortingRepository {
+
+}
\ No newline at end of file
diff --git a/cars/src/main/java/com/mooveit/cars/repositories/BrandRepository.java b/cars/src/main/java/com/mooveit/cars/repositories/BrandRepository.java
new file mode 100644
index 0000000..cba2e6b
--- /dev/null
+++ b/cars/src/main/java/com/mooveit/cars/repositories/BrandRepository.java
@@ -0,0 +1,15 @@
+package com.mooveit.cars.repositories;
+
+import java.util.Optional;
+
+import org.springframework.data.repository.PagingAndSortingRepository;
+import org.springframework.stereotype.Repository;
+
+import com.mooveit.cars.domain.Brand;
+
+@Repository
+public interface BrandRepository extends PagingAndSortingRepository {
+
+ public Optional findByName(String name);
+
+}
diff --git a/cars/src/main/java/com/mooveit/cars/repositories/EngineRepository.java b/cars/src/main/java/com/mooveit/cars/repositories/EngineRepository.java
new file mode 100644
index 0000000..ac3a1b4
--- /dev/null
+++ b/cars/src/main/java/com/mooveit/cars/repositories/EngineRepository.java
@@ -0,0 +1,20 @@
+package com.mooveit.cars.repositories;
+
+import java.util.Optional;
+
+import org.springframework.data.repository.PagingAndSortingRepository;
+import org.springframework.data.rest.core.annotation.RestResource;
+import org.springframework.stereotype.Repository;
+
+import com.mooveit.cars.domain.Engine;
+import com.mooveit.cars.domain.EngineType;
+
+@Repository
+@RestResource(exported = false)
+public interface EngineRepository extends PagingAndSortingRepository {
+
+ public Optional findByPowerAndType(Integer power, EngineType type);
+
+ public Optional findByPower(Integer power);
+
+}
diff --git a/cars/src/main/java/com/mooveit/cars/repositories/IngestionDTO.java b/cars/src/main/java/com/mooveit/cars/repositories/IngestionDTO.java
new file mode 100644
index 0000000..6afa785
--- /dev/null
+++ b/cars/src/main/java/com/mooveit/cars/repositories/IngestionDTO.java
@@ -0,0 +1,6 @@
+package com.mooveit.cars.repositories;
+
+public interface IngestionDTO {
+
+ String getSource();
+}
diff --git a/cars/src/main/java/com/mooveit/cars/repositories/IngestionRepository.java b/cars/src/main/java/com/mooveit/cars/repositories/IngestionRepository.java
new file mode 100644
index 0000000..bf8de9a
--- /dev/null
+++ b/cars/src/main/java/com/mooveit/cars/repositories/IngestionRepository.java
@@ -0,0 +1,19 @@
+package com.mooveit.cars.repositories;
+
+import java.util.Date;
+import java.util.Optional;
+import java.util.Set;
+
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.stereotype.Repository;
+
+import com.mooveit.cars.domain.Ingestion;
+
+@Repository
+public interface IngestionRepository extends CrudRepository {
+
+ public Optional findBySourceAndDate(String source, Date date);
+
+ public Set findAllByBrandName(String brandName);
+
+}
diff --git a/cars/src/main/java/com/mooveit/cars/repositories/SpecificationRepository.java b/cars/src/main/java/com/mooveit/cars/repositories/SpecificationRepository.java
new file mode 100644
index 0000000..e106cf7
--- /dev/null
+++ b/cars/src/main/java/com/mooveit/cars/repositories/SpecificationRepository.java
@@ -0,0 +1,20 @@
+package com.mooveit.cars.repositories;
+
+import java.util.List;
+
+import org.springframework.data.repository.PagingAndSortingRepository;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+
+import com.mooveit.cars.domain.EngineType;
+import com.mooveit.cars.domain.Specification;
+
+@Repository
+public interface SpecificationRepository extends PagingAndSortingRepository {
+
+ public List findByName(@Param(value = "name") String name);
+
+ public List findByEngineType(EngineType name);
+
+ public List findByBrandName(@Param(value = "brand") String name);
+}
diff --git a/cars/src/main/java/com/mooveit/cars/repositories/WheelRepository.java b/cars/src/main/java/com/mooveit/cars/repositories/WheelRepository.java
new file mode 100644
index 0000000..71214f7
--- /dev/null
+++ b/cars/src/main/java/com/mooveit/cars/repositories/WheelRepository.java
@@ -0,0 +1,17 @@
+package com.mooveit.cars.repositories;
+
+import java.util.Optional;
+
+import org.springframework.data.repository.PagingAndSortingRepository;
+import org.springframework.data.rest.core.annotation.RestResource;
+import org.springframework.stereotype.Repository;
+
+import com.mooveit.cars.domain.Wheel;
+
+@Repository
+@RestResource(exported = false)
+public interface WheelRepository extends PagingAndSortingRepository {
+
+ public Optional findBySizeAndType(String R15, String type);
+
+}
diff --git a/cars/src/main/java/com/mooveit/cars/services/ImageService.java b/cars/src/main/java/com/mooveit/cars/services/ImageService.java
new file mode 100644
index 0000000..01df220
--- /dev/null
+++ b/cars/src/main/java/com/mooveit/cars/services/ImageService.java
@@ -0,0 +1,45 @@
+package com.mooveit.cars.services;
+
+import java.io.IOException;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+import org.springframework.web.multipart.MultipartFile;
+
+import com.mooveit.cars.domain.AbstractSpec;
+import com.mooveit.cars.domain.Image;
+import com.mooveit.cars.repositories.AbstractSpecRepository;
+
+@Service
+public class ImageService {
+
+ @Autowired
+ private AbstractSpecRepository abstractSpecRepository;
+
+ public Image storeFile(Long specId, MultipartFile file) {
+
+ // Normalize file name
+ String fileName = StringUtils.cleanPath(file.getOriginalFilename());
+
+ try {
+
+ AbstractSpec spec = abstractSpecRepository.findById(specId).orElseThrow(() -> new ImageStorageException(
+ String.format("Parent specification with id %d is doesn't exists", specId)));
+
+ Image newImage = new Image(fileName, file.getContentType(), file.getBytes(), file.getSize());
+ spec.setImage(newImage);
+
+ spec = abstractSpecRepository.save(spec);
+ return spec.getImage();
+
+ } catch (IOException ex) {
+ throw new ImageStorageException("Could not store image " + fileName + ". Please try again!", ex);
+ }
+ }
+
+ public Image getFile(Long specId) {
+ return abstractSpecRepository.findById(specId).map(spec -> spec.getImage())
+ .orElseThrow(() -> new ImageStorageException("Image not found for Specification id " + specId));
+ }
+}
diff --git a/cars/src/main/java/com/mooveit/cars/services/ImageStorageException.java b/cars/src/main/java/com/mooveit/cars/services/ImageStorageException.java
new file mode 100644
index 0000000..54d3817
--- /dev/null
+++ b/cars/src/main/java/com/mooveit/cars/services/ImageStorageException.java
@@ -0,0 +1,13 @@
+package com.mooveit.cars.services;
+
+public class ImageStorageException extends RuntimeException {
+
+ public ImageStorageException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ImageStorageException(String message) {
+ super(message);
+ }
+
+}
diff --git a/cars/src/main/java/com/mooveit/cars/tasks/BuildersConfigurations.java b/cars/src/main/java/com/mooveit/cars/tasks/BuildersConfigurations.java
new file mode 100644
index 0000000..b291d34
--- /dev/null
+++ b/cars/src/main/java/com/mooveit/cars/tasks/BuildersConfigurations.java
@@ -0,0 +1,23 @@
+package com.mooveit.cars.tasks;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import com.mooveit.cars.ingestion.BrandBuilder;
+import com.mooveit.cars.ingestion.FordBrandBuilder;
+
+@Configuration
+public class BuildersConfigurations {
+
+ public class CollectionConfig {
+
+ @Bean
+ public List brandBuilders() {
+ return Arrays.asList(new FordBrandBuilder());
+ }
+ }
+
+}
diff --git a/cars/src/main/java/com/mooveit/cars/tasks/FordIngesterTask.java b/cars/src/main/java/com/mooveit/cars/tasks/FordIngesterTask.java
deleted file mode 100644
index a04f791..0000000
--- a/cars/src/main/java/com/mooveit/cars/tasks/FordIngesterTask.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.mooveit.cars.tasks;
-
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.scheduling.annotation.Scheduled;
-import org.springframework.stereotype.Service;
-
-@Slf4j
-@Service
-public class FordIngesterTask {
-
- @Scheduled(cron = "${cars.ford.ingester.runCron}")
- public void ingestFile() {
- log.warn("Not implemented yet.");
- }
-}
diff --git a/cars/src/main/java/com/mooveit/cars/tasks/IngesterTask.java b/cars/src/main/java/com/mooveit/cars/tasks/IngesterTask.java
new file mode 100644
index 0000000..7b97bc4
--- /dev/null
+++ b/cars/src/main/java/com/mooveit/cars/tasks/IngesterTask.java
@@ -0,0 +1,93 @@
+package com.mooveit.cars.tasks;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+
+import com.mooveit.cars.domain.Brand;
+import com.mooveit.cars.domain.Ingestion;
+import com.mooveit.cars.ingestion.BrandBuilder;
+import com.mooveit.cars.ingestion.IngestStrategy;
+import com.mooveit.cars.repositories.IngestionDTO;
+import com.mooveit.cars.repositories.IngestionRepository;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Service
+public class IngesterTask {
+
+ private static final Logger log = LoggerFactory.getLogger(IngesterTask.class);
+
+ @Autowired
+ private IngestStrategy strategy;
+
+ @Autowired
+ private IngestionRepository ingestionRepo;
+
+ @Autowired
+ private List brandBuilders;
+
+ public IngesterTask() {
+ super();
+ }
+
+ @Scheduled(cron = "${cars.ford.ingester.runCron}")
+ public void ingestBrands() {
+
+ for (BrandBuilder builder : brandBuilders) {
+
+ String brandName = builder.getBrandName();
+ log.debug("Retrieving already imported sources for brand %s", brandName);
+ Set ingestions = ingestionRepo.findAllByBrandName(brandName);
+ Set sourcesToOmit = ingestions.stream().map(idto -> idto.getSource()).collect(Collectors.toSet());
+
+ if (log.isDebugEnabled()) {
+ String omitedSources = sourcesToOmit.stream().collect(Collectors.joining(", "));
+ log.debug("Creating all non imported catalogs, excluding [%d] ", omitedSources);
+ }
+
+ log.debug("Creating brands for %s ", brandName);
+ Map brands = builder.createBrands(sourcesToOmit);
+ for (String source : brands.keySet()) {
+
+ log.debug("Ingesting %s's brand from source %s", brandName, source);
+ Ingestion ingestion = strategy.ingest(source, brands.get(source));
+ ingestionRepo.save(ingestion);
+ }
+ }
+
+ }
+
+ public IngestStrategy getStrategy() {
+ return strategy;
+ }
+
+ public void setStrategy(IngestStrategy strategy) {
+ this.strategy = strategy;
+ }
+
+ public IngestionRepository getIngestionRepo() {
+ return ingestionRepo;
+ }
+
+ public void setIngestionRepo(IngestionRepository ingestionRepo) {
+ this.ingestionRepo = ingestionRepo;
+ }
+
+ public List getBrandBuilders() {
+ return brandBuilders;
+ }
+
+ public void setBrandBuilders(List brandBuilders) {
+ this.brandBuilders = brandBuilders;
+ }
+
+}
diff --git a/cars/src/main/resources/application.yml b/cars/src/main/resources/application.yml
index 436f591..00064cc 100644
--- a/cars/src/main/resources/application.yml
+++ b/cars/src/main/resources/application.yml
@@ -1,14 +1,64 @@
-cars:
- ford:
- ingester:
- runCron: '0 * * ? * *' #each minute
-
spring:
+ profiles.active: dev
+ main.allow-bean-definition-overriding : true
+
+---
+spring:
+ profiles: dev
+
datasource:
- url: jdbc:h2:mem:carsdb
+ url: jdbc:h2:mem:carsdb;DB_CLOSE_ON_EXIT=FALSE
driverClassName: org.h2.Driver
username: sa
- password:
+ password: null
+
jpa:
- database-platform: 'org.hibernate.dialect.H2Dialect'
- h2.console.enabled: true
+ database-platform: org.hibernate.dialect.H2Dialect
+ properties:
+ hibernate:
+ ddl-auto: create-drop
+ show_sql: false
+ use_sql_comments: false
+ format_sql: false
+ h2:
+ console:
+ enabled: true
+ path: /console
+ settings:
+ trace: false
+ web-allow-others: false
+
+cars:
+ ford:
+ ingester:
+ runCron: 0 * * * * ?
+
+logging:
+ file: logs/dev_app.log
+ pattern:
+ console: '%d %-5level %logger : %msg%n'
+ file: '%d %-5level [%thread] %logger : %msg%n'
+ level:
+ org.springframework.web: DEBUG
+ guru.springframework.controllers: DEBUG
+ org.hibernate: DEBUG
+
+---
+spring:
+ profiles: prod
+
+cars:
+ ford:
+ ingester:
+ runCron: 0 * * ? * *
+
+logging:
+ file: logs/dev_app.log
+ pattern:
+ console: '%d %-5level %logger : %msg%n'
+ file: '%d %-5level [%thread] %logger : %msg%n'
+ level:
+ org.springframework.web: WARN
+ guru.springframework.controllers: WARN
+ org.hibernate: WARN
+
\ No newline at end of file
diff --git a/cars/src/test/java/com/mooveit/cars/CarsApplicationTests.java b/cars/src/test/java/com/mooveit/cars/CarsApplicationTests.java
deleted file mode 100644
index 426b96d..0000000
--- a/cars/src/test/java/com/mooveit/cars/CarsApplicationTests.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.mooveit.cars;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.test.context.junit4.SpringRunner;
-
-@RunWith(SpringRunner.class)
-@SpringBootTest
-public class CarsApplicationTests {
-
- @Test
- public void contextLoads() {
- }
-
-}
diff --git a/cars/src/test/java/com/mooveit/cars/ingestion/FordBrandBuilderTest.java b/cars/src/test/java/com/mooveit/cars/ingestion/FordBrandBuilderTest.java
new file mode 100644
index 0000000..8bfa7ab
--- /dev/null
+++ b/cars/src/test/java/com/mooveit/cars/ingestion/FordBrandBuilderTest.java
@@ -0,0 +1,75 @@
+package com.mooveit.cars.ingestion;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.HashSet;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import org.junit.Test;
+
+import com.mooveit.cars.domain.Brand;
+import com.mooveit.cars.domain.Engine;
+
+public class FordBrandBuilderTest {
+
+ @Test
+ public void testCreateBrandFromXmlFile() {
+
+ FordBrandBuilder builder = new FordBrandBuilder();
+
+ InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("ford-test-example.xml");
+
+ Brand brand = builder.createBrandFromXml(inputStream);
+ assertNotNull(brand);
+
+ brand.getSpecifications().forEach(x -> assertNotNull(x.getEngine()));
+ brand.getSpecifications().forEach(x -> assertNotNull(x.getWheel()));
+ }
+
+ @Test
+ public void testCreateBrandFromXmlString() {
+
+ FordBrandBuilder builder = new FordBrandBuilder();
+
+ String xml = String.join("/n",
+ "" + "", "",
+ "", "");
+
+ InputStream inputStream = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8));
+
+ Brand brand = builder.createBrandFromXml(inputStream);
+ assertNotNull(brand);
+
+ Engine eng = brand.getSpecifications().findFirst().get().getEngine();
+
+ // Empty Engine
+ assertNotNull(eng);
+ assertNull(eng.getType());
+ assertNull(eng.getPower());
+
+ // Null Wheel
+ assertNull(brand.getSpecifications().findFirst().get().getWheel());
+
+ }
+
+ @Test
+ public void testGetSourceXMLFiles() {
+
+ FordBrandBuilder builder = new FordBrandBuilder();
+
+ assertTrue(Pattern.compile(String.format("^(.+)%sford-(.+).xml$", File.separator))
+ .matcher("/User/nicolas/ford-anytexthere.xml").matches());
+
+ List testFiles = builder.getSourceXMLFiles(new HashSet<>());
+ assertTrue(testFiles.size() == 1);
+
+ }
+
+}
diff --git a/cars/src/test/java/com/mooveit/cars/repositories/AbtractSpecRepositoryTest.java b/cars/src/test/java/com/mooveit/cars/repositories/AbtractSpecRepositoryTest.java
new file mode 100644
index 0000000..d3cc6ed
--- /dev/null
+++ b/cars/src/test/java/com/mooveit/cars/repositories/AbtractSpecRepositoryTest.java
@@ -0,0 +1,70 @@
+package com.mooveit.cars.repositories;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.Optional;
+
+import javax.transaction.Transactional;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import com.mooveit.cars.domain.AbstractSpec;
+import com.mooveit.cars.domain.Brand;
+import com.mooveit.cars.domain.Engine;
+import com.mooveit.cars.domain.EngineType;
+import com.mooveit.cars.domain.Modification;
+import com.mooveit.cars.domain.Specification;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+@Transactional
+public class AbtractSpecRepositoryTest {
+
+ @Autowired
+ private SpecificationRepository specRepository;
+
+ @Autowired
+ private AbstractSpecRepository abstractRepository;
+
+ @Autowired
+ private BrandRepository brandRepository;
+
+ private Specification createCarSpec(boolean withModifications) {
+ Brand brand = new Brand("Ford");
+ brand = brandRepository.save(brand);
+
+ Engine eng = new Engine(100, EngineType.HYBRID);
+ Specification spec = new Specification(brand,"Specification A", 1994, 1996, "subcompact", eng, null);
+
+ if (withModifications){
+ spec.addModification(new Modification("Modification A.1", 1900, 1950, "high-line", null, null));
+ }
+ return specRepository.save(spec);
+ }
+
+
+ @Test
+ public void testSaveAndDeleteSpecificationWitnModifications() throws Exception {
+ Specification savedSpec = createCarSpec(true);
+
+ // Check collection was persisted using cascade strategy
+ assertTrue(savedSpec.hasModifications());
+ Modification savedModif = savedSpec.getModification(0);
+ assertNotNull(savedModif.getId());
+
+ Iterable iterSpecs = abstractRepository.findAllById(Arrays.asList(savedSpec.getId(), savedModif.getId()));
+ for (AbstractSpec abstractSpec : iterSpecs) {
+ assertNotNull(abstractSpec.getId());
+ }
+
+ }
+
+
+}
diff --git a/cars/src/test/java/com/mooveit/cars/repositories/AllRepositoryTests.java b/cars/src/test/java/com/mooveit/cars/repositories/AllRepositoryTests.java
new file mode 100644
index 0000000..55a9437
--- /dev/null
+++ b/cars/src/test/java/com/mooveit/cars/repositories/AllRepositoryTests.java
@@ -0,0 +1,14 @@
+package com.mooveit.cars.repositories;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
+
+@RunWith(Suite.class)
+@SuiteClasses({ EngineRepositoryTest.class,
+ SpecificationRepositoryTest.class,
+ WheelRepositoryTest.class,
+ IngestionRepositoryTest.class})
+public class AllRepositoryTests {
+
+}
diff --git a/cars/src/test/java/com/mooveit/cars/repositories/EngineRepositoryTest.java b/cars/src/test/java/com/mooveit/cars/repositories/EngineRepositoryTest.java
new file mode 100644
index 0000000..47abd9f
--- /dev/null
+++ b/cars/src/test/java/com/mooveit/cars/repositories/EngineRepositoryTest.java
@@ -0,0 +1,53 @@
+package com.mooveit.cars.repositories;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Optional;
+
+import javax.transaction.Transactional;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import com.mooveit.cars.domain.Engine;
+import com.mooveit.cars.domain.EngineType;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+@Transactional
+public class EngineRepositoryTest {
+
+ @Autowired
+ private EngineRepository engineRepository;
+
+ @Test
+ public void testSaveAndDeleteSingleEngine() throws Exception {
+
+ Engine savedEngine = engineRepository.save( new Engine(1400, EngineType.GAS));
+
+ Long entityId = savedEngine.getId();
+ assertTrue(engineRepository.existsById(entityId));
+ assertTrue(engineRepository.findById(entityId).isPresent());
+
+ engineRepository.deleteById(savedEngine.getId());
+
+ assertFalse(engineRepository.existsById(entityId));
+ assertFalse(engineRepository.findById(entityId).isPresent());
+
+ }
+
+ @Test
+ public void testFindBySizeAndType() throws Exception {
+
+ engineRepository.save( new Engine(1400, EngineType.GAS));
+
+ Optional resultList = engineRepository.findByPower(1400);
+ assertTrue(resultList.isPresent());
+
+ }
+
+}
diff --git a/cars/src/test/java/com/mooveit/cars/repositories/IngestionRepositoryTest.java b/cars/src/test/java/com/mooveit/cars/repositories/IngestionRepositoryTest.java
new file mode 100644
index 0000000..31834e2
--- /dev/null
+++ b/cars/src/test/java/com/mooveit/cars/repositories/IngestionRepositoryTest.java
@@ -0,0 +1,66 @@
+package com.mooveit.cars.repositories;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Date;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.transaction.Transactional;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import com.mooveit.cars.domain.Brand;
+import com.mooveit.cars.domain.Ingestion;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+@Transactional
+public class IngestionRepositoryTest {
+
+ @Autowired
+ private IngestionRepository ingestionRepository;
+
+ @Autowired
+ private BrandRepository brandRepository;
+
+ @Test
+ public void testSaveAndDeleteSingleIngestion() throws Exception {
+
+ Brand brand = brandRepository.save(new Brand("Ford"));
+ Ingestion ingestion = new Ingestion(brand, new Date(), "fileName.xml", 10L, 10L);
+
+ Ingestion savedIng = ingestionRepository.save(ingestion);
+
+ Long entityId = savedIng.getId();
+ assertTrue(ingestionRepository.existsById(entityId));
+ assertTrue(ingestionRepository.findById(entityId).isPresent());
+
+ ingestionRepository.delete(savedIng);
+
+ assertFalse(ingestionRepository.existsById(entityId));
+ assertFalse(ingestionRepository.findById(entityId).isPresent());
+
+ }
+
+ @Test
+ public void testFindAllByBrandName() throws Exception {
+
+ Brand brand = brandRepository.save(new Brand("Ford"));
+ Ingestion ingestion = new Ingestion(brand, new Date(), "fileName.xml", 10L, 10L);
+
+ Ingestion savedIng = ingestionRepository.save(ingestion);
+
+ Set ingestionSet = ingestionRepository.findAllByBrandName("Ford");
+ assertFalse(ingestionSet.isEmpty());
+ assertTrue(ingestionSet.stream().map(i -> i.getSource()).collect(Collectors.toSet())
+ .contains(savedIng.getSource()));
+
+ }
+
+}
diff --git a/cars/src/test/java/com/mooveit/cars/repositories/SpecificationRepositoryTest.java b/cars/src/test/java/com/mooveit/cars/repositories/SpecificationRepositoryTest.java
new file mode 100644
index 0000000..c1788dd
--- /dev/null
+++ b/cars/src/test/java/com/mooveit/cars/repositories/SpecificationRepositoryTest.java
@@ -0,0 +1,92 @@
+package com.mooveit.cars.repositories;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import javax.transaction.Transactional;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import com.mooveit.cars.domain.Brand;
+import com.mooveit.cars.domain.Engine;
+import com.mooveit.cars.domain.EngineType;
+import com.mooveit.cars.domain.Modification;
+import com.mooveit.cars.domain.Specification;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+@Transactional
+public class SpecificationRepositoryTest {
+
+ @Autowired
+ private SpecificationRepository specRepository;
+
+ @Autowired
+ private BrandRepository brandRepository;
+
+ private Specification createCarSpec(boolean withModifications) {
+ Brand brand = new Brand("Ford");
+ brand = brandRepository.save(brand);
+
+ Engine eng = new Engine(100, EngineType.HYBRID);
+ Specification spec = new Specification(brand,"Specification A", 1994, 1996, "subcompact", eng, null);
+
+ if (withModifications){
+ spec.addModification(new Modification("Modification A.1", 1900, 1950, "high-line", null, null));
+ }
+ return specRepository.save(spec);
+ }
+
+ @Test
+ public void testSaveAndDeleteSingleSpec() throws Exception {
+ Specification savedSpec = createCarSpec(false);
+
+ Long entityId = savedSpec.getId();
+ assertTrue(specRepository.existsById(entityId));
+ assertTrue(specRepository.findById(entityId).isPresent());
+
+ specRepository.deleteById(entityId);
+
+ assertFalse(specRepository.existsById(entityId));
+ assertFalse(specRepository.findById(entityId).isPresent());
+
+ }
+
+
+ @Test
+ public void testSaveAndDeleteSpecificationWitnModifications() throws Exception {
+ Specification savedSpec = createCarSpec(true);
+
+ // Check collection was persisted using cascade strategy
+ assertTrue(savedSpec.hasModifications());
+ Modification savedModif = savedSpec.getModification(0);
+ assertNotNull(savedModif.getId());
+
+ // Check all entities are removed using cascade strategy
+ specRepository.deleteById(savedSpec.getId());
+ assertFalse(specRepository.existsById(savedSpec.getId()));
+ assertFalse(specRepository.existsById(savedModif.getId()));
+ }
+
+ @Test
+ public void testFindByName() throws Exception {
+ Specification savedSpec = createCarSpec(false);
+
+ Iterable result = specRepository.findByName(savedSpec.getName());
+ assertTrue(result.iterator().hasNext());
+ }
+
+ @Test
+ public void testFindByEngineType() throws Exception {
+ Specification savedSpec = createCarSpec(false);
+
+ Iterable result = specRepository.findByEngineType(savedSpec.getEngine().getType());
+ assertTrue(result.iterator().hasNext());
+ }
+
+}
diff --git a/cars/src/test/java/com/mooveit/cars/repositories/WheelRepositoryTest.java b/cars/src/test/java/com/mooveit/cars/repositories/WheelRepositoryTest.java
new file mode 100644
index 0000000..4a2791c
--- /dev/null
+++ b/cars/src/test/java/com/mooveit/cars/repositories/WheelRepositoryTest.java
@@ -0,0 +1,52 @@
+package com.mooveit.cars.repositories;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Optional;
+
+import javax.transaction.Transactional;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import com.mooveit.cars.domain.Wheel;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+@Transactional
+public class WheelRepositoryTest {
+
+ @Autowired
+ private WheelRepository wheelRepository;
+
+ @Test
+ public void testSaveAndDeleteSingleWheel() throws Exception {
+
+ Wheel savedWheel = wheelRepository.save(new Wheel("R15", "STEEL"));
+
+ Long entityId = savedWheel.getId();
+ assertTrue(wheelRepository.existsById(entityId));
+ assertTrue(wheelRepository.findById(entityId).isPresent());
+
+ wheelRepository.deleteById(savedWheel.getId());
+
+ assertFalse(wheelRepository.existsById(entityId));
+ assertFalse(wheelRepository.findById(entityId).isPresent());
+
+ }
+
+ @Test
+ public void testFindBySizeAndType() throws Exception {
+
+ wheelRepository.save(new Wheel("R15", "STEEL"));
+
+ Optional resultList = wheelRepository.findBySizeAndType("R15", "STEEL");
+ assertTrue(resultList.isPresent());
+
+ }
+
+}
diff --git a/cars/src/test/java/com/mooveit/cars/tasks/IngesterTaskTest.java b/cars/src/test/java/com/mooveit/cars/tasks/IngesterTaskTest.java
new file mode 100644
index 0000000..1bb3106
--- /dev/null
+++ b/cars/src/test/java/com/mooveit/cars/tasks/IngesterTaskTest.java
@@ -0,0 +1,95 @@
+package com.mooveit.cars.tasks;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.eq;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import com.mooveit.cars.domain.Brand;
+import com.mooveit.cars.domain.Ingestion;
+import com.mooveit.cars.ingestion.BrandBuilder;
+import com.mooveit.cars.ingestion.MergeBrandsIngestStrategy;
+import com.mooveit.cars.repositories.IngestionDTO;
+import com.mooveit.cars.repositories.IngestionRepository;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class IngesterTaskTest {
+
+ @Autowired
+ IngesterTask task;
+
+ @MockBean
+ MergeBrandsIngestStrategy strategy;
+
+ @MockBean
+ IngestionRepository ingestRepository;
+
+ @Autowired
+ List brandBuilders;
+
+
+ private static String testSource = "sample-catalog.xml";
+
+ private static String testBrandName = "Test Brand";
+
+ private static Brand brandMock;
+
+ @TestConfiguration
+ static class TestContextConfiguration {
+
+ @Bean
+ public List brandBuilders() {
+
+ // Create Mock Builder
+ Map brands = new HashMap();
+ brands.put(testSource, brandMock);
+
+ BrandBuilder bb = mock(BrandBuilder.class);
+ when(bb.getBrandName()).thenReturn(testBrandName);
+ when(bb.createBrands(any())).thenReturn(brands);
+
+ return Arrays.asList(bb);
+ }
+ }
+
+ @Before
+ public void setUp(){
+
+ }
+
+ @Test
+ public void testIngestFiles() {
+
+ // Empty source to omit
+ when(ingestRepository.findAllByBrandName(testBrandName)).thenReturn(new HashSet());
+ when(strategy.ingest(testSource, brandMock)).thenReturn(mock(Ingestion.class));
+
+ task.ingestBrands();
+
+ verify(ingestRepository, times(1)).findAllByBrandName(eq(testBrandName));
+ verify(strategy, times(1)).ingest(eq(testSource), eq(brandMock));
+ verify(ingestRepository, times(1)).save(any(Ingestion.class));
+
+ }
+
+}
+
diff --git a/cars/src/test/resources/ford-test-example.xml b/cars/src/test/resources/ford-test-example.xml
new file mode 100644
index 0000000..1c375a6
--- /dev/null
+++ b/cars/src/test/resources/ford-test-example.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file