From 469e13be79640a161838a0d8f7f20680814e6116 Mon Sep 17 00:00:00 2001 From: Martin Ledvinka Date: Fri, 29 Nov 2024 13:05:43 +0100 Subject: [PATCH] [kbss-cvut/termit-ui#581] Modify REST API to support term translations import. --- .../termit/rest/VocabularyController.java | 36 +++++++++++------ .../service/business/VocabularyService.java | 13 +++++++ .../document/TermOccurrenceResolver.java | 3 +- .../VocabularyAuthorizationService.java | 6 +++ .../termit/rest/VocabularyControllerTest.java | 39 ++++++++++++++++--- 5 files changed, 77 insertions(+), 20 deletions(-) diff --git a/src/main/java/cz/cvut/kbss/termit/rest/VocabularyController.java b/src/main/java/cz/cvut/kbss/termit/rest/VocabularyController.java index f3416c040..9f8475ad0 100644 --- a/src/main/java/cz/cvut/kbss/termit/rest/VocabularyController.java +++ b/src/main/java/cz/cvut/kbss/termit/rest/VocabularyController.java @@ -235,12 +235,22 @@ public ResponseEntity createVocabulary( example = ApiDoc.ID_NAMESPACE_EXAMPLE) @RequestParam(name = QueryParams.NAMESPACE, required = false) Optional namespace, - @Parameter(description = "File containing a SKOS glossary in RDF.") - @RequestParam(name = "file") MultipartFile file) { + @Parameter( + description = "File containing a SKOS glossary in RDF or an Excel file with supported structure.") + @RequestParam(name = "file") MultipartFile file, + @Parameter(description = "Whether to import only translations of existing terms from the vocabulary.") + @RequestParam(name = "translationsOnly", required = false, + defaultValue = "false") boolean translationsOnly) { final URI vocabularyIri = resolveVocabularyUri(localName, namespace); - final Vocabulary vocabulary = vocabularyService.importVocabulary(vocabularyIri, file); - LOG.debug("Vocabulary {} re-imported.", vocabulary); - return ResponseEntity.created(locationWithout(generateLocation(vocabulary.getUri()), "/import/" + localName)) + final Vocabulary result; + if (translationsOnly) { + result = vocabularyService.importTermTranslations(vocabularyIri, file); + LOG.debug("Translations of terms in vocabulary {} imported.", result); + } else { + result = vocabularyService.importVocabulary(vocabularyIri, file); + LOG.debug("Vocabulary {} re-imported.", result); + } + return ResponseEntity.created(locationWithout(generateLocation(result.getUri()), "/import/" + localName)) .build(); } @@ -310,13 +320,14 @@ public List getDetailedHistoryOfContent( example = ApiDoc.ID_NAMESPACE_EXAMPLE) @RequestParam(name = QueryParams.NAMESPACE, required = false) Optional namespace, @Parameter(description = ChangeRecordFilterDto.ApiDoc.TERM_NAME_DESCRIPTION) @RequestParam(name = "term", - required = false, - defaultValue = "") String termName, + required = false, + defaultValue = "") String termName, @Parameter(description = ChangeRecordFilterDto.ApiDoc.CHANGE_TYPE_DESCRIPTION) @RequestParam(name = "type", - required = false) URI changeType, - @Parameter(description = ChangeRecordFilterDto.ApiDoc.AUTHOR_NAME_DESCRIPTION) @RequestParam(name = "author", - required = false, - defaultValue = "") String authorName, + required = false) URI changeType, + @Parameter(description = ChangeRecordFilterDto.ApiDoc.AUTHOR_NAME_DESCRIPTION) @RequestParam( + name = "author", + required = false, + defaultValue = "") String authorName, @Parameter(description = ChangeRecordFilterDto.ApiDoc.CHANGED_ATTRIBUTE_DESCRIPTION) @RequestParam( name = "attribute", required = false, defaultValue = "") String changedAttributeName, @@ -327,7 +338,8 @@ public List getDetailedHistoryOfContent( name = Constants.QueryParams.PAGE, required = false, defaultValue = DEFAULT_PAGE) Integer pageNo) { final Pageable pageReq = createPageRequest(pageSize, pageNo); final Vocabulary vocabulary = vocabularyService.getReference(resolveVocabularyUri(localName, namespace)); - final ChangeRecordFilterDto filter = new ChangeRecordFilterDto(termName, changedAttributeName, authorName, changeType); + final ChangeRecordFilterDto filter = new ChangeRecordFilterDto(termName, changedAttributeName, authorName, + changeType); return vocabularyService.getDetailedHistoryOfContent(vocabulary, filter, pageReq); } diff --git a/src/main/java/cz/cvut/kbss/termit/service/business/VocabularyService.java b/src/main/java/cz/cvut/kbss/termit/service/business/VocabularyService.java index ca2e75465..e697b9498 100644 --- a/src/main/java/cz/cvut/kbss/termit/service/business/VocabularyService.java +++ b/src/main/java/cz/cvut/kbss/termit/service/business/VocabularyService.java @@ -281,6 +281,19 @@ public Vocabulary importVocabulary(URI vocabularyIri, MultipartFile file) { return repositoryService.importVocabulary(vocabularyIri, file); } + /** + * Imports translations of terms in the specified vocabulary from the specified file. + * @param vocabularyIri IRI of vocabulary for whose terms to import translations + * @param file File from which to import the translations + * @return The imported vocabulary metadata + * @throws cz.cvut.kbss.termit.exception.importing.VocabularyImportException If the import fails + */ + @PreAuthorize("@vocabularyAuthorizationService.canModify(#vocabularyIri)") + public Vocabulary importTermTranslations(URI vocabularyIri, MultipartFile file) { + // TODO + return null; + } + /** * Gets an Excel template file that can be used to import terms into TermIt. * diff --git a/src/main/java/cz/cvut/kbss/termit/service/document/TermOccurrenceResolver.java b/src/main/java/cz/cvut/kbss/termit/service/document/TermOccurrenceResolver.java index 616c0707d..6cb6d66ec 100644 --- a/src/main/java/cz/cvut/kbss/termit/service/document/TermOccurrenceResolver.java +++ b/src/main/java/cz/cvut/kbss/termit/service/document/TermOccurrenceResolver.java @@ -31,7 +31,6 @@ import java.net.URI; import java.util.Collections; import java.util.List; -import java.util.function.Consumer; /** * Base class for resolving term occurrences in an annotated document. @@ -50,7 +49,7 @@ protected TermOccurrenceResolver(TermRepositoryService termService) { * Parses the specified input into some abstract representation from which new terms and term occurrences can be * extracted. *

- * Note that this method has to be called before calling {@link #findTermOccurrences(Consumer)}. + * Note that this method has to be called before calling {@link #findTermOccurrences(OccurrenceConsumer)}. * * @param input The input to parse * @param source Original source of the input. Used for term occurrence generation diff --git a/src/main/java/cz/cvut/kbss/termit/service/security/authorization/VocabularyAuthorizationService.java b/src/main/java/cz/cvut/kbss/termit/service/security/authorization/VocabularyAuthorizationService.java index 37f99ff3d..777f7413c 100644 --- a/src/main/java/cz/cvut/kbss/termit/service/security/authorization/VocabularyAuthorizationService.java +++ b/src/main/java/cz/cvut/kbss/termit/service/security/authorization/VocabularyAuthorizationService.java @@ -165,6 +165,12 @@ public boolean canRead(VocabularyDto dto) { return canRead(new Vocabulary(dto.getUri())); } + public boolean canModify(URI vocabularyIri) { + Objects.requireNonNull(vocabularyIri); + final Vocabulary vocabulary = new Vocabulary(vocabularyIri); + return canModify(vocabulary); + } + @Override public boolean canModify(Vocabulary asset) { Objects.requireNonNull(asset); diff --git a/src/test/java/cz/cvut/kbss/termit/rest/VocabularyControllerTest.java b/src/test/java/cz/cvut/kbss/termit/rest/VocabularyControllerTest.java index 119681497..0a5a6b9e1 100644 --- a/src/test/java/cz/cvut/kbss/termit/rest/VocabularyControllerTest.java +++ b/src/test/java/cz/cvut/kbss/termit/rest/VocabularyControllerTest.java @@ -442,7 +442,7 @@ void getHistoryReturnsListOfChangeRecordsForSpecifiedVocabulary() throws Excepti }); assertNotNull(result); assertEquals(records, result); - verify(serviceMock).getChanges(vocabulary,emptyFilter); + verify(serviceMock).getChanges(vocabulary, emptyFilter); } @Test @@ -653,15 +653,18 @@ void getDetailedHistoryOfContentReturnsListOfChangeRecordsWhenNoFilterIsSpecifie final int pageSize = Integer.parseInt(VocabularyController.DEFAULT_PAGE_SIZE); final Vocabulary vocabulary = generateVocabularyAndInitReferenceResolution(); final Term term = Generator.generateTermWithId(); - final List changeRecords = IntStream.range(0, 5).mapToObj(i -> Generator.generateChangeRecords(term, user)).flatMap(List::stream).toList(); + final List changeRecords = IntStream.range(0, 5).mapToObj( + i -> Generator.generateChangeRecords(term, user)).flatMap(List::stream).toList(); final ChangeRecordFilterDto filter = new ChangeRecordFilterDto(); final Pageable pageable = Pageable.ofSize(pageSize); doReturn(changeRecords).when(serviceMock).getDetailedHistoryOfContent(vocabulary, filter, pageable); - final MvcResult mvcResult = mockMvc.perform(get(PATH + "/" + FRAGMENT + "/history-of-content/detail")).andExpect(status().isOk()).andReturn(); + final MvcResult mvcResult = mockMvc.perform(get(PATH + "/" + FRAGMENT + "/history-of-content/detail")) + .andExpect(status().isOk()).andReturn(); final List result = - readValue(mvcResult, new TypeReference>() {}); + readValue(mvcResult, new TypeReference<>() { + }); assertNotNull(result); assertEquals(changeRecords, result); verify(serviceMock).getDetailedHistoryOfContent(vocabulary, filter, pageable); @@ -673,9 +676,33 @@ void getLanguagesRetrievesAndReturnsListOfLanguagesUsedInVocabulary() throws Exc final List languages = List.of(Environment.LANGUAGE, "cs", "de"); when(serviceMock.getLanguages(VOCABULARY_URI)).thenReturn(languages); - final MvcResult mvcResult = mockMvc.perform(get(PATH + "/" + FRAGMENT + "/languages").queryParam(QueryParams.NAMESPACE, NAMESPACE)).andReturn(); - final List result = readValue(mvcResult, new TypeReference>() {}); + final MvcResult mvcResult = mockMvc.perform( + get(PATH + "/" + FRAGMENT + "/languages").queryParam(QueryParams.NAMESPACE, NAMESPACE)).andReturn(); + final List result = readValue(mvcResult, new TypeReference<>() { + }); assertEquals(languages, result); verify(serviceMock).getLanguages(VOCABULARY_URI); } + + @Test + void reImportVocabularyRunsTermTranslationsImportForUploadedFileWhenTranslationsOnlyIsSpecified() throws Exception { + when(configMock.getNamespace().getVocabulary()).thenReturn(NAMESPACE); + final Vocabulary vocabulary = Generator.generateVocabulary(); + vocabulary.setUri(URI.create(NAMESPACE + FRAGMENT)); + when(idResolverMock.resolveIdentifier(NAMESPACE, FRAGMENT)).thenReturn(vocabulary.getUri()); + when(serviceMock.importTermTranslations(any(URI.class), any())).thenReturn(vocabulary); + final MockMultipartFile upload = new MockMultipartFile("file", "vocabulary.xlsx", + Constants.MediaType.EXCEL, + Environment.loadFile("data/import-simple-en-cs.xlsx")); + final MvcResult mvcResult = mockMvc.perform(multipart(PATH + "/" + FRAGMENT + "/import").file(upload) + .queryParam( + "translationsOnly", + "true")) + .andExpect(status().isCreated()) + .andReturn(); + verifyLocationEquals(PATH + "/" + FRAGMENT, mvcResult); + assertThat(mvcResult.getResponse().getHeader(HttpHeaders.LOCATION), + containsString(QueryParams.NAMESPACE + "=" + NAMESPACE)); + verify(serviceMock).importTermTranslations(vocabulary.getUri(), upload); + } }