diff --git a/src/main/java/org/wikitolearn/midtier/course/client/ChapterClient.java b/src/main/java/org/wikitolearn/midtier/course/client/ChapterClient.java index 870c068..9d2835d 100644 --- a/src/main/java/org/wikitolearn/midtier/course/client/ChapterClient.java +++ b/src/main/java/org/wikitolearn/midtier/course/client/ChapterClient.java @@ -1,114 +1,122 @@ package org.wikitolearn.midtier.course.client; import java.net.URI; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.stereotype.Component; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; import org.wikitolearn.midtier.course.entity.Chapter; import org.wikitolearn.midtier.course.entity.EntityList; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import lombok.extern.slf4j.Slf4j; @Component @Slf4j public class ChapterClient { private final RestTemplate client; @Value("${application.clients.chapters-backend}") private String baseUrl; + + @Autowired + private ObjectMapper objectMapper; public ChapterClient(RestTemplateBuilder restTemplateBuilder) { HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(); this.client = restTemplateBuilder.requestFactory(() -> requestFactory).build(); } @HystrixCommand(fallbackMethod = "defaultChapters") public EntityList findAll() { URI uri = UriComponentsBuilder.fromHttpUrl(baseUrl + "/chapters").build().encode().toUri(); return client .exchange(uri, HttpMethod.GET, null, new ParameterizedTypeReference>() { }).getBody(); } public Chapter find(String chapterId, MultiValueMap params) { URI uri = UriComponentsBuilder.fromHttpUrl(baseUrl + "/chapters/" + chapterId) .queryParams(params) .build() .encode() .toUri(); return client.getForObject(uri, Chapter.class); } - public EntityList findByPageId(String pageId) { + public EntityList findByPageId(String pageId) throws JsonProcessingException { MultiValueMap query = new LinkedMultiValueMap<>(); - query.add("where", "{\"pages._id\":\"" + pageId + "\"}"); + ObjectNode whereJsonObject = objectMapper.getNodeFactory().objectNode().put("pages._id", pageId); + query.add("where", objectMapper.writeValueAsString(whereJsonObject)); + URI uri = UriComponentsBuilder.fromHttpUrl(baseUrl + "/chapters") .queryParams(query) .build() .encode() .toUri(); return client .exchange(uri, HttpMethod.GET, null, new ParameterizedTypeReference>() { }).getBody(); } public Chapter store(Chapter chapter) throws JsonProcessingException { URI uri = UriComponentsBuilder.fromHttpUrl(baseUrl + "/chapters") .build() .encode() .toUri(); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntity httpEntity = new HttpEntity (chapter.toSchemaCompliant(), headers); return client.postForObject(uri, httpEntity, Chapter.class); } public Chapter update(Chapter chapter) throws JsonProcessingException { URI uri = UriComponentsBuilder.fromHttpUrl(baseUrl + "/chapters/" + chapter.getId()) .build() .encode() .toUri(); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.setIfMatch(chapter.getEtag()); HttpEntity httpEntity = new HttpEntity (chapter.toSchemaCompliant(), headers); return client.patchForObject(uri, httpEntity, Chapter.class); } public Chapter delete(Chapter chapter) throws JsonProcessingException { URI uri = UriComponentsBuilder.fromHttpUrl(baseUrl + "/chapters/" + chapter.getId()) .build() .encode() .toUri(); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.setIfMatch(chapter.getEtag()); HttpEntity httpEntity = new HttpEntity(headers); return client.exchange(uri, HttpMethod.DELETE, httpEntity, Chapter.class).getBody(); } private String defaultChapters() { return "Hello default!"; } } diff --git a/src/main/java/org/wikitolearn/midtier/course/client/CourseClient.java b/src/main/java/org/wikitolearn/midtier/course/client/CourseClient.java index a6ad2cd..a5ab28a 100644 --- a/src/main/java/org/wikitolearn/midtier/course/client/CourseClient.java +++ b/src/main/java/org/wikitolearn/midtier/course/client/CourseClient.java @@ -1,139 +1,145 @@ package org.wikitolearn.midtier.course.client; import java.net.URI; import org.modelmapper.ModelMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.stereotype.Component; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; import org.wikitolearn.midtier.course.entity.Course; import org.wikitolearn.midtier.course.entity.EntityList; import org.wikitolearn.midtier.course.entity.dto.UpdateCourseClientDto; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import lombok.extern.slf4j.Slf4j; @Component @Slf4j public class CourseClient { private final RestTemplate client; @Autowired private ModelMapper modelMapper; @Value("${application.clients.courses-backend}") private String baseUrl; + + @Autowired + private ObjectMapper objectMapper; public CourseClient(RestTemplateBuilder restTemplateBuilder) { HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(); this.client = restTemplateBuilder.requestFactory(() -> requestFactory).build(); } @HystrixCommand(fallbackMethod = "defaultCourses") public EntityList findAll(MultiValueMap params) { URI uri = UriComponentsBuilder.fromHttpUrl(baseUrl + "/courses") .queryParams(params) .build() .encode() .toUri(); return client .exchange(uri, HttpMethod.GET, null, new ParameterizedTypeReference>() { }).getBody(); } public Course find(String courseId, MultiValueMap params) { URI uri = UriComponentsBuilder.fromHttpUrl(baseUrl + "/courses/" + courseId) .queryParams(params) .build() .encode() .toUri(); return client.getForObject(uri, Course.class); } - public EntityList findByChapterId(String chapterId) { + public EntityList findByChapterId(String chapterId) throws JsonProcessingException { MultiValueMap query = new LinkedMultiValueMap<>(); - query.add("where", "{\"chapters._id\":\"" + chapterId + "\"}"); + ObjectNode whereJsonObject = objectMapper.getNodeFactory().objectNode().put("chapters._id", chapterId); + query.add("where", objectMapper.writeValueAsString(whereJsonObject)); URI uri = UriComponentsBuilder.fromHttpUrl(baseUrl + "/courses") .queryParams(query) .build() .encode() .toUri(); return client .exchange(uri, HttpMethod.GET, null, new ParameterizedTypeReference>() { }).getBody(); } public EntityList getAllCourseVersions(String courseId, MultiValueMap params) { URI uri = UriComponentsBuilder.fromHttpUrl(baseUrl + "/courses/" + courseId) .queryParams(params) .build() .encode() .toUri(); return client .exchange(uri, HttpMethod.GET, null, new ParameterizedTypeReference>() { }).getBody(); } public Course save(Course course) throws JsonProcessingException { URI uri = UriComponentsBuilder.fromHttpUrl(baseUrl + "/courses") .build() .encode() .toUri(); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntity httpEntity = new HttpEntity (course.toSchemaCompliant(), headers); return client.postForObject(uri, httpEntity, Course.class); } public Course update(Course course) throws JsonProcessingException { URI uri = UriComponentsBuilder.fromHttpUrl(baseUrl + "/courses/" + course.getId()) .build() .encode() .toUri(); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.setIfMatch(course.getEtag()); HttpEntity httpEntity = new HttpEntity (modelMapper.map(course, UpdateCourseClientDto.class), headers); return client.patchForObject(uri, httpEntity, Course.class); } public Course delete(Course course) throws JsonProcessingException { URI uri = UriComponentsBuilder.fromHttpUrl(baseUrl + "/courses/" + course.getId()) .build() .encode() .toUri(); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.setIfMatch(course.getEtag()); HttpEntity httpEntity = new HttpEntity(headers); return client.exchange(uri, HttpMethod.DELETE, httpEntity, Course.class).getBody(); } private String defaultCourses() { return "Hello default!"; } } diff --git a/src/main/java/org/wikitolearn/midtier/course/config/JacksonConfiguration.java b/src/main/java/org/wikitolearn/midtier/course/config/JacksonConfiguration.java new file mode 100644 index 0000000..a443490 --- /dev/null +++ b/src/main/java/org/wikitolearn/midtier/course/config/JacksonConfiguration.java @@ -0,0 +1,14 @@ +package org.wikitolearn.midtier.course.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.fasterxml.jackson.databind.ObjectMapper; + +@Configuration +public class JacksonConfiguration { + @Bean + public ObjectMapper objectMapper() { + return new ObjectMapper(); + } +} diff --git a/src/main/java/org/wikitolearn/midtier/course/service/ChapterService.java b/src/main/java/org/wikitolearn/midtier/course/service/ChapterService.java index 2b108a6..f7db985 100644 --- a/src/main/java/org/wikitolearn/midtier/course/service/ChapterService.java +++ b/src/main/java/org/wikitolearn/midtier/course/service/ChapterService.java @@ -1,171 +1,171 @@ package org.wikitolearn.midtier.course.service; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.function.Predicate; import java.util.stream.Collectors; import org.apache.commons.collections4.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.wikitolearn.midtier.course.client.ChapterClient; import org.wikitolearn.midtier.course.entity.Chapter; import org.wikitolearn.midtier.course.entity.EntityList; import org.wikitolearn.midtier.course.entity.Page; import org.wikitolearn.midtier.course.event.ChapterDeleted; import org.wikitolearn.midtier.course.event.ChapterUpdated; import org.wikitolearn.midtier.course.event.PageDeleted; import org.wikitolearn.midtier.course.event.PageUpdated; import org.wikitolearn.midtier.course.exception.InvalidResourceCreateException; import org.wikitolearn.midtier.course.exception.InvalidResourceUpdateException; import com.fasterxml.jackson.core.JsonProcessingException; import lombok.extern.slf4j.Slf4j; @Service @Slf4j public class ChapterService { @Autowired private ApplicationEventPublisher applicationEventPublisher; @Autowired private ChapterClient chapterClient; @Autowired private PageService pageService; public EntityList findAll() { return chapterClient.findAll(); } public Chapter find(String chapterId, MultiValueMap params) { return chapterClient.find(chapterId, params); } public List save(List chapters) { chapters.stream().forEach(c -> { Chapter saved; try { saved = this.save(c); c.setId(saved.getId()); c.setVersion(saved.getVersion()); } catch (JsonProcessingException e) { log.error(e.getMessage()); } }); return chapters; } - public Chapter findByPageId(String pageId) { + public Chapter findByPageId(String pageId) throws JsonProcessingException { EntityList chapters = chapterClient.findByPageId(pageId); return chapters.getItems().get(0); } public Chapter save(Chapter chapter) throws JsonProcessingException { if(chapter.getPages() != null && !chapter.getPages().isEmpty()) { chapter.setPages(pageService.save(chapter.getPages())); } return chapterClient.store(chapter); } public Chapter update(Chapter chapter) throws JsonProcessingException { Chapter updatedChapter = chapterClient.update(chapter); applicationEventPublisher.publishEvent(new ChapterUpdated(this, updatedChapter)); return updatedChapter; } public Chapter delete(Chapter chapter, boolean isBulkDelete) throws JsonProcessingException { MultiValueMap chapterParams = new LinkedMultiValueMap<>(); chapterParams.add("projection", "{\"title\":1, \"pages\":1}"); chapter = this.find(chapter.getId(), chapterParams); log.info(chapter.toString()); Optional .ofNullable(chapter.getPages()) .orElseGet(Collections::emptyList) .stream().forEachOrdered(p -> { try { pageService.delete(p, true); } catch (JsonProcessingException e) { // FIXME log.error(e.getMessage()); } }); Chapter deletedChapter = chapterClient.delete(chapter); if (!isBulkDelete) { applicationEventPublisher.publishEvent(new ChapterDeleted(this, chapter)); } return deletedChapter; } public Chapter updatePages(Chapter chapter) throws JsonProcessingException, InvalidResourceCreateException { List currentPages = this.find(chapter.getId(), null).getPages(); if(currentPages.size() == chapter.getPages().size() && currentPages.containsAll(chapter.getPages())) { return this.update(chapter); } else { log.warn("Invalid chapter's pages update request"); throw new InvalidResourceUpdateException("Invalid chapter's pages update request"); } } public Chapter addPages(Chapter chapter) throws JsonProcessingException, InvalidResourceCreateException { List currentPages = this.find(chapter.getId(), null).getPages(); List pagesToAdd = new ArrayList<>(CollectionUtils.disjunction(chapter.getPages(), currentPages)); if(pagesToAdd.size() != 1) { log.warn("Invalid chapter's pages create request"); throw new InvalidResourceCreateException("Invalid chapter's pages create request"); } Page addedPage = pageService.save(pagesToAdd.get(0)); chapter.getPages().stream().forEachOrdered(p -> { if(pagesToAdd.get(0).getTitle().equals(p.getTitle())) { p.setVersion(addedPage.getLatestVersion()); p.setId(addedPage.getId()); } }); return this.update(chapter); } @EventListener public void handlePageUpdatedEvent(PageUpdated event) throws JsonProcessingException { Page updatedPage = event.getPage(); Chapter chapter = this.findByPageId(updatedPage.getId()); chapter.getPages().stream().forEach(p -> { if(p.getId().equals(updatedPage.getId())) { p.setVersion(updatedPage.getLatestVersion()); } }); this.update(chapter); } @EventListener public void handlePageDeletedEvent(PageDeleted event) throws JsonProcessingException { Page deletedPage = event.getPage(); Chapter chapter = this.findByPageId(deletedPage.getId()); List pages = chapter.getPages() .stream() .sequential() .filter(removeDeletedPage(deletedPage.getId())) .collect(Collectors.toList()); chapter.setPages(pages); this.update(chapter); } private static Predicate removeDeletedPage(String deletedPageId) { return p -> !deletedPageId.equals(p.getId()); } } diff --git a/src/main/java/org/wikitolearn/midtier/course/service/CourseService.java b/src/main/java/org/wikitolearn/midtier/course/service/CourseService.java index a62ca49..153fd04 100644 --- a/src/main/java/org/wikitolearn/midtier/course/service/CourseService.java +++ b/src/main/java/org/wikitolearn/midtier/course/service/CourseService.java @@ -1,145 +1,145 @@ package org.wikitolearn.midtier.course.service; import java.util.ArrayList; import java.util.List; import java.util.function.Predicate; import java.util.stream.Collectors; import org.apache.commons.collections4.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; import org.springframework.util.MultiValueMap; import org.wikitolearn.midtier.course.client.CourseClient; import org.wikitolearn.midtier.course.entity.Chapter; import org.wikitolearn.midtier.course.entity.Course; import org.wikitolearn.midtier.course.entity.EntityList; import org.wikitolearn.midtier.course.event.ChapterDeleted; import org.wikitolearn.midtier.course.event.ChapterUpdated; import org.wikitolearn.midtier.course.exception.InvalidResourceCreateException; import org.wikitolearn.midtier.course.exception.InvalidResourceUpdateException; import com.fasterxml.jackson.core.JsonProcessingException; import lombok.extern.slf4j.Slf4j; @Service @Slf4j public class CourseService { @Autowired private CourseClient courseClient; @Autowired private ChapterService chapterService; public EntityList findAll(MultiValueMap params) { return courseClient.findAll(params); } public Course find(String courseId, MultiValueMap params) { return courseClient.find(courseId, params); } - public Course findByChapterId(String chapterId) { + public Course findByChapterId(String chapterId) throws JsonProcessingException { EntityList courses = courseClient.findByChapterId(chapterId); return courses.getItems().get(0); } public EntityList getAllCourseVersions(String courseId, MultiValueMap params) { return courseClient.getAllCourseVersions(courseId, params); } public Course save(Course course) throws JsonProcessingException { if(course.getChapters()!= null && !course.getChapters().isEmpty()) { course.setChapters(chapterService.save(course.getChapters())); } return courseClient.save(course); } public Course update(Course course) throws JsonProcessingException { return courseClient.update(course); } public Course delete(Course course) throws JsonProcessingException { course = this.find(course.getId(), null); course.getChapters().stream().forEachOrdered(c -> { try { this.chapterService.delete(c, true); } catch (JsonProcessingException e) { // FIXME log.error(e.getMessage()); } }); return courseClient.delete(course); } public Course updateChapters(Course course) throws JsonProcessingException, InvalidResourceCreateException { List currentChapters = this.find(course.getId(), null).getChapters(); if(currentChapters.size() == course.getChapters().size() && currentChapters.containsAll(course.getChapters())) { return courseClient.update(course); } else { log.warn("Invalid course's chapters update request"); throw new InvalidResourceUpdateException("Invalid course's chapters update request"); } } public Course addChapters(Course course) throws JsonProcessingException, InvalidResourceCreateException { List currentChapters = this.find(course.getId(), null).getChapters(); List chaptersToAdd = new ArrayList<>(CollectionUtils.disjunction(course.getChapters(), currentChapters)); if(chaptersToAdd.size() != 1) { log.warn("Invalid course's chapter create request"); throw new InvalidResourceCreateException("Invalid course's chapter create request"); } Chapter addedChapter = chapterService.save(chaptersToAdd.get(0)); course.getChapters() .parallelStream() .filter(c -> chaptersToAdd.get(0).getTitle().equals(c.getTitle())) .findFirst() .map(c -> { c.setVersion(addedChapter.getLatestVersion()); c.setId(addedChapter.getId()); return c; }); return this.update(course); } @EventListener public void handleChapterUpdatedEvent(ChapterUpdated event) throws JsonProcessingException { Chapter updatedChapter = event.getChapter(); Course course = this.findByChapterId(updatedChapter.getId()); course.getChapters().stream().forEach(c -> { if(c.getId().equals(updatedChapter.getId())) { c.setVersion(updatedChapter.getLatestVersion()); } }); this.update(course); } @EventListener public void handleChapterDeletedEvent(ChapterDeleted event) throws JsonProcessingException { Chapter deletedChapter = event.getChapter(); Course course = this.findByChapterId(deletedChapter.getId()); List chapters = course.getChapters() .stream() .sequential() .filter(removeDeletedChapter(deletedChapter.getId())) .collect(Collectors.toList()); course.setChapters(chapters); this.update(course); } private static Predicate removeDeletedChapter(String deletedChapterId) { return c -> !deletedChapterId.equals(c.getId()); } } diff --git a/src/main/java/org/wikitolearn/midtier/course/web/rest/CourseController.java b/src/main/java/org/wikitolearn/midtier/course/web/rest/CourseController.java index 8fcb1bd..2349d61 100644 --- a/src/main/java/org/wikitolearn/midtier/course/web/rest/CourseController.java +++ b/src/main/java/org/wikitolearn/midtier/course/web/rest/CourseController.java @@ -1,141 +1,148 @@ package org.wikitolearn.midtier.course.web.rest; import java.util.Collections; import java.util.Optional; import java.util.stream.Collectors; import org.modelmapper.ModelMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.wikitolearn.midtier.course.entity.Course; import org.wikitolearn.midtier.course.entity.EntityList; import org.wikitolearn.midtier.course.entity.dto.AddCourseChaptersDto; import org.wikitolearn.midtier.course.entity.dto.GetCourseDto; import org.wikitolearn.midtier.course.entity.dto.GetCoursesDto; import org.wikitolearn.midtier.course.entity.dto.UpdateCourseChaptersDto; import org.wikitolearn.midtier.course.entity.dto.UpdateCourseDto; import org.wikitolearn.midtier.course.entity.dto.UpdatedCourseDto; import org.wikitolearn.midtier.course.exception.InvalidResourceCreateException; import org.wikitolearn.midtier.course.service.ChapterService; import org.wikitolearn.midtier.course.service.CourseService; import org.wikitolearn.midtier.course.service.PageService; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; @RestController @Slf4j @RequestMapping(value = "/courses") public class CourseController { @Autowired private ModelMapper modelMapper; @Autowired private CourseService courseService; @Autowired private ChapterService chapterService; @Autowired private PageService pageService; + + @Autowired + private ObjectMapper objectMapper; @GetMapping(produces = MediaType.APPLICATION_JSON_UTF8_VALUE) - public GetCoursesDto getCourses(@RequestParam(value="page", required=false) String page) { + public GetCoursesDto getCourses(@RequestParam(value="page", required=false) Integer page) { MultiValueMap params = new LinkedMultiValueMap<>(); - params.add("page", page); + params.add("page", String.valueOf(page)); EntityList courses = courseService.findAll(params); return modelMapper.map(courses, GetCoursesDto.class); } @GetMapping(value = "/{courseId}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE) - public GetCourseDto getCourse(@PathVariable String courseId) { + public GetCourseDto getCourse(@PathVariable String courseId) throws JsonProcessingException { MultiValueMap chapterParams = new LinkedMultiValueMap<>(); - chapterParams.add("projection", "{\"title\":1, \"pages\":1}"); + ObjectNode projectionJsonObject = objectMapper.getNodeFactory().objectNode().put("title", 1).put("pages", 1); + chapterParams.add("projection", objectMapper.writeValueAsString(projectionJsonObject)); MultiValueMap pagesParams = new LinkedMultiValueMap<>(); - pagesParams.add("projection", "{\"title\":1}"); + projectionJsonObject = objectMapper.getNodeFactory().objectNode().put("title", 1); + pagesParams.add("projection", objectMapper.writeValueAsString(projectionJsonObject)); Course course = courseService.find(courseId, null); course.setChapters( Optional .ofNullable(course.getChapters()) .orElseGet(Collections::emptyList) .parallelStream() .map(chapter -> { chapter = chapterService.find(chapter.getId(), chapterParams); chapter.setPages( Optional .ofNullable(chapter.getPages()) .orElseGet(Collections::emptyList) .parallelStream() .map(page -> pageService.find(page.getId(), pagesParams)) .collect(Collectors.toList())); return chapter; }) .collect(Collectors.toList())); return modelMapper.map(course, GetCourseDto.class); } @PostMapping(produces = MediaType.APPLICATION_JSON_UTF8_VALUE, consumes = MediaType.APPLICATION_JSON_UTF8_VALUE) public ResponseEntity storeCourse(@RequestBody Course course) throws JsonProcessingException { ResponseEntity response = new ResponseEntity<>(courseService.save(course), HttpStatus.CREATED); return response; } @PatchMapping(value = "/{courseId}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE, consumes = MediaType.APPLICATION_JSON_UTF8_VALUE) public UpdatedCourseDto updateCourse(@PathVariable("courseId") String courseId, @RequestBody UpdateCourseDto course, @RequestHeader("If-Match") String etag) throws JsonProcessingException { Course courseToUpdate = modelMapper.map(course, Course.class); courseToUpdate.setId(courseId); courseToUpdate.setEtag(etag); Course updatedCourse = courseService.update(courseToUpdate); return modelMapper.map(updatedCourse, UpdatedCourseDto.class); } @DeleteMapping(value = "/{courseId}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE, consumes = MediaType.APPLICATION_JSON_UTF8_VALUE) public Course deleteCourse(@PathVariable("courseId") String courseId, @RequestHeader("If-Match") String etag) throws JsonProcessingException { Course course = new Course(); course.setId(courseId); course.setEtag(etag); return courseService.delete(course); } @PatchMapping(value = "/{courseId}/chapters", produces = MediaType.APPLICATION_JSON_UTF8_VALUE, consumes = MediaType.APPLICATION_JSON_UTF8_VALUE) public UpdatedCourseDto updateChapters(@PathVariable("courseId") String courseId, @RequestBody UpdateCourseChaptersDto course, @RequestHeader("If-Match") String etag) throws JsonProcessingException, InvalidResourceCreateException { Course courseToUpdate = modelMapper.map(course, Course.class); courseToUpdate.setId(courseId); courseToUpdate.setEtag(etag); Course updatedCourse = courseService.updateChapters(courseToUpdate); return modelMapper.map(updatedCourse, UpdatedCourseDto.class); } @PostMapping(value = "/{courseId}/chapters", produces = MediaType.APPLICATION_JSON_UTF8_VALUE, consumes = MediaType.APPLICATION_JSON_UTF8_VALUE) public UpdatedCourseDto addChapters(@PathVariable("courseId") String courseId, @RequestBody AddCourseChaptersDto course, @RequestHeader("If-Match") String etag) throws JsonProcessingException, InvalidResourceCreateException { Course courseToUpdate = modelMapper.map(course, Course.class); courseToUpdate.setId(courseId); courseToUpdate.setEtag(etag); Course updatedCourse = courseService.addChapters(courseToUpdate); return modelMapper.map(updatedCourse, UpdatedCourseDto.class); } } diff --git a/src/main/java/org/wikitolearn/midtier/course/web/rest/HistoryController.java b/src/main/java/org/wikitolearn/midtier/course/web/rest/HistoryController.java index ea4a783..3e38fd5 100644 --- a/src/main/java/org/wikitolearn/midtier/course/web/rest/HistoryController.java +++ b/src/main/java/org/wikitolearn/midtier/course/web/rest/HistoryController.java @@ -1,140 +1,150 @@ package org.wikitolearn.midtier.course.web.rest; import java.util.Collections; import java.util.Optional; import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; 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.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.wikitolearn.midtier.course.entity.Chapter; import org.wikitolearn.midtier.course.entity.Course; import org.wikitolearn.midtier.course.entity.EntityList; import org.wikitolearn.midtier.course.entity.Page; import org.wikitolearn.midtier.course.exception.ResourceNotFoundException; import org.wikitolearn.midtier.course.service.ChapterService; import org.wikitolearn.midtier.course.service.CourseService; import org.wikitolearn.midtier.course.service.PageService; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; + import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; @RestController @Slf4j @RequestMapping(value = "/history/courses") public class HistoryController { @Autowired private CourseService courseService; @Autowired private ChapterService chapterService; @Autowired private PageService pageService; + @Autowired + private ObjectMapper objectMapper; + @ApiOperation(value = "getCourseVersion") @GetMapping(value = "/{courseId}/versions/{version}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public Course getCourseVersion(@PathVariable(value = "courseId", required = true) String courseId, - @PathVariable(value = "version", required = true) String version) { + @PathVariable(value = "version", required = true) Integer version) throws JsonProcessingException { MultiValueMap courseParams = new LinkedMultiValueMap<>(); - courseParams.add("version", version); + courseParams.add("version", String.valueOf(version)); MultiValueMap chapterParams = new LinkedMultiValueMap<>(); - chapterParams.add("projection", "{\"title\":1, \"pages\":1}"); + ObjectNode projectionJsonObject = objectMapper.getNodeFactory().objectNode().put("title", 1).put("pages", 1); + chapterParams.add("projection", objectMapper.writeValueAsString(projectionJsonObject)); chapterParams.add("version", ""); MultiValueMap pagesParams = new LinkedMultiValueMap<>(); - pagesParams.add("projection", "{\"title\":1}"); + projectionJsonObject = objectMapper.getNodeFactory().objectNode().put("title", 1); + pagesParams.add("projection", objectMapper.writeValueAsString(projectionJsonObject)); pagesParams.add("version", ""); Course course = courseService.find(courseId, courseParams); course.setChapters( Optional .ofNullable(course.getChapters()) .orElseGet(Collections::emptyList) .parallelStream() .map(chapter -> { chapterParams.set("version", String.valueOf(chapter.getVersion())); chapter = chapterService.find(chapter.getId(), chapterParams); chapter.setPages( Optional .ofNullable(chapter.getPages()) .orElseGet(Collections::emptyList) .parallelStream() .map(page -> { pagesParams.set("version", String.valueOf(page.getVersion())); return pageService.find(page.getId(), pagesParams); }) .collect(Collectors.toList())); return chapter; }) .collect(Collectors.toList())); return course; } @GetMapping(value = "/{courseId}/versions", produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public EntityList getCourseVersions(@PathVariable(value = "courseId", required = true) String courseId, - @RequestParam(value="page", required=false) String page) { + @RequestParam(value="page", required=false) Integer page) { MultiValueMap courseParams = new LinkedMultiValueMap<>(); - courseParams.add("page", page); + courseParams.add("page", String.valueOf(page)); courseParams.add("version", "all"); EntityList courseVersions = courseService.getAllCourseVersions(courseId, courseParams); return courseVersions; } @GetMapping(value = "/{courseId}/versions/{version}", params = "pageId", produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public Page getPage(@PathVariable(value = "courseId", required = true) String courseId, - @PathVariable(value = "version", required = true) String version, - @RequestParam(value = "pageId", required = true) String pageId) { + @PathVariable(value = "version", required = true) Integer version, + @RequestParam(value = "pageId", required = true) String pageId) throws JsonProcessingException { MultiValueMap courseParams = new LinkedMultiValueMap<>(); - courseParams.add("version", version); + courseParams.add("version", String.valueOf(version)); MultiValueMap chapterParams = new LinkedMultiValueMap<>(); - chapterParams.add("projection", "{\"title\":1, \"pages\":1}"); + ObjectNode projectionJsonObject = objectMapper.getNodeFactory().objectNode().put("title", 1).put("pages", 1); + chapterParams.add("projection", objectMapper.writeValueAsString(projectionJsonObject)); chapterParams.add("version", ""); MultiValueMap pageParams = new LinkedMultiValueMap<>(); pageParams.add("version", ""); Course course = courseService.find(courseId, courseParams); Chapter chapterTarget = Optional .ofNullable(course.getChapters()) .orElseGet(Collections::emptyList) .parallelStream() .filter(chapter -> { chapterParams.set("version", String.valueOf(chapter.getVersion())); Chapter enrichedChapter = chapterService.find(chapter.getId(), chapterParams); boolean isPagePresent = Optional.ofNullable(enrichedChapter.getPages()).orElseGet(Collections::emptyList) .parallelStream().filter(page -> pageId.equals(page.getId())).findFirst().isPresent(); if (!isPagePresent) { return false; } chapter.setPages(enrichedChapter.getPages()); return true; }) .findFirst() .orElseThrow(() -> new ResourceNotFoundException()); return Optional.ofNullable(chapterTarget.getPages()).orElseGet(Collections::emptyList).parallelStream() .filter(page -> pageId.equals(page.getId())) .findFirst() .map(page -> { pageParams.set("version", String.valueOf(page.getVersion())); return pageService.find(page.getId(), pageParams); }) .orElseThrow(() -> new ResourceNotFoundException()); } }