From 3e1287fa53e67701311147ff383266e52fb022ea Mon Sep 17 00:00:00 2001 From: Prasanth Date: Thu, 17 Nov 2022 12:54:09 +0000 Subject: [PATCH] API implementation changes. --- Dockerfile | 10 + README.txt | 33 +++- pom.xml | 178 +++++++++++------- .../java/com.waracle.cakemgr/CakeServlet.java | 106 ----------- .../com.waracle.cakemgr/HibernateUtil.java | 36 ---- .../cakemgr/CakeManagerApplication.java | 21 +++ .../waracle/cakemgr/api/CakeManagerApi.java | 23 +++ .../cakemgr/config/CakeManagerApiConfig.java | 26 +++ .../constants/ApplicationConstants.java | 18 ++ .../controller/CakeManagerController.java | 30 +++ .../waracle/cakemgr/entity}/CakeEntity.java | 15 +- .../exception/CakeManagerException.java | 11 ++ .../cakemgr/model/CakeSaveResponse.java | 34 ++++ .../repository/CakeManagerRepository.java | 10 + .../cakemgr/service/CakeManagerService.java | 171 +++++++++++++++++ src/main/resources/application.yml | 23 +++ src/main/resources/banner.txt | 6 + src/main/resources/hibernate.cfg.xml | 17 -- src/main/webapp/WEB-INF/web.xml | 8 - src/main/webapp/index.jsp | 5 - .../config/CakeManagerApiConfigTest.java | 23 +++ .../controller/CakeManagerControllerTest.java | 103 ++++++++++ .../service/CakeManagerServiceTest.java | 124 ++++++++++++ 23 files changed, 783 insertions(+), 248 deletions(-) create mode 100644 Dockerfile delete mode 100644 src/main/java/com.waracle.cakemgr/CakeServlet.java delete mode 100644 src/main/java/com.waracle.cakemgr/HibernateUtil.java create mode 100644 src/main/java/com/waracle/cakemgr/CakeManagerApplication.java create mode 100644 src/main/java/com/waracle/cakemgr/api/CakeManagerApi.java create mode 100644 src/main/java/com/waracle/cakemgr/config/CakeManagerApiConfig.java create mode 100644 src/main/java/com/waracle/cakemgr/constants/ApplicationConstants.java create mode 100644 src/main/java/com/waracle/cakemgr/controller/CakeManagerController.java rename src/main/java/{com.waracle.cakemgr => com/waracle/cakemgr/entity}/CakeEntity.java (71%) create mode 100644 src/main/java/com/waracle/cakemgr/exception/CakeManagerException.java create mode 100644 src/main/java/com/waracle/cakemgr/model/CakeSaveResponse.java create mode 100644 src/main/java/com/waracle/cakemgr/repository/CakeManagerRepository.java create mode 100644 src/main/java/com/waracle/cakemgr/service/CakeManagerService.java create mode 100644 src/main/resources/application.yml create mode 100644 src/main/resources/banner.txt delete mode 100644 src/main/resources/hibernate.cfg.xml delete mode 100644 src/main/webapp/WEB-INF/web.xml delete mode 100644 src/main/webapp/index.jsp create mode 100644 src/test/java/com/waracle/cakemgr/config/CakeManagerApiConfigTest.java create mode 100644 src/test/java/com/waracle/cakemgr/controller/CakeManagerControllerTest.java create mode 100644 src/test/java/com/waracle/cakemgr/service/CakeManagerServiceTest.java diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..f4689f3f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +# get base image +FROM adoptopenjdk/openjdk8 + +# install dependencies +ENV APP_HOME=usr/app +WORKDIR $APP_HOME +COPY ./target/cake-manager-1.0.0-SNAPSHOT.jar /usr/app + +# Run default command +CMD ["java" ,"-jar","cake-manager-1.0.0-SNAPSHOT.jar"] diff --git a/README.txt b/README.txt index 3a66e0de..d2bb1a64 100644 --- a/README.txt +++ b/README.txt @@ -31,11 +31,27 @@ Original Project Info To run a server locally execute the following command: -`mvn jetty:run` +1# Run CakeManagerApplication class in any IDE will start the API from IDE. +2# + a. Compile application using 'mvn clean install' command. + b. run jar file using 'java -jar cake-manager-1.0.0-SNAPSHOT.jar' +3# Once the application is compiled and jar is ready create docker build and run from docker using below commands. + a. docker build -t cake-manager . + b. docker run -p 8282:8282 cake-manager + +Note: 2 & 3 methods should run from application context from command terminal. + and access the following URL: -`http://localhost:8282/` +API testing and see sample JSON schema. +http://localhost:8282/swagger-ui.html + +API usage for Integration +GET REQUEST: will display all the cakes list available in the system +http://localhost:8282/cakes +POST REQUEST: Alternatively system will allow to new cake entry to the system using post method using same endpoint. +http://localhost:8282/cakes Feel free to change how the project is run, but clear instructions must be given in README You can use any IDE you like, so long as the project can build and run with Maven or Gradle. @@ -54,4 +70,15 @@ share it with us. Please also keep a log of the changes you make as a text file and provide this to us with your submission. -Good luck! +Changes made: + +1. upgraded project to Spring boot 2.7 version +2. Fixed issues in the existing implementation and API is now working +3. created a new POST method to create new entries in the system. +4. created a few unit tests to cover basic functionalities. Jacoco integrated to visualize test coverage can be find. In the target folder. +5. Simple Docker file is created to build and test the API service. +6. Integrated Swagger API to visualize and try out the API endpoints. +7. Documentation updated for all public methods. +8. YAML configuration added. +9. project structure changed for easy access and readability. + diff --git a/pom.xml b/pom.xml index c8cbf9d5..7b0fd769 100644 --- a/pom.xml +++ b/pom.xml @@ -1,77 +1,123 @@ + - 4.0.0 - com.waracle - cake-manager - war - 1.0-SNAPSHOT - cake-manager Maven Webapp - http://maven.apache.org - + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.7.5 + + + com.waracle + cake-manager + 1.0.0-SNAPSHOT + cake-manager + Cake Manager API + + 1.8 + + + + org.springframework.boot + spring-boot-starter + - - - javax.servlet - javax.servlet-api - 3.1.0 - + + org.springframework.boot + spring-boot-starter-web + - - - com.fasterxml.jackson.core - jackson-core - 2.8.0 - + + org.springframework.boot + spring-boot-starter-test + test + - - - org.hibernate - hibernate-entitymanager - 4.3.6.Final - + + org.springframework.boot + spring-boot-devtools + runtime + - - - org.hsqldb - hsqldb - 2.3.4 - + + + com.fasterxml.jackson.core + jackson-core + 2.14.0 + - - - junit - junit - 4.1 - test - + + + org.springframework.boot + spring-boot-starter-data-jpa + - - - cake-manager - + + + org.hsqldb + hsqldb + 2.3.4 + - - maven-compiler-plugin - 2.3.2 - - 1.8 - 1.8 - - + + + junit + junit + 4.1 + test + - - org.eclipse.jetty - jetty-maven-plugin - - 10 - STOP - 8005 - - 8282 - - - + + org.apache.logging.log4j + log4j-api + + + + io.springfox + springfox-swagger2 + 2.9.2 + + + io.springfox + springfox-swagger-ui + 2.9.2 + + + + org.jacoco + jacoco-maven-plugin + 0.8.8 + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.jacoco + jacoco-maven-plugin + 0.8.8 + + + + prepare-agent + + + + report + prepare-package + + report + + + + + + + - - diff --git a/src/main/java/com.waracle.cakemgr/CakeServlet.java b/src/main/java/com.waracle.cakemgr/CakeServlet.java deleted file mode 100644 index 9bd32f76..00000000 --- a/src/main/java/com.waracle.cakemgr/CakeServlet.java +++ /dev/null @@ -1,106 +0,0 @@ -package com.waracle.cakemgr; - -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import org.hibernate.Session; -import org.hibernate.exception.ConstraintViolationException; - -import javax.servlet.ServletException; -import javax.servlet.annotation.WebServlet; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.*; -import java.net.URL; -import java.util.List; - -@WebServlet("/cakes") -public class CakeServlet extends HttpServlet { - - @Override - public void init() throws ServletException { - super.init(); - - System.out.println("init started"); - - - System.out.println("downloading cake json"); - try (InputStream inputStream = new URL("https://gist.githubusercontent.com/hart88/198f29ec5114a3ec3460/raw/8dd19a88f9b8d24c23d9960f3300d0c917a4f07c/cake.json").openStream()) { - BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); - - StringBuffer buffer = new StringBuffer(); - String line = reader.readLine(); - while (line != null) { - buffer.append(line); - line = reader.readLine(); - } - - System.out.println("parsing cake json"); - JsonParser parser = new JsonFactory().createParser(buffer.toString()); - if (JsonToken.START_ARRAY != parser.nextToken()) { - throw new Exception("bad token"); - } - - JsonToken nextToken = parser.nextToken(); - while(nextToken == JsonToken.START_OBJECT) { - System.out.println("creating cake entity"); - - CakeEntity cakeEntity = new CakeEntity(); - System.out.println(parser.nextFieldName()); - cakeEntity.setTitle(parser.nextTextValue()); - - System.out.println(parser.nextFieldName()); - cakeEntity.setDescription(parser.nextTextValue()); - - System.out.println(parser.nextFieldName()); - cakeEntity.setImage(parser.nextTextValue()); - - Session session = HibernateUtil.getSessionFactory().openSession(); - try { - session.beginTransaction(); - session.persist(cakeEntity); - System.out.println("adding cake entity"); - session.getTransaction().commit(); - } catch (ConstraintViolationException ex) { - - } - session.close(); - - nextToken = parser.nextToken(); - System.out.println(nextToken); - - nextToken = parser.nextToken(); - System.out.println(nextToken); - } - - } catch (Exception ex) { - throw new ServletException(ex); - } - - System.out.println("init finished"); - } - - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - - Session session = HibernateUtil.getSessionFactory().openSession(); - List list = session.createCriteria(CakeEntity.class).list(); - - resp.getWriter().println("["); - - for (CakeEntity entity : list) { - resp.getWriter().println("\t{"); - - resp.getWriter().println("\t\t\"title\" : " + entity.getTitle() + ", "); - resp.getWriter().println("\t\t\"desc\" : " + entity.getDescription() + ","); - resp.getWriter().println("\t\t\"image\" : " + entity.getImage()); - - resp.getWriter().println("\t}"); - } - - resp.getWriter().println("]"); - - } - -} diff --git a/src/main/java/com.waracle.cakemgr/HibernateUtil.java b/src/main/java/com.waracle.cakemgr/HibernateUtil.java deleted file mode 100644 index 41ef137b..00000000 --- a/src/main/java/com.waracle.cakemgr/HibernateUtil.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.waracle.cakemgr; - -import org.hibernate.SessionFactory; -import org.hibernate.boot.registry.StandardServiceRegistryBuilder; -import org.hibernate.cfg.Configuration; -import org.hibernate.service.ServiceRegistry; - -public class HibernateUtil { - - private static SessionFactory sessionFactory = buildSessionFactory(); - - private static SessionFactory buildSessionFactory() { - try { - if (sessionFactory == null) { - Configuration configuration = new Configuration().configure(HibernateUtil.class.getResource("/hibernate.cfg.xml")); - StandardServiceRegistryBuilder serviceRegistryBuilder = new StandardServiceRegistryBuilder(); - serviceRegistryBuilder.applySettings(configuration.getProperties()); - ServiceRegistry serviceRegistry = serviceRegistryBuilder.build(); - sessionFactory = configuration.buildSessionFactory(serviceRegistry); - } - return sessionFactory; - } catch (Throwable ex) { - System.err.println("Initial SessionFactory creation failed." + ex); - throw new ExceptionInInitializerError(ex); - } - } - - public static SessionFactory getSessionFactory() { - return sessionFactory; - } - - public static void shutdown() { - getSessionFactory().close(); - } - -} diff --git a/src/main/java/com/waracle/cakemgr/CakeManagerApplication.java b/src/main/java/com/waracle/cakemgr/CakeManagerApplication.java new file mode 100644 index 00000000..b6dba4a4 --- /dev/null +++ b/src/main/java/com/waracle/cakemgr/CakeManagerApplication.java @@ -0,0 +1,21 @@ +package com.waracle.cakemgr; + +import com.waracle.cakemgr.service.CakeManagerService; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@EnableAutoConfiguration +@SpringBootApplication +public class CakeManagerApplication { + + + public static void main(String[] args) throws Exception { + CakeManagerService service = SpringApplication.run(CakeManagerApplication.class, args).getBean(CakeManagerService.class); + service.initialize(); + } + + +} + + diff --git a/src/main/java/com/waracle/cakemgr/api/CakeManagerApi.java b/src/main/java/com/waracle/cakemgr/api/CakeManagerApi.java new file mode 100644 index 00000000..c36802aa --- /dev/null +++ b/src/main/java/com/waracle/cakemgr/api/CakeManagerApi.java @@ -0,0 +1,23 @@ +package com.waracle.cakemgr.api; + +import com.waracle.cakemgr.entity.CakeEntity; +import com.waracle.cakemgr.model.CakeSaveResponse; +import io.swagger.annotations.Api; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +import java.util.List; + +@Validated +@Api(value = "Cake Manager") +public interface CakeManagerApi { + + @GetMapping("/cakes") + List allCakes() ; + + @PostMapping("/cakes") + ResponseEntity saveCakesInfo(@RequestBody CakeEntity cakeEntity); +} diff --git a/src/main/java/com/waracle/cakemgr/config/CakeManagerApiConfig.java b/src/main/java/com/waracle/cakemgr/config/CakeManagerApiConfig.java new file mode 100644 index 00000000..d40ddb2c --- /dev/null +++ b/src/main/java/com/waracle/cakemgr/config/CakeManagerApiConfig.java @@ -0,0 +1,26 @@ +package com.waracle.cakemgr.config; + + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2; + +@Configuration +@EnableSwagger2 +public class CakeManagerApiConfig +{ + @Bean + public Docket swaggerApi() { + return new Docket(DocumentationType.SWAGGER_2) + .select() + .paths(PathSelectors.ant("/**")) + .apis(RequestHandlerSelectors.basePackage("com.waracle")) + .build(); + } + + +} diff --git a/src/main/java/com/waracle/cakemgr/constants/ApplicationConstants.java b/src/main/java/com/waracle/cakemgr/constants/ApplicationConstants.java new file mode 100644 index 00000000..83006899 --- /dev/null +++ b/src/main/java/com/waracle/cakemgr/constants/ApplicationConstants.java @@ -0,0 +1,18 @@ +package com.waracle.cakemgr.constants; + +public class ApplicationConstants { + /** + * Application constants class can not be initialized from outside. + */ + private ApplicationConstants(){ + //Default Implementation ignored + } + + public static final String URL = "https://gist.githubusercontent.com/hart88/198f29ec5114a3ec3460/raw/8dd19a88f9b8d24c23d9960f3300d0c917a4f07c/cake.json"; + public static final String STATUS_CODE_BAD_REQUEST = "400"; + public static final String STATUS_CODE_INTERNAL_ERROR = "500"; + public static final String STATUS_CODE_SUCCESS = "200"; + public static final String STATUS_SUCCESS = "SUCCESS"; + public static final String STATUS_FAIL = "FAIL"; + +} diff --git a/src/main/java/com/waracle/cakemgr/controller/CakeManagerController.java b/src/main/java/com/waracle/cakemgr/controller/CakeManagerController.java new file mode 100644 index 00000000..3e43e80d --- /dev/null +++ b/src/main/java/com/waracle/cakemgr/controller/CakeManagerController.java @@ -0,0 +1,30 @@ +package com.waracle.cakemgr.controller; + +import com.waracle.cakemgr.api.CakeManagerApi; +import com.waracle.cakemgr.entity.CakeEntity; +import com.waracle.cakemgr.model.CakeSaveResponse; +import com.waracle.cakemgr.service.CakeManagerService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +@RestController +@Validated +public class CakeManagerController implements CakeManagerApi { + + @Autowired + CakeManagerService cakeManagerService; + + @Override + public List allCakes(){ + return cakeManagerService.getCakes(); + } + + @Override + public ResponseEntity saveCakesInfo(@RequestBody CakeEntity cakeEntity){ + return cakeManagerService.saveCakes(cakeEntity); + } +} diff --git a/src/main/java/com.waracle.cakemgr/CakeEntity.java b/src/main/java/com/waracle/cakemgr/entity/CakeEntity.java similarity index 71% rename from src/main/java/com.waracle.cakemgr/CakeEntity.java rename to src/main/java/com/waracle/cakemgr/entity/CakeEntity.java index 7927bd5d..37fb8089 100644 --- a/src/main/java/com.waracle.cakemgr/CakeEntity.java +++ b/src/main/java/com/waracle/cakemgr/entity/CakeEntity.java @@ -1,12 +1,13 @@ -package com.waracle.cakemgr; +package com.waracle.cakemgr.entity; -import java.io.Serializable; +import org.springframework.validation.annotation.Validated; import javax.persistence.*; +import java.io.Serializable; @Entity -@org.hibernate.annotations.Entity(dynamicUpdate = true) -@Table(name = "Employee", uniqueConstraints = {@UniqueConstraint(columnNames = "ID"), @UniqueConstraint(columnNames = "EMAIL")}) +@Table(name = "Employee", uniqueConstraints = {@UniqueConstraint(columnNames = "ID")}) +@Validated public class CakeEntity implements Serializable { private static final long serialVersionUID = -1798070786993154676L; @@ -16,13 +17,13 @@ public class CakeEntity implements Serializable { @Column(name = "ID", unique = true, nullable = false) private Integer employeeId; - @Column(name = "EMAIL", unique = true, nullable = false, length = 100) + @Column(name = "EMAIL", nullable = false, length = 100) private String title; - @Column(name = "FIRST_NAME", unique = false, nullable = false, length = 100) + @Column(name = "FIRST_NAME", nullable = false, length = 100) private String description; - @Column(name = "LAST_NAME", unique = false, nullable = false, length = 300) + @Column(name = "LAST_NAME", nullable = false, length = 300) private String image; public String getTitle() { diff --git a/src/main/java/com/waracle/cakemgr/exception/CakeManagerException.java b/src/main/java/com/waracle/cakemgr/exception/CakeManagerException.java new file mode 100644 index 00000000..4afcb9e5 --- /dev/null +++ b/src/main/java/com/waracle/cakemgr/exception/CakeManagerException.java @@ -0,0 +1,11 @@ +package com.waracle.cakemgr.exception; + +public class CakeManagerException extends RuntimeException{ + public CakeManagerException(String customMessage, Throwable cause){ + super(customMessage, cause); + } + + public CakeManagerException(String message){ + super(message); + } +} diff --git a/src/main/java/com/waracle/cakemgr/model/CakeSaveResponse.java b/src/main/java/com/waracle/cakemgr/model/CakeSaveResponse.java new file mode 100644 index 00000000..c1084935 --- /dev/null +++ b/src/main/java/com/waracle/cakemgr/model/CakeSaveResponse.java @@ -0,0 +1,34 @@ +package com.waracle.cakemgr.model; + +import com.waracle.cakemgr.entity.CakeEntity; + + +public class CakeSaveResponse { + private String status; + private String statusCode; + private CakeEntity cakeEntity; + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getStatusCode() { + return statusCode; + } + + public void setStatusCode(String statusCode) { + this.statusCode = statusCode; + } + + public CakeEntity getCakeEntity() { + return cakeEntity; + } + + public void setCakeEntity(CakeEntity cakeEntity) { + this.cakeEntity = cakeEntity; + } +} diff --git a/src/main/java/com/waracle/cakemgr/repository/CakeManagerRepository.java b/src/main/java/com/waracle/cakemgr/repository/CakeManagerRepository.java new file mode 100644 index 00000000..6538ee8c --- /dev/null +++ b/src/main/java/com/waracle/cakemgr/repository/CakeManagerRepository.java @@ -0,0 +1,10 @@ +package com.waracle.cakemgr.repository; + +import com.waracle.cakemgr.entity.CakeEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface CakeManagerRepository extends JpaRepository { + +} diff --git a/src/main/java/com/waracle/cakemgr/service/CakeManagerService.java b/src/main/java/com/waracle/cakemgr/service/CakeManagerService.java new file mode 100644 index 00000000..558ab8dd --- /dev/null +++ b/src/main/java/com/waracle/cakemgr/service/CakeManagerService.java @@ -0,0 +1,171 @@ +package com.waracle.cakemgr.service; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.waracle.cakemgr.constants.ApplicationConstants; +import com.waracle.cakemgr.entity.CakeEntity; +import com.waracle.cakemgr.exception.CakeManagerException; +import com.waracle.cakemgr.model.CakeSaveResponse; +import com.waracle.cakemgr.repository.CakeManagerRepository; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.hibernate.exception.ConstraintViolationException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +@Service +public class CakeManagerService { + @Autowired + private final CakeManagerRepository cakeManagerRepository; + + public CakeManagerService(CakeManagerRepository cakeManagerRepository){ + this.cakeManagerRepository = cakeManagerRepository; + } + + Logger logger = LogManager.getLogger(CakeManagerService.class); + + /** + * service to load initial data from external source to in memory data store. + * + * @throws CakeManagerException will be thrown with cause to the exception + */ + public void initialize() throws CakeManagerException, IOException { + logger.info("downloading cake json"); + JsonParser parser = null; + try (InputStream inputStream = new URL(ApplicationConstants.URL).openStream()) { + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + StringBuilder buffer = new StringBuilder(); + String line = reader.readLine(); + while (line != null) { + buffer.append(line); + line = reader.readLine(); + } + + logger.info("parsing cake json"); + parser = new JsonFactory().createParser(buffer.toString()); + if (JsonToken.START_ARRAY != parser.nextToken()) { + throw new CakeManagerException("bad token while parsing JSON"); + } + + JsonToken nextToken = parser.nextToken(); + processInitialData(parser, nextToken); + + } catch (Exception ex) { + String errorMessage = "an exception occurred while processing initial data load."; + logger.error(errorMessage); + throw new CakeManagerException(errorMessage, ex.getCause()); + } finally { + closeParser(parser); + } + } + + private void closeParser(JsonParser parser) throws IOException { + if (parser != null) { + try { + parser.close(); + } catch (IOException e) { + logger.error("Exception raised while closing the parser."); + throw e; + } + } + } + + /** + * Parse JSON data received from external source and will prepare Cake Entity to write to database + * + * @param parser JSON parser + * @param nextToken JSON token + * @throws IOException when parser read resulted in failure + */ + private void processInitialData(JsonParser parser, JsonToken nextToken) throws IOException { + while (nextToken == JsonToken.START_OBJECT) { + logger.info("creating cake entity"); + + String nextFieldName = parser.nextFieldName(); + CakeEntity cakeEntity = new CakeEntity(); + logger.info(nextFieldName); + cakeEntity.setTitle(parser.nextTextValue()); + + nextFieldName = parser.nextFieldName(); + logger.info(nextFieldName); + cakeEntity.setDescription(parser.nextTextValue()); + + nextFieldName = parser.nextFieldName(); + logger.info(nextFieldName); + cakeEntity.setImage(parser.nextTextValue()); + + persistData(cakeEntity); + nextToken = parser.nextToken(); + logger.info(nextToken); + + nextToken = parser.nextToken(); + logger.info(nextToken); + } + } + + /** + * Cake Entities will be persisted to in memory database. + * + * @param cakeEntity final cake entity to persist data to database + */ + private void persistData(CakeEntity cakeEntity) { + try { + cakeManagerRepository.save(cakeEntity); + } catch (ConstraintViolationException ex) { + logger.error(ex.getMessage()); + throw ex; + } + } + + /** + * List all the cake products data available in database. + * + * @return List of cake products successfully retrieved (Status Code 200) + * or bad request (status code 400) + * or Not Found (status code 404) + * or Internal server error (status code 500) + * or Gateway Timeout (status code 504) + */ + public List getCakes() { + List cakesList = cakeManagerRepository.findAll(); + return Optional.ofNullable(cakesList).orElse(new ArrayList<>()); + } + + public ResponseEntity saveCakes(CakeEntity cakeEntity) { + CakeSaveResponse cakeSaveResponse = new CakeSaveResponse(); + if (cakeEntity == null) { + cakeSaveResponse.setStatusCode(ApplicationConstants.STATUS_CODE_BAD_REQUEST); + return new ResponseEntity<>(cakeSaveResponse, getHttpHeaders(), HttpStatus.BAD_REQUEST); + } + try { + cakeManagerRepository.save(cakeEntity); + cakeSaveResponse.setStatus(ApplicationConstants.STATUS_SUCCESS); + cakeSaveResponse.setCakeEntity(cakeEntity); + cakeSaveResponse.setStatusCode(ApplicationConstants.STATUS_CODE_SUCCESS); + } catch (Exception e) { + logger.error(e.getMessage()); + cakeSaveResponse.setStatus(ApplicationConstants.STATUS_FAIL); + throw e; + } + HttpHeaders headers = getHttpHeaders(); + return new ResponseEntity<>(cakeSaveResponse, headers, HttpStatus.OK); + } + + private HttpHeaders getHttpHeaders() { + return new HttpHeaders(); + + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 00000000..ec2ebf91 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,23 @@ +spring: + datasource: + driverClassName: org.hsqldb.jdbcDriver + username: sa + jpa: + show-sql: true + properties: + hibernate: + format_sql: true + generate_statistics: true + hibernate: + ddl-auto: create + mvc: + pathmatch: + matching-strategy: ant_path_matcher +logging: + level: + org: + hibernate: + type: info + stat: debug +server: + port: ${SERVER_PORT:8282} diff --git a/src/main/resources/banner.txt b/src/main/resources/banner.txt new file mode 100644 index 00000000..60fbeeda --- /dev/null +++ b/src/main/resources/banner.txt @@ -0,0 +1,6 @@ + ____ _ __ __ _ ____ ___ + / ___|__ _| | _____ | \/ | __ _ _ __ __ _ ___ _ __ / \ | _ \_ _| + | | / _` | |/ / _ \ | |\/| |/ _` | '_ \ / _` |/ _ \ '__| / _ \ | |_) | | + | |__| (_| | < __/ | | | | (_| | | | | (_| | __/ | / ___ \| __/| | + \____\__,_|_|\_\___| |_| |_|\__,_|_| |_|\__, |\___|_| /_/ \_\_| |___| + |___/ diff --git a/src/main/resources/hibernate.cfg.xml b/src/main/resources/hibernate.cfg.xml deleted file mode 100644 index 0ae06d63..00000000 --- a/src/main/resources/hibernate.cfg.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - class,hbm - org.hibernate.dialect.HSQLDialect - true - org.hsqldb.jdbcDriver - sa - - jdbc:hsqldb:mem:db - create - - - \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index d004447f..00000000 --- a/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - Archetype Created Web Application - - diff --git a/src/main/webapp/index.jsp b/src/main/webapp/index.jsp deleted file mode 100644 index c38169bb..00000000 --- a/src/main/webapp/index.jsp +++ /dev/null @@ -1,5 +0,0 @@ - - -

Hello World!

- - diff --git a/src/test/java/com/waracle/cakemgr/config/CakeManagerApiConfigTest.java b/src/test/java/com/waracle/cakemgr/config/CakeManagerApiConfigTest.java new file mode 100644 index 00000000..ce894e5b --- /dev/null +++ b/src/test/java/com/waracle/cakemgr/config/CakeManagerApiConfigTest.java @@ -0,0 +1,23 @@ +package com.waracle.cakemgr.config; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.junit.jupiter.MockitoExtension; +import springfox.documentation.spring.web.plugins.Docket; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@ExtendWith(MockitoExtension.class) +class CakeManagerApiConfigTest { + @InjectMocks + private CakeManagerApiConfig cakeManagerApiConfig; + + @DisplayName("Verify Swagger docket is created with out any exception") + @Test + void validateBeanCreation(){ + Docket docket = cakeManagerApiConfig.swaggerApi(); + assertNotNull(docket); + } +} diff --git a/src/test/java/com/waracle/cakemgr/controller/CakeManagerControllerTest.java b/src/test/java/com/waracle/cakemgr/controller/CakeManagerControllerTest.java new file mode 100644 index 00000000..c2359d12 --- /dev/null +++ b/src/test/java/com/waracle/cakemgr/controller/CakeManagerControllerTest.java @@ -0,0 +1,103 @@ +package com.waracle.cakemgr.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.waracle.cakemgr.entity.CakeEntity; +import com.waracle.cakemgr.model.CakeSaveResponse; +import com.waracle.cakemgr.service.CakeManagerService; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.stubbing.Answer; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MockMvcBuilder; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.ResultMatcher; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.util.Collections; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@ExtendWith(SpringExtension.class) +@SpringBootTest(classes = {CakeManagerController.class}, properties = {"spring.main.allow-bean-definition-overriding=true"}) +@ComponentScan(basePackages = {"com.waracle.cakemgr"}) +@ActiveProfiles("test") +@AutoConfigureMockMvc +class CakeManagerControllerTest { + + @InjectMocks + CakeManagerController cakeManagerController; + + @Autowired + MockMvc mvc; + + @Mock + CakeManagerService cakeManagerService; + + private HttpHeaders httpHeaders; + + + @Test + void validateAPIResponse() throws Exception { + when(cakeManagerService.getCakes()).thenReturn(Collections.singletonList(new CakeEntity())); + mvc = MockMvcBuilders.standaloneSetup(cakeManagerController).build(); + MockHttpServletRequestBuilder builder = get("/cakes"); + ResultActions resultActions = mvc.perform(builder); + resultActions.andExpect(status().isOk()); + assertNotNull(resultActions.andReturn()); + + } + + @DisplayName("verify API request without post request body should return bad request") + @Test + void validateAPIPostResponseIsBadRequest() throws Exception { + CakeSaveResponse cakeSave= new CakeSaveResponse(); + ResponseEntity cakeSaveResponse = new ResponseEntity(cakeSave, new HttpHeaders(),HttpStatus.OK); + + when(cakeManagerService.saveCakes(any(CakeEntity.class))).thenReturn( cakeSaveResponse); + mvc = MockMvcBuilders.standaloneSetup(cakeManagerController).build(); + MockHttpServletRequestBuilder builder = post("/cakes"); + ResultActions resultActions = mvc.perform(builder); + resultActions.andExpect(status().isBadRequest()); + } + + @DisplayName("verify API request without post request body should return bad request") + @Test + void validateAPIPostResponseIsValid() throws Exception { + CakeSaveResponse cakeSave= new CakeSaveResponse(); + ResponseEntity cakeSaveResponse = new ResponseEntity(cakeSave, new HttpHeaders(),HttpStatus.OK); + + when(cakeManagerService.saveCakes(any(CakeEntity.class))).thenReturn( cakeSaveResponse); + mvc = MockMvcBuilders.standaloneSetup(cakeManagerController).build(); + CakeEntity cakeEntity = new CakeEntity(); + cakeEntity.setDescription("test"); + cakeEntity.setTitle("new test cake"); + MockHttpServletRequestBuilder builder = post("/cakes") + .contentType(MediaType.APPLICATION_JSON) + .content(new ObjectMapper().writeValueAsString(cakeEntity)); + ResultActions resultActions = mvc.perform(builder); + resultActions.andExpect(status().isOk()); + } + + +} diff --git a/src/test/java/com/waracle/cakemgr/service/CakeManagerServiceTest.java b/src/test/java/com/waracle/cakemgr/service/CakeManagerServiceTest.java new file mode 100644 index 00000000..643d1b5b --- /dev/null +++ b/src/test/java/com/waracle/cakemgr/service/CakeManagerServiceTest.java @@ -0,0 +1,124 @@ +package com.waracle.cakemgr.service; + +import com.waracle.cakemgr.entity.CakeEntity; +import com.waracle.cakemgr.exception.CakeManagerException; +import com.waracle.cakemgr.model.CakeSaveResponse; +import com.waracle.cakemgr.repository.CakeManagerRepository; +import org.hibernate.exception.ConstraintViolationException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.NullSource; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.ResponseEntity; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; + +@ExtendWith(MockitoExtension.class) +class CakeManagerServiceTest { + + @InjectMocks + private CakeManagerService cakeManagerService; + @Mock + CakeManagerRepository cakeManagerRepository; + + @BeforeEach + void init(){ + //cakeManagerService = new CakeManagerService(); + } + + @DisplayName("Test 400 bad request and response is thrown") + @ParameterizedTest + @NullSource + void validateResponseWhenInputIsNull(CakeEntity cakeEntity){ + ResponseEntity response = cakeManagerService.saveCakes(cakeEntity); + assertEquals("400 BAD_REQUEST", response.getStatusCode().toString()); + } + + + @ParameterizedTest + @MethodSource("apiRequestSource") + void validateSuccessResponse(CakeEntity cakeEntity) { + //Mockito.when(cakeManagerRepository.save(any(CakeEntity.class))).thenReturn(cakeEntity); + ResponseEntity response = cakeManagerService.saveCakes(cakeEntity); + assertEquals("200 OK", response.getStatusCode().toString()); + + } + + @Test + void validateException(){ + Mockito.when(cakeManagerRepository.save(any(CakeEntity.class))).thenThrow(IllegalArgumentException.class); + CakeEntity cakeEntity = new CakeEntity(); + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> + cakeManagerService.saveCakes(cakeEntity)); + assertTrue(exception instanceof IllegalArgumentException ); + } + + @DisplayName("verify the result returned.") + @ParameterizedTest + @MethodSource("supplyGetResults") + void validateGetResult(List expectedResult){ + Mockito.when(cakeManagerRepository.findAll()).thenReturn(expectedResult); + List resultList = cakeManagerService.getCakes(); + assertEquals(expectedResult.size(), resultList.size()); + } + + @DisplayName("verify the result returned when no entries in DB") + @Test + void validateGetResultWhenNull(){ + Mockito.when(cakeManagerRepository.findAll()).thenReturn(null); + List resultList = cakeManagerService.getCakes(); + assertEquals(0, resultList.size()); + } + + @DisplayName("Verify constraint violation exception thrown while saving data to DB") + @Test + void validateExceptionWhenWritingDatatoDB(){ + Mockito.when(cakeManagerRepository.save(any(CakeEntity.class))).thenThrow(ConstraintViolationException.class); + CakeManagerException exception = assertThrows(CakeManagerException.class, () -> + cakeManagerService.initialize()); + assertTrue(exception instanceof CakeManagerException ); + } + + + + @DisplayName("Verify result set returned without any exception") + @Test + void validateInitialDataLoadWithOutException() throws Exception { + cakeManagerService.initialize(); + List cakeEntityList = cakeManagerService.getCakes(); + assertNotNull(cakeEntityList); + } + + + private static Stream apiRequestSource (){ + CakeEntity cakeEntity1 = new CakeEntity(); + cakeEntity1.setImage("http://www.villageinn.com/i/pies/profile/carrotcake_main1.jpg"); + cakeEntity1.setTitle("new cake 1"); + cakeEntity1.setDescription("Bugs bunnys favourite"); + return Stream.of(cakeEntity1); + } + + private static Stream> supplyGetResults(){ + List list = new ArrayList<>(); + CakeEntity cakeEntity1 = new CakeEntity(); + cakeEntity1.setImage("http://www.villageinn.com/i/pies/profile/carrotcake_main1.jpg"); + cakeEntity1.setTitle("new cake 1"); + cakeEntity1.setDescription("Bugs bunnys favourite"); + list.add(cakeEntity1); + return Stream.of(new ArrayList<>(), list); + } + +}