diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 0000000..5008ddf
Binary files /dev/null and b/.DS_Store differ
diff --git a/pom.xml b/pom.xml
index 17fd7ad..2d416d4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -16,6 +16,8 @@
1.8
+ 1.8
+ 1.8
@@ -25,12 +27,17 @@
org.springframework.boot
- spring-boot-starter-data-jdbc
+ spring-boot-starter-data-jpa
org.springframework.boot
spring-boot-starter-web
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
org.flywaydb
@@ -49,6 +56,22 @@
spring-boot-starter-test
test
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+
+
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-yaml
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
@@ -57,6 +80,14 @@
org.springframework.boot
spring-boot-maven-plugin
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 1.8
+ 1.8
+
+
diff --git a/src/main/java/com/zoomcare/candidatechallenge/constants/ApplicationConstants.java b/src/main/java/com/zoomcare/candidatechallenge/constants/ApplicationConstants.java
new file mode 100644
index 0000000..d67e094
--- /dev/null
+++ b/src/main/java/com/zoomcare/candidatechallenge/constants/ApplicationConstants.java
@@ -0,0 +1,14 @@
+package com.zoomcare.candidatechallenge.constants;
+
+/**
+ * Message values for application
+ */
+public final class ApplicationConstants {
+
+ /** Personalized message for employee not found error **/
+ public static final String EMPLOYEE_NOT_FOUND = "Employee ID %d not found";
+
+ /** Personalized message for employee ID required **/
+ public static final String EMPLOYEE_ID_REQUIRED = "Employee ID required";
+
+}
diff --git a/src/main/java/com/zoomcare/candidatechallenge/dao/EmployeeDAO.java b/src/main/java/com/zoomcare/candidatechallenge/dao/EmployeeDAO.java
new file mode 100644
index 0000000..83e8c7b
--- /dev/null
+++ b/src/main/java/com/zoomcare/candidatechallenge/dao/EmployeeDAO.java
@@ -0,0 +1,12 @@
+package com.zoomcare.candidatechallenge.dao;
+
+import org.springframework.data.repository.CrudRepository;
+
+import com.zoomcare.candidatechallenge.model.Employee;
+
+/**
+ * DAO for employee
+ */
+public interface EmployeeDAO extends CrudRepository {
+
+}
diff --git a/src/main/java/com/zoomcare/candidatechallenge/dto/EmployeeDTO.java b/src/main/java/com/zoomcare/candidatechallenge/dto/EmployeeDTO.java
new file mode 100644
index 0000000..4a8804a
--- /dev/null
+++ b/src/main/java/com/zoomcare/candidatechallenge/dto/EmployeeDTO.java
@@ -0,0 +1,46 @@
+package com.zoomcare.candidatechallenge.dto;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+
+/**
+ * DTO to return employee
+ */
+@JsonInclude(Include.NON_NULL)
+public abstract class EmployeeDTO {
+
+ /** Employee ID */
+ private Long id;
+
+ /** Properties map **/
+ private Map properties;
+
+ public EmployeeDTO() {
+ properties = new HashMap<>();
+ }
+
+ public EmployeeDTO(Long id, Map properties) {
+ this.id = id;
+ this.properties = properties;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public Map getProperties() {
+ return properties;
+ }
+
+ public void setProperties(Map properties) {
+ this.properties = properties;
+ }
+
+}
diff --git a/src/main/java/com/zoomcare/candidatechallenge/dto/EmployeeInfoDTO.java b/src/main/java/com/zoomcare/candidatechallenge/dto/EmployeeInfoDTO.java
new file mode 100644
index 0000000..b7e6689
--- /dev/null
+++ b/src/main/java/com/zoomcare/candidatechallenge/dto/EmployeeInfoDTO.java
@@ -0,0 +1,50 @@
+package com.zoomcare.candidatechallenge.dto;
+
+import java.util.Map;
+
+/**
+ * DTO to return employee with supervisor information
+ */
+public class EmployeeInfoDTO extends EmployeeDTO {
+
+ /** Supervisor information **/
+ private EmployeeInfoDTO supervisor;
+
+ /** Error message **/
+ private String error;
+
+ public EmployeeInfoDTO() {
+ super();
+ }
+
+ public EmployeeInfoDTO(String error) {
+ this.error = error;
+ this.setProperties(null);
+ }
+
+ public EmployeeInfoDTO(Long id, EmployeeInfoDTO supervisor, Map properties) {
+ super(id, properties);
+ this.supervisor = supervisor;
+ }
+
+ public String toString() {
+ return String.format("EmployeeInfo[id: %d, supervisor: %d]", getId(), null != supervisor ? supervisor.getId() : null);
+ }
+
+ public EmployeeInfoDTO getSupervisor() {
+ return supervisor;
+ }
+
+ public void setSupervisor(EmployeeInfoDTO supervisor) {
+ this.supervisor = supervisor;
+ }
+
+ public String getError() {
+ return error;
+ }
+
+ public void setError(String error) {
+ this.error = error;
+ }
+
+}
diff --git a/src/main/java/com/zoomcare/candidatechallenge/dto/OrganizationStructureDTO.java b/src/main/java/com/zoomcare/candidatechallenge/dto/OrganizationStructureDTO.java
new file mode 100644
index 0000000..c230858
--- /dev/null
+++ b/src/main/java/com/zoomcare/candidatechallenge/dto/OrganizationStructureDTO.java
@@ -0,0 +1,35 @@
+package com.zoomcare.candidatechallenge.dto;
+
+import java.util.Map;
+
+/**
+ * DTO to create organization estructure
+ */
+public class OrganizationStructureDTO extends EmployeeDTO {
+
+ /** Supervisor ID **/
+ private Long supervisor;
+
+ public OrganizationStructureDTO() {
+ super();
+ }
+
+ public OrganizationStructureDTO(Long id, Map properties, Long supervisor) {
+ super(id, properties);
+ this.supervisor = supervisor;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("OrganizationStructure[id; %d, supervidor: %d]", getId(), supervisor);
+ }
+
+ public Long getSupervisor() {
+ return supervisor;
+ }
+
+ public void setSupervisor(Long supervisor) {
+ this.supervisor = null != supervisor ? supervisor : -1l;
+ }
+
+}
diff --git a/src/main/java/com/zoomcare/candidatechallenge/dto/TopLevelListDTO.java b/src/main/java/com/zoomcare/candidatechallenge/dto/TopLevelListDTO.java
new file mode 100644
index 0000000..0e83d57
--- /dev/null
+++ b/src/main/java/com/zoomcare/candidatechallenge/dto/TopLevelListDTO.java
@@ -0,0 +1,37 @@
+package com.zoomcare.candidatechallenge.dto;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * DTO to create top level list organization
+ */
+public class TopLevelListDTO extends EmployeeDTO {
+
+ /** underling employees **/
+ private List underling;
+
+ public TopLevelListDTO() {
+ super();
+ underling = new ArrayList<>();
+ }
+
+ public TopLevelListDTO(Long id, Map properties, List underling) {
+ super(id, properties);
+ this.underling = underling;
+ }
+
+ public String toString() {
+ return String.format("TopLevelLis[id: %d, underling: %d]", getId(), underling.size());
+ }
+
+ public List getUnderling() {
+ return underling;
+ }
+
+ public void setUnderling(List underling) {
+ this.underling = underling;
+ }
+
+}
diff --git a/src/main/java/com/zoomcare/candidatechallenge/exception/DataRequiredException.java b/src/main/java/com/zoomcare/candidatechallenge/exception/DataRequiredException.java
new file mode 100644
index 0000000..6786e78
--- /dev/null
+++ b/src/main/java/com/zoomcare/candidatechallenge/exception/DataRequiredException.java
@@ -0,0 +1,18 @@
+package com.zoomcare.candidatechallenge.exception;
+
+import org.springframework.http.HttpStatus;
+
+/**
+ * Exception to report data required
+ */
+public class DataRequiredException extends EmployeeException {
+
+ private static final long serialVersionUID = 1L;
+
+ public DataRequiredException(String message) {
+ super();
+ this.message = message;
+ this.httpStatus = HttpStatus.BAD_REQUEST;
+ }
+
+}
diff --git a/src/main/java/com/zoomcare/candidatechallenge/exception/EmployeeException.java b/src/main/java/com/zoomcare/candidatechallenge/exception/EmployeeException.java
new file mode 100644
index 0000000..1374c6c
--- /dev/null
+++ b/src/main/java/com/zoomcare/candidatechallenge/exception/EmployeeException.java
@@ -0,0 +1,36 @@
+package com.zoomcare.candidatechallenge.exception;
+
+import org.springframework.http.HttpStatus;
+
+/**
+ * Basic exception for employee operations
+ */
+public class EmployeeException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ /** Error message **/
+ protected String message;
+
+ /** Status response **/
+ protected HttpStatus httpStatus;
+
+ public EmployeeException() {
+ super();
+ }
+
+ public EmployeeException(String message, HttpStatus httpStatus) {
+ super();
+ this.message = message;
+ this.httpStatus = httpStatus;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public HttpStatus getHttpStatus() {
+ return httpStatus;
+ }
+
+}
diff --git a/src/main/java/com/zoomcare/candidatechallenge/exception/EmployeeNotFoundException.java b/src/main/java/com/zoomcare/candidatechallenge/exception/EmployeeNotFoundException.java
new file mode 100644
index 0000000..4dee046
--- /dev/null
+++ b/src/main/java/com/zoomcare/candidatechallenge/exception/EmployeeNotFoundException.java
@@ -0,0 +1,17 @@
+package com.zoomcare.candidatechallenge.exception;
+
+import org.springframework.http.HttpStatus;
+
+import com.zoomcare.candidatechallenge.constants.ApplicationConstants;
+
+public class EmployeeNotFoundException extends EmployeeException {
+
+ private static final long serialVersionUID = 1L;
+
+ public EmployeeNotFoundException(Long emplooyeeId) {
+ super();
+ message = String.format(ApplicationConstants.EMPLOYEE_NOT_FOUND, emplooyeeId);
+ httpStatus = HttpStatus.NOT_FOUND;
+ }
+
+}
diff --git a/src/main/java/com/zoomcare/candidatechallenge/model/Employee.java b/src/main/java/com/zoomcare/candidatechallenge/model/Employee.java
new file mode 100644
index 0000000..efc820e
--- /dev/null
+++ b/src/main/java/com/zoomcare/candidatechallenge/model/Employee.java
@@ -0,0 +1,67 @@
+package com.zoomcare.candidatechallenge.model;
+
+import java.io.Serializable;
+import java.util.List;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+
+/**
+ * Entity for table Employee
+ */
+@Entity
+@Table(name="EMPLOYEE")
+public class Employee implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @Id
+ private Long id;
+
+ private Long supervisorId;
+
+ @OneToMany(mappedBy = "employee", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
+ private List properties;
+
+ public Employee() { }
+
+ public Employee(Long id, Long supervisorId, List properties) {
+ this.id = id;
+ this.supervisorId = supervisorId;
+ this.properties = properties;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("Employee[id: %d, supervisorId: %d]", id, supervisorId);
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public Long getSupervisorId() {
+ return supervisorId;
+ }
+
+ public void setSupervisorId(Long supervisorId) {
+ this.supervisorId = supervisorId;
+ }
+
+ public List getProperties() {
+ return properties;
+ }
+
+ public void setProperties(List properties) {
+ this.properties = properties;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/zoomcare/candidatechallenge/model/Properties.java b/src/main/java/com/zoomcare/candidatechallenge/model/Properties.java
new file mode 100644
index 0000000..293e998
--- /dev/null
+++ b/src/main/java/com/zoomcare/candidatechallenge/model/Properties.java
@@ -0,0 +1,75 @@
+package com.zoomcare.candidatechallenge.model;
+
+import java.io.Serializable;
+
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+/**
+ * Entity for table Property
+ */
+@Entity
+@Table(name="PROPERTY")
+public class Properties implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @Id
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "employeeId")
+ private Employee employee;
+
+ @Id
+ private String key;
+
+ @Id
+ private String value;
+
+ public Properties() { }
+
+ public Properties(String key, String value) {
+ super();
+ this.key = key;
+ this.value = value;
+ }
+
+ public Properties(Employee employee, String key, String value) {
+ this.employee = employee;
+ this.key = key;
+ this.value = value;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("Properties[employeeId: %d, key: %s, value: %s]", employee.getId(), key, value);
+ }
+
+ public Employee getEmployee() {
+ return employee;
+ }
+
+ public void setEmployee(Employee employee) {
+ this.employee = employee;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public void setKey(String key) {
+ this.key = key;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/zoomcare/candidatechallenge/repository/EmployeeRepository.java b/src/main/java/com/zoomcare/candidatechallenge/repository/EmployeeRepository.java
new file mode 100644
index 0000000..b9cdfbc
--- /dev/null
+++ b/src/main/java/com/zoomcare/candidatechallenge/repository/EmployeeRepository.java
@@ -0,0 +1,55 @@
+package com.zoomcare.candidatechallenge.repository;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Repository;
+
+import com.zoomcare.candidatechallenge.constants.ApplicationConstants;
+import com.zoomcare.candidatechallenge.dao.EmployeeDAO;
+import com.zoomcare.candidatechallenge.exception.DataRequiredException;
+import com.zoomcare.candidatechallenge.exception.EmployeeException;
+import com.zoomcare.candidatechallenge.model.Employee;
+
+/**
+ * Repository for employees
+ */
+@Repository
+public class EmployeeRepository {
+
+ private static final Logger LOG = LoggerFactory.getLogger(EmployeeRepository.class);
+
+ private final EmployeeDAO employeeDAO;
+
+ @Autowired
+ public EmployeeRepository(EmployeeDAO employeeDAO) {
+ this.employeeDAO = employeeDAO;
+ }
+
+ /**
+ * Get employee information
+ * @param id - Employee ID
+ * @return Employee information
+ * @throws EmployeeException
+ */
+ public Employee getEmployeeById(Long id) throws EmployeeException {
+
+ if (null == id) {
+ LOG.error("Employee ID is null");
+ throw new DataRequiredException(ApplicationConstants.EMPLOYEE_ID_REQUIRED);
+ }
+
+ LOG.info(String.format("Getting Employee by Id: %d", id));
+ return employeeDAO.findById(id).orElse(null);
+ }
+
+ /**
+ * Get all employees
+ * @return Employees
+ */
+ public Iterable getAllEmployees(){
+ LOG.info("Get all employees");
+ return employeeDAO.findAll();
+ }
+
+}
diff --git a/src/main/java/com/zoomcare/candidatechallenge/resource/EmployeeResource.java b/src/main/java/com/zoomcare/candidatechallenge/resource/EmployeeResource.java
new file mode 100644
index 0000000..add9d50
--- /dev/null
+++ b/src/main/java/com/zoomcare/candidatechallenge/resource/EmployeeResource.java
@@ -0,0 +1,70 @@
+package com.zoomcare.candidatechallenge.resource;
+
+import java.util.List;
+
+import javax.validation.constraints.NotBlank;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+
+import com.zoomcare.candidatechallenge.dto.EmployeeInfoDTO;
+import com.zoomcare.candidatechallenge.dto.TopLevelListDTO;
+import com.zoomcare.candidatechallenge.exception.EmployeeException;
+import com.zoomcare.candidatechallenge.service.EmployeeService;
+
+/**
+ * Employee APIs
+ */
+@RestController
+@RequestMapping("/v1")
+public class EmployeeResource {
+
+ private static final Logger LOG = LoggerFactory.getLogger(EmployeeResource.class);
+
+ private final EmployeeService employeeService;
+
+ @Autowired
+ public EmployeeResource(EmployeeService employeeService) {
+ this.employeeService = employeeService;
+ }
+
+ /**
+ * Return employee information
+ * @param id - Employee ID
+ * @return Employee information
+ */
+ @GetMapping(value = "/employee/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
+ public ResponseEntity getEmployeeInformation(@PathVariable("id") @NotBlank Long id) {
+
+ LOG.info(String.format("Get employee information for employee ID: %d", id));
+ try {
+ EmployeeInfoDTO employee = employeeService.getEmployeeInformation(id);
+ LOG.info(String.format("Employee found: %s", employee.toString()));
+ return ResponseEntity.status(HttpStatus.OK).body(employee);
+
+ } catch (EmployeeException e) {
+ LOG.error(e.getMessage());
+ return ResponseEntity.status(e.getHttpStatus()).body(new EmployeeInfoDTO(e.getMessage()));
+ }
+ }
+
+ /**
+ * Get all employees
+ * @return Employee list
+ */
+ @GetMapping(value = "/employee/all", produces = MediaType.APPLICATION_JSON_VALUE)
+ public ResponseEntity> getAllEmployees() {
+ List employeeList = employeeService.getAllEmployees();
+ return ResponseEntity.status(HttpStatus.OK).body(employeeList);
+ }
+
+
+}
diff --git a/src/main/java/com/zoomcare/candidatechallenge/service/EmployeeService.java b/src/main/java/com/zoomcare/candidatechallenge/service/EmployeeService.java
new file mode 100644
index 0000000..335e087
--- /dev/null
+++ b/src/main/java/com/zoomcare/candidatechallenge/service/EmployeeService.java
@@ -0,0 +1,186 @@
+package com.zoomcare.candidatechallenge.service;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.zoomcare.candidatechallenge.dto.EmployeeInfoDTO;
+import com.zoomcare.candidatechallenge.dto.OrganizationStructureDTO;
+import com.zoomcare.candidatechallenge.dto.TopLevelListDTO;
+import com.zoomcare.candidatechallenge.exception.EmployeeException;
+import com.zoomcare.candidatechallenge.exception.EmployeeNotFoundException;
+import com.zoomcare.candidatechallenge.model.Employee;
+import com.zoomcare.candidatechallenge.repository.EmployeeRepository;
+
+/**
+ * Service for employees
+ */
+@Service
+public class EmployeeService {
+
+ private static final Logger LOG = LoggerFactory.getLogger(EmployeeService.class);
+
+ private final EmployeeRepository employeeRepository;
+
+ @Autowired
+ public EmployeeService(EmployeeRepository employeeRepository) {
+ this.employeeRepository = employeeRepository;
+ }
+
+ /**
+ * Get employee information
+ * @param id - Employee ID
+ * @return Employee information
+ * @throws EmployeeException
+ */
+ public EmployeeInfoDTO getEmployeeInformation(Long id) throws EmployeeException {
+ LOG.info("Get employee information");
+ Employee employee = getEmployeeById(id);
+
+ if (null == employee) {
+ throw new EmployeeNotFoundException(id);
+ }
+
+ EmployeeInfoDTO employeeDTO = convertEmployeeToEmployeeDTO(employee);
+
+ if (null != employee.getSupervisorId()) {
+ LOG.info("Get supervisor id: %d", employee.getSupervisorId());
+ Employee supervisor = getEmployeeById(employee.getSupervisorId());
+ employeeDTO.setSupervisor(convertEmployeeToEmployeeDTO(supervisor));
+ }
+ return employeeDTO;
+ }
+
+ /**
+ * Get all employees
+ * @return Employee list
+ */
+ public List getAllEmployees() {
+
+ Iterable employeeIterable = employeeRepository.getAllEmployees();
+ List organizationList = new ArrayList<>();
+ Map> employeeOrg = new HashMap<>();
+
+ employeeIterable.forEach(employee -> {
+ organizationList.add(convertEmployeeToOrganizationDTO(employee));
+ });
+
+ organizationList.stream()
+ .collect(Collectors.groupingBy(OrganizationStructureDTO::getSupervisor))
+ .values()
+ .stream()
+ .forEach(emp -> {
+ employeeOrg.put(emp.get(0).getSupervisor(), emp);
+ });
+
+
+ return mapOrganization(employeeOrg);
+ }
+
+ /**
+ * Get employee by ID
+ * @param id - Employee ID
+ * @return Employee
+ * @throws EmployeeException
+ */
+ private Employee getEmployeeById(Long id) throws EmployeeException {
+ return employeeRepository.getEmployeeById(id);
+ }
+
+ /**
+ * Convert employee to EmployeeDTO
+ * @param employee - Employee
+ * @return Employee DTO
+ */
+ private EmployeeInfoDTO convertEmployeeToEmployeeDTO (Employee employee) {
+ if (null != employee) {
+ LOG.info("Information found");
+ EmployeeInfoDTO employeeDTO = new EmployeeInfoDTO();
+
+ employeeDTO.setId(employee.getId());
+ employee.getProperties().forEach(emp -> {
+ employeeDTO.getProperties().put(emp.getKey(), emp.getValue());
+ });
+
+ return employeeDTO;
+ }
+
+ return null;
+ }
+
+ /**
+ * Convert employee to OrganizationDTO
+ * @param employee - Employee
+ * @return OrganizationStructureDTO
+ */
+ private OrganizationStructureDTO convertEmployeeToOrganizationDTO(Employee employee) {
+ if (null != employee) {
+ OrganizationStructureDTO organizationDTO = new OrganizationStructureDTO();
+ organizationDTO.setId(employee.getId());
+ organizationDTO.setSupervisor(employee.getSupervisorId());
+ employee.getProperties().forEach(emp -> {
+ organizationDTO.getProperties().put(emp.getKey(), emp.getValue());
+ });
+
+ return organizationDTO;
+ }
+
+ return null;
+ }
+
+ /**
+ * Create map organization for all employees
+ * @param organizationMap - map by supervisors
+ * @return Organization map nested by supervisor
+ */
+ private List mapOrganization(Map> organizationMap) {
+ List topLevelList = new ArrayList<>();
+ List firstLevel = organizationMap.get(-1l);
+
+ firstLevel.forEach(first -> {
+ TopLevelListDTO topLevelListDTO = new TopLevelListDTO();
+ topLevelListDTO.setId(first.getId());
+ topLevelListDTO.setProperties(first.getProperties());
+ topLevelListDTO.getUnderling().addAll(mapNextLevels(first.getId(), organizationMap));
+ topLevelList.add(topLevelListDTO);
+ });
+
+ return topLevelList;
+ }
+
+ /**
+ * Create map organization for nested levels
+ * @param supervisorId - Supervisor ID
+ * @param organizationMap - map by supervisors
+ * @return Organization map nested by supervisor
+ */
+ private List mapNextLevels(Long supervisorId,
+ Map> organizationMap) {
+
+ List topLevelList = new ArrayList<>();
+ List nextLevel = organizationMap.get(supervisorId);
+
+ nextLevel.forEach(next -> {
+ TopLevelListDTO topLevelListDTO = new TopLevelListDTO();
+ topLevelListDTO.setId(next.getId());
+ topLevelListDTO.setProperties(next.getProperties());
+
+ if (null != organizationMap.get(next.getId())) {
+ topLevelListDTO.getUnderling().addAll(mapNextLevels(next.getId(), organizationMap));
+ } else {
+ topLevelListDTO.setUnderling(null);
+ }
+ topLevelList.add(topLevelListDTO);
+ });
+
+ return topLevelList;
+ }
+
+}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 4408d17..fb76e0b 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -2,6 +2,15 @@ spring:
h2:
console:
enabled: true
+ datasource:
+ url: jdbc:h2:mem:testdb
+ username: sa
+ driverClassName: org.h2.Driver
+ jpa:
+ show-sql: true
+ properties:
+ hibernate:
+ format_sql: true
management:
endpoints:
web:
diff --git a/src/main/resources/log4j.yml b/src/main/resources/log4j.yml
new file mode 100644
index 0000000..89f3a07
--- /dev/null
+++ b/src/main/resources/log4j.yml
@@ -0,0 +1,23 @@
+Configutation:
+ name: Default
+ Properties:
+ Property:
+ name: log_pattern
+ value: "%d{yyyy-MM-dd HH:mm:ss.SSS} %5p ${hostName} --- [%15.15t] %-40.40c{1.} : %m%n%ex"
+ Appenders:
+ Console:
+ name: Console_Appender
+ target: SYSTEM_OUT
+ PatternLayout:
+ pattern: ${log_pattern}
+ Loggers:
+ Logger:
+ - name: com.zoomcare.candidatechallenge
+ level: debug
+ additivity: false
+ AppenderRef:
+ - ref: Console_Appender
+ Root:
+ level: info
+ AppenderRef:
+ - ref: Console_Appender
\ No newline at end of file
diff --git a/src/test/java/com/zoomcare/candidatechallenge/repository/EmployeeRepositoryTest.java b/src/test/java/com/zoomcare/candidatechallenge/repository/EmployeeRepositoryTest.java
new file mode 100644
index 0000000..93f751f
--- /dev/null
+++ b/src/test/java/com/zoomcare/candidatechallenge/repository/EmployeeRepositoryTest.java
@@ -0,0 +1,101 @@
+package com.zoomcare.candidatechallenge.repository;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import com.zoomcare.candidatechallenge.dao.EmployeeDAO;
+import com.zoomcare.candidatechallenge.exception.EmployeeException;
+import com.zoomcare.candidatechallenge.model.Employee;
+import com.zoomcare.candidatechallenge.model.Properties;
+
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+@RunWith(SpringRunner.class)
+public class EmployeeRepositoryTest {
+
+ @Mock
+ private EmployeeDAO employeeDAO;
+
+ @InjectMocks
+ private EmployeeRepository employeeRepository;
+
+ // POSITIVE TEST
+
+ @Test
+ public void givenValidIdThenshouldReturnEmployeeInformationWithSupervisor() throws EmployeeException {
+ Optional employeeOptional = Optional.of(new Employee(2l, 1l, Arrays.asList(
+ new Properties("region", "North America"),
+ new Properties("title", "Regional Director of Sales"))));
+
+ when(employeeDAO.findById(any())).thenReturn(employeeOptional);
+
+ Employee employee = employeeRepository.getEmployeeById(2l);
+
+ assertNotNull(employee);
+ assertNotNull(employee.getSupervisorId());
+ }
+
+ @Test
+ public void givenValidIdThenshouldReturnEmployeeInformationWithoutSupervisor() throws EmployeeException {
+ Optional employeeOptional = Optional.of(new Employee(1l, null, Arrays.asList(
+ new Properties("title", "CEO"))));
+
+ when(employeeDAO.findById(any())).thenReturn(employeeOptional);
+
+ Employee employee = employeeRepository.getEmployeeById(2l);
+
+ assertNotNull(employee);
+ assertNull(employee.getSupervisorId());
+ }
+
+ @Test
+ public void withExistingEmployeesThenShoulReturnEmployeesList() {
+ List employeeList = Arrays.asList(
+ new Employee(1l, null, Arrays.asList(
+ new Properties("title", "CEO"))),
+ new Employee(2l, 1l, Arrays.asList(
+ new Properties("region", "North America"),
+ new Properties("title", "Regional Director of Sales")))
+ );
+
+ when(employeeDAO.findAll()).thenReturn((Iterable)employeeList);
+
+ Iterable employeeIterable = employeeRepository.getAllEmployees();
+
+ assertNotNull(employeeIterable);
+ assertTrue(employeeIterable.iterator().hasNext());
+ }
+
+ // NEGATIVE TEST
+
+ @Test
+ public void givenNotExistingIdThenShouldReturnNull() throws EmployeeException {
+ when(employeeDAO.findById(any())).thenReturn(Optional.empty());
+ Employee employee = employeeRepository.getEmployeeById(1l);
+ assertNull(employee);
+ }
+
+ @Test
+ public void givenNullIdThenShouldReturnException() {
+
+ ThrowingCallable fail = () -> {
+ employeeRepository.getEmployeeById(null);
+ };
+
+ assertThatCode(fail).isInstanceOf(EmployeeException.class);
+ }
+
+}
diff --git a/src/test/java/com/zoomcare/candidatechallenge/resource/EmployeeResourceTest.java b/src/test/java/com/zoomcare/candidatechallenge/resource/EmployeeResourceTest.java
new file mode 100644
index 0000000..ec7e289
--- /dev/null
+++ b/src/test/java/com/zoomcare/candidatechallenge/resource/EmployeeResourceTest.java
@@ -0,0 +1,65 @@
+package com.zoomcare.candidatechallenge.resource;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+@AutoConfigureMockMvc
+public class EmployeeResourceTest {
+
+ @Autowired
+ private MockMvc webTestClient;
+
+ @Test
+ public void givenValidIdThenshouldReturnEmployeeInformationWithoutSupervisor() throws Exception {
+
+ webTestClient.perform(MockMvcRequestBuilders
+ .get("/v1/employee/1")
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andExpect(MockMvcResultMatchers.jsonPath("$.id").value(1))
+ .andDo(MockMvcResultHandlers.print());;
+ }
+
+ @Test
+ public void givenValidIdThenshouldReturnEmployeeInformationWithSupervisor() throws Exception {
+
+ webTestClient.perform(MockMvcRequestBuilders
+ .get("/v1/employee/2")
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andExpect(MockMvcResultMatchers.jsonPath("$.id").value(2))
+ .andDo(MockMvcResultHandlers.print());;
+ }
+
+ @Test
+ public void givenInvalidIdThenshouldReturnNotFoundMessage() throws Exception {
+
+ webTestClient.perform(MockMvcRequestBuilders
+ .get("/v1/employee/100")
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(MockMvcResultMatchers.status().isNotFound())
+ .andExpect(MockMvcResultMatchers.jsonPath("$.error").value("Employee ID 100 not found"))
+ .andDo(MockMvcResultHandlers.print());
+ }
+
+ @Test
+ public void withExistingEmployeesThenShoulReturnEmployeesList() throws Exception {
+ webTestClient.perform(MockMvcRequestBuilders
+ .get("/v1/employee/all")
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andDo(MockMvcResultHandlers.print());
+ }
+
+}
diff --git a/src/test/java/com/zoomcare/candidatechallenge/service/EmployeeServiceTest.java b/src/test/java/com/zoomcare/candidatechallenge/service/EmployeeServiceTest.java
new file mode 100644
index 0000000..e54fefa
--- /dev/null
+++ b/src/test/java/com/zoomcare/candidatechallenge/service/EmployeeServiceTest.java
@@ -0,0 +1,104 @@
+package com.zoomcare.candidatechallenge.service;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import com.zoomcare.candidatechallenge.dto.EmployeeInfoDTO;
+import com.zoomcare.candidatechallenge.dto.TopLevelListDTO;
+import com.zoomcare.candidatechallenge.exception.EmployeeException;
+import com.zoomcare.candidatechallenge.model.Employee;
+import com.zoomcare.candidatechallenge.model.Properties;
+import com.zoomcare.candidatechallenge.repository.EmployeeRepository;
+
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+@RunWith(SpringRunner.class)
+public class EmployeeServiceTest {
+
+ @Mock
+ private EmployeeRepository employeeRepository;
+
+ @InjectMocks
+ private EmployeeService employeeService;
+
+ // POSITIVE TEST
+
+ @Test
+ public void givenValidIdThenshouldReturnEmployeeInformationWithSupervisor() throws EmployeeException {
+ Employee employeeResult = new Employee(2l, 1l, Arrays.asList(
+ new Properties("region", "North America"),
+ new Properties("title", "Regional Director of Sales")));
+
+ when(employeeRepository.getEmployeeById(any())).thenReturn(employeeResult);
+ EmployeeInfoDTO employeeInfo = employeeService.getEmployeeInformation(2l);
+
+ assertNotNull(employeeInfo);
+ assertNotNull(employeeInfo.getSupervisor());
+ }
+
+ @Test
+ public void givenValidIdThenshouldReturnEmployeeInformationWithoutSupervisor() throws EmployeeException {
+ Employee employeeResult = new Employee(1l, null, Arrays.asList(
+ new Properties("title", "CEO")));
+
+ when(employeeRepository.getEmployeeById(any())).thenReturn(employeeResult);
+ EmployeeInfoDTO employeeInfo = employeeService.getEmployeeInformation(1l);
+
+ assertNotNull(employeeInfo);
+ assertNull(employeeInfo.getSupervisor());
+ }
+
+ @Test
+ public void withExistingEmployeesThenShoulReturnEmployeesList() {
+ List employeeList = Arrays.asList(
+ new Employee(1l, null, Arrays.asList(
+ new Properties("title", "CEO"))),
+ new Employee(2l, 1l, Arrays.asList(
+ new Properties("region", "North America"),
+ new Properties("title", "Regional Director of Sales")))
+ );
+
+ when(employeeRepository.getAllEmployees()).thenReturn((Iterable)employeeList);
+
+ List topLevelList = employeeService.getAllEmployees();
+
+ assertNotNull(topLevelList);
+ assertEquals(topLevelList.size(), 1);
+ assertNotNull(topLevelList.get(0).getUnderling());
+ assertEquals(topLevelList.get(0).getUnderling().size(), 1);
+ }
+
+ // NEGATIVE TEST
+
+ @Test
+ public void givenNotExistingIdThenShouldReturnException() throws EmployeeException {
+ when(employeeRepository.getEmployeeById(any())).thenReturn(null);
+ ThrowingCallable fail = () -> {
+ employeeService.getEmployeeInformation(20l);
+ };
+
+ assertThatCode(fail).isInstanceOf(EmployeeException.class);
+ }
+
+ @Test
+ public void givenNullIdThenShouldReturnException() {
+
+ ThrowingCallable fail = () -> {
+ employeeService.getEmployeeInformation(null);
+ };
+
+ assertThatCode(fail).isInstanceOf(EmployeeException.class);
+ }
+}