From 46ef1b71e86c381327a1b1dcb6534289484735cc Mon Sep 17 00:00:00 2001 From: Martin Ledvinka Date: Sat, 31 Dec 2022 17:57:27 +0100 Subject: [PATCH 01/23] [Doc] Extend documentation of Configuration. --- .../cvut/kbss/termit/util/Configuration.java | 42 ++++++++++++++++--- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/src/main/java/cz/cvut/kbss/termit/util/Configuration.java b/src/main/java/cz/cvut/kbss/termit/util/Configuration.java index 6981f229b..f3bcbe508 100644 --- a/src/main/java/cz/cvut/kbss/termit/util/Configuration.java +++ b/src/main/java/cz/cvut/kbss/termit/util/Configuration.java @@ -26,6 +26,9 @@ *

* The runtime configuration consists of predefined default values and configuration loaded from config files on * classpath. Values from config files supersede the default values. + *

+ * The configuration can be also set via OS + * environment variables. These override any statically configured values. */ @org.springframework.context.annotation.Configuration @ConfigurationProperties("termit") @@ -33,8 +36,16 @@ public class Configuration { /** * TermIt frontend URL. + *

+ * It is used, for example, for links in emails sent to users. */ private String url = "http://localhost:3000/#"; + /** + * Name of the JMX bean exported by TermIt. + *

+ * Normally should not need to change unless multiple instances of TermIt are running in the same application + * server. + */ private String jmxBeanName = "TermItAdminBean"; private Persistence persistence = new Persistence(); private Repository repository = new Repository(); @@ -229,6 +240,8 @@ public static class Repository { String url; /** * Public URL of the main application repository. + *

+ * Can be used to provide read-only no authorization access to the underlying data. */ Optional publicUrl = Optional.empty(); /** @@ -308,7 +321,7 @@ public void setExtension(String extension) { @ConfigurationProperties(prefix = "comments") public static class Comments { /** - * IRI of the repository context used to store comments (discussion to assets) + * IRI of the repository context used to store comments (discussion to assets). */ @NotNull String context; @@ -358,10 +371,10 @@ public static class Namespace { * Since File identifier is given by the identifier of the Document it belongs to and its own normalized label, * this separator is used to (optionally) configure the File identifier namespace. *

- * For example, if we have a Document with IRI {@code http://www.example.org/ontologies/resources/metropolitan-plan} + * For example, if we have a Document with IRI {@code http://www.example.org/ontologies/resources/metropolitan-plan/document} * and a File with normalized label {@code main-file}, the resulting IRI will be {@code - * http://www.example.org/ontologies/resources/metropolitan-plan/SEPARATOR/main-file}, where 'SEPARATOR' is the - * value of this configuration parameter. + * http://www.example.org/ontologies/resources/metropolitan-plan/document/SEPARATOR/main-file}, where + * 'SEPARATOR' is the value of this configuration parameter. */ private NamespaceDetail file = new NamespaceDetail(); @@ -440,12 +453,12 @@ public void setSeparator(String separator) { @ConfigurationProperties(prefix = "admin") public static class Admin { /** - * Specifies folder in which admin credentials are saved when his account is generated. + * Specifies the folder in which admin credentials are saved when its account is generated. */ @NotNull String credentialsLocation; /** - * Name of the file in which admin credentials are saved when his account is generated. + * Name of the file in which admin credentials are saved when its account is generated. */ @NotNull String credentialsFile; @@ -517,7 +530,10 @@ public static class TextAnalysis { *

* More specifically, when annotated file content is being processed, term occurrences with sufficient score * will cause creation of corresponding term assignments to the file. + * + * @deprecated This configuration is currently not used. */ + @Deprecated @NotNull String termAssignmentMinScore; @@ -590,6 +606,14 @@ public void setWhiteListProperties(final Set whiteListProperties) { @org.springframework.context.annotation.Configuration public static class Workspace { + /** + * Whether all vocabularies in the repository are editable. + *

+ * Allows running TermIt in two modes - one is that all vocabularies represent the current version and can be + * edited. The other mode is that working copies of vocabularies are created and the user only selects a subset + * of these working copies to edit (the so-called workspace), while all other vocabularies are read-only for + * them. + */ @NotNull private boolean allVocabulariesEditable = true; @@ -604,6 +628,9 @@ public void setAllVocabulariesEditable(boolean allVocabulariesEditable) { @org.springframework.context.annotation.Configuration public static class Cors { + /** + * A comma-separated list of allowed origins for CORS. + */ @NotNull private String allowedOrigins = "http://localhost:3000"; @@ -663,6 +690,9 @@ public void setComments(String comments) { @org.springframework.context.annotation.Configuration public static class Mail { + /** + * Human-readable name to use as email sender. + */ private String sender; public String getSender() { From 6a5f14057242adf53ae3bda7e91bb87fdf202c83 Mon Sep 17 00:00:00 2001 From: Martin Ledvinka Date: Tue, 3 Jan 2023 14:28:33 +0100 Subject: [PATCH 02/23] [kbss-cvut/termit-ui#347] Extend DocumentManager with support for getting file version at specific timestamp. --- .../document/DefaultDocumentManager.java | 60 ++++++- .../service/document/DocumentManager.java | 41 +++-- .../document/DefaultDocumentManagerTest.java | 155 ++++++++++++++---- 3 files changed, 200 insertions(+), 56 deletions(-) diff --git a/src/main/java/cz/cvut/kbss/termit/service/document/DefaultDocumentManager.java b/src/main/java/cz/cvut/kbss/termit/service/document/DefaultDocumentManager.java index 82055fa50..8ad3dfa14 100644 --- a/src/main/java/cz/cvut/kbss/termit/service/document/DefaultDocumentManager.java +++ b/src/main/java/cz/cvut/kbss/termit/service/document/DefaultDocumentManager.java @@ -38,11 +38,11 @@ import java.io.InputStream; import java.nio.file.Files; import java.nio.file.StandardCopyOption; +import java.time.Instant; import java.time.ZoneId; import java.time.format.DateTimeFormatter; -import java.util.List; -import java.util.Objects; -import java.util.Optional; +import java.time.temporal.TemporalAccessor; +import java.util.*; import java.util.function.Consumer; /** @@ -53,8 +53,9 @@ public class DefaultDocumentManager implements DocumentManager { private static final Logger LOG = LoggerFactory.getLogger(DefaultDocumentManager.class); - private final DateTimeFormatter timestampFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd_HHmmss_S") - .withZone(ZoneId.systemDefault()); + static final String BACKUP_NAME_SEPARATOR = "~"; + static final DateTimeFormatter BACKUP_TIMESTAMP_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd_HHmmss_S") + .withZone(ZoneId.systemDefault()); private final Configuration config; @@ -99,6 +100,47 @@ public TypeAwareResource getAsResource(File file) { return new TypeAwareFileSystemResource(resolveFile(file, true), getMediaType(file)); } + @Override + public TypeAwareResource getAsResource(File file, Instant at) { + Objects.requireNonNull(file); + Objects.requireNonNull(at); + final String fileName = IdentifierResolver.sanitizeFileName(file.getLabel()); + final java.io.File directory = new java.io.File(config.getFile() + .getStorage() + java.io.File.separator + file.getDirectoryName() + java.io.File.separator); + if (!directory.exists() || !directory.isDirectory()) { + LOG.error("Document directory not found for file {} at location {}.", file, directory.getPath()); + throw new NotFoundException("File " + file + " not found on file system."); + } + final List candidates = Arrays.asList( + Objects.requireNonNull(directory.listFiles((dir, filename) -> filename.startsWith(fileName)))); + if (candidates.isEmpty()) { + LOG.error("File {} not found at location {}.", file, directory.getPath()); + throw new NotFoundException("File " + file + " not found on file system."); + } + return new TypeAwareFileSystemResource(resolveFileVersionAt(at, candidates), getMediaType(file)); + } + + private java.io.File resolveFileVersionAt(Instant at, List candidates) { + final Map backups = new HashMap<>(); + candidates.forEach(f -> { + if (!f.getName().contains(BACKUP_NAME_SEPARATOR)) { + backups.put(Utils.timestamp(), f); + return; + } + final String strTimestamp = f.getName().substring(f.getName().indexOf(BACKUP_NAME_SEPARATOR) + 1); + final TemporalAccessor backupTimestamp = BACKUP_TIMESTAMP_FORMAT.parse(strTimestamp); + backups.put(Instant.from(backupTimestamp), f); + }); + final List backupTimestamps = new ArrayList<>(backups.keySet()); + Collections.sort(backupTimestamps); + for (Instant timestamp : backupTimestamps) { + if (timestamp.isAfter(at)) { + return backups.get(timestamp); + } + } + throw new DocumentManagerException("Unable to find file version at " + at); + } + @Override public void saveFileContent(File file, InputStream content) { try { @@ -132,7 +174,7 @@ public void createBackup(File file) { */ private String generateBackupFileName(File file) { final String origName = IdentifierResolver.sanitizeFileName(file.getLabel()); - return origName + "~" + timestampFormat.format(Utils.timestamp()); + return origName + BACKUP_NAME_SEPARATOR + BACKUP_TIMESTAMP_FORMAT.format(Utils.timestamp()); } @Override @@ -276,9 +318,9 @@ private java.io.File moveFolder(File changedFile, java.io.File physicalOriginal, tmpNewFile.setUri(changedFile.getUri()); tmpNewFile.setLabel(event.getNewName()); final java.io.File newDirectory = new java.io.File(originalDirectory.getParentFile().getAbsolutePath() + - java.io.File.separator + tmpNewFile.getDirectoryName()); + java.io.File.separator + tmpNewFile.getDirectoryName()); LOG.trace("Moving file parent directory from '{}' to '{}' due to file rename.", - originalDirectory.getAbsolutePath(), newDirectory.getAbsolutePath()); + originalDirectory.getAbsolutePath(), newDirectory.getAbsolutePath()); Files.move(originalDirectory.toPath(), newDirectory.toPath()); return new java.io.File( newDirectory.getAbsolutePath() + java.io.File.separator + physicalOriginal.getName()); @@ -291,7 +333,7 @@ private void moveFile(File original, java.io.File physicalOriginal, FileRenameEv tempNewFile.setLabel(event.getNewName()); final java.io.File newFile = resolveFile(tempNewFile, false); LOG.debug("Moving content from '{}' to '{}' due to file rename.", event.getOriginalName(), - event.getNewName()); + event.getNewName()); Files.move(physicalOriginal.toPath(), newFile.toPath()); moveBackupFiles(original, physicalOriginal.getParentFile(), event); } diff --git a/src/main/java/cz/cvut/kbss/termit/service/document/DocumentManager.java b/src/main/java/cz/cvut/kbss/termit/service/document/DocumentManager.java index de02a569d..75bf8961d 100644 --- a/src/main/java/cz/cvut/kbss/termit/service/document/DocumentManager.java +++ b/src/main/java/cz/cvut/kbss/termit/service/document/DocumentManager.java @@ -1,19 +1,16 @@ /** - * TermIt - * Copyright (C) 2019 Czech Technical University in Prague - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * TermIt Copyright (C) 2019 Czech Technical University in Prague + *

+ * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later + * version. + *

+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + *

+ * You should have received a copy of the GNU General Public License along with this program. If not, see + * . */ package cz.cvut.kbss.termit.service.document; @@ -23,6 +20,7 @@ import cz.cvut.kbss.termit.util.TypeAwareResource; import java.io.InputStream; +import java.time.Instant; import java.util.Optional; /** @@ -57,6 +55,19 @@ public interface DocumentManager { */ TypeAwareResource getAsResource(File file); + /** + * Gets a version the file valid at the specified instant as a {@link org.springframework.core.io.Resource}. + *

+ * This method can be used to retrieve a backup of the specified file. Note that if the {@code at} parameter + * specifies a time before the file was uploaded to TermIt, the first available version is retrieved. + * + * @param file File representing the physical item + * @param at Timestamp of the version of the retrieved file + * @return Resource representation of a matching version of the physical item + * @throws NotFoundException If the file cannot be found + */ + TypeAwareResource getAsResource(File file, Instant at); + /** * Saves the specified content to a physical location represented by the specified file. *

diff --git a/src/test/java/cz/cvut/kbss/termit/service/document/DefaultDocumentManagerTest.java b/src/test/java/cz/cvut/kbss/termit/service/document/DefaultDocumentManagerTest.java index 439a79b1b..7e4e918d1 100644 --- a/src/test/java/cz/cvut/kbss/termit/service/document/DefaultDocumentManagerTest.java +++ b/src/test/java/cz/cvut/kbss/termit/service/document/DefaultDocumentManagerTest.java @@ -1,26 +1,19 @@ /** * TermIt Copyright (C) 2019 Czech Technical University in Prague *

- * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later + * version. *

- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. *

- * You should have received a copy of the GNU General Public License along with this program. If not, see . + * You should have received a copy of the GNU General Public License along with this program. If not, see + * . */ package cz.cvut.kbss.termit.service.document; -import static cz.cvut.kbss.termit.environment.Environment.loadFile; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.greaterThan; -import static org.hamcrest.Matchers.startsWith; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - import cz.cvut.kbss.termit.environment.Generator; import cz.cvut.kbss.termit.environment.PropertyMockingApplicationContextInitializer; import cz.cvut.kbss.termit.event.DocumentRenameEvent; @@ -33,19 +26,29 @@ import cz.cvut.kbss.termit.service.IdentifierResolver; import cz.cvut.kbss.termit.util.Configuration; import cz.cvut.kbss.termit.util.TypeAwareResource; +import cz.cvut.kbss.termit.util.Utils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.util.MimeTypeUtils; + import java.io.ByteArrayInputStream; import java.io.InputStream; import java.nio.file.Files; +import java.time.Instant; +import java.time.temporal.TemporalAccessor; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.MediaType; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.util.MimeTypeUtils; + +import static cz.cvut.kbss.termit.environment.Environment.loadFile; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.startsWith; +import static org.junit.jupiter.api.Assertions.*; @ContextConfiguration(initializers = {PropertyMockingApplicationContextInitializer.class}) class DefaultDocumentManagerTest extends BaseServiceTestRunner { @@ -77,7 +80,7 @@ private java.io.File generateDirectory() throws Exception { dir.deleteOnExit(); configuration.getFile().setStorage(dir.getAbsolutePath()); final java.io.File docDir = new java.io.File(dir.getAbsolutePath() + java.io.File.separator + - document.getDirectoryName()); + document.getDirectoryName()); docDir.mkdir(); docDir.deleteOnExit(); return docDir; @@ -130,7 +133,7 @@ private java.io.File generateFileWithoutParentDocument(File file) throws Excepti dir.deleteOnExit(); configuration.getFile().setStorage(dir.getAbsolutePath()); final java.io.File fileDir = new java.io.File(dir.getAbsolutePath() + java.io.File.separator + - file.getDirectoryName()); + file.getDirectoryName()); fileDir.mkdir(); fileDir.deleteOnExit(); final java.io.File content = new java.io.File(fileDir + java.io.File.separator + file.getLabel()); @@ -226,6 +229,7 @@ void createBackupCreatesBackupOfFileWithoutExtension() throws Exception { assertNotNull(docDir.listFiles()); sut.createBackup(file); final java.io.File[] files = docDir.listFiles((d, name) -> name.startsWith("withoutExtension")); + assertNotNull(files); assertEquals(2, files.length); for (java.io.File f : files) { f.deleteOnExit(); @@ -325,7 +329,7 @@ void createBackupSanitizesFileLabelToEnsureValidFileName() throws Exception { dir.deleteOnExit(); configuration.getFile().setStorage(dir.getAbsolutePath()); final java.io.File docDir = new java.io.File(dir.getAbsolutePath() + java.io.File.separator + - document.getDirectoryName()); + document.getDirectoryName()); docDir.mkdir(); docDir.deleteOnExit(); final File file = new File(); @@ -397,11 +401,15 @@ private List createTestBackups(java.io.File file) throws Exception final List backupFiles = new ArrayList<>(3); for (int i = 0; i < 3; i++) { final String path = file.getAbsolutePath(); - final String newPath = path + "~" + System.currentTimeMillis() + "-" + i; + final String newPath = path + DefaultDocumentManager.BACKUP_NAME_SEPARATOR + DefaultDocumentManager.BACKUP_TIMESTAMP_FORMAT.format( + Instant.ofEpochMilli(System.currentTimeMillis() - (i + 1) * 10000)); final java.io.File target = new java.io.File(newPath); Files.copy(file.toPath(), target.toPath()); backupFiles.add(target); + target.deleteOnExit(); } + // So that the oldest is first + Collections.reverse(backupFiles); return backupFiles; } @@ -482,7 +490,7 @@ void onFileRenameMovesPhysicalFileInDocumentAccordingToNewName() throws Exceptio sut.onFileRename(new FileRenameEvent(file, physicalFile.getName(), newName)); final java.io.File newFile = new java.io.File( - configuration.getFile().getStorage() + java.io.File.separator + file.getDirectoryName() + + configuration.getFile().getStorage() + java.io.File.separator + file.getDirectoryName() + java.io.File.separator + file.getLabel()); assertTrue(newFile.exists()); newFile.deleteOnExit(); @@ -498,7 +506,7 @@ void onFileRenameDoesNothingWhenPhysicalFileDoesNotExist() { sut.onFileRename(new FileRenameEvent(file, oldName, file.getLabel())); final java.io.File docDir = new java.io.File( - configuration.getFile().getStorage() + java.io.File.separator + file.getDirectoryName()); + configuration.getFile().getStorage() + java.io.File.separator + file.getDirectoryName()); assertFalse(docDir.exists()); } @@ -514,7 +522,7 @@ void onFileRenameMovesBackupsOfPhysicalFileAsWell() throws Exception { sut.onFileRename(new FileRenameEvent(file, physicalFile.getName(), newName)); for (java.io.File backup : backups) { final java.io.File newBackup = new java.io.File( - configuration.getFile().getStorage() + java.io.File.separator + + configuration.getFile().getStorage() + java.io.File.separator + file.getDirectoryName() + java.io.File.separator + backup.getName().replace(physicalFile.getName(), newName)); assertTrue(newBackup.exists()); @@ -522,7 +530,7 @@ void onFileRenameMovesBackupsOfPhysicalFileAsWell() throws Exception { assertFalse(backup.exists()); } final java.io.File newFile = new java.io.File( - configuration.getFile().getStorage() + java.io.File.separator + file.getDirectoryName() + + configuration.getFile().getStorage() + java.io.File.separator + file.getDirectoryName() + java.io.File.separator + file.getLabel()); assertTrue(newFile.exists()); newFile.deleteOnExit(); @@ -539,10 +547,11 @@ void onFileRenameMovesWholeDirectoryWhenFileHasNoDocument() throws Exception { sut.onFileRename(new FileRenameEvent(file, physicalOriginal.getName(), newName)); final java.io.File newDirectory = new java.io.File( - configuration.getFile().getStorage() + java.io.File.separator + file.getDirectoryName()); + configuration.getFile().getStorage() + java.io.File.separator + file.getDirectoryName()); assertTrue(newDirectory.exists()); newDirectory.deleteOnExit(); - final java.io.File newFile = new java.io.File(configuration.getFile().getStorage() + java.io.File.separator + file.getDirectoryName() + + final java.io.File newFile = new java.io.File( + configuration.getFile().getStorage() + java.io.File.separator + file.getDirectoryName() + java.io.File.separator + file.getLabel()); assertTrue(newFile.exists()); newFile.deleteOnExit(); @@ -559,10 +568,92 @@ void onDocumentRenameMovesWholeDirectory() throws Exception { document.setLabel(newDirLabel); sut.onDocumentRename(new DocumentRenameEvent(document, oldDirLabel, document.getLabel())); - final java.io.File newDirectory = new java.io.File( configuration.getFile().getStorage() + java.io.File.separator + document.getDirectoryName()); + final java.io.File newDirectory = new java.io.File( + configuration.getFile().getStorage() + java.io.File.separator + document.getDirectoryName()); assertTrue(newDirectory.exists()); newDirectory.deleteOnExit(); assertFalse(oldDirectory.exists()); } + + @Test + void getAsResourceAtTimestampReturnsBackupCreatedMostSoonAfterTimestamp() throws Exception { + final File file = new File(); + final java.io.File physicalFile = generateFile(); + file.setLabel(physicalFile.getName()); + document.addFile(file); + file.setDocument(document); + + final List files = createTestBackups(physicalFile); + final java.io.File expected = files.get(Generator.randomIndex(files)); + final String strBackupTimestamp = expected.getName().substring( + expected.getName().indexOf(DefaultDocumentManager.BACKUP_NAME_SEPARATOR) + 1); + final TemporalAccessor backupTimestamp = DefaultDocumentManager.BACKUP_TIMESTAMP_FORMAT.parse( + strBackupTimestamp); + final Instant timestamp = Instant.from(backupTimestamp).minusSeconds(5); + + final org.springframework.core.io.Resource result = sut.getAsResource(file, timestamp); + assertEquals(expected, result.getFile()); + } + + @Test + void getAsResourceAtTimestampReturnsOldestBackupWhenTimestampIsEpoch() throws Exception { + final File file = new File(); + final java.io.File physicalFile = generateFile(); + file.setLabel(physicalFile.getName()); + document.addFile(file); + file.setDocument(document); + + final List files = createTestBackups(physicalFile); + final org.springframework.core.io.Resource result = sut.getAsResource(file, Instant.EPOCH); + assertEquals(files.get(0), result.getFile()); + } + + @Test + void getAsResourceWithTimestampReturnsCurrentFileWhenTimestampIsNow() throws Exception { + // Slightly back in time to prevent issues with test speed and instant comparison + final Instant at = Utils.timestamp().minusMillis(100); + final File file = new File(); + final java.io.File physicalFile = generateFile(); + file.setLabel(physicalFile.getName()); + document.addFile(file); + file.setDocument(document); + + createTestBackups(physicalFile); + final org.springframework.core.io.Resource result = sut.getAsResource(file, at); + assertEquals(physicalFile, result.getFile()); + } + + @Test + void getAsResourceWithTimestampThrowsNotFoundExceptionWhenParentDocumentDirectoryDoesNotExistOnFileSystem() { + final File file = Generator.generateFileWithId("test.html"); + document.addFile(file); + file.setDocument(document); + assertThrows(NotFoundException.class, () -> sut.getAsResource(file, Utils.timestamp())); + } + + @Test + void getAsResourceWithTimestampThrowsNotFoundExceptionWhenFileDoesNotExistInParentDocumentDirectoryOnFileSystem() throws Exception { + final File file = new File(); + final java.io.File physicalFile = generateFile(); + file.setLabel(physicalFile.getName()); + document.addFile(file); + file.setDocument(document); + physicalFile.delete(); + + assertThrows(NotFoundException.class, () -> sut.getAsResource(file, Utils.timestamp())); + } + + @Test + void getContentTypeResolvesMIMETypeOfSpecifiedFile() throws Exception { + final File file = new File(); + final java.io.File physicalFile = generateFile(); + file.setLabel(physicalFile.getName()); + document.addFile(file); + file.setDocument(document); + + final Optional result = sut.getContentType(file); + assertTrue(result.isPresent()); + assertEquals(MediaType.TEXT_HTML_VALUE, result.get()); + } } From a047af0ffbf6597a7a0add2e634650a5aaaba597 Mon Sep 17 00:00:00 2001 From: Martin Ledvinka Date: Tue, 3 Jan 2023 16:10:36 +0100 Subject: [PATCH 03/23] [kbss-cvut/termit-ui#347] Implement API for retrieving resource content valid at specified timestamp. --- .../kbss/termit/rest/ResourceController.java | 13 ++- .../service/business/ResourceService.java | 49 ++++++--- .../termit/rest/ResourceControllerTest.java | 103 +++++++++++------- .../service/business/ResourceServiceTest.java | 37 +++---- 4 files changed, 128 insertions(+), 74 deletions(-) diff --git a/src/main/java/cz/cvut/kbss/termit/rest/ResourceController.java b/src/main/java/cz/cvut/kbss/termit/rest/ResourceController.java index 539918791..00c6273bd 100644 --- a/src/main/java/cz/cvut/kbss/termit/rest/ResourceController.java +++ b/src/main/java/cz/cvut/kbss/termit/rest/ResourceController.java @@ -23,6 +23,7 @@ import cz.cvut.kbss.termit.model.changetracking.AbstractChangeRecord; import cz.cvut.kbss.termit.model.resource.File; import cz.cvut.kbss.termit.model.resource.Resource; +import cz.cvut.kbss.termit.rest.util.RestUtils; import cz.cvut.kbss.termit.security.SecurityConstants; import cz.cvut.kbss.termit.service.IdentifierResolver; import cz.cvut.kbss.termit.service.business.ResourceService; @@ -42,6 +43,7 @@ import java.io.IOException; import java.net.URI; +import java.time.Instant; import java.util.List; import java.util.Optional; import java.util.Set; @@ -84,10 +86,17 @@ public void updateResource(@PathVariable String normalizedName, public ResponseEntity getContent( @PathVariable String normalizedName, @RequestParam(name = QueryParams.NAMESPACE, required = false) Optional namespace, - @RequestParam(name = "attachment", required = false) boolean asAttachment) { + @RequestParam(name = "attachment", required = false) boolean asAttachment, + @RequestParam(name = "at", required = false) Optional at) { final Resource resource = getResource(normalizedName, namespace); try { - final TypeAwareResource content = resourceService.getContent(resource); + final TypeAwareResource content; + if (at.isPresent()) { + final Instant timestamp = RestUtils.parseTimestamp(at.get()); + content = resourceService.getContent(resource, timestamp); + } else { + content = resourceService.getContent(resource); + } final ResponseEntity.BodyBuilder builder = ResponseEntity.ok() .contentLength(content.contentLength()) .contentType(MediaType.parseMediaType( diff --git a/src/main/java/cz/cvut/kbss/termit/service/business/ResourceService.java b/src/main/java/cz/cvut/kbss/termit/service/business/ResourceService.java index a69570855..73a012a7e 100644 --- a/src/main/java/cz/cvut/kbss/termit/service/business/ResourceService.java +++ b/src/main/java/cz/cvut/kbss/termit/service/business/ResourceService.java @@ -44,6 +44,7 @@ import java.io.InputStream; import java.net.URI; +import java.time.Instant; import java.util.*; /** @@ -118,13 +119,37 @@ public boolean hasContent(Resource resource) { * @param resource Resource whose content should be retrieved * @return Representation of the resource content * @throws UnsupportedAssetOperationException When content of the specified resource cannot be retrieved + * @throws NotFoundException When the specified resource has no content stored */ public TypeAwareResource getContent(Resource resource) { Objects.requireNonNull(resource); + verifyFileOperationPossible(resource, "Content retrieval"); + return documentManager.getAsResource((File) resource); + } + + private void verifyFileOperationPossible(Resource resource, String operation) { if (!(resource instanceof File)) { - throw new UnsupportedAssetOperationException("Content retrieval is not supported for resource " + resource); + throw new UnsupportedAssetOperationException(operation + " is not supported for resource " + resource); } - return documentManager.getAsResource((File) resource); + } + + /** + * Gets content of the specified resource valid at the specified timestamp. + *

+ * This method provides access to backups of the specified resource. If the specified timestamp is older than the + * first version of the specified resource, this version is returned. Similarly, if the timestamp is later than the + * most recent backup of the resource, the current version is returned. + * + * @param resource Resource whose content should be retrieved + * @param at Timestamp of the version of the retrieved resource + * @return Representation of the resource content + * @throws UnsupportedAssetOperationException When content of the specified resource cannot be retrieved + * @throws NotFoundException When the specified resource has no content stored + */ + public TypeAwareResource getContent(Resource resource, Instant at) { + Objects.requireNonNull(resource); + verifyFileOperationPossible(resource, "Content retrieval"); + return documentManager.getAsResource((File) resource, at); } /** @@ -138,9 +163,7 @@ public TypeAwareResource getContent(Resource resource) { public void saveContent(Resource resource, InputStream content) { Objects.requireNonNull(resource); Objects.requireNonNull(content); - if (!(resource instanceof File)) { - throw new UnsupportedAssetOperationException("Content saving is not supported for resource " + resource); - } + verifyFileOperationPossible(resource, "Content saving"); LOG.trace("Saving new content of resource {}.", resource); final File file = (File) resource; if (documentManager.exists(file)) { @@ -196,7 +219,7 @@ public void addFileToDocument(Resource document, File file) { } else { repositoryService.persist(file); } - if (!getReference(document.getUri()).isPresent()) { + if (getReference(document.getUri()).isEmpty()) { repositoryService.persist(document); } else { update(doc); @@ -238,9 +261,7 @@ public void removeFile(File file) { public void runTextAnalysis(Resource resource, Set vocabularies) { Objects.requireNonNull(resource); Objects.requireNonNull(vocabularies); - if (!(resource instanceof File)) { - throw new UnsupportedAssetOperationException("Text analysis is not supported for resource " + resource); - } + verifyFileOperationPossible(resource, "Text analysis"); LOG.trace("Invoking text analysis on resource {}.", resource); final File file = (File) resource; if (vocabularies.isEmpty()) { @@ -249,7 +270,8 @@ public void runTextAnalysis(Resource resource, Set vocabularies) { "Cannot analyze file without specifying vocabulary context."); } textAnalysisService.analyzeFile(file, - includeImportedVocabularies(Collections.singleton(file.getDocument().getVocabulary()))); + includeImportedVocabularies( + Collections.singleton(file.getDocument().getVocabulary()))); } else { textAnalysisService.analyzeFile(file, includeImportedVocabularies(vocabularies)); } @@ -304,14 +326,13 @@ private Optional createFileOrDocumentLabelUpdateNotification(R if (instance instanceof File) { final Resource original = findRequired(instance.getUri()); if (!Objects.equals(original.getLabel(), instance.getLabel())) { - return Optional.of(new FileRenameEvent((File) instance, original.getLabel(), - instance.getLabel())); + return Optional.of(new FileRenameEvent((File) instance, original.getLabel(), instance.getLabel())); } } else if (instance instanceof Document) { final Resource original = findRequired(instance.getUri()); if (!Objects.equals(original.getLabel(), instance.getLabel())) { - return Optional.of(new DocumentRenameEvent((Document) instance, original.getLabel(), - instance.getLabel())); + return Optional.of( + new DocumentRenameEvent((Document) instance, original.getLabel(), instance.getLabel())); } } return Optional.empty(); diff --git a/src/test/java/cz/cvut/kbss/termit/rest/ResourceControllerTest.java b/src/test/java/cz/cvut/kbss/termit/rest/ResourceControllerTest.java index 8406fcc81..7bd27c881 100644 --- a/src/test/java/cz/cvut/kbss/termit/rest/ResourceControllerTest.java +++ b/src/test/java/cz/cvut/kbss/termit/rest/ResourceControllerTest.java @@ -1,13 +1,16 @@ /** * TermIt Copyright (C) 2019 Czech Technical University in Prague *

- * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later + * version. *

- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. *

- * You should have received a copy of the GNU General Public License along with this program. If not, see . + * You should have received a copy of the GNU General Public License along with this program. If not, see + * . */ package cz.cvut.kbss.termit.rest; @@ -26,6 +29,7 @@ import cz.cvut.kbss.termit.service.business.ResourceService; import cz.cvut.kbss.termit.service.document.util.TypeAwareFileSystemResource; import cz.cvut.kbss.termit.util.Configuration; +import cz.cvut.kbss.termit.util.Constants; import cz.cvut.kbss.termit.util.Constants.QueryParams; import cz.cvut.kbss.termit.util.Utils; import org.junit.jupiter.api.AfterEach; @@ -45,6 +49,8 @@ import java.io.InputStream; import java.net.URI; import java.nio.file.Files; +import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -99,10 +105,11 @@ void updateResourcePassesUpdateDataToService() throws Exception { final Resource resource = Generator.generateResource(); resource.setLabel(RESOURCE_NAME); resource.setUri(RESOURCE_URI); - when(identifierResolverMock.resolveIdentifier(configMock.getNamespace().getResource(), RESOURCE_NAME)).thenReturn(resource.getUri()); + when(identifierResolverMock.resolveIdentifier(configMock.getNamespace().getResource(), + RESOURCE_NAME)).thenReturn(resource.getUri()); mockMvc.perform( - put(PATH + "/" + RESOURCE_NAME).content(toJson(resource)).contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isNoContent()); + put(PATH + "/" + RESOURCE_NAME).content(toJson(resource)).contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNoContent()); verify(identifierResolverMock).resolveIdentifier(configMock.getNamespace().getResource(), RESOURCE_NAME); verify(resourceServiceMock).update(resource); } @@ -113,9 +120,9 @@ void updateResourceThrowsConflictExceptionWhenRequestUrlIdentifierDiffersFromEnt resource.setLabel(RESOURCE_NAME); when(identifierResolverMock.resolveIdentifier(RESOURCE_NAMESPACE, RESOURCE_NAME)).thenReturn(RESOURCE_URI); final MvcResult mvcResult = mockMvc.perform( - put(PATH + "/" + RESOURCE_NAME).content(toJson(resource)).contentType(MediaType.APPLICATION_JSON) - .param(QueryParams.NAMESPACE, RESOURCE_NAMESPACE)) - .andExpect(status().isConflict()).andReturn(); + put(PATH + "/" + RESOURCE_NAME).content(toJson(resource)).contentType(MediaType.APPLICATION_JSON) + .param(QueryParams.NAMESPACE, RESOURCE_NAMESPACE)) + .andExpect(status().isConflict()).andReturn(); final ErrorInfo errorInfo = readValue(mvcResult, ErrorInfo.class); assertThat(errorInfo.getMessage(), containsString("does not match the ID of the specified entity")); } @@ -160,14 +167,14 @@ void saveContentSavesContentViaServiceAndReturnsNoContentStatus() throws Excepti final java.io.File attachment = createTemporaryHtmlFile(); final MockMultipartFile upload = new MockMultipartFile("file", file.getLabel(), MediaType.TEXT_HTML_VALUE, - Files.readAllBytes(attachment.toPath()) + Files.readAllBytes(attachment.toPath()) ); mockMvc.perform(multipart(PATH + "/" + FILE_NAME + "/content").file(upload) - .with(req -> { - req.setMethod(HttpMethod.PUT.toString()); - return req; - })) - .andExpect(status().isNoContent()); + .with(req -> { + req.setMethod(HttpMethod.PUT.toString()); + return req; + })) + .andExpect(status().isNoContent()); verify(resourceServiceMock).saveContent(eq(file), any(InputStream.class)); } @@ -177,7 +184,7 @@ void runTextAnalysisInvokesTextAnalysisOnSpecifiedResource() throws Exception { when(identifierResolverMock.resolveIdentifier(RESOURCE_NAMESPACE, FILE_NAME)).thenReturn(file.getUri()); when(resourceServiceMock.findRequired(file.getUri())).thenReturn(file); mockMvc.perform(put(PATH + "/" + FILE_NAME + "/text-analysis").param(QueryParams.NAMESPACE, RESOURCE_NAMESPACE)) - .andExpect(status().isNoContent()); + .andExpect(status().isNoContent()); verify(resourceServiceMock).runTextAnalysis(file, Collections.emptySet()); } @@ -187,11 +194,11 @@ void runTextAnalysisInvokesTextAnalysisWithSpecifiedVocabulariesAsTermSources() when(identifierResolverMock.resolveIdentifier(RESOURCE_NAMESPACE, FILE_NAME)).thenReturn(file.getUri()); when(resourceServiceMock.findRequired(file.getUri())).thenReturn(file); final Set vocabularies = IntStream.range(0, 3).mapToObj(i -> Generator.generateUri().toString()) - .collect(Collectors.toSet()); + .collect(Collectors.toSet()); mockMvc.perform(put(PATH + "/" + FILE_NAME + "/text-analysis").param(QueryParams.NAMESPACE, RESOURCE_NAMESPACE) - .param("vocabulary", - vocabularies.toArray(new String[0]))) - .andExpect(status().isNoContent()); + .param("vocabulary", + vocabularies.toArray(new String[0]))) + .andExpect(status().isNoContent()); verify(resourceServiceMock) .runTextAnalysis(file, vocabularies.stream().map(URI::create).collect(Collectors.toSet())); } @@ -225,7 +232,7 @@ void getFilesReturnsConflictWhenRequestedResourceIsNotDocument() throws Exceptio when(resourceServiceMock.getRequiredReference(RESOURCE_URI)).thenReturn(resource); when(resourceServiceMock.getFiles(resource)).thenThrow(UnsupportedAssetOperationException.class); mockMvc.perform(get(PATH + "/" + RESOURCE_NAME + "/files").param(QueryParams.NAMESPACE, RESOURCE_NAMESPACE)) - .andExpect(status().isConflict()); + .andExpect(status().isConflict()); } @Test @@ -238,9 +245,9 @@ void addFileToDocumentSavesFileToDocumentWithSpecifiedIdentifier() throws Except when(identifierResolverMock.resolveIdentifier(RESOURCE_NAMESPACE, RESOURCE_NAME)).thenReturn(document.getUri()); mockMvc.perform(post(PATH + "/" + RESOURCE_NAME + "/files").param(QueryParams.NAMESPACE, RESOURCE_NAMESPACE) - .content(toJson(fOne)) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isCreated()); + .content(toJson(fOne)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()); verify(resourceServiceMock).addFileToDocument(document, fOne); } @@ -255,8 +262,8 @@ void addFileToDocumentReturnsLocationHeaderForNewFile() throws Exception { final MvcResult mvcResult = mockMvc .perform(post(PATH + "/" + RESOURCE_NAME + "/files").param(QueryParams.NAMESPACE, RESOURCE_NAMESPACE) - .content(toJson(fOne)) - .contentType(MediaType.APPLICATION_JSON)) + .content(toJson(fOne)) + .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isCreated()).andReturn(); verifyLocationEquals(PATH + "/" + fOne.getLabel(), mvcResult); } @@ -271,9 +278,9 @@ void addFileToDocumentReturnsConflictWhenRequestedResourceIsNotDocument() throws when(resourceServiceMock.findRequired(RESOURCE_URI)).thenReturn(resource); doThrow(UnsupportedAssetOperationException.class).when(resourceServiceMock).addFileToDocument(resource, fOne); mockMvc.perform(post(PATH + "/" + RESOURCE_NAME + "/files").param(QueryParams.NAMESPACE, RESOURCE_NAMESPACE) - .content(toJson(fOne)) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isConflict()); + .content(toJson(fOne)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isConflict()); } @Test @@ -283,7 +290,8 @@ void removeFileFromDocumentReturnsOkForAValidFile() throws Exception { document.setUri(RESOURCE_URI); final File file = generateFile(); document.addFile(file); - when(identifierResolverMock.resolveIdentifier(configMock.getNamespace().getResource(), FILE_NAME)).thenReturn(file.getUri()); + when(identifierResolverMock.resolveIdentifier(configMock.getNamespace().getResource(), FILE_NAME)).thenReturn( + file.getUri()); when(resourceServiceMock.findRequired(file.getUri())).thenReturn(file); mockMvc .perform(delete(PATH + "/" + RESOURCE_NAME + "/files/" + FILE_NAME, RESOURCE_NAMESPACE)) @@ -307,7 +315,8 @@ void getLatestTextAnalysisRecordRetrievesAnalysisRecordFromService() throws Exce record.setVocabularies(Collections.singleton(Generator.generateUri())); when(resourceServiceMock.findLatestTextAnalysisRecord(file)).thenReturn(record); final MvcResult mvcResult = mockMvc.perform(get(PATH + "/" + FILE_NAME + "/text-analysis/records/latest") - .param(QueryParams.NAMESPACE, RESOURCE_NAMESPACE)).andExpect(status().isOk()).andReturn(); + .param(QueryParams.NAMESPACE, RESOURCE_NAMESPACE)) + .andExpect(status().isOk()).andReturn(); final TextAnalysisRecord result = readValue(mvcResult, TextAnalysisRecord.class); assertNotNull(result); assertEquals(record.getAnalyzedResource().getUri(), result.getAnalyzedResource().getUri()); @@ -325,7 +334,7 @@ void hasContentChecksForContentExistenceInService() throws Exception { when(resourceServiceMock.getContent(file)) .thenReturn(new TypeAwareFileSystemResource(content, MediaType.TEXT_HTML_VALUE)); mockMvc.perform(head(PATH + "/" + FILE_NAME + "/content").param(QueryParams.NAMESPACE, RESOURCE_NAMESPACE)) - .andExpect(status().isNoContent()); + .andExpect(status().isNoContent()); verify(resourceServiceMock).hasContent(file); } @@ -339,8 +348,8 @@ void hasContentReturnsMimeType() throws Exception { when(resourceServiceMock.getContent(file)) .thenReturn(new TypeAwareFileSystemResource(content, MediaType.TEXT_HTML_VALUE)); mockMvc.perform(head(PATH + "/" + FILE_NAME + "/content") - .param(QueryParams.NAMESPACE, RESOURCE_NAMESPACE)) - .andExpect(header().string(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_HTML_VALUE)); + .param(QueryParams.NAMESPACE, RESOURCE_NAMESPACE)) + .andExpect(header().string(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_HTML_VALUE)); verify(resourceServiceMock).hasContent(file); } @@ -359,7 +368,7 @@ void getContentSupportsReturningContentAsAttachment() throws Exception { .andExpect(status().isOk()).andReturn(); assertThat(mvcResult.getResponse().getHeader(HttpHeaders.CONTENT_DISPOSITION), containsString("attachment")); assertThat(mvcResult.getResponse().getHeader(HttpHeaders.CONTENT_DISPOSITION), - containsString("filename=\"" + FILE_NAME + "\"")); + containsString("filename=\"" + FILE_NAME + "\"")); final String resultContent = mvcResult.getResponse().getContentAsString(); assertEquals(HTML_CONTENT, resultContent); assertEquals(MediaType.TEXT_HTML_VALUE, mvcResult.getResponse().getHeader(HttpHeaders.CONTENT_TYPE)); @@ -384,4 +393,24 @@ void getHistoryReturnsListOfChangeRecordsForSpecifiedVocabulary() throws Excepti assertEquals(records, result); verify(resourceServiceMock).getChanges(resource); } + + @Test + void getContentWithTimestampReturnsContentOfRequestedFileAtSpecifiedTimestamp() throws Exception { + final File file = generateFile(); + when(identifierResolverMock.resolveIdentifier(any(), eq(FILE_NAME))) + .thenReturn(file.getUri()); + when(resourceServiceMock.findRequired(file.getUri())).thenReturn(file); + final java.io.File content = createTemporaryHtmlFile(); + final Instant at = Utils.timestamp().truncatedTo(ChronoUnit.SECONDS); + when(resourceServiceMock.getContent(eq(file), any(Instant.class))) + .thenReturn(new TypeAwareFileSystemResource(content, MediaType.TEXT_HTML_VALUE)); + final MvcResult mvcResult = mockMvc + .perform(get(PATH + "/" + FILE_NAME + "/content") + .queryParam("at", Constants.TIMESTAMP_FORMATTER.format(at))) + .andExpect(status().isOk()).andReturn(); + final String resultContent = mvcResult.getResponse().getContentAsString(); + assertEquals(HTML_CONTENT, resultContent); + assertEquals(MediaType.TEXT_HTML_VALUE, mvcResult.getResponse().getHeader(HttpHeaders.CONTENT_TYPE)); + verify(resourceServiceMock).getContent(file, at); + } } diff --git a/src/test/java/cz/cvut/kbss/termit/service/business/ResourceServiceTest.java b/src/test/java/cz/cvut/kbss/termit/service/business/ResourceServiceTest.java index de5aae389..79d4b9135 100644 --- a/src/test/java/cz/cvut/kbss/termit/service/business/ResourceServiceTest.java +++ b/src/test/java/cz/cvut/kbss/termit/service/business/ResourceServiceTest.java @@ -39,6 +39,7 @@ import java.io.ByteArrayInputStream; import java.net.URI; +import java.time.Instant; import java.util.*; import static org.junit.jupiter.api.Assertions.*; @@ -110,9 +111,7 @@ void removeEnsuresAttributesForDocumentManagerArePresent() { @Test void getContentLoadsContentOfFileFromDocumentManager() { - final File file = new File(); - file.setLabel("Test"); - file.setUri(Generator.generateUri()); + final File file = Generator.generateFileWithId("test.html"); sut.getContent(file); verify(documentManager).getAsResource(file); } @@ -127,9 +126,7 @@ void getContentThrowsUnsupportedAssetOperationWhenResourceIsNotFile() { @Test void saveContentSavesFileContentViaDocumentManager() { final ByteArrayInputStream bis = new ByteArrayInputStream("test".getBytes()); - final File file = new File(); - file.setLabel("Test"); - file.setUri(Generator.generateUri()); + final File file = Generator.generateFileWithId("test.html"); sut.saveContent(file, bis); verify(documentManager).saveFileContent(file, bis); } @@ -145,9 +142,7 @@ void saveContentThrowsUnsupportedAssetOperationExceptionWhenResourceIsNotFile() @Test void saveContentCreatesBackupBeforeSavingFileContentInDocumentManager() { final ByteArrayInputStream bis = new ByteArrayInputStream("test".getBytes()); - final File file = new File(); - file.setLabel("Test"); - file.setUri(Generator.generateUri()); + final File file = Generator.generateFileWithId("test.html"); when(documentManager.exists(file)).thenReturn(true); sut.saveContent(file, bis); final InOrder inOrder = Mockito.inOrder(documentManager); @@ -158,9 +153,7 @@ void saveContentCreatesBackupBeforeSavingFileContentInDocumentManager() { @Test void saveContentDoesNotCreateBackupWhenFileDoesNotYetExist() { final ByteArrayInputStream bis = new ByteArrayInputStream("test".getBytes()); - final File file = new File(); - file.setLabel("Test"); - file.setUri(Generator.generateUri()); + final File file = Generator.generateFileWithId("test.html"); when(documentManager.exists(file)).thenReturn(false); sut.saveContent(file, bis); verify(documentManager, never()).createBackup(file); @@ -256,9 +249,7 @@ void getRequiredReferenceDelegatesCallToRepositoryService() { @Test void getFilesReturnsFilesFromDocument() { - final Document doc = new Document(); - doc.setLabel("test document"); - doc.setUri(Generator.generateUri()); + final Document doc = Generator.generateDocumentWithId(); final File fOne = Generator.generateFileWithId("test.html"); doc.addFile(fOne); when(resourceRepositoryService.findRequired(doc.getUri())).thenReturn(doc); @@ -270,9 +261,7 @@ void getFilesReturnsFilesFromDocument() { @Test void getFilesReturnsFilesSortedByLabel() { - final Document doc = new Document(); - doc.setLabel("test document"); - doc.setUri(Generator.generateUri()); + final Document doc = Generator.generateDocumentWithId(); final File fOne = Generator.generateFileWithId("test.html"); doc.addFile(fOne); final File fTwo = Generator.generateFileWithId("act.html"); @@ -284,9 +273,7 @@ void getFilesReturnsFilesSortedByLabel() { @Test void getFilesReturnsEmptyListWhenDocumentHasNoFiles() { - final Document doc = new Document(); - doc.setLabel("test document"); - doc.setUri(Generator.generateUri()); + final Document doc = Generator.generateDocumentWithId(); when(resourceRepositoryService.findRequired(doc.getUri())).thenReturn(doc); final List result = sut.getFiles(doc); assertNotNull(result); @@ -476,4 +463,12 @@ void removeThrowsAssetRemovalExceptionWhenNonEmptyDocumentIsRemoved() { assertThrows(AssetRemovalException.class, () -> sut.remove(document)); verify(resourceRepositoryService, never()).remove(any()); } + + @Test + void getContentAtTimestampLoadsContentOfFileAtTimestampFromDocumentManager() { + final File file = Generator.generateFileWithId("test.hml"); + final Instant at = Utils.timestamp(); + sut.getContent(file, at); + verify(documentManager).getAsResource(file, at); + } } From 007c503933a6adb7f48047c4c2e0143395373a22 Mon Sep 17 00:00:00 2001 From: Martin Ledvinka Date: Tue, 3 Jan 2023 17:40:32 +0100 Subject: [PATCH 04/23] [kbss-cvut/termit-ui#347] Handle legacy backup file name patterns and extreme timestamp values. --- .../document/DefaultDocumentManager.java | 21 ++++++--- .../document/DefaultDocumentManagerTest.java | 43 ++++++++++++++++++- 2 files changed, 57 insertions(+), 7 deletions(-) diff --git a/src/main/java/cz/cvut/kbss/termit/service/document/DefaultDocumentManager.java b/src/main/java/cz/cvut/kbss/termit/service/document/DefaultDocumentManager.java index 8ad3dfa14..55f26ae5e 100644 --- a/src/main/java/cz/cvut/kbss/termit/service/document/DefaultDocumentManager.java +++ b/src/main/java/cz/cvut/kbss/termit/service/document/DefaultDocumentManager.java @@ -41,6 +41,7 @@ import java.time.Instant; import java.time.ZoneId; import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; import java.time.temporal.TemporalAccessor; import java.util.*; import java.util.function.Consumer; @@ -54,6 +55,7 @@ public class DefaultDocumentManager implements DocumentManager { private static final Logger LOG = LoggerFactory.getLogger(DefaultDocumentManager.class); static final String BACKUP_NAME_SEPARATOR = "~"; + private static final int BACKUP_TIMESTAMP_LENGTH = 19; static final DateTimeFormatter BACKUP_TIMESTAMP_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd_HHmmss_S") .withZone(ZoneId.systemDefault()); @@ -117,19 +119,25 @@ public TypeAwareResource getAsResource(File file, Instant at) { LOG.error("File {} not found at location {}.", file, directory.getPath()); throw new NotFoundException("File " + file + " not found on file system."); } - return new TypeAwareFileSystemResource(resolveFileVersionAt(at, candidates), getMediaType(file)); + return new TypeAwareFileSystemResource(resolveFileVersionAt(file, at, candidates), getMediaType(file)); } - private java.io.File resolveFileVersionAt(Instant at, List candidates) { + private java.io.File resolveFileVersionAt(File file, Instant at, List candidates) { final Map backups = new HashMap<>(); candidates.forEach(f -> { if (!f.getName().contains(BACKUP_NAME_SEPARATOR)) { backups.put(Utils.timestamp(), f); return; } - final String strTimestamp = f.getName().substring(f.getName().indexOf(BACKUP_NAME_SEPARATOR) + 1); - final TemporalAccessor backupTimestamp = BACKUP_TIMESTAMP_FORMAT.parse(strTimestamp); - backups.put(Instant.from(backupTimestamp), f); + String strTimestamp = f.getName().substring(f.getName().indexOf(BACKUP_NAME_SEPARATOR) + 1); + // Cut off possibly legacy extra millis places + strTimestamp = strTimestamp.substring(0, BACKUP_TIMESTAMP_LENGTH); + try { + final TemporalAccessor backupTimestamp = BACKUP_TIMESTAMP_FORMAT.parse(strTimestamp); + backups.put(Instant.from(backupTimestamp), f); + } catch (DateTimeParseException e) { + LOG.warn("Unable to parse backup timestamp {}. Skipping file.", strTimestamp); + } }); final List backupTimestamps = new ArrayList<>(backups.keySet()); Collections.sort(backupTimestamps); @@ -138,7 +146,8 @@ private java.io.File resolveFileVersionAt(Instant at, List candida return backups.get(timestamp); } } - throw new DocumentManagerException("Unable to find file version at " + at); + LOG.warn("Unable to find version of {} valid at {}, returning current file.", file, at); + return resolveFile(file, true); } @Override diff --git a/src/test/java/cz/cvut/kbss/termit/service/document/DefaultDocumentManagerTest.java b/src/test/java/cz/cvut/kbss/termit/service/document/DefaultDocumentManagerTest.java index 7e4e918d1..7975164b4 100644 --- a/src/test/java/cz/cvut/kbss/termit/service/document/DefaultDocumentManagerTest.java +++ b/src/test/java/cz/cvut/kbss/termit/service/document/DefaultDocumentManagerTest.java @@ -38,6 +38,8 @@ import java.io.InputStream; import java.nio.file.Files; import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; import java.time.temporal.TemporalAccessor; import java.util.ArrayList; import java.util.Collections; @@ -624,6 +626,20 @@ void getAsResourceWithTimestampReturnsCurrentFileWhenTimestampIsNow() throws Exc assertEquals(physicalFile, result.getFile()); } + @Test + void getAsResourceWithTimestampReturnsCurrentFileWhenTimestampIsInFuture() throws Exception { + final Instant at = Utils.timestamp().plusSeconds(100); + final File file = new File(); + final java.io.File physicalFile = generateFile(); + file.setLabel(physicalFile.getName()); + document.addFile(file); + file.setDocument(document); + + createTestBackups(physicalFile); + final org.springframework.core.io.Resource result = sut.getAsResource(file, at); + assertEquals(physicalFile, result.getFile()); + } + @Test void getAsResourceWithTimestampThrowsNotFoundExceptionWhenParentDocumentDirectoryDoesNotExistOnFileSystem() { final File file = Generator.generateFileWithId("test.html"); @@ -633,7 +649,8 @@ void getAsResourceWithTimestampThrowsNotFoundExceptionWhenParentDocumentDirector } @Test - void getAsResourceWithTimestampThrowsNotFoundExceptionWhenFileDoesNotExistInParentDocumentDirectoryOnFileSystem() throws Exception { + void getAsResourceWithTimestampThrowsNotFoundExceptionWhenFileDoesNotExistInParentDocumentDirectoryOnFileSystem() + throws Exception { final File file = new File(); final java.io.File physicalFile = generateFile(); file.setLabel(physicalFile.getName()); @@ -656,4 +673,28 @@ void getContentTypeResolvesMIMETypeOfSpecifiedFile() throws Exception { assertTrue(result.isPresent()); assertEquals(MediaType.TEXT_HTML_VALUE, result.get()); } + + @Test + void getAsResourceAtTimestampHandlesLegacyBackupTimestampPatterns() throws Exception { + final File file = new File(); + final java.io.File physicalFile = generateFile(); + file.setLabel(physicalFile.getName()); + document.addFile(file); + file.setDocument(document); + + final String path = physicalFile.getAbsolutePath(); + // Legacy pattern used multiple millis places + final String newPath = path + DefaultDocumentManager.BACKUP_NAME_SEPARATOR + DateTimeFormatter.ofPattern( + "yyyy-MM-dd_HHmmss_SSS") + .withZone( + ZoneId.systemDefault()) + .format(Instant.now() + .minusSeconds( + 10)); + final java.io.File backup = new java.io.File(newPath); + Files.copy(physicalFile.toPath(), backup.toPath()); + backup.deleteOnExit(); + final org.springframework.core.io.Resource result = sut.getAsResource(file, Instant.EPOCH); + assertEquals(backup, result.getFile()); + } } From 75ff94ed095b4ecd2776a3f4aa31e9460e0727e1 Mon Sep 17 00:00:00 2001 From: Martin Ledvinka Date: Wed, 4 Jan 2023 17:18:07 +0100 Subject: [PATCH 05/23] [kbss-cvut/termit-ui#184] Override Czech-specific validation rules to support i18n of TermIt deployments. --- pom.xml | 2 +- .../persistence/validation/Validator.java | 84 ++++++++++++++----- src/main/resources/validation/g2.ttl | 15 ++++ src/main/resources/validation/g4.ttl | 14 ++++ .../persistence/validation/ValidatorTest.java | 71 ++++++++++++++++ 5 files changed, 166 insertions(+), 20 deletions(-) create mode 100644 src/main/resources/validation/g2.ttl create mode 100644 src/main/resources/validation/g4.ttl create mode 100644 src/test/java/cz/cvut/kbss/termit/persistence/validation/ValidatorTest.java diff --git a/pom.xml b/pom.xml index cb525c358..078d5f32a 100644 --- a/pom.xml +++ b/pom.xml @@ -58,7 +58,7 @@ com.github.sgov sgov-validator - 1.6.5 + 1.6.6-SNAPSHOT diff --git a/src/main/java/cz/cvut/kbss/termit/persistence/validation/Validator.java b/src/main/java/cz/cvut/kbss/termit/persistence/validation/Validator.java index 489ddf136..a9ae4d77a 100644 --- a/src/main/java/cz/cvut/kbss/termit/persistence/validation/Validator.java +++ b/src/main/java/cz/cvut/kbss/termit/persistence/validation/Validator.java @@ -5,10 +5,13 @@ import cz.cvut.kbss.jopa.model.MultilingualString; import cz.cvut.kbss.termit.exception.TermItException; import cz.cvut.kbss.termit.model.validation.ValidationResult; +import cz.cvut.kbss.termit.util.Configuration; +import cz.cvut.kbss.termit.util.Utils; import org.apache.jena.rdf.model.Literal; import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.ModelFactory; import org.apache.jena.rdf.model.RDFNode; +import org.apache.jena.util.FileUtils; import org.eclipse.rdf4j.model.IRI; import org.eclipse.rdf4j.model.ValueFactory; import org.eclipse.rdf4j.repository.RepositoryConnection; @@ -18,6 +21,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.annotation.Scope; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; @@ -37,13 +42,30 @@ public class Validator implements VocabularyContentValidator { private static final Logger LOG = LoggerFactory.getLogger(Validator.class); + /** + * TermIt overrides some glossary validation rules predefined by SGoV. + *

+ * The main reason for overriding is missing internationalization of the built-in rules. + */ + private static final Set GLOSSARY_RULES_TO_OVERRIDE = Set.of("g2.ttl", "g4.ttl"); + + /** + * Model validation rules to add. + *

+ * TermIt does not support all the modeling features validated by SGoV validator. So we use only those we support. + */ + private static final Set MODEL_RULES_TO_ADD = Set.of("m1.ttl", "m2.ttl"); + private final org.eclipse.rdf4j.repository.Repository repository; private final ValueFactory vf; + private final String language; + @Autowired - public Validator(EntityManager em) { + public Validator(EntityManager em, Configuration config) { this.repository = em.unwrap(org.eclipse.rdf4j.repository.Repository.class); vf = repository.getValueFactory(); + this.language = config.getPersistence().getLanguage(); } private Model getModelFromRdf4jRepository(final Collection vocabularyIris) @@ -57,8 +79,8 @@ private Model getModelFromRdf4jRepository(final Collection vocabularyIris) writer.close(); final byte[] savedData = baos.toByteArray(); final ByteArrayInputStream bais = new ByteArrayInputStream(savedData); - org.apache.jena.rdf.model.Model model = ModelFactory.createDefaultModel(); - model.read(bais, null, "TURTLE"); + Model model = ModelFactory.createDefaultModel(); + model.read(bais, null, FileUtils.langTurtle); return model; } @@ -67,30 +89,24 @@ private Model getModelFromRdf4jRepository(final Collection vocabularyIris) public List validate(final Collection vocabularyIris) { LOG.debug("Validating {}", vocabularyIris); final com.github.sgov.server.Validator validator = new com.github.sgov.server.Validator(); - final Set rules = new HashSet<>(); - rules.addAll(validator.getGlossaryRules()); - rules.addAll( - // Currently, only using content rules, not OntoUml, as TermIt does not support adding OntoUml rules - validator.getModelRules().stream().filter(r -> - r.toString().contains("m1.ttl") || r.toString().contains("m2.ttl")) - .collect(Collectors.toList()) - ); - try { - final Model model = getModelFromRdf4jRepository(vocabularyIris); - org.topbraid.shacl.validation.ValidationReport report = validator.validate(model, rules); + final Model validationModel = initValidationModel(validator); + final Model dataModel = getModelFromRdf4jRepository(vocabularyIris); + org.topbraid.shacl.validation.ValidationReport report = validator.validate(dataModel, validationModel); LOG.debug("Done."); return report.results().stream() - .sorted(new ValidationResultSeverityComparator()).map(result -> { + .sorted(new ValidationResultSeverityComparator()).map(result -> { final URI termUri = URI.create(result.getFocusNode().toString()); final URI severity = URI.create(result.getSeverity().getURI()); final URI errorUri = result.getSourceShape().isURIResource() ? - URI.create(result.getSourceShape().getURI()) : null; + URI.create(result.getSourceShape().getURI()) : null; final URI resultPath = result.getPath() != null && result.getPath().isURIResource() ? - URI.create(result.getPath().getURI()) : null; + URI.create(result.getPath().getURI()) : null; final MultilingualString messages = new MultilingualString(result.getMessages().stream() - .map(RDFNode::asLiteral) - .collect(Collectors.toMap(Literal::getLanguage, Literal::getLexicalForm))); + .map(RDFNode::asLiteral) + .collect(Collectors.toMap( + Literal::getLanguage, + Literal::getLexicalForm))); return new ValidationResult() .setTermUri(termUri) @@ -103,4 +119,34 @@ public List validate(final Collection vocabularyIris) { throw new TermItException("Validation of vocabularies " + vocabularyIris + " failed.", e); } } + + private Model initValidationModel(com.github.sgov.server.Validator validator) throws IOException { + // TODO We could cache the validation model between calls + final Set rules = new HashSet<>(); + rules.addAll(validator.getGlossaryRules().stream() + .filter(r -> GLOSSARY_RULES_TO_OVERRIDE.stream().noneMatch(s -> r.toString().contains(s))) + .collect(Collectors.toSet())); + rules.addAll( + // Currently, only using content rules, not OntoUml, as TermIt does not support adding OntoUml rules + validator.getModelRules().stream() + .filter(r -> MODEL_RULES_TO_ADD.stream().anyMatch(s -> r.toString().contains(s))) + .collect(Collectors.toList()) + ); + final Model validationModel = com.github.sgov.server.Validator.getRulesModel(rules); + loadOverrideRules(validationModel); + return validationModel; + } + + private void loadOverrideRules(Model validationModel) throws IOException { + final ClassLoader classLoader = Validator.class.getClassLoader(); + final PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(classLoader); + + Resource[] resources = resolver.getResources("classpath:/validation/*.ttl"); + for (Resource r : resources) { + String rule = Utils.loadClasspathResource("validation/" + r.getFilename()); + rule = rule.replace("$lang", language); + validationModel.read(new ByteArrayInputStream(rule.getBytes(StandardCharsets.UTF_8)), null, + FileUtils.langTurtle); + } + } } diff --git a/src/main/resources/validation/g2.ttl b/src/main/resources/validation/g2.ttl new file mode 100644 index 000000000..87cdf922b --- /dev/null +++ b/src/main/resources/validation/g2.ttl @@ -0,0 +1,15 @@ +@prefix skos: . +@prefix sh: . +@prefix j-sgov-obecný: . + +j-sgov-obecný:g2 + a sh:NodeShape ; + sh:severity sh:Violation ; + sh:message "Pojem nemá název v primárním jazyce této instance TermItu."@cs, + "The term does not have a preferred label in the primary configured language of this deployment of TermIt."@en ; + sh:targetClass skos:Concept ; + sh:path skos:prefLabel ; + sh:qualifiedValueShape [ sh:languageIn ( "$lang" ) ] ; + sh:qualifiedMinCount 1 ; + sh:minLength 1 +. diff --git a/src/main/resources/validation/g4.ttl b/src/main/resources/validation/g4.ttl new file mode 100644 index 000000000..f17e36244 --- /dev/null +++ b/src/main/resources/validation/g4.ttl @@ -0,0 +1,14 @@ +@prefix skos: . +@prefix sh: . +@prefix j-sgov-obecný: . + +j-sgov-obecný:g4 + a sh:NodeShape ; + sh:severity sh:Warning ; + sh:message "Pojem nemá definici v primárním jazyce této instance TermItu."@cs, + "The term does not have a definition in the primary configured language of this deployment of TermIt."@en ; + sh:targetClass skos:Concept ; + sh:path skos:definition ; + sh:qualifiedValueShape [ sh:languageIn ( "$lang" ) ; sh:minLength 1] ; + sh:qualifiedMinCount 1 +. diff --git a/src/test/java/cz/cvut/kbss/termit/persistence/validation/ValidatorTest.java b/src/test/java/cz/cvut/kbss/termit/persistence/validation/ValidatorTest.java new file mode 100644 index 000000000..b00373b74 --- /dev/null +++ b/src/test/java/cz/cvut/kbss/termit/persistence/validation/ValidatorTest.java @@ -0,0 +1,71 @@ +package cz.cvut.kbss.termit.persistence.validation; + +import cz.cvut.kbss.jopa.model.EntityManager; +import cz.cvut.kbss.termit.environment.Environment; +import cz.cvut.kbss.termit.environment.Generator; +import cz.cvut.kbss.termit.model.Term; +import cz.cvut.kbss.termit.model.User; +import cz.cvut.kbss.termit.model.UserAccount; +import cz.cvut.kbss.termit.model.Vocabulary; +import cz.cvut.kbss.termit.model.validation.ValidationResult; +import cz.cvut.kbss.termit.persistence.context.DescriptorFactory; +import cz.cvut.kbss.termit.persistence.dao.BaseDaoTestRunner; +import cz.cvut.kbss.termit.util.Configuration; +import cz.cvut.kbss.termit.util.Constants; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static org.junit.jupiter.api.Assertions.*; + +class ValidatorTest extends BaseDaoTestRunner { + + @Autowired + private EntityManager em; + + @Autowired + private DescriptorFactory descriptorFactory; + + @Autowired + private Configuration config; + + @BeforeEach + void setUp() { + final User author = Generator.generateUserWithId(); + transactional(() -> em.persist(author)); + Environment.setCurrentUser(author); + } + + @Test + void validateUsesOverrideRulesToAllowI18n() { + final Vocabulary vocabulary = generateVocabulary(); + transactional(() -> { + final Validator sut = new Validator(em, config); + final List result = sut.validate(Collections.singleton(vocabulary.getUri())); + assertTrue(result.stream().noneMatch( + vr -> vr.getMessage().get("en").contains("The term does not have a preferred label in Czech"))); + assertTrue(result.stream().noneMatch( + vr -> vr.getMessage().get("en").contains("The term does not have a definition in Czech"))); + assertTrue(result.stream().anyMatch(vr -> vr.getMessage().get("en").contains( + "The term does not have a preferred label in the primary configured language of this deployment of TermIt"))); + }); + } + + private Vocabulary generateVocabulary() { + final Vocabulary vocabulary = Generator.generateVocabularyWithId(); + final Term term = Generator.generateTermWithId(vocabulary.getUri()); + term.getLabel().remove(Constants.DEFAULT_LANGUAGE); + term.getLabel().set("de", "Apfelbaum, der"); + vocabulary.getGlossary().addRootTerm(term); + transactional(() -> { + em.persist(vocabulary, descriptorFactory.vocabularyDescriptor(vocabulary)); + em.persist(term, descriptorFactory.termDescriptor(vocabulary)); + }); + return vocabulary; + } +} From 7f81215c918a2894d1116df70927c1bc50a18d10 Mon Sep 17 00:00:00 2001 From: Martin Ledvinka Date: Wed, 4 Jan 2023 17:20:20 +0100 Subject: [PATCH 06/23] [kbss-cvut/termit-ui#184] Close repository connection after exporting vocabulary content from it for validation. --- .../kbss/termit/persistence/validation/Validator.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/cz/cvut/kbss/termit/persistence/validation/Validator.java b/src/main/java/cz/cvut/kbss/termit/persistence/validation/Validator.java index a9ae4d77a..4d9321aae 100644 --- a/src/main/java/cz/cvut/kbss/termit/persistence/validation/Validator.java +++ b/src/main/java/cz/cvut/kbss/termit/persistence/validation/Validator.java @@ -72,11 +72,12 @@ private Model getModelFromRdf4jRepository(final Collection vocabularyIris) throws IOException { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final OutputStreamWriter writer = new OutputStreamWriter(baos, StandardCharsets.UTF_8); - final RepositoryConnection c = repository.getConnection(); - final List iris = new ArrayList<>(); - vocabularyIris.forEach(i -> iris.add(vf.createIRI(i.toString()))); - c.export(new TurtleWriter(writer), iris.toArray(new IRI[]{})); - writer.close(); + try (final RepositoryConnection c = repository.getConnection()) { + final List iris = new ArrayList<>(); + vocabularyIris.forEach(i -> iris.add(vf.createIRI(i.toString()))); + c.export(new TurtleWriter(writer), iris.toArray(new IRI[]{})); + writer.close(); + } final byte[] savedData = baos.toByteArray(); final ByteArrayInputStream bais = new ByteArrayInputStream(savedData); Model model = ModelFactory.createDefaultModel(); From ac96e2a43dd7a867f8908300d3fe50643f86b013 Mon Sep 17 00:00:00 2001 From: Martin Ledvinka Date: Thu, 5 Jan 2023 08:53:26 +0100 Subject: [PATCH 07/23] [kbss-cvut/termit-ui#184] Handle backup files with invalid timestamp. --- .../document/DefaultDocumentManager.java | 2 +- .../document/DefaultDocumentManagerTest.java | 26 ++++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/main/java/cz/cvut/kbss/termit/service/document/DefaultDocumentManager.java b/src/main/java/cz/cvut/kbss/termit/service/document/DefaultDocumentManager.java index 55f26ae5e..a5d23dfec 100644 --- a/src/main/java/cz/cvut/kbss/termit/service/document/DefaultDocumentManager.java +++ b/src/main/java/cz/cvut/kbss/termit/service/document/DefaultDocumentManager.java @@ -131,7 +131,7 @@ private java.io.File resolveFileVersionAt(File file, Instant at, List Date: Thu, 5 Jan 2023 09:39:54 +0100 Subject: [PATCH 08/23] [kbss-cvut/termit-ui#184] Update to published SGoV validator 1.6.7. --- pom.xml | 2 +- .../kbss/termit/persistence/validation/ValidatorTest.java | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 078d5f32a..bed5ee720 100644 --- a/pom.xml +++ b/pom.xml @@ -58,7 +58,7 @@ com.github.sgov sgov-validator - 1.6.6-SNAPSHOT + 1.6.7 diff --git a/src/test/java/cz/cvut/kbss/termit/persistence/validation/ValidatorTest.java b/src/test/java/cz/cvut/kbss/termit/persistence/validation/ValidatorTest.java index b00373b74..760b1ac00 100644 --- a/src/test/java/cz/cvut/kbss/termit/persistence/validation/ValidatorTest.java +++ b/src/test/java/cz/cvut/kbss/termit/persistence/validation/ValidatorTest.java @@ -5,7 +5,6 @@ import cz.cvut.kbss.termit.environment.Generator; import cz.cvut.kbss.termit.model.Term; import cz.cvut.kbss.termit.model.User; -import cz.cvut.kbss.termit.model.UserAccount; import cz.cvut.kbss.termit.model.Vocabulary; import cz.cvut.kbss.termit.model.validation.ValidationResult; import cz.cvut.kbss.termit.persistence.context.DescriptorFactory; @@ -18,10 +17,8 @@ import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.IntStream; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertTrue; class ValidatorTest extends BaseDaoTestRunner { From 266eba19130c9e62bd7b2068b56e94aeedaf9ff0 Mon Sep 17 00:00:00 2001 From: Martin Ledvinka Date: Thu, 5 Jan 2023 10:07:32 +0100 Subject: [PATCH 09/23] [kbss-cvut/termit-ui#184] Reuse validator and validation rules to improve vocabulary validation performance. --- .../persistence/validation/Validator.java | 117 ++++++++++-------- 1 file changed, 64 insertions(+), 53 deletions(-) diff --git a/src/main/java/cz/cvut/kbss/termit/persistence/validation/Validator.java b/src/main/java/cz/cvut/kbss/termit/persistence/validation/Validator.java index 4d9321aae..d542ae7f4 100644 --- a/src/main/java/cz/cvut/kbss/termit/persistence/validation/Validator.java +++ b/src/main/java/cz/cvut/kbss/termit/persistence/validation/Validator.java @@ -13,14 +13,12 @@ import org.apache.jena.rdf.model.RDFNode; import org.apache.jena.util.FileUtils; import org.eclipse.rdf4j.model.IRI; -import org.eclipse.rdf4j.model.ValueFactory; +import org.eclipse.rdf4j.repository.Repository; import org.eclipse.rdf4j.repository.RepositoryConnection; import org.eclipse.rdf4j.rio.turtle.TurtleWriter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.config.ConfigurableBeanFactory; -import org.springframework.context.annotation.Scope; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.stereotype.Component; @@ -37,7 +35,6 @@ import java.util.stream.Collectors; @Component -@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class Validator implements VocabularyContentValidator { private static final Logger LOG = LoggerFactory.getLogger(Validator.class); @@ -56,42 +53,68 @@ public class Validator implements VocabularyContentValidator { */ private static final Set MODEL_RULES_TO_ADD = Set.of("m1.ttl", "m2.ttl"); - private final org.eclipse.rdf4j.repository.Repository repository; - private final ValueFactory vf; + private final EntityManager em; - private final String language; + private com.github.sgov.server.Validator validator; + private Model validationModel; @Autowired public Validator(EntityManager em, Configuration config) { - this.repository = em.unwrap(org.eclipse.rdf4j.repository.Repository.class); - vf = repository.getValueFactory(); - this.language = config.getPersistence().getLanguage(); + this.em = em; + initValidator(config.getPersistence().getLanguage()); } - private Model getModelFromRdf4jRepository(final Collection vocabularyIris) - throws IOException { - final ByteArrayOutputStream baos = new ByteArrayOutputStream(); - final OutputStreamWriter writer = new OutputStreamWriter(baos, StandardCharsets.UTF_8); - try (final RepositoryConnection c = repository.getConnection()) { - final List iris = new ArrayList<>(); - vocabularyIris.forEach(i -> iris.add(vf.createIRI(i.toString()))); - c.export(new TurtleWriter(writer), iris.toArray(new IRI[]{})); - writer.close(); + /** + * Initializes the validator. + *

+ * Note that the validator can be reused as long as the language stays the same. If TermIt starts supporting + * selection of language per vocabulary, this initialization will have to change. + * + * @param language Primary language of the instance, used to parameterize validation rules + */ + private void initValidator(String language) { + try { + this.validator = new com.github.sgov.server.Validator(); + this.validationModel = initValidationModel(validator, language); + } catch (IOException e) { + throw new TermItException("Unable to initialize validator.", e); + } + } + + private Model initValidationModel(com.github.sgov.server.Validator validator, String language) throws IOException { + final Set rules = new HashSet<>(); + rules.addAll(validator.getGlossaryRules().stream() + .filter(r -> GLOSSARY_RULES_TO_OVERRIDE.stream().noneMatch(s -> r.toString().contains(s))) + .collect(Collectors.toSet())); + rules.addAll( + // Currently, only using content rules, not OntoUml, as TermIt does not support adding OntoUml rules + validator.getModelRules().stream() + .filter(r -> MODEL_RULES_TO_ADD.stream().anyMatch(s -> r.toString().contains(s))) + .collect(Collectors.toList()) + ); + final Model validationModel = com.github.sgov.server.Validator.getRulesModel(rules); + loadOverrideRules(validationModel, language); + return validationModel; + } + + private void loadOverrideRules(Model validationModel, String language) throws IOException { + final ClassLoader classLoader = Validator.class.getClassLoader(); + final PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(classLoader); + + Resource[] resources = resolver.getResources("classpath:/validation/*.ttl"); + for (Resource r : resources) { + String rule = Utils.loadClasspathResource("validation/" + r.getFilename()); + rule = rule.replace("$lang", language); + validationModel.read(new ByteArrayInputStream(rule.getBytes(StandardCharsets.UTF_8)), null, + FileUtils.langTurtle); } - final byte[] savedData = baos.toByteArray(); - final ByteArrayInputStream bais = new ByteArrayInputStream(savedData); - Model model = ModelFactory.createDefaultModel(); - model.read(bais, null, FileUtils.langTurtle); - return model; } @Transactional(readOnly = true) @Override public List validate(final Collection vocabularyIris) { LOG.debug("Validating {}", vocabularyIris); - final com.github.sgov.server.Validator validator = new com.github.sgov.server.Validator(); try { - final Model validationModel = initValidationModel(validator); final Model dataModel = getModelFromRdf4jRepository(vocabularyIris); org.topbraid.shacl.validation.ValidationReport report = validator.validate(dataModel, validationModel); LOG.debug("Done."); @@ -121,33 +144,21 @@ public List validate(final Collection vocabularyIris) { } } - private Model initValidationModel(com.github.sgov.server.Validator validator) throws IOException { - // TODO We could cache the validation model between calls - final Set rules = new HashSet<>(); - rules.addAll(validator.getGlossaryRules().stream() - .filter(r -> GLOSSARY_RULES_TO_OVERRIDE.stream().noneMatch(s -> r.toString().contains(s))) - .collect(Collectors.toSet())); - rules.addAll( - // Currently, only using content rules, not OntoUml, as TermIt does not support adding OntoUml rules - validator.getModelRules().stream() - .filter(r -> MODEL_RULES_TO_ADD.stream().anyMatch(s -> r.toString().contains(s))) - .collect(Collectors.toList()) - ); - final Model validationModel = com.github.sgov.server.Validator.getRulesModel(rules); - loadOverrideRules(validationModel); - return validationModel; - } - - private void loadOverrideRules(Model validationModel) throws IOException { - final ClassLoader classLoader = Validator.class.getClassLoader(); - final PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(classLoader); - - Resource[] resources = resolver.getResources("classpath:/validation/*.ttl"); - for (Resource r : resources) { - String rule = Utils.loadClasspathResource("validation/" + r.getFilename()); - rule = rule.replace("$lang", language); - validationModel.read(new ByteArrayInputStream(rule.getBytes(StandardCharsets.UTF_8)), null, - FileUtils.langTurtle); + private Model getModelFromRdf4jRepository(final Collection vocabularyIris) + throws IOException { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + final OutputStreamWriter writer = new OutputStreamWriter(baos, StandardCharsets.UTF_8); + final Repository repository = em.unwrap(Repository.class); + try (final RepositoryConnection c = repository.getConnection()) { + final List iris = new ArrayList<>(); + vocabularyIris.forEach(i -> iris.add(repository.getValueFactory().createIRI(i.toString()))); + c.export(new TurtleWriter(writer), iris.toArray(new IRI[]{})); + writer.close(); } + final byte[] savedData = baos.toByteArray(); + final ByteArrayInputStream bais = new ByteArrayInputStream(savedData); + Model model = ModelFactory.createDefaultModel(); + model.read(bais, null, FileUtils.langTurtle); + return model; } } From f81990649b36fb662743c7d11ad1ac186c471efb Mon Sep 17 00:00:00 2001 From: Martin Ledvinka Date: Thu, 12 Jan 2023 09:48:47 +0100 Subject: [PATCH 10/23] [kbss-cvut/termit-ui#357] Generate language-specific sheets in Excel glossary export. --- pom.xml | 7 ++ .../service/export/ExcelTermExporter.java | 48 ++++---- .../export/ExcelVocabularyExporter.java | 103 +++++++++++------- .../export/util/TabularTermExportUtils.java | 11 -- .../cz/cvut/kbss/termit/util/Constants.java | 16 +++ .../java/cz/cvut/kbss/termit/util/Utils.java | 4 +- src/main/resources/template/cs/export.xlsx | Bin 6332 -> 0 bytes src/main/resources/template/en/export.xlsx | Bin 6575 -> 0 bytes .../kbss/termit/rest/TermControllerTest.java | 5 +- .../service/export/CsvTermExporterTest.java | 4 +- .../service/export/ExcelTermExporterTest.java | 34 +++--- .../export/ExcelVocabularyExporterTest.java | 53 +++++++-- 12 files changed, 171 insertions(+), 114 deletions(-) delete mode 100644 src/main/resources/template/cs/export.xlsx delete mode 100644 src/main/resources/template/en/export.xlsx diff --git a/pom.xml b/pom.xml index bed5ee720..24e5e065f 100644 --- a/pom.xml +++ b/pom.xml @@ -296,6 +296,13 @@ 5.2.2 + + + com.neovisionaries + nv-i18n + 1.29 + + org.apache.velocity diff --git a/src/main/java/cz/cvut/kbss/termit/service/export/ExcelTermExporter.java b/src/main/java/cz/cvut/kbss/termit/service/export/ExcelTermExporter.java index 7c3239f6a..2ee3b5ac8 100644 --- a/src/main/java/cz/cvut/kbss/termit/service/export/ExcelTermExporter.java +++ b/src/main/java/cz/cvut/kbss/termit/service/export/ExcelTermExporter.java @@ -12,7 +12,6 @@ import java.net.URI; import java.util.Map; import java.util.Objects; -import java.util.function.Function; import java.util.stream.Collectors; /** @@ -21,34 +20,29 @@ public class ExcelTermExporter { private final Map prefixes; + private final String langCode; - public ExcelTermExporter(Map prefixes) { + public ExcelTermExporter(Map prefixes, String langCode) { this.prefixes = prefixes; + this.langCode = langCode; } public void export(Term t, Row row) { Objects.requireNonNull(row); row.createCell(0).setCellValue(prefixedUri(t.getVocabulary(), t)); - row.createCell(1) - .setCellValue(TabularTermExportUtils.exportMultilingualString(t.getLabel(), Function.identity(), false)); - row.createCell(2) - .setCellValue(String.join(TabularTermExportUtils.STRING_DELIMITER, - Utils.emptyIfNull(t.getAltLabels()).stream() - .map(str -> TabularTermExportUtils.exportMultilingualString(str, - Function.identity(), - false)) - .collect(Collectors.toSet()))); - row.createCell(3) - .setCellValue(String.join(TabularTermExportUtils.STRING_DELIMITER, - Utils.emptyIfNull(t.getHiddenLabels()).stream() - .map(str -> TabularTermExportUtils.exportMultilingualString(str, - Function.identity(), - false)) - .collect(Collectors.toSet()))); - row.createCell(4).setCellValue( - TabularTermExportUtils.exportMultilingualString(t.getDefinition(), Utils::markdownToPlainText, false)); + row.createCell(1).setCellValue(t.getLabel().get(langCode)); + row.createCell(2).setCellValue(Utils.emptyIfNull(t.getAltLabels()).stream() + .map(str -> str.get(langCode)) + .filter(Objects::nonNull) + .collect(Collectors.joining(TabularTermExportUtils.STRING_DELIMITER))); + row.createCell(3).setCellValue(Utils.emptyIfNull(t.getHiddenLabels()).stream() + .map(str -> str.get(langCode)) + .filter(Objects::nonNull) + .collect(Collectors.joining(TabularTermExportUtils.STRING_DELIMITER))); + row.createCell(4) + .setCellValue(Utils.markdownToPlainText(t.getDefinition() != null ? t.getDefinition().get(langCode) : null)); row.createCell(5).setCellValue( - TabularTermExportUtils.exportMultilingualString(t.getDescription(), Utils::markdownToPlainText, false)); + Utils.markdownToPlainText(t.getDescription() != null ? t.getDescription().get(langCode) : null)); row.createCell(6) .setCellValue(String.join(TabularTermExportUtils.STRING_DELIMITER, Utils.emptyIfNull(t.getTypes()))); row.createCell(7) @@ -57,10 +51,9 @@ public void export(Term t, Row row) { .setCellValue(Utils.emptyIfNull(t.getParentTerms()).stream().map(pt -> prefixedUri(pt.getVocabulary(), pt)) .collect(Collectors.joining(TabularTermExportUtils.STRING_DELIMITER))); row.createCell(9) - .setCellValue(String.join(TabularTermExportUtils.STRING_DELIMITER, - Utils.emptyIfNull(t.getSubTerms()).stream() - .map(this::termInfoPrefixedUri) - .collect(Collectors.toSet()))); + .setCellValue(Utils.emptyIfNull(t.getSubTerms()).stream() + .map(this::termInfoPrefixedUri) + .collect(Collectors.joining(TabularTermExportUtils.STRING_DELIMITER))); row.createCell(10).setCellValue(Utils.joinCollections(t.getRelated(), t.getInverseRelated()).stream() .map(this::termInfoPrefixedUri) .distinct() @@ -79,9 +72,8 @@ public void export(Term t, Row row) { row.createCell(14) .setCellValue(String.join(TabularTermExportUtils.STRING_DELIMITER, Utils.emptyIfNull(t.getNotations()))); row.createCell(15).setCellValue(Utils.emptyIfNull(t.getExamples()).stream() - .map(str -> TabularTermExportUtils.exportMultilingualString(str, - Function.identity(), - false)) + .map(str -> str.get(langCode)) + .filter(Objects::nonNull) .collect(Collectors.joining(TabularTermExportUtils.STRING_DELIMITER))); if (t.getProperties() != null && !Utils.emptyIfNull(t.getProperties().get(DC.Terms.REFERENCES)).isEmpty()) { row.createCell(16).setCellValue(t.getProperties().get(DC.Terms.REFERENCES).toString()); diff --git a/src/main/java/cz/cvut/kbss/termit/service/export/ExcelVocabularyExporter.java b/src/main/java/cz/cvut/kbss/termit/service/export/ExcelVocabularyExporter.java index 6c6596f22..a2fb58239 100644 --- a/src/main/java/cz/cvut/kbss/termit/service/export/ExcelVocabularyExporter.java +++ b/src/main/java/cz/cvut/kbss/termit/service/export/ExcelVocabularyExporter.java @@ -14,6 +14,7 @@ */ package cz.cvut.kbss.termit.service.export; +import com.neovisionaries.i18n.LanguageCode; import cz.cvut.kbss.termit.dto.PrefixDeclaration; import cz.cvut.kbss.termit.dto.TermInfo; import cz.cvut.kbss.termit.exception.TermItException; @@ -23,23 +24,20 @@ import cz.cvut.kbss.termit.service.business.VocabularyService; import cz.cvut.kbss.termit.service.export.util.TypeAwareByteArrayResource; import cz.cvut.kbss.termit.service.repository.TermRepositoryService; -import cz.cvut.kbss.termit.util.Configuration; import cz.cvut.kbss.termit.util.Constants; import cz.cvut.kbss.termit.util.TypeAwareResource; import cz.cvut.kbss.termit.util.Utils; +import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.Row; -import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.xssf.usermodel.XSSFFont; +import org.apache.poi.xssf.usermodel.XSSFSheet; import org.apache.poi.xssf.usermodel.XSSFWorkbook; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.InputStream; import java.net.URI; import java.util.*; import java.util.stream.Collectors; @@ -49,17 +47,6 @@ */ @Service("excel") public class ExcelVocabularyExporter implements VocabularyExporter { - - private static final Logger LOG = LoggerFactory.getLogger(ExcelVocabularyExporter.class); - - /** - * Index of the main sheet containing the exported glossary - */ - static final int GLOSSARY_SHEET_INDEX = 0; - /** - * Index of the sheet with prefix mapping - */ - static final int PREFIX_SHEET_INDEX = 1; /** * Name of the prefix column in the prefix mapping sheet */ @@ -71,19 +58,16 @@ public class ExcelVocabularyExporter implements VocabularyExporter { private static final String FONT = "Arial"; private static final short FONT_SIZE = (short) 10; + private static final int COLUMN_WIDTH = 20; private final TermRepositoryService termService; private final VocabularyService vocabularyService; - private final Configuration config; - @Autowired - public ExcelVocabularyExporter(TermRepositoryService termService, VocabularyService vocabularyService, - Configuration config) { + public ExcelVocabularyExporter(TermRepositoryService termService, VocabularyService vocabularyService) { this.termService = termService; this.vocabularyService = vocabularyService; - this.config = config; } @Override @@ -98,9 +82,9 @@ public TypeAwareResource exportGlossary(Vocabulary vocabulary, ExportConfig conf private TypeAwareResource exportGlossary(Vocabulary vocabulary) { Objects.requireNonNull(vocabulary); - try (final XSSFWorkbook wb = new XSSFWorkbook(loadWorkbookTemplate())) { + try (final XSSFWorkbook wb = new XSSFWorkbook()) { final Map prefixes = new HashMap<>(); - generateGlossarySheet(vocabulary, wb, prefixes); + generateGlossarySheets(vocabulary, wb, prefixes); generatePrefixMappingSheet(wb, prefixes); final ByteArrayOutputStream bos = new ByteArrayOutputStream(); wb.write(bos); @@ -111,20 +95,28 @@ private TypeAwareResource exportGlossary(Vocabulary vocabulary) { } } - private InputStream loadWorkbookTemplate() { - final InputStream templateIs = ExcelVocabularyExporter.class.getClassLoader().getResourceAsStream( - "template/" + config.getPersistence().getLanguage() + "/export.xlsx"); - if (templateIs == null) { - LOG.warn("Localized Excel export template file not found. Falling back to the default one."); - return ExcelVocabularyExporter.class.getClassLoader().getResourceAsStream( - "template/" + Constants.DEFAULT_LANGUAGE + "/export.xlsx"); - } - return templateIs; + private void generateGlossarySheets(Vocabulary vocabulary, XSSFWorkbook wb, Map prefixes) { + final List terms = termService.findAllFull(vocabulary); + final List uniqueLangCodes = extractUniqueLanguages(terms); + uniqueLangCodes.forEach(langCode -> { + final LanguageCode lang = LanguageCode.getByCodeIgnoreCase(langCode); + final XSSFSheet sheet = wb.createSheet(lang != null ? lang.getName() : langCode); + generateHeader(sheet, langCode); + generateTermRows(terms, sheet, langCode, prefixes); + }); } - private void generateGlossarySheet(Vocabulary vocabulary, XSSFWorkbook wb, Map prefixes) { - final Sheet sheet = wb.getSheetAt(GLOSSARY_SHEET_INDEX); - generateTermRows(termService.findAllFull(vocabulary), wb, sheet, prefixes); + private List extractUniqueLanguages(List terms) { + final Set uniqueLanguages = new HashSet<>(); + for (Term t : terms) { + uniqueLanguages.addAll(t.getLabel().getLanguages()); + uniqueLanguages.addAll(t.getDefinition().getLanguages()); + uniqueLanguages.addAll(t.getDescription().getLanguages()); + Utils.emptyIfNull(t.getAltLabels()).forEach(ms -> uniqueLanguages.addAll(ms.getLanguages())); + Utils.emptyIfNull(t.getHiddenLabels()).forEach(ms -> uniqueLanguages.addAll(ms.getLanguages())); + Utils.emptyIfNull(t.getExamples()).forEach(ms -> uniqueLanguages.addAll(ms.getLanguages())); + } + return uniqueLanguages.stream().filter(Objects::nonNull).sorted().collect(Collectors.toList()); } private static XSSFFont initFont(XSSFWorkbook wb) { @@ -134,13 +126,35 @@ private static XSSFFont initFont(XSSFWorkbook wb) { return font; } - private void generateTermRows(List terms, XSSFWorkbook wb, Sheet sheet, + private void generateHeader(XSSFSheet sheet, String langCode) { + final List columns = Constants.EXPORT_COLUMN_LABELS.getOrDefault(langCode, + Constants.EXPORT_COLUMN_LABELS.get( + Constants.DEFAULT_LANGUAGE)); + final Row row = generateHeaderRow(sheet); + for (int i = 0; i < columns.size(); i++) { + sheet.setColumnWidth(i, COLUMN_WIDTH * 256); + final Cell cell = row.createCell(i); + cell.setCellValue(columns.get(i)); + } + } + + private Row generateHeaderRow(XSSFSheet sheet) { + final XSSFFont font = initFont(sheet.getWorkbook()); + font.setBold(true); + final CellStyle cellStyle = sheet.getWorkbook().createCellStyle(); + cellStyle.setFont(font); + final Row row = sheet.createRow(0); + row.setRowStyle(cellStyle); + return row; + } + + private void generateTermRows(List terms, XSSFSheet sheet, String langCode, Map prefixes) { - final XSSFFont font = initFont(wb); - final CellStyle style = wb.createCellStyle(); + final XSSFFont font = initFont(sheet.getWorkbook()); + final CellStyle style = sheet.getWorkbook().createCellStyle(); style.setFont(font); style.setWrapText(true); - final ExcelTermExporter termExporter = new ExcelTermExporter(prefixes); + final ExcelTermExporter termExporter = new ExcelTermExporter(prefixes, langCode); for (int i = 0; i < terms.size(); i++) { // Row no. 0 is the header final Row row = sheet.createRow(i + 1); @@ -168,7 +182,8 @@ private void resolvePrefixes(Term t, Map prefixes) { } private void generatePrefixMappingSheet(XSSFWorkbook wb, Map prefixes) { - final Sheet sheet = wb.getSheetAt(PREFIX_SHEET_INDEX); + final XSSFSheet sheet = wb.createSheet(PREFIX_COLUMN); + generatePrefixSheetHeader(sheet); final XSSFFont font = initFont(wb); final CellStyle style = wb.createCellStyle(); style.setFont(font); @@ -186,6 +201,14 @@ private void generatePrefixMappingSheet(XSSFWorkbook wb, Map EXPORT_COLUMNS = List.of("IRI", "Label", "Synonyms", "Search strings", - "Definition", "Scope note", "Type", "Source", - "Parent terms", "Sub terms", "Related terms", - "Related match terms", "Exact matches", "Status", - "Notation", "Example", "References" - ); - /** * Delimiter of a joined list of strings. */ diff --git a/src/main/java/cz/cvut/kbss/termit/util/Constants.java b/src/main/java/cz/cvut/kbss/termit/util/Constants.java index 9d585f387..63ae6b116 100644 --- a/src/main/java/cz/cvut/kbss/termit/util/Constants.java +++ b/src/main/java/cz/cvut/kbss/termit/util/Constants.java @@ -22,6 +22,8 @@ import java.time.Instant; import java.time.ZoneId; import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -114,6 +116,20 @@ public class Constants { SKOS.BROAD_MATCH, SKOS.NARROW_MATCH, SKOS.EXACT_MATCH, SKOS.RELATED_MATCH ).map(URI::create).collect(Collectors.toSet()); + /** + * Labels of columns representing exported term attributes in various supported languages. + */ + public static final Map> EXPORT_COLUMN_LABELS = Map.of( + "cs", + List.of("Identifikátor", "Název", "Synonyma", "Vyhledávací texty", "Definice", "Doplňující poznámka", "Typ", + "Zdroj", "Nadřazené pojmy", "Podřazené pojmy", "Související pojmy", "Externí související pojmy", + "Pojmy se stejným významem", "Stav pojmu", "Notace", "Příklady", "Reference"), + DEFAULT_LANGUAGE, + List.of("Identifier", "Label", "Synonyms", "Search strings", "Definition", "Scope note", "Type", "Source", + "Parent terms", "Sub terms", "Related terms", "Related match terms", "Exact matches", "Status", + "Notation", "Example", "References") + ); + private Constants() { throw new AssertionError(); } diff --git a/src/main/java/cz/cvut/kbss/termit/util/Utils.java b/src/main/java/cz/cvut/kbss/termit/util/Utils.java index 5294ad6f9..336c926bb 100644 --- a/src/main/java/cz/cvut/kbss/termit/util/Utils.java +++ b/src/main/java/cz/cvut/kbss/termit/util/Utils.java @@ -310,7 +310,9 @@ public static String htmlToPlainText(String html) { * @return Text content of the input string */ public static String markdownToPlainText(String markdown) { - Objects.requireNonNull(markdown); + if (markdown == null) { + return null; + } final Parser parser = Parser.builder().build(); final Node document = parser.parse(markdown); final HtmlRenderer renderer = HtmlRenderer.builder().build(); diff --git a/src/main/resources/template/cs/export.xlsx b/src/main/resources/template/cs/export.xlsx deleted file mode 100644 index 643c2db21dab09c82f30bbc8eb0194d4583d0db1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6332 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 LD7fN3y4U^(c9l_s diff --git a/src/test/java/cz/cvut/kbss/termit/rest/TermControllerTest.java b/src/test/java/cz/cvut/kbss/termit/rest/TermControllerTest.java index 91fd3c7f0..4ef3dfef9 100644 --- a/src/test/java/cz/cvut/kbss/termit/rest/TermControllerTest.java +++ b/src/test/java/cz/cvut/kbss/termit/rest/TermControllerTest.java @@ -7,7 +7,6 @@ import cz.cvut.kbss.jsonld.JsonLd; import cz.cvut.kbss.termit.dto.Snapshot; import cz.cvut.kbss.termit.dto.TermStatus; -import cz.cvut.kbss.termit.service.export.util.TabularTermExportUtils; import cz.cvut.kbss.termit.dto.listing.TermDto; import cz.cvut.kbss.termit.environment.Environment; import cz.cvut.kbss.termit.environment.Generator; @@ -321,7 +320,7 @@ void getAllExportsTermsToCsvWhenAcceptMediaTypeIsSetToCsv() throws Exception { final cz.cvut.kbss.termit.model.Vocabulary vocabulary = Generator.generateVocabulary(); vocabulary.setUri(URI.create(VOCABULARY_URI)); when(termServiceMock.findVocabularyRequired(vocabulary.getUri())).thenReturn(vocabulary); - final String content = String.join(",", TabularTermExportUtils.EXPORT_COLUMNS); + final String content = String.join(",", Constants.EXPORT_COLUMN_LABELS.get(Constants.DEFAULT_LANGUAGE)); final TypeAwareByteArrayResource export = new TypeAwareByteArrayResource(content.getBytes(), ExportFormat.CSV.getMediaType(), ExportFormat.CSV.getFileExtension()); @@ -341,7 +340,7 @@ void getAllReturnsCsvAsAttachmentWhenAcceptMediaTypeIsCsv() throws Exception { final cz.cvut.kbss.termit.model.Vocabulary vocabulary = Generator.generateVocabulary(); vocabulary.setUri(URI.create(VOCABULARY_URI)); when(termServiceMock.findVocabularyRequired(vocabulary.getUri())).thenReturn(vocabulary); - final String content = String.join(",", TabularTermExportUtils.EXPORT_COLUMNS); + final String content = String.join(",", Constants.EXPORT_COLUMN_LABELS.get(Constants.DEFAULT_LANGUAGE)); final TypeAwareByteArrayResource export = new TypeAwareByteArrayResource(content.getBytes(), ExportFormat.CSV.getMediaType(), ExportFormat.CSV.getFileExtension()); diff --git a/src/test/java/cz/cvut/kbss/termit/service/export/CsvTermExporterTest.java b/src/test/java/cz/cvut/kbss/termit/service/export/CsvTermExporterTest.java index ecd4a1673..0b34c768e 100644 --- a/src/test/java/cz/cvut/kbss/termit/service/export/CsvTermExporterTest.java +++ b/src/test/java/cz/cvut/kbss/termit/service/export/CsvTermExporterTest.java @@ -5,7 +5,7 @@ import cz.cvut.kbss.termit.environment.Environment; import cz.cvut.kbss.termit.environment.Generator; import cz.cvut.kbss.termit.model.Term; -import cz.cvut.kbss.termit.service.export.util.TabularTermExportUtils; +import cz.cvut.kbss.termit.util.Constants; import cz.cvut.kbss.termit.util.Vocabulary; import org.junit.jupiter.api.Test; @@ -35,7 +35,7 @@ void exportOutputsAllColumnsEvenIfNotAllAttributesArePresent() { count++; } } - assertEquals(TabularTermExportUtils.EXPORT_COLUMNS.size(), count); + assertEquals(Constants.EXPORT_COLUMN_LABELS.get(Constants.DEFAULT_LANGUAGE).size(), count); } @Test diff --git a/src/test/java/cz/cvut/kbss/termit/service/export/ExcelTermExporterTest.java b/src/test/java/cz/cvut/kbss/termit/service/export/ExcelTermExporterTest.java index 85fd45bd4..0be304613 100644 --- a/src/test/java/cz/cvut/kbss/termit/service/export/ExcelTermExporterTest.java +++ b/src/test/java/cz/cvut/kbss/termit/service/export/ExcelTermExporterTest.java @@ -25,7 +25,7 @@ class ExcelTermExporterTest { - private final ExcelTermExporter sut = new ExcelTermExporter(new HashMap<>()); + private final ExcelTermExporter sut = new ExcelTermExporter(new HashMap<>(), Environment.LANGUAGE); @Test void exportExportsTermToExcelRow() { @@ -112,8 +112,8 @@ void exportHandlesSkippingEmptyColumns() { @Test void exportHandlesNullAltLabelsAttribute() { final Term term = Generator.generateTermWithId(); - final MultilingualString hiddenOne = MultilingualString.create("budova", "cs"); - final MultilingualString hiddenTwo = MultilingualString.create("budovy", "cs"); + final MultilingualString hiddenOne = MultilingualString.create("building", Environment.LANGUAGE); + final MultilingualString hiddenTwo = MultilingualString.create("buildings", Environment.LANGUAGE); term.setAltLabels(null); term.setHiddenLabels(new HashSet<>(Arrays.asList(hiddenOne, hiddenTwo))); @@ -127,7 +127,7 @@ void exportHandlesNullAltLabelsAttribute() { @Test void exportIncludesRelatedAndInverseRelatedTerms() { - final Term term = Generator.generateMultiLingualTerm(Environment.LANGUAGE, "cs"); + final Term term = Generator.generateTermWithId(); term.setVocabulary(Generator.generateUri()); term.setRelated(IntStream.range(0, 5) .mapToObj(i -> new TermInfo(Generator.generateTermWithId(term.getVocabulary()))) @@ -146,7 +146,7 @@ void exportIncludesRelatedAndInverseRelatedTerms() { @Test void exportIncludesRelatedMatchAndInverseRelatedMatchTerms() { - final Term term = Generator.generateMultiLingualTerm(Environment.LANGUAGE, "cs"); + final Term term = Generator.generateTermWithId(); term.setVocabulary(Generator.generateUri()); term.setRelatedMatch(IntStream.range(0, 5).mapToObj(i -> new TermInfo(Generator.generateTermWithId())) .collect(Collectors.toSet())); @@ -163,7 +163,7 @@ void exportIncludesRelatedMatchAndInverseRelatedMatchTerms() { @Test void exportIncludesExactMatchAndInverseExactMatchTerms() { - final Term term = Generator.generateMultiLingualTerm(Environment.LANGUAGE, "cs"); + final Term term = Generator.generateTermWithId(); term.setVocabulary(Generator.generateUri()); term.setExactMatchTerms(IntStream.range(0, 5).mapToObj(i -> new TermInfo(Generator.generateTermWithId())) .collect(Collectors.toSet())); @@ -180,7 +180,7 @@ void exportIncludesExactMatchAndInverseExactMatchTerms() { @Test void exportEnsuresNoDuplicatesInRelatedRelatedMatchAndExactMatchTerms() { - final Term term = Generator.generateMultiLingualTerm(Environment.LANGUAGE, "cs"); + final Term term = Generator.generateTermWithId(); term.setVocabulary(Generator.generateUri()); final Set asserted = IntStream.range(0, 5).mapToObj(i -> new TermInfo(Generator.generateTermWithId())) .collect(Collectors.toSet()); @@ -208,22 +208,18 @@ void exportEnsuresNoDuplicatesInRelatedRelatedMatchAndExactMatchTerms() { } @Test - void exportExportsMultilingualAttributesAsValuesWithLanguageInParentheses() { - final Term term = Generator.generateMultiLingualTerm(Environment.LANGUAGE, "cs"); + void exportExportsSpecifiedLanguageOfMultilingualString() { + final String lang = "cs"; + final ExcelTermExporter sut = new ExcelTermExporter(new HashMap<>(), lang); + final Term term = Generator.generateMultiLingualTerm(Environment.LANGUAGE, lang); final XSSFRow row = generateExcel(); sut.export(term, row); final String label = row.getCell(1).getStringCellValue(); - assertTrue(label.matches(".+;.+")); - term.getLabel().getValue() - .forEach((lang, value) -> assertThat(label, containsString(value + "(" + lang + ")"))); + assertEquals(term.getLabel().get(lang), label); final String definition = row.getCell(4).getStringCellValue(); - assertTrue(definition.matches(".+;.+")); - term.getDefinition().getValue() - .forEach((lang, value) -> assertThat(definition, containsString(value + "(" + lang + ")"))); + assertEquals(term.getDefinition().get(lang), definition); final String description = row.getCell(5).getStringCellValue(); - assertTrue(description.matches(".+;.+")); - term.getDescription().getValue() - .forEach((lang, value) -> assertThat(description, containsString(value + "(" + lang + ")"))); + assertEquals(term.getDescription().get(lang), description); } @Test @@ -251,7 +247,7 @@ void exportReplacesFullIrisWithPrefixedWhenPossible() { term.setSubTerms(Collections.singleton(new TermInfo(Generator.generateTermWithId(vocabularyUri)))); final Map prefixes = Collections.singletonMap(vocabularyUri, new PrefixDeclaration(prefix, namespace)); - final ExcelTermExporter sut = new ExcelTermExporter(prefixes); + final ExcelTermExporter sut = new ExcelTermExporter(prefixes, Environment.LANGUAGE); final XSSFRow row = generateExcel(); sut.export(term, row); diff --git a/src/test/java/cz/cvut/kbss/termit/service/export/ExcelVocabularyExporterTest.java b/src/test/java/cz/cvut/kbss/termit/service/export/ExcelVocabularyExporterTest.java index 6cc20e36e..aa2398300 100644 --- a/src/test/java/cz/cvut/kbss/termit/service/export/ExcelVocabularyExporterTest.java +++ b/src/test/java/cz/cvut/kbss/termit/service/export/ExcelVocabularyExporterTest.java @@ -14,6 +14,7 @@ */ package cz.cvut.kbss.termit.service.export; +import com.neovisionaries.i18n.LanguageCode; import cz.cvut.kbss.termit.dto.PrefixDeclaration; import cz.cvut.kbss.termit.dto.TermInfo; import cz.cvut.kbss.termit.environment.Generator; @@ -22,7 +23,6 @@ import cz.cvut.kbss.termit.service.IdentifierResolver; import cz.cvut.kbss.termit.service.business.VocabularyService; import cz.cvut.kbss.termit.service.repository.TermRepositoryService; -import cz.cvut.kbss.termit.util.Configuration; import cz.cvut.kbss.termit.util.Constants; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.xssf.usermodel.XSSFRow; @@ -30,7 +30,6 @@ import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Answers; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -59,9 +58,6 @@ class ExcelVocabularyExporterTest { @Mock private VocabularyService vocabularyService; - @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private Configuration config; - @InjectMocks private ExcelVocabularyExporter sut; @@ -72,14 +68,14 @@ private static ExportConfig exportConfig() { } @Test - void exportVocabularyGlossaryOutputsGlossaryTermsIntoSheet() throws Exception { + void exportGlossaryOutputsGlossaryTermsIntoSheet() throws Exception { when(vocabularyService.resolvePrefix(any())).thenReturn(new PrefixDeclaration()); final List terms = IntStream.range(0, 5).mapToObj(i -> Generator.generateTermWithId()).collect( Collectors.toList()); when(termService.findAllFull(vocabulary)).thenReturn(terms); final Resource result = sut.exportGlossary(vocabulary, exportConfig()); final XSSFWorkbook wb = new XSSFWorkbook(result.getInputStream()); - final XSSFSheet sheet = wb.getSheetAt(ExcelVocabularyExporter.GLOSSARY_SHEET_INDEX); + final XSSFSheet sheet = wb.getSheet(LanguageCode.getByCodeIgnoreCase(Constants.DEFAULT_LANGUAGE).getName()); assertNotNull(sheet); int i; for (i = 1; i < terms.size(); i++) { @@ -101,7 +97,7 @@ void supportsReturnsFalseForNonExcelMediaType() { } @Test - void exportVocabularyGlossaryUsesVocabularyServiceToRetrievePrefixes() { + void exportGlossaryUsesVocabularyServiceToRetrievePrefixes() { final URI vocabularyUri = Generator.generateUri(); final URI exactMatchVocabularyUri = Generator.generateUri(); final List terms = IntStream.range(0, 5).mapToObj(i -> Generator.generateTermWithId(vocabularyUri)) @@ -117,7 +113,7 @@ void exportVocabularyGlossaryUsesVocabularyServiceToRetrievePrefixes() { } @Test - void exportVocabularyGeneratesExtraSheetWithPrefixMapping() throws Exception { + void exportGlossaryGeneratesExtraSheetWithPrefixMapping() throws Exception { final URI vocabularyUri = vocabulary.getUri(); final String vocabularyPrefix = "pOne"; when(vocabularyService.resolvePrefix(vocabularyUri)).thenReturn(new PrefixDeclaration(vocabularyPrefix, @@ -135,8 +131,9 @@ void exportVocabularyGeneratesExtraSheetWithPrefixMapping() throws Exception { when(termService.findAllFull(vocabulary)).thenReturn(terms); final Resource result = sut.exportGlossary(vocabulary, exportConfig()); final XSSFWorkbook wb = new XSSFWorkbook(result.getInputStream()); - final XSSFSheet sheet = wb.getSheetAt(ExcelVocabularyExporter.PREFIX_SHEET_INDEX); + final XSSFSheet sheet = wb.getSheetAt(wb.getNumberOfSheets() - 1); assertNotNull(sheet); + assertEquals(ExcelVocabularyExporter.PREFIX_COLUMN, sheet.getSheetName()); // First row is the header final Row header = sheet.getRow(0); assertEquals(ExcelVocabularyExporter.PREFIX_COLUMN, header.getCell(0).getStringCellValue()); @@ -150,4 +147,40 @@ void exportVocabularyGeneratesExtraSheetWithPrefixMapping() throws Exception { IdentifierResolver.extractIdentifierNamespace(exactMatchVocabularyUri))); } } + + @Test + void exportGlossaryOutputsLocalizedVersionsOfTermsIntoSeparateSheets() throws Exception { + when(vocabularyService.resolvePrefix(any())).thenReturn(new PrefixDeclaration()); + final String[] languages = {"en", "cs"}; + final List terms = List.of(Generator.generateMultiLingualTerm(languages), + Generator.generateMultiLingualTerm(languages)); + when(termService.findAllFull(vocabulary)).thenReturn(terms); + final Resource result = sut.exportGlossary(vocabulary, exportConfig()); + final XSSFWorkbook wb = new XSSFWorkbook(result.getInputStream()); + for (String langCode : languages) { + final XSSFSheet sheet = wb.getSheet(LanguageCode.getByCodeIgnoreCase(langCode).getName()); + assertNotNull(sheet); + int i; + for (i = 1; i < terms.size(); i++) { + final XSSFRow row = sheet.getRow(i); + final String id = row.getCell(0).getStringCellValue(); + assertTrue(terms.stream().anyMatch(t -> t.getUri().toString().equals(id))); + } + assertNull(sheet.getRow(i + 1)); + } + } + + @Test + void exportGlossarySkipsNullLanguages() throws Exception { + when(vocabularyService.resolvePrefix(any())).thenReturn(new PrefixDeclaration()); + final String[] languages = {"en", "cs"}; + final List terms = List.of(Generator.generateMultiLingualTerm(languages), + Generator.generateMultiLingualTerm(languages)); + terms.get(0).getLabel().set("Language-less"); + when(termService.findAllFull(vocabulary)).thenReturn(terms); + final Resource result = sut.exportGlossary(vocabulary, exportConfig()); + final XSSFWorkbook wb = new XSSFWorkbook(result.getInputStream()); + // Glossary sheets + prefix sheet + assertEquals(languages.length + 1, wb.getNumberOfSheets()); + } } From aa5db1516027152a23dd214ce1b9771aa37172bd Mon Sep 17 00:00:00 2001 From: Martin Ledvinka Date: Thu, 12 Jan 2023 10:03:13 +0100 Subject: [PATCH 11/23] [kbss-cvut/termit-ui#357] Reuse column header definitions from Excel also for CSV export. Remove obsolete localized header template files. --- .../service/export/CsvVocabularyExporter.java | 53 +++++++------------ src/main/resources/template/cs/export.csv | 2 - src/main/resources/template/en/export.csv | 2 - .../export/CsvVocabularyExporterTest.java | 14 ++--- 4 files changed, 26 insertions(+), 45 deletions(-) delete mode 100644 src/main/resources/template/cs/export.csv delete mode 100644 src/main/resources/template/en/export.csv diff --git a/src/main/java/cz/cvut/kbss/termit/service/export/CsvVocabularyExporter.java b/src/main/java/cz/cvut/kbss/termit/service/export/CsvVocabularyExporter.java index 45bef3316..ae2374053 100644 --- a/src/main/java/cz/cvut/kbss/termit/service/export/CsvVocabularyExporter.java +++ b/src/main/java/cz/cvut/kbss/termit/service/export/CsvVocabularyExporter.java @@ -1,23 +1,19 @@ /** - * TermIt - * Copyright (C) 2019 Czech Technical University in Prague - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * TermIt Copyright (C) 2019 Czech Technical University in Prague + *

+ * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later + * version. + *

+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + *

+ * You should have received a copy of the GNU General Public License along with this program. If not, see + * . */ package cz.cvut.kbss.termit.service.export; -import cz.cvut.kbss.termit.exception.ResourceNotFoundException; import cz.cvut.kbss.termit.exception.UnsupportedOperationException; import cz.cvut.kbss.termit.model.Term; import cz.cvut.kbss.termit.model.Vocabulary; @@ -26,9 +22,6 @@ import cz.cvut.kbss.termit.util.Configuration; import cz.cvut.kbss.termit.util.Constants; import cz.cvut.kbss.termit.util.TypeAwareResource; -import cz.cvut.kbss.termit.util.Utils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -41,11 +34,6 @@ @Service("csv") public class CsvVocabularyExporter implements VocabularyExporter { - private static final Logger LOG = LoggerFactory.getLogger(CsvVocabularyExporter.class); - - private static final String DIRECTORY = "template"; - private static final String TEMPLATE_FILE = "export.csv"; - private final TermRepositoryService termService; private final Configuration config; @@ -68,7 +56,7 @@ public TypeAwareResource exportGlossary(Vocabulary vocabulary, ExportConfig conf private TypeAwareByteArrayResource exportGlossary(Vocabulary vocabulary) { Objects.requireNonNull(vocabulary); - final StringBuilder export = new StringBuilder(loadHeader().trim()); + final StringBuilder export = new StringBuilder(generateHeader().trim()); final List terms = termService.findAllFull(vocabulary); final CsvTermExporter termExporter = new CsvTermExporter(); terms.forEach(t -> export.append('\n').append(termExporter.export(t))); @@ -76,16 +64,11 @@ private TypeAwareByteArrayResource exportGlossary(Vocabulary vocabulary) { ExportFormat.CSV.getFileExtension()); } - private String loadHeader() { - try { - // First try loading a localized header - return Utils.loadClasspathResource( - DIRECTORY + "/" + config.getPersistence().getLanguage() + "/" + TEMPLATE_FILE); - } catch (ResourceNotFoundException e) { - // If we do not find it, fall back to the default one - LOG.warn("Unable to find localized CSV export header. Falling back to the default one.", e); - return Utils.loadClasspathResource(DIRECTORY + "/" + Constants.DEFAULT_LANGUAGE + "/" + TEMPLATE_FILE); - } + private String generateHeader() { + final String langCode = config.getPersistence().getLanguage(); + return String.join(",", Constants.EXPORT_COLUMN_LABELS.getOrDefault(langCode, + Constants.EXPORT_COLUMN_LABELS.get( + Constants.DEFAULT_LANGUAGE))); } @Override diff --git a/src/main/resources/template/cs/export.csv b/src/main/resources/template/cs/export.csv deleted file mode 100644 index 140b92e1b..000000000 --- a/src/main/resources/template/cs/export.csv +++ /dev/null @@ -1,2 +0,0 @@ -Identifikátor,Název,Synonyma,Vyhledávací texty,Definice,Doplňující poznámka,Typ,Zdroj,Nadřazené pojmy,Podřazené pojmy,Související pojmy,Externí související pojmy,Pojmy se stejným významem,Stav pojmu,Notace,Příklady,Reference - diff --git a/src/main/resources/template/en/export.csv b/src/main/resources/template/en/export.csv deleted file mode 100644 index fda0051e7..000000000 --- a/src/main/resources/template/en/export.csv +++ /dev/null @@ -1,2 +0,0 @@ -Identifier,Label,Synonyms,Search strings,Definition,Scope note,Type,Source,Parent terms,Sub terms,Related terms,Related match terms,Exact matches,Status,Notation,Example,References - diff --git a/src/test/java/cz/cvut/kbss/termit/service/export/CsvVocabularyExporterTest.java b/src/test/java/cz/cvut/kbss/termit/service/export/CsvVocabularyExporterTest.java index 75c65d64c..44916c314 100644 --- a/src/test/java/cz/cvut/kbss/termit/service/export/CsvVocabularyExporterTest.java +++ b/src/test/java/cz/cvut/kbss/termit/service/export/CsvVocabularyExporterTest.java @@ -21,10 +21,8 @@ import cz.cvut.kbss.termit.service.repository.TermRepositoryService; import cz.cvut.kbss.termit.util.Configuration; import cz.cvut.kbss.termit.util.Constants; -import cz.cvut.kbss.termit.util.Utils; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Answers; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -48,7 +46,7 @@ class CsvVocabularyExporterTest { @Mock private TermRepositoryService termService; - @Mock(answer = Answers.RETURNS_DEEP_STUBS) + @Mock private Configuration config; @InjectMocks @@ -58,13 +56,14 @@ class CsvVocabularyExporterTest { @Test void exportVocabularyGlossaryOutputsHeaderContainingColumnNamesIntoResult() throws Exception { + final Configuration.Persistence persistenceConfig = new Configuration.Persistence(); + persistenceConfig.setLanguage(Environment.LANGUAGE); + when(config.getPersistence()).thenReturn(persistenceConfig); when(termService.findAllFull(vocabulary)).thenReturn(Collections.emptyList()); final Resource result = sut.exportGlossary(vocabulary, exportConfig()); try (final BufferedReader reader = new BufferedReader(new InputStreamReader(result.getInputStream()))) { final String header = reader.readLine(); - assertEquals( - Utils.loadClasspathResource("template" + "/" + Environment.LANGUAGE + "/" + "export.csv").trim(), - header); + assertEquals(String.join(",", Constants.EXPORT_COLUMN_LABELS.get(Environment.LANGUAGE)), header); } } @@ -74,6 +73,9 @@ private static ExportConfig exportConfig() { @Test void exportVocabularyGlossaryOutputsTermsContainedInVocabularyAsCsv() throws Exception { + final Configuration.Persistence persistenceConfig = new Configuration.Persistence(); + persistenceConfig.setLanguage(Environment.LANGUAGE); + when(config.getPersistence()).thenReturn(persistenceConfig); final List terms = IntStream.range(0, 10).mapToObj(i -> Generator.generateTermWithId()).collect( Collectors.toList()); when(termService.findAllFull(vocabulary)).thenReturn(terms); From a5f3d72e548ae50ea593a0ee08f26b6275d9d2f3 Mon Sep 17 00:00:00 2001 From: Martin Ledvinka Date: Thu, 12 Jan 2023 10:22:03 +0100 Subject: [PATCH 12/23] [kbss-cvut/termit-ui#357] Sort prefixes in the Prefix sheet in Excel export alphabetically. --- .../kbss/termit/dto/PrefixDeclaration.java | 22 ++++++++++++++----- .../termit/persistence/dao/VocabularyDao.java | 2 +- .../export/ExcelVocabularyExporter.java | 8 ++++--- .../export/ExcelVocabularyExporterTest.java | 8 +++---- 4 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/main/java/cz/cvut/kbss/termit/dto/PrefixDeclaration.java b/src/main/java/cz/cvut/kbss/termit/dto/PrefixDeclaration.java index df633cad8..f7bc40245 100644 --- a/src/main/java/cz/cvut/kbss/termit/dto/PrefixDeclaration.java +++ b/src/main/java/cz/cvut/kbss/termit/dto/PrefixDeclaration.java @@ -2,7 +2,12 @@ import java.util.Objects; -public class PrefixDeclaration { +public class PrefixDeclaration implements Comparable { + + /** + * Empty prefix declaration, i.e. no prefix is declared. + */ + public static final PrefixDeclaration EMPTY_PREFIX = new PrefixDeclaration(null, null); /** * Prefix and local name separator. @@ -13,10 +18,6 @@ public class PrefixDeclaration { private final String namespace; - public PrefixDeclaration() { - this(null, null); - } - public PrefixDeclaration(String prefix, String namespace) { this.prefix = prefix; this.namespace = namespace; @@ -46,4 +47,15 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(prefix, namespace); } + + @Override + public int compareTo(PrefixDeclaration prefixDeclaration) { + assert prefixDeclaration != null; + if (prefix == null) { + return 1; + } else if (prefixDeclaration.prefix == null) { + return -1; + } + return prefix.compareTo(prefixDeclaration.prefix); + } } diff --git a/src/main/java/cz/cvut/kbss/termit/persistence/dao/VocabularyDao.java b/src/main/java/cz/cvut/kbss/termit/persistence/dao/VocabularyDao.java index 64d0239e4..b20934957 100644 --- a/src/main/java/cz/cvut/kbss/termit/persistence/dao/VocabularyDao.java +++ b/src/main/java/cz/cvut/kbss/termit/persistence/dao/VocabularyDao.java @@ -454,7 +454,7 @@ public PrefixDeclaration resolvePrefix(URI vocabularyUri) { cz.cvut.kbss.termit.util.Vocabulary.s_p_preferredNamespaceUri)) .getResultList(); if (result.size() == 0) { - return new PrefixDeclaration(); + return PrefixDeclaration.EMPTY_PREFIX; } assert result.get(0) instanceof Object[]; return new PrefixDeclaration(((Object[]) result.get(0))[0].toString(), diff --git a/src/main/java/cz/cvut/kbss/termit/service/export/ExcelVocabularyExporter.java b/src/main/java/cz/cvut/kbss/termit/service/export/ExcelVocabularyExporter.java index a2fb58239..629cb7551 100644 --- a/src/main/java/cz/cvut/kbss/termit/service/export/ExcelVocabularyExporter.java +++ b/src/main/java/cz/cvut/kbss/termit/service/export/ExcelVocabularyExporter.java @@ -85,7 +85,7 @@ private TypeAwareResource exportGlossary(Vocabulary vocabulary) { try (final XSSFWorkbook wb = new XSSFWorkbook()) { final Map prefixes = new HashMap<>(); generateGlossarySheets(vocabulary, wb, prefixes); - generatePrefixMappingSheet(wb, prefixes); + generatePrefixMappingSheet(wb, prefixes.values()); final ByteArrayOutputStream bos = new ByteArrayOutputStream(); wb.write(bos); return new TypeAwareByteArrayResource(bos.toByteArray(), ExportFormat.EXCEL.getMediaType(), @@ -181,7 +181,7 @@ private void resolvePrefixes(Term t, Map prefixes) { .forEach(ti -> prefixes.put(ti.getVocabulary(), vocabularyService.resolvePrefix(ti.getVocabulary()))); } - private void generatePrefixMappingSheet(XSSFWorkbook wb, Map prefixes) { + private void generatePrefixMappingSheet(XSSFWorkbook wb, Collection prefixes) { final XSSFSheet sheet = wb.createSheet(PREFIX_COLUMN); generatePrefixSheetHeader(sheet); final XSSFFont font = initFont(wb); @@ -190,7 +190,9 @@ private void generatePrefixMappingSheet(XSSFWorkbook wb, Map prefixList = new ArrayList<>(prefixes); + Collections.sort(prefixList); + for (PrefixDeclaration pd : prefixList) { if (pd.getPrefix() == null) { continue; } diff --git a/src/test/java/cz/cvut/kbss/termit/service/export/ExcelVocabularyExporterTest.java b/src/test/java/cz/cvut/kbss/termit/service/export/ExcelVocabularyExporterTest.java index aa2398300..832270d7b 100644 --- a/src/test/java/cz/cvut/kbss/termit/service/export/ExcelVocabularyExporterTest.java +++ b/src/test/java/cz/cvut/kbss/termit/service/export/ExcelVocabularyExporterTest.java @@ -69,7 +69,7 @@ private static ExportConfig exportConfig() { @Test void exportGlossaryOutputsGlossaryTermsIntoSheet() throws Exception { - when(vocabularyService.resolvePrefix(any())).thenReturn(new PrefixDeclaration()); + when(vocabularyService.resolvePrefix(any())).thenReturn(PrefixDeclaration.EMPTY_PREFIX); final List terms = IntStream.range(0, 5).mapToObj(i -> Generator.generateTermWithId()).collect( Collectors.toList()); when(termService.findAllFull(vocabulary)).thenReturn(terms); @@ -104,7 +104,7 @@ void exportGlossaryUsesVocabularyServiceToRetrievePrefixes() { .collect(Collectors.toList()); terms.get(0).setExactMatchTerms( Collections.singleton(new TermInfo(Generator.generateTermWithId(exactMatchVocabularyUri)))); - when(vocabularyService.resolvePrefix(any())).thenReturn(new PrefixDeclaration()); + when(vocabularyService.resolvePrefix(any())).thenReturn(PrefixDeclaration.EMPTY_PREFIX); when(termService.findAllFull(vocabulary)).thenReturn(terms); sut.exportGlossary(vocabulary, exportConfig()); @@ -150,7 +150,7 @@ void exportGlossaryGeneratesExtraSheetWithPrefixMapping() throws Exception { @Test void exportGlossaryOutputsLocalizedVersionsOfTermsIntoSeparateSheets() throws Exception { - when(vocabularyService.resolvePrefix(any())).thenReturn(new PrefixDeclaration()); + when(vocabularyService.resolvePrefix(any())).thenReturn(PrefixDeclaration.EMPTY_PREFIX); final String[] languages = {"en", "cs"}; final List terms = List.of(Generator.generateMultiLingualTerm(languages), Generator.generateMultiLingualTerm(languages)); @@ -172,7 +172,7 @@ void exportGlossaryOutputsLocalizedVersionsOfTermsIntoSeparateSheets() throws Ex @Test void exportGlossarySkipsNullLanguages() throws Exception { - when(vocabularyService.resolvePrefix(any())).thenReturn(new PrefixDeclaration()); + when(vocabularyService.resolvePrefix(any())).thenReturn(PrefixDeclaration.EMPTY_PREFIX); final String[] languages = {"en", "cs"}; final List terms = List.of(Generator.generateMultiLingualTerm(languages), Generator.generateMultiLingualTerm(languages)); From 4bb13f4a0a3173d6d1a7296cb1accea40b2aea2f Mon Sep 17 00:00:00 2001 From: Martin Ledvinka Date: Thu, 12 Jan 2023 11:02:34 +0100 Subject: [PATCH 13/23] [kbss-cvut/termit-ui#357] Fix possible NPX when exported term does not have a definition or a label. --- .../service/export/ExcelVocabularyExporter.java | 4 ++-- .../service/export/ExcelVocabularyExporterTest.java | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/main/java/cz/cvut/kbss/termit/service/export/ExcelVocabularyExporter.java b/src/main/java/cz/cvut/kbss/termit/service/export/ExcelVocabularyExporter.java index 629cb7551..e3dc3859d 100644 --- a/src/main/java/cz/cvut/kbss/termit/service/export/ExcelVocabularyExporter.java +++ b/src/main/java/cz/cvut/kbss/termit/service/export/ExcelVocabularyExporter.java @@ -110,8 +110,8 @@ private List extractUniqueLanguages(List terms) { final Set uniqueLanguages = new HashSet<>(); for (Term t : terms) { uniqueLanguages.addAll(t.getLabel().getLanguages()); - uniqueLanguages.addAll(t.getDefinition().getLanguages()); - uniqueLanguages.addAll(t.getDescription().getLanguages()); + uniqueLanguages.addAll(t.getDefinition() != null ? t.getDefinition().getLanguages() : Collections.emptyList()); + uniqueLanguages.addAll(t.getDescription() != null ? t.getDescription().getLanguages() : Collections.emptyList()); Utils.emptyIfNull(t.getAltLabels()).forEach(ms -> uniqueLanguages.addAll(ms.getLanguages())); Utils.emptyIfNull(t.getHiddenLabels()).forEach(ms -> uniqueLanguages.addAll(ms.getLanguages())); Utils.emptyIfNull(t.getExamples()).forEach(ms -> uniqueLanguages.addAll(ms.getLanguages())); diff --git a/src/test/java/cz/cvut/kbss/termit/service/export/ExcelVocabularyExporterTest.java b/src/test/java/cz/cvut/kbss/termit/service/export/ExcelVocabularyExporterTest.java index 832270d7b..04db7aa3e 100644 --- a/src/test/java/cz/cvut/kbss/termit/service/export/ExcelVocabularyExporterTest.java +++ b/src/test/java/cz/cvut/kbss/termit/service/export/ExcelVocabularyExporterTest.java @@ -183,4 +183,16 @@ void exportGlossarySkipsNullLanguages() throws Exception { // Glossary sheets + prefix sheet assertEquals(languages.length + 1, wb.getNumberOfSheets()); } + + @Test + void exportGlossaryHandlesTermsWithoutDescriptionAndDefinition() throws Exception { + when(vocabularyService.resolvePrefix(any())).thenReturn(PrefixDeclaration.EMPTY_PREFIX); + final String[] languages = {"en", "cs"}; + final List terms = List.of(Generator.generateMultiLingualTerm(languages), + Generator.generateMultiLingualTerm(languages)); + terms.get(0).setDescription(null); + terms.get(1).setDefinition(null); + when(termService.findAllFull(vocabulary)).thenReturn(terms); + assertDoesNotThrow(() -> sut.exportGlossary(vocabulary, exportConfig())); + } } From 579242def2b6718d477aa2f092a4e8743f73e535 Mon Sep 17 00:00:00 2001 From: Martin Ledvinka Date: Thu, 12 Jan 2023 12:05:35 +0100 Subject: [PATCH 14/23] [kbss-cvut/termit-ui#357] Improve styling of the Excel export. --- .../kbss/termit/service/export/ExcelVocabularyExporter.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/cz/cvut/kbss/termit/service/export/ExcelVocabularyExporter.java b/src/main/java/cz/cvut/kbss/termit/service/export/ExcelVocabularyExporter.java index e3dc3859d..fb72ddf8c 100644 --- a/src/main/java/cz/cvut/kbss/termit/service/export/ExcelVocabularyExporter.java +++ b/src/main/java/cz/cvut/kbss/termit/service/export/ExcelVocabularyExporter.java @@ -162,6 +162,9 @@ private void generateTermRows(List terms, XSSFSheet sheet, String langCode final Term t = terms.get(i); resolvePrefixes(t, prefixes); termExporter.export(t, row); + for (short j = 0; j < row.getLastCellNum(); j++) { + row.getCell(j).setCellStyle(style); + } } } From 8402c59262d490dc4b07d6d338f922f9d2cb7b50 Mon Sep 17 00:00:00 2001 From: Martin Ledvinka Date: Thu, 12 Jan 2023 13:13:48 +0100 Subject: [PATCH 15/23] Allow running TermIt and edit terms without text analysis service. --- .../service/document/TextAnalysisService.java | 29 +++++++++++++------ .../cvut/kbss/termit/util/Configuration.java | 1 - .../document/TextAnalysisServiceTest.java | 12 ++++++++ src/test/resources/application.yml | 2 +- 4 files changed, 33 insertions(+), 11 deletions(-) diff --git a/src/main/java/cz/cvut/kbss/termit/service/document/TextAnalysisService.java b/src/main/java/cz/cvut/kbss/termit/service/document/TextAnalysisService.java index aa505f802..02367081e 100644 --- a/src/main/java/cz/cvut/kbss/termit/service/document/TextAnalysisService.java +++ b/src/main/java/cz/cvut/kbss/termit/service/document/TextAnalysisService.java @@ -95,9 +95,12 @@ private TextAnalysisInput createAnalysisInput(File file) { private void invokeTextAnalysisOnFile(File file, TextAnalysisInput input) { try { - final Resource result = invokeTextAnalysisService(input); + final Optional result = invokeTextAnalysisService(input); + if (result.isEmpty()) { + return; + } documentManager.createBackup(file); - try (final InputStream is = result.getInputStream()) { + try (final InputStream is = result.get().getInputStream()) { annotationGenerator.generateAnnotations(is, file); } storeTextAnalysisRecord(file, input); @@ -110,18 +113,23 @@ private void invokeTextAnalysisOnFile(File file, TextAnalysisInput input) { } } - private Resource invokeTextAnalysisService(TextAnalysisInput input) { + private Optional invokeTextAnalysisService(TextAnalysisInput input) { + final String taUrl = config.getTextAnalysis().getUrl(); + if (taUrl == null || taUrl.isBlank()) { + LOG.warn("Text analysis service URL not configured. Text analysis will not be invoked."); + return Optional.empty(); + } final HttpHeaders headers = new HttpHeaders(); headers.add(HttpHeaders.ACCEPT, MediaType.APPLICATION_XML_VALUE); LOG.debug("Invoking text analysis service on input: {}", input); final ResponseEntity resp = restClient .exchange(config.getTextAnalysis().getUrl(), HttpMethod.POST, - new HttpEntity<>(input, headers), Resource.class); + new HttpEntity<>(input, headers), Resource.class); if (!resp.hasBody()) { throw new WebServiceIntegrationException("Text analysis service returned empty response."); } assert resp.getBody() != null; - return resp.getBody(); + return Optional.of(resp.getBody()); } private void storeTextAnalysisRecord(File file, TextAnalysisInput config) { @@ -146,7 +154,7 @@ public Optional findLatestAnalysisRecord(cz.cvut.kbss.termit /** * Invokes text analysis on the specified term's definition. *

- * The the specified vocabulary context is used for analysis. Analysis results are stored as definitional term + * The specified vocabulary context is used for analysis. Analysis results are stored as definitional term * occurrences. * * @param term Term whose definition is to be analyzed. @@ -156,7 +164,7 @@ public void analyzeTermDefinition(AbstractTerm term, URI vocabularyContext) { final String language = config.getPersistence().getLanguage(); if (term.getDefinition() != null && term.getDefinition().contains(language)) { final TextAnalysisInput input = new TextAnalysisInput(term.getDefinition().get(language), language, - URI.create(config.getRepository().getUrl())); + URI.create(config.getRepository().getUrl())); input.addVocabularyContext(vocabularyContext); invokeTextAnalysisOnTerm(term, input); @@ -165,8 +173,11 @@ public void analyzeTermDefinition(AbstractTerm term, URI vocabularyContext) { private void invokeTextAnalysisOnTerm(AbstractTerm term, TextAnalysisInput input) { try { - final Resource result = invokeTextAnalysisService(input); - try (final InputStream is = result.getInputStream()) { + final Optional result = invokeTextAnalysisService(input); + if (result.isEmpty()) { + return; + } + try (final InputStream is = result.get().getInputStream()) { annotationGenerator.generateAnnotations(is, term); } } catch (WebServiceIntegrationException e) { diff --git a/src/main/java/cz/cvut/kbss/termit/util/Configuration.java b/src/main/java/cz/cvut/kbss/termit/util/Configuration.java index f3bcbe508..f8eb464e5 100644 --- a/src/main/java/cz/cvut/kbss/termit/util/Configuration.java +++ b/src/main/java/cz/cvut/kbss/termit/util/Configuration.java @@ -522,7 +522,6 @@ public static class TextAnalysis { /** * URL of the text analysis service. */ - @NotNull String url; /** diff --git a/src/test/java/cz/cvut/kbss/termit/service/document/TextAnalysisServiceTest.java b/src/test/java/cz/cvut/kbss/termit/service/document/TextAnalysisServiceTest.java index 0697d0e2d..bebbb85dd 100644 --- a/src/test/java/cz/cvut/kbss/termit/service/document/TextAnalysisServiceTest.java +++ b/src/test/java/cz/cvut/kbss/termit/service/document/TextAnalysisServiceTest.java @@ -335,4 +335,16 @@ void analyzeTermDefinitionDoesNotInvokeTextAnalysisServiceWhenDefinitionInConfig mockServer.verify(); verify(annotationGeneratorMock, never()).generateAnnotations(any(), any(Term.class)); } + + @Test + void analyzeTermDefinitionDoesNothingWhenTextAnalysisServiceUrlIsNotConfigured() { + final Term term = Generator.generateTermWithId(); + term.setVocabulary(vocabulary.getUri()); + config.getTextAnalysis().setUrl(null); + + sut.analyzeTermDefinition(term, vocabulary.getUri()); + mockServer.verify(); + verify(annotationGeneratorMock, never()).generateAnnotations(any(), any(Term.class)); + verify(textAnalysisRecordDao, never()).persist(any()); + } } diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index 330c0e5f3..3d73082a4 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -29,7 +29,7 @@ termit: file: storage: /tmp/termit textAnalysis: - url: + url: http://localhost/annotace termAssignmentMinScore: 1 termOccurrenceMinScore: 0.49 comments: From 2b2d4735c074b0ee474e6b199bbf8e7eb887e847 Mon Sep 17 00:00:00 2001 From: Martin Ledvinka Date: Thu, 12 Jan 2023 14:12:56 +0100 Subject: [PATCH 16/23] [kbss-cvut/termit-ui#294] Prune blank translations from multilingual strings on term persist/update. --- .../repository/TermRepositoryService.java | 12 +++++ .../java/cz/cvut/kbss/termit/util/Utils.java | 23 ++++++++- .../repository/TermRepositoryServiceTest.java | 29 +++++++++++ .../cz/cvut/kbss/termit/util/UtilsTest.java | 48 +++++++++++-------- 4 files changed, 90 insertions(+), 22 deletions(-) diff --git a/src/main/java/cz/cvut/kbss/termit/service/repository/TermRepositoryService.java b/src/main/java/cz/cvut/kbss/termit/service/repository/TermRepositoryService.java index 8e10acdb7..d0176b70a 100644 --- a/src/main/java/cz/cvut/kbss/termit/service/repository/TermRepositoryService.java +++ b/src/main/java/cz/cvut/kbss/termit/service/repository/TermRepositoryService.java @@ -35,6 +35,7 @@ import cz.cvut.kbss.termit.service.term.AssertedInferredValueDifferentiator; import cz.cvut.kbss.termit.service.term.OrphanedInverseTermRelationshipRemover; import cz.cvut.kbss.termit.util.Configuration; +import cz.cvut.kbss.termit.util.Utils; import org.apache.jena.vocabulary.SKOS; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -101,6 +102,16 @@ protected void preUpdate(Term instance) { differentiator.differentiateExactMatchTerms(instance, original); orphanedRelationshipRemover.removeOrphanedInverseTermRelationships(instance, original); instance.splitExternalAndInternalParents(); + pruneEmptyTranslations(instance); + } + + private void pruneEmptyTranslations(Term instance) { + assert instance != null; + Utils.pruneBlankTranslations(instance.getLabel()); + Utils.pruneBlankTranslations(instance.getDefinition()); + Utils.pruneBlankTranslations(instance.getDescription()); + Utils.emptyIfNull(instance.getAltLabels()).forEach(Utils::pruneBlankTranslations); + Utils.emptyIfNull(instance.getHiddenLabels()).forEach(Utils::pruneBlankTranslations); } @Override @@ -143,6 +154,7 @@ private void prepareTermForPersist(Term instance, URI vocabularyUri) { instance.setUri(generateIdentifier(vocabularyUri, instance.getLabel())); } verifyIdentifierUnique(instance); + pruneEmptyTranslations(instance); } private URI generateIdentifier(URI vocabularyUri, MultilingualString multilingualString) { diff --git a/src/main/java/cz/cvut/kbss/termit/util/Utils.java b/src/main/java/cz/cvut/kbss/termit/util/Utils.java index 336c926bb..ae40714e5 100644 --- a/src/main/java/cz/cvut/kbss/termit/util/Utils.java +++ b/src/main/java/cz/cvut/kbss/termit/util/Utils.java @@ -3,6 +3,7 @@ import com.vladsch.flexmark.html.HtmlRenderer; import com.vladsch.flexmark.parser.Parser; import com.vladsch.flexmark.util.ast.Node; +import cz.cvut.kbss.jopa.model.MultilingualString; import cz.cvut.kbss.termit.exception.ResourceNotFoundException; import cz.cvut.kbss.termit.exception.TermItException; import org.eclipse.rdf4j.model.*; @@ -12,7 +13,10 @@ import org.jsoup.nodes.Document; import org.jsoup.safety.Safelist; -import java.io.*; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.net.URI; import java.nio.charset.StandardCharsets; import java.time.Instant; @@ -319,4 +323,21 @@ public static String markdownToPlainText(String markdown) { final String html = renderer.render(document); return htmlToPlainText(html); } + + /** + * Removes blank translations from the specified {@link MultilingualString}. + *

+ * That is, deletes records in languages where the value is a blank string. + * + * @param str Multilingual string to prune, possibly {@code null} + */ + public static void pruneBlankTranslations(MultilingualString str) { + if (str == null) { + return; + } + // TODO Rewrite to entrySet.removeIf once JOPA MultilingualString.getValue does not return unmodifiable Map + final Set langsToRemove = str.getValue().entrySet().stream().filter(e -> e.getValue().isBlank()) + .map(Map.Entry::getKey).collect(Collectors.toSet()); + langsToRemove.forEach(str::remove); + } } diff --git a/src/test/java/cz/cvut/kbss/termit/service/repository/TermRepositoryServiceTest.java b/src/test/java/cz/cvut/kbss/termit/service/repository/TermRepositoryServiceTest.java index e2366fd7a..417e6bc3b 100644 --- a/src/test/java/cz/cvut/kbss/termit/service/repository/TermRepositoryServiceTest.java +++ b/src/test/java/cz/cvut/kbss/termit/service/repository/TermRepositoryServiceTest.java @@ -15,6 +15,7 @@ package cz.cvut.kbss.termit.service.repository; import cz.cvut.kbss.jopa.model.EntityManager; +import cz.cvut.kbss.jopa.model.MultilingualString; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.vocabulary.SKOS; import cz.cvut.kbss.termit.dto.TermInfo; @@ -852,4 +853,32 @@ void removingExactMatchFromInverseSideWorksInTransaction() { final Term resultTerm = em.find(Term.class, term.getUri()); assertThat(resultTerm.getExactMatchTerms(), anyOf(nullValue(), emptyCollectionOf(TermInfo.class))); } + + @Test + void updatePrunesEmptyTranslationsInMultilingualAttributes() { + final Term term = Generator.generateTermWithId(vocabulary.getUri()); + transactional(() -> em.persist(term, descriptorFactory.termDescriptor(term))); + term.getLabel().set(Environment.LANGUAGE, "update"); + term.getLabel().set("cs", ""); + final MultilingualString expected = new MultilingualString(term.getLabel().getValue()); + expected.remove("cs"); + transactional(() -> sut.update(term)); + + final Term result = em.find(Term.class, term.getUri()); + assertEquals(expected, result.getLabel()); + } + + @Test + void persistPreparationPrunesEmptyTranslationsInMultilingualAttributes() { + final Term term = Generator.generateTermWithId(vocabulary.getUri()); + term.getDefinition().set(Environment.LANGUAGE, " "); + term.getDefinition().set("cs", "Test"); + final MultilingualString expected = new MultilingualString(term.getDefinition().getValue()); + expected.remove(Environment.LANGUAGE); + + sut.addRootTermToVocabulary(term, vocabulary); + + final Term result = em.find(Term.class, term.getUri()); + assertEquals(expected, result.getDefinition()); + } } diff --git a/src/test/java/cz/cvut/kbss/termit/util/UtilsTest.java b/src/test/java/cz/cvut/kbss/termit/util/UtilsTest.java index ad51b389e..b75e1fb8d 100644 --- a/src/test/java/cz/cvut/kbss/termit/util/UtilsTest.java +++ b/src/test/java/cz/cvut/kbss/termit/util/UtilsTest.java @@ -1,22 +1,20 @@ /** - * TermIt - * Copyright (C) 2019 Czech Technical University in Prague + * TermIt Copyright (C) 2019 Czech Technical University in Prague *

- * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later + * version. *

- * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. *

- * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * You should have received a copy of the GNU General Public License along with this program. If not, see + * . */ package cz.cvut.kbss.termit.util; +import cz.cvut.kbss.jopa.model.MultilingualString; import org.eclipse.rdf4j.model.IRI; import org.eclipse.rdf4j.model.Model; import org.eclipse.rdf4j.model.ValueFactory; @@ -55,7 +53,7 @@ public void getUniqueIriFromBaseReturnsBaseIfCheckFails() { @Test public void getUniqueIriFromBaseReturnsBaseIfCheckSucceeds() { - final Function f = Mockito.mock(Function.class); + final Function> f = Mockito.mock(Function.class); when(f.apply(BASE)).thenReturn(Optional.of(new Object())); when(f.apply(BASE + "-0")).thenReturn(Optional.empty()); Assert.equals(BASE + "-0", Utils.getUniqueIriFromBase(BASE, f)); @@ -100,9 +98,8 @@ public void getVocabularyIriReturnsCorrectVocabularyIriForExternalHashVocabulari @Test public void getVocabularyIriThrowsExceptionIfNoConceptIsProvided() { - Assertions.assertThrows(IllegalArgumentException.class, () -> { - Utils.getVocabularyIri(Collections.emptySet(), "/pojem"); - }); + Assertions.assertThrows(IllegalArgumentException.class, + () -> Utils.getVocabularyIri(Collections.emptySet(), "/pojem")); } @Test @@ -110,9 +107,7 @@ public void getVocabularyIriThrowsExceptionIfConceptsWithDifferentNamespacesAreP final Set conceptIris = new HashSet<>(); conceptIris.add("https://example.org/pojem/A"); conceptIris.add("https://example2.org/pojem/B"); - Assertions.assertThrows(IllegalArgumentException.class, () -> { - Utils.getVocabularyIri(conceptIris, "/pojem"); - }); + Assertions.assertThrows(IllegalArgumentException.class, () -> Utils.getVocabularyIri(conceptIris, "/pojem")); } @Test @@ -176,8 +171,9 @@ public void getLanguageTagsPerPropertiesReturnsCorrectLanguageTags() { model.add(iriA, iriP1, f.createLiteral("a label cs", "cs")); model.add(iriA, iriP2, f.createLiteral("a label")); - final Set expected = Stream.of("cs","").collect(Collectors.toSet()); - final Set actual = Utils.getLanguageTagsPerProperties(model, Stream.of(p1, p2).collect(Collectors.toSet())); + final Set expected = Stream.of("cs", "").collect(Collectors.toSet()); + final Set actual = Utils.getLanguageTagsPerProperties(model, + Stream.of(p1, p2).collect(Collectors.toSet())); assertEquals(expected, actual); } @@ -223,4 +219,14 @@ void htmlToPlaintextReturnsArgumentWhenItDoesNotContainMarkup() { "It uses just newline"; assertEquals(text, Utils.htmlToPlainText(text)); } + + @Test + void pruneBlankTranslationsRemovesTranslationsThatAreBlankStrings() { + final MultilingualString str = MultilingualString.create("test", "en"); + str.set("cs", ""); + str.set("de", " "); + Utils.pruneBlankTranslations(str); + assertEquals(1, str.getValue().size()); + assertEquals("test", str.get("en")); + } } From fb374f228f995bc32b59190f9c5f6480544f7278 Mon Sep 17 00:00:00 2001 From: Martin Ledvinka Date: Thu, 12 Jan 2023 15:08:04 +0100 Subject: [PATCH 17/23] [Enhancement #194] Extract retrieval tests of TermRepositoryService into a separate test class with one-time set up. --- .../TermRepositoryServiceRetrieveTest.java | 175 ++++++++++++++++++ .../repository/TermRepositoryServiceTest.java | 129 +------------ 2 files changed, 179 insertions(+), 125 deletions(-) create mode 100644 src/test/java/cz/cvut/kbss/termit/service/repository/TermRepositoryServiceRetrieveTest.java diff --git a/src/test/java/cz/cvut/kbss/termit/service/repository/TermRepositoryServiceRetrieveTest.java b/src/test/java/cz/cvut/kbss/termit/service/repository/TermRepositoryServiceRetrieveTest.java new file mode 100644 index 000000000..b551a63ef --- /dev/null +++ b/src/test/java/cz/cvut/kbss/termit/service/repository/TermRepositoryServiceRetrieveTest.java @@ -0,0 +1,175 @@ +package cz.cvut.kbss.termit.service.repository; + +import cz.cvut.kbss.jopa.model.EntityManager; +import cz.cvut.kbss.termit.dto.listing.TermDto; +import cz.cvut.kbss.termit.environment.Environment; +import cz.cvut.kbss.termit.environment.Generator; +import cz.cvut.kbss.termit.model.Asset; +import cz.cvut.kbss.termit.model.Term; +import cz.cvut.kbss.termit.model.UserAccount; +import cz.cvut.kbss.termit.model.Vocabulary; +import cz.cvut.kbss.termit.persistence.context.DescriptorFactory; +import cz.cvut.kbss.termit.service.BaseServiceTestRunner; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.PageRequest; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import static cz.cvut.kbss.termit.environment.Environment.termsToDtos; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.not; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * This test class contains tests of retrieval behavior of {@link TermRepositoryService}. + * + * It utilizes a once-time set up, i.e., test data are generated only once for all tests. + */ +public class TermRepositoryServiceRetrieveTest extends BaseServiceTestRunner { + + private static final String MATCHING_LABEL_BASE = "Matching "; + + @Autowired + private EntityManager em; + + @Autowired + private DescriptorFactory descriptorFactory; + + @Autowired + private TermRepositoryService sut; + + private static UserAccount user; + private static Vocabulary vocabulary; + private static Vocabulary childVocabulary; + + private static List terms; + + private static boolean initialized = false; + + @BeforeEach + void setUp() { + init(); + } + + private void init() { + if (initialized) { + return; + } + user = Generator.generateUserAccountWithPassword(); + transactional(() -> em.persist(user)); + Environment.setCurrentUser(user); + + vocabulary = Generator.generateVocabularyWithId(); + childVocabulary = Generator.generateVocabularyWithId(); + childVocabulary.setImportedVocabularies(Collections.singleton(vocabulary.getUri())); + transactional(() -> { + em.persist(vocabulary, descriptorFactory.vocabularyDescriptor(vocabulary)); + em.persist(childVocabulary, descriptorFactory.vocabularyDescriptor(childVocabulary)); + }); + terms = generateAndPersistTerms(); + initialized = true; + } + + private List generateAndPersistTerms() { + final List terms = Generator.generateTermsWithIds(10); + terms.subList(0, terms.size() / 2).forEach(t -> t.getLabel().set(Environment.LANGUAGE, + MATCHING_LABEL_BASE + t.getLabel() + .get(Environment.LANGUAGE))); + vocabulary.getGlossary().setRootTerms(terms.stream().map(Asset::getUri).collect(Collectors.toSet())); + transactional(() -> { + terms.forEach(t -> { + t.setVocabulary(vocabulary.getUri()); + em.persist(t, descriptorFactory.termDescriptor(vocabulary)); + }); + em.merge(vocabulary.getGlossary(), descriptorFactory.glossaryDescriptor(vocabulary)); + }); + return terms; + } + + @Test + void findAllRootsReturnsRootTermsOnMatchingPage() { + final List resultOne = sut.findAllRoots(vocabulary, PageRequest.of(0, 5), Collections.emptyList()); + final List resultTwo = sut.findAllRoots(vocabulary, PageRequest.of(1, 5), Collections.emptyList()); + + assertEquals(5, resultOne.size()); + assertEquals(5, resultTwo.size()); + + final List expectedDtos = termsToDtos(terms); + resultOne.forEach(t -> { + assertThat(expectedDtos, hasItem(t)); + assertThat(resultTwo, not(hasItem(t))); + }); + resultTwo.forEach(t -> assertThat(expectedDtos, hasItem(t))); + } + + @Test + void existsInVocabularyChecksForTermWithMatchingLabel() { + final Term term = terms.get(Generator.randomIndex(terms)); + + assertTrue(sut.existsInVocabulary(term.getLabel().get(Environment.LANGUAGE), vocabulary, Environment.LANGUAGE)); + } + + @Test + void findAllRootsIncludingImportsReturnsRootTermsOnMatchingPage() { + final List resultOne = sut + .findAllRootsIncludingImported(vocabulary, PageRequest.of(0, 5), Collections.emptyList()); + final List resultTwo = sut + .findAllRootsIncludingImported(vocabulary, PageRequest.of(1, 5), Collections.emptyList()); + + assertEquals(5, resultOne.size()); + assertEquals(5, resultTwo.size()); + + final List expectedDtos = termsToDtos(terms); + resultOne.forEach(t -> { + assertThat(expectedDtos, hasItem(t)); + assertThat(resultTwo, not(hasItem(t))); + }); + resultTwo.forEach(t -> assertThat(expectedDtos, hasItem(t))); + } + + @Test + void findTermsBySearchStringReturnsMatchingTerms() { + final List matching = terms.stream().filter(t -> t.getLabel().get(Environment.LANGUAGE) + .startsWith(MATCHING_LABEL_BASE)) + .collect(Collectors.toList()); + + List result = sut.findAll( + MATCHING_LABEL_BASE.substring(0, MATCHING_LABEL_BASE.length() - Generator.randomInt(0, 3)), vocabulary); + assertEquals(matching.size(), result.size()); + assertTrue(termsToDtos(matching).containsAll(result)); + } + + @Test + void findAllIncludingImportedBySearchStringReturnsMatchingTerms() { + final List matching = terms.stream().filter(t -> t.getLabel().get(Environment.LANGUAGE) + .startsWith(MATCHING_LABEL_BASE)) + .collect(Collectors.toList()); + final String searchString = MATCHING_LABEL_BASE.substring(0, + MATCHING_LABEL_BASE.length() - Generator.randomInt(0, + 3)); + + List result = sut.findAllIncludingImported(searchString, vocabulary); + assertEquals(matching.size(), result.size()); + assertTrue(termsToDtos(matching).containsAll(result)); + } + + @Test + void findAllWithSearchStringReturnsMatchingTerms() { + final List matching = terms.stream().filter(t -> t.getLabel().get(Environment.LANGUAGE) + .startsWith(MATCHING_LABEL_BASE)) + .collect(Collectors.toList()); + final String searchString = MATCHING_LABEL_BASE.substring(0, + MATCHING_LABEL_BASE.length() - Generator.randomInt(0, + 3)); + + List result = sut.findAll(searchString); + assertEquals(matching.size(), result.size()); + assertTrue(termsToDtos(matching).containsAll(result)); + } +} diff --git a/src/test/java/cz/cvut/kbss/termit/service/repository/TermRepositoryServiceTest.java b/src/test/java/cz/cvut/kbss/termit/service/repository/TermRepositoryServiceTest.java index 417e6bc3b..0167879c0 100644 --- a/src/test/java/cz/cvut/kbss/termit/service/repository/TermRepositoryServiceTest.java +++ b/src/test/java/cz/cvut/kbss/termit/service/repository/TermRepositoryServiceTest.java @@ -16,7 +16,6 @@ import cz.cvut.kbss.jopa.model.EntityManager; import cz.cvut.kbss.jopa.model.MultilingualString; -import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.vocabulary.SKOS; import cz.cvut.kbss.termit.dto.TermInfo; import cz.cvut.kbss.termit.dto.TermStatus; @@ -27,7 +26,10 @@ import cz.cvut.kbss.termit.exception.ResourceExistsException; import cz.cvut.kbss.termit.exception.UnsupportedOperationException; import cz.cvut.kbss.termit.exception.ValidationException; -import cz.cvut.kbss.termit.model.*; +import cz.cvut.kbss.termit.model.Glossary; +import cz.cvut.kbss.termit.model.Term; +import cz.cvut.kbss.termit.model.UserAccount; +import cz.cvut.kbss.termit.model.Vocabulary; import cz.cvut.kbss.termit.model.assignment.TermOccurrence; import cz.cvut.kbss.termit.model.resource.Document; import cz.cvut.kbss.termit.model.resource.File; @@ -41,7 +43,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.PageRequest; import org.springframework.test.annotation.DirtiesContext; import java.net.URI; @@ -49,9 +50,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; -import java.util.stream.Collectors; -import static cz.cvut.kbss.termit.environment.Environment.termsToDtos; import static cz.cvut.kbss.termit.environment.Generator.generateTermWithId; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; @@ -271,62 +270,6 @@ void addChildThrowsResourceExistsExceptionWhenTermWithIdenticalIdentifierAlready assertThrows(ResourceExistsException.class, () -> sut.addChildTerm(child, parent)); } - @Test - void findAllRootsReturnsRootTermsOnMatchingPage() { - final List terms = Generator.generateTermsWithIds(10); - vocabulary.getGlossary().setRootTerms(terms.stream().map(Asset::getUri).collect(Collectors.toSet())); - transactional(() -> { - terms.forEach(t -> em.persist(t, descriptorFactory.termDescriptor(vocabulary))); - em.merge(vocabulary.getGlossary(), descriptorFactory.glossaryDescriptor(vocabulary)); - }); - - final List resultOne = sut.findAllRoots(vocabulary, PageRequest.of(0, 5), Collections.emptyList()); - final List resultTwo = sut.findAllRoots(vocabulary, PageRequest.of(1, 5), Collections.emptyList()); - - assertEquals(5, resultOne.size()); - assertEquals(5, resultTwo.size()); - - final List expectedDtos = termsToDtos(terms); - resultOne.forEach(t -> { - assertThat(expectedDtos, hasItem(t)); - assertThat(resultTwo, not(hasItem(t))); - }); - resultTwo.forEach(t -> assertThat(expectedDtos, hasItem(t))); - } - - @Test - void findTermsBySearchStringReturnsMatchingTerms() { - final List terms = Generator.generateTermsWithIds(10); - terms.forEach(t -> t.setVocabulary(vocabulary.getUri())); - final List matching = terms.subList(0, 5); - matching.forEach(t -> t.getLabel().set(Environment.LANGUAGE, "Result + " + t.getLabel())); - - vocabulary.getGlossary().setRootTerms(terms.stream().map(Asset::getUri).collect(Collectors.toSet())); - final Descriptor termDescriptor = descriptorFactory.termDescriptor(vocabulary); - transactional(() -> { - terms.forEach(t -> em.persist(t, termDescriptor)); - em.merge(vocabulary.getGlossary(), descriptorFactory.glossaryDescriptor(vocabulary)); - }); - - List result = sut.findAll("Result", vocabulary); - assertEquals(matching.size(), result.size()); - assertTrue(termsToDtos(matching).containsAll(result)); - } - - @Test - void existsInVocabularyChecksForTermWithMatchingLabel() { - final Term t = Generator.generateTermWithId(); - transactional(() -> { - t.setVocabulary(vocabulary.getUri()); - vocabulary.getGlossary().addRootTerm(t); - em.persist(t, descriptorFactory.termDescriptor(vocabulary)); - em.merge(vocabulary.getGlossary(), descriptorFactory.glossaryDescriptor(vocabulary)); - }); - - assertTrue(sut.existsInVocabulary(t.getLabel().get(Environment.LANGUAGE), vocabulary, - Environment.LANGUAGE)); - } - @Test void updateUpdatesTermWithParent() { final Term t = Generator.generateTermWithId(); @@ -447,70 +390,6 @@ void updateAddsTermToRootTermsWhenParentIsRemovedFromIt() { assertTrue(result.getRootTerms().contains(child.getUri())); } - @Test - void findAllRootsIncludingImportsReturnsRootTermsOnMatchingPage() { - final List terms = Generator.generateTermsWithIds(10); - vocabulary.getGlossary().setRootTerms(terms.stream().map(Asset::getUri).collect(Collectors.toSet())); - transactional(() -> { - terms.forEach(t -> em.persist(t, descriptorFactory.termDescriptor(vocabulary))); - em.merge(vocabulary.getGlossary(), descriptorFactory.glossaryDescriptor(vocabulary)); - }); - - final List resultOne = sut - .findAllRootsIncludingImported(vocabulary, PageRequest.of(0, 5), Collections.emptyList()); - final List resultTwo = sut - .findAllRootsIncludingImported(vocabulary, PageRequest.of(1, 5), Collections.emptyList()); - - assertEquals(5, resultOne.size()); - assertEquals(5, resultTwo.size()); - - final List expectedDtos = termsToDtos(terms); - resultOne.forEach(t -> { - assertThat(expectedDtos, hasItem(t)); - assertThat(resultTwo, not(hasItem(t))); - }); - resultTwo.forEach(t -> assertThat(expectedDtos, hasItem(t))); - } - - @Test - void findAllIncludingImportedBySearchStringReturnsMatchingTerms() { - final List terms = Generator.generateTermsWithIds(10); - final String searchString = "Result"; - final List matching = terms.subList(0, 5); - matching.forEach(t -> t.getLabel().set(Environment.LANGUAGE, searchString + " " + t.getLabel())); - - vocabulary.getGlossary().setRootTerms(terms.stream().map(Asset::getUri).collect(Collectors.toSet())); - terms.forEach(t -> t.setVocabulary(vocabulary.getUri())); - final Descriptor termDescriptor = descriptorFactory.termDescriptor(vocabulary); - transactional(() -> { - terms.forEach(t -> em.persist(t, termDescriptor)); - em.merge(vocabulary.getGlossary(), descriptorFactory.glossaryDescriptor(vocabulary)); - }); - - List result = sut.findAllIncludingImported(searchString, vocabulary); - assertEquals(matching.size(), result.size()); - assertTrue(termsToDtos(matching).containsAll(result)); - } - - @Test - void findAllWithSearchStringReturnsMatchingTerms() { - final List terms = Generator - .generateTermsWithIds(10) - .stream().map(TermDto::new).collect(Collectors.toList()); - final String searchString = "Result"; - final List matching = terms.subList(0, 5); - matching.forEach(t -> t.getLabel().set(Environment.LANGUAGE, searchString + " " + t.getLabel())); - - vocabulary.getGlossary().setRootTerms(terms.stream().map(Asset::getUri).collect(Collectors.toSet())); - terms.forEach(t -> t.setVocabulary(vocabulary.getUri())); - final Descriptor termDescriptor = descriptorFactory.termDescriptor(vocabulary); - transactional(() -> terms.forEach(t -> em.persist(t, termDescriptor))); - - List result = sut.findAll(searchString); - assertEquals(matching.size(), result.size()); - assertTrue(matching.containsAll(result)); - } - @Test void addChildTermAllowsAddingChildTermToDifferentVocabularyThanParent() { final Term parentTerm = generateParentTermFromDifferentVocabulary(); From 56c3078cc5f8d21b1d7aefe6bd931facadda4b31 Mon Sep 17 00:00:00 2001 From: Martin Ledvinka Date: Thu, 12 Jan 2023 15:55:28 +0100 Subject: [PATCH 18/23] [Enhancement #194] Remove TermRepositoryService tests that duplicate logic tested by TermDaoTest. These tests were using methods that do not do anything else than calling the underlying DAO. --- .../TermRepositoryServiceRetrieveTest.java | 175 ------------------ 1 file changed, 175 deletions(-) delete mode 100644 src/test/java/cz/cvut/kbss/termit/service/repository/TermRepositoryServiceRetrieveTest.java diff --git a/src/test/java/cz/cvut/kbss/termit/service/repository/TermRepositoryServiceRetrieveTest.java b/src/test/java/cz/cvut/kbss/termit/service/repository/TermRepositoryServiceRetrieveTest.java deleted file mode 100644 index b551a63ef..000000000 --- a/src/test/java/cz/cvut/kbss/termit/service/repository/TermRepositoryServiceRetrieveTest.java +++ /dev/null @@ -1,175 +0,0 @@ -package cz.cvut.kbss.termit.service.repository; - -import cz.cvut.kbss.jopa.model.EntityManager; -import cz.cvut.kbss.termit.dto.listing.TermDto; -import cz.cvut.kbss.termit.environment.Environment; -import cz.cvut.kbss.termit.environment.Generator; -import cz.cvut.kbss.termit.model.Asset; -import cz.cvut.kbss.termit.model.Term; -import cz.cvut.kbss.termit.model.UserAccount; -import cz.cvut.kbss.termit.model.Vocabulary; -import cz.cvut.kbss.termit.persistence.context.DescriptorFactory; -import cz.cvut.kbss.termit.service.BaseServiceTestRunner; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.PageRequest; - -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -import static cz.cvut.kbss.termit.environment.Environment.termsToDtos; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.hasItem; -import static org.hamcrest.Matchers.not; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * This test class contains tests of retrieval behavior of {@link TermRepositoryService}. - * - * It utilizes a once-time set up, i.e., test data are generated only once for all tests. - */ -public class TermRepositoryServiceRetrieveTest extends BaseServiceTestRunner { - - private static final String MATCHING_LABEL_BASE = "Matching "; - - @Autowired - private EntityManager em; - - @Autowired - private DescriptorFactory descriptorFactory; - - @Autowired - private TermRepositoryService sut; - - private static UserAccount user; - private static Vocabulary vocabulary; - private static Vocabulary childVocabulary; - - private static List terms; - - private static boolean initialized = false; - - @BeforeEach - void setUp() { - init(); - } - - private void init() { - if (initialized) { - return; - } - user = Generator.generateUserAccountWithPassword(); - transactional(() -> em.persist(user)); - Environment.setCurrentUser(user); - - vocabulary = Generator.generateVocabularyWithId(); - childVocabulary = Generator.generateVocabularyWithId(); - childVocabulary.setImportedVocabularies(Collections.singleton(vocabulary.getUri())); - transactional(() -> { - em.persist(vocabulary, descriptorFactory.vocabularyDescriptor(vocabulary)); - em.persist(childVocabulary, descriptorFactory.vocabularyDescriptor(childVocabulary)); - }); - terms = generateAndPersistTerms(); - initialized = true; - } - - private List generateAndPersistTerms() { - final List terms = Generator.generateTermsWithIds(10); - terms.subList(0, terms.size() / 2).forEach(t -> t.getLabel().set(Environment.LANGUAGE, - MATCHING_LABEL_BASE + t.getLabel() - .get(Environment.LANGUAGE))); - vocabulary.getGlossary().setRootTerms(terms.stream().map(Asset::getUri).collect(Collectors.toSet())); - transactional(() -> { - terms.forEach(t -> { - t.setVocabulary(vocabulary.getUri()); - em.persist(t, descriptorFactory.termDescriptor(vocabulary)); - }); - em.merge(vocabulary.getGlossary(), descriptorFactory.glossaryDescriptor(vocabulary)); - }); - return terms; - } - - @Test - void findAllRootsReturnsRootTermsOnMatchingPage() { - final List resultOne = sut.findAllRoots(vocabulary, PageRequest.of(0, 5), Collections.emptyList()); - final List resultTwo = sut.findAllRoots(vocabulary, PageRequest.of(1, 5), Collections.emptyList()); - - assertEquals(5, resultOne.size()); - assertEquals(5, resultTwo.size()); - - final List expectedDtos = termsToDtos(terms); - resultOne.forEach(t -> { - assertThat(expectedDtos, hasItem(t)); - assertThat(resultTwo, not(hasItem(t))); - }); - resultTwo.forEach(t -> assertThat(expectedDtos, hasItem(t))); - } - - @Test - void existsInVocabularyChecksForTermWithMatchingLabel() { - final Term term = terms.get(Generator.randomIndex(terms)); - - assertTrue(sut.existsInVocabulary(term.getLabel().get(Environment.LANGUAGE), vocabulary, Environment.LANGUAGE)); - } - - @Test - void findAllRootsIncludingImportsReturnsRootTermsOnMatchingPage() { - final List resultOne = sut - .findAllRootsIncludingImported(vocabulary, PageRequest.of(0, 5), Collections.emptyList()); - final List resultTwo = sut - .findAllRootsIncludingImported(vocabulary, PageRequest.of(1, 5), Collections.emptyList()); - - assertEquals(5, resultOne.size()); - assertEquals(5, resultTwo.size()); - - final List expectedDtos = termsToDtos(terms); - resultOne.forEach(t -> { - assertThat(expectedDtos, hasItem(t)); - assertThat(resultTwo, not(hasItem(t))); - }); - resultTwo.forEach(t -> assertThat(expectedDtos, hasItem(t))); - } - - @Test - void findTermsBySearchStringReturnsMatchingTerms() { - final List matching = terms.stream().filter(t -> t.getLabel().get(Environment.LANGUAGE) - .startsWith(MATCHING_LABEL_BASE)) - .collect(Collectors.toList()); - - List result = sut.findAll( - MATCHING_LABEL_BASE.substring(0, MATCHING_LABEL_BASE.length() - Generator.randomInt(0, 3)), vocabulary); - assertEquals(matching.size(), result.size()); - assertTrue(termsToDtos(matching).containsAll(result)); - } - - @Test - void findAllIncludingImportedBySearchStringReturnsMatchingTerms() { - final List matching = terms.stream().filter(t -> t.getLabel().get(Environment.LANGUAGE) - .startsWith(MATCHING_LABEL_BASE)) - .collect(Collectors.toList()); - final String searchString = MATCHING_LABEL_BASE.substring(0, - MATCHING_LABEL_BASE.length() - Generator.randomInt(0, - 3)); - - List result = sut.findAllIncludingImported(searchString, vocabulary); - assertEquals(matching.size(), result.size()); - assertTrue(termsToDtos(matching).containsAll(result)); - } - - @Test - void findAllWithSearchStringReturnsMatchingTerms() { - final List matching = terms.stream().filter(t -> t.getLabel().get(Environment.LANGUAGE) - .startsWith(MATCHING_LABEL_BASE)) - .collect(Collectors.toList()); - final String searchString = MATCHING_LABEL_BASE.substring(0, - MATCHING_LABEL_BASE.length() - Generator.randomInt(0, - 3)); - - List result = sut.findAll(searchString); - assertEquals(matching.size(), result.size()); - assertTrue(termsToDtos(matching).containsAll(result)); - } -} From 10fbd136fa9ccf8fbc7ac9a17dc936c4809ee80c Mon Sep 17 00:00:00 2001 From: Martin Ledvinka Date: Mon, 16 Jan 2023 10:36:09 +0100 Subject: [PATCH 19/23] [Enhancement #194] Decrease the amount of generated test data in TermDaoTest. --- .../termit/persistence/dao/TermDaoTest.java | 108 +++++++++--------- 1 file changed, 51 insertions(+), 57 deletions(-) diff --git a/src/test/java/cz/cvut/kbss/termit/persistence/dao/TermDaoTest.java b/src/test/java/cz/cvut/kbss/termit/persistence/dao/TermDaoTest.java index fe2fad3cd..a5bff5caa 100644 --- a/src/test/java/cz/cvut/kbss/termit/persistence/dao/TermDaoTest.java +++ b/src/test/java/cz/cvut/kbss/termit/persistence/dao/TermDaoTest.java @@ -38,6 +38,7 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; +import static cz.cvut.kbss.termit.environment.Environment.termsToDtos; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; import static org.junit.jupiter.api.Assertions.*; @@ -53,15 +54,6 @@ void setUp() { super.setUp(); } - @Test - void findAllRootsWithDefaultPageSpecReturnsAllTerms() { - final List terms = generateTerms(10); - addTermsAndSave(new HashSet<>(terms), vocabulary); - - final List result = sut.findAllRoots(vocabulary, Constants.DEFAULT_PAGE_SPEC, Collections.emptyList()); - assertEquals(toDtos(terms), result); - } - private static List toDtos(List terms) { return Environment.termsToDtos(terms); } @@ -73,7 +65,7 @@ private void addTermsAndSave(Collection terms, Vocabulary vocabulary) { terms.forEach(t -> { t.setGlossary(vocabulary.getGlossary().getUri()); em.persist(t, descriptorFactory.termDescriptor(vocabulary)); - addTermInVocabularyRelationship(t, vocabulary.getUri()); + Generator.addTermInVocabularyRelationship(t, vocabulary.getUri(), em); }); }); } @@ -84,40 +76,42 @@ private List generateTerms(int count) { .collect(Collectors.toList()); } - private void addTermInVocabularyRelationship(Term term, URI vocabularyIri) { - Generator.addTermInVocabularyRelationship(term, vocabularyIri, em); - } - @Test void findAllRootsReturnsMatchingPageWithTerms() { final List terms = generateTerms(10); addTermsAndSave(new HashSet<>(terms), vocabulary); - // Paging starts at 0 - final List result = sut - .findAllRoots(vocabulary, PageRequest.of(1, terms.size() / 2), Collections.emptyList()); - final List subList = terms.subList(terms.size() / 2, terms.size()); - assertEquals(toDtos(subList), result); + final List resultOne = sut.findAllRoots(vocabulary, PageRequest.of(0, 5), Collections.emptyList()); + final List resultTwo = sut.findAllRoots(vocabulary, PageRequest.of(1, 5), Collections.emptyList()); + + assertEquals(5, resultOne.size()); + assertEquals(5, resultTwo.size()); + + final List expectedDtos = termsToDtos(terms); + resultOne.forEach(t -> { + assertThat(expectedDtos, hasItem(t)); + assertThat(resultTwo, not(hasItem(t))); + }); + resultTwo.forEach(t -> assertThat(expectedDtos, hasItem(t))); } @Test void findAllRootsReturnsOnlyTermsInSpecifiedVocabulary() { - final List terms = generateTerms(10); + final List terms = generateTerms(2); addTermsAndSave(new HashSet<>(terms), vocabulary); final Vocabulary another = Generator.generateVocabulary(); another.setUri(Generator.generateUri()); - another.getGlossary().setRootTerms(generateTerms(4).stream().map(Asset::getUri).collect(Collectors.toSet())); + another.getGlossary().setRootTerms(generateTerms(2).stream().map(Asset::getUri).collect(Collectors.toSet())); transactional(() -> em.persist(another)); - final List result = sut - .findAllRoots(vocabulary, PageRequest.of(0, terms.size() / 2), Collections.emptyList()); - assertEquals(terms.size() / 2, result.size()); - assertTrue(toDtos(terms).containsAll(result)); + final List result = sut.findAllRoots(vocabulary, Constants.DEFAULT_PAGE_SPEC, Collections.emptyList()); + assertEquals(terms.size(), result.size()); + assertThat(result, hasItems(toDtos(terms).toArray(new TermDto[]{}))); } @Test void findAllRootsWithoutVocabularyReturnsMatchingPageWithTerms() { - final List terms = generateTerms(10); + final List terms = generateTerms(4); addTermsAndSave(new HashSet<>(terms), vocabulary); // Paging starts at 0 @@ -129,7 +123,7 @@ void findAllRootsWithoutVocabularyReturnsMatchingPageWithTerms() { @Test void findAllRootsWithoutVocabularyReturnsOnlyRootTerms() { - final List rootTerms = generateTerms(10); + final List rootTerms = generateTerms(4); addTermsAndSave(new HashSet<>(rootTerms), vocabulary); transactional(() -> rootTerms.forEach(t -> { final Term child = Generator.generateTermWithId(vocabulary.getUri()); @@ -158,7 +152,7 @@ void findAllRootsWithoutVocabularyReturnsOnlyRootTerms() { @Test void findAllRootsReturnsOnlyRootTerms() { - final List rootTerms = generateTerms(10); + final List rootTerms = generateTerms(4); addTermsAndSave(new HashSet<>(rootTerms), vocabulary); transactional(() -> rootTerms.forEach(t -> { final Term child = Generator.generateTermWithId(vocabulary.getUri()); @@ -173,8 +167,8 @@ void findAllRootsReturnsOnlyRootTerms() { @Test void findAllBySearchStringReturnsTermsWithMatchingLabel() { - final List terms = generateTerms(10); - addTermsAndSave(new HashSet<>(terms), vocabulary); + final List terms = generateTerms(4); + addTermsAndSave(terms, vocabulary); final List result = sut.findAll(terms.get(0).getLabel().get(Environment.LANGUAGE), vocabulary); assertEquals(1, result.size()); @@ -183,8 +177,8 @@ void findAllBySearchStringReturnsTermsWithMatchingLabel() { @Test void findAllBySearchStringWithoutVocabularyReturnsTermsWithMatchingLabel() { - final List terms = generateTerms(10); - addTermsAndSave(new HashSet<>(terms), vocabulary); + final List terms = generateTerms(4); + addTermsAndSave(terms, vocabulary); final List result = sut.findAll(terms.get(0).getLabel().get(Environment.LANGUAGE)); assertEquals(1, result.size()); @@ -194,7 +188,7 @@ void findAllBySearchStringWithoutVocabularyReturnsTermsWithMatchingLabel() { @Test void findAllBySearchStringReturnsTermsWithMatchingLabelWhichAreNotRoots() { enableRdfsInference(em); - final List terms = generateTerms(10); + final List terms = generateTerms(4); addTermsAndSave(new HashSet<>(terms), vocabulary); final Term root = terms.get(Generator.randomIndex(terms)); final Term child = Generator.generateTermWithId(vocabulary.getUri()); @@ -217,7 +211,7 @@ void findAllBySearchStringReturnsTermsWithMatchingLabelWhichAreNotRoots() { @Test void existsInVocabularyReturnsTrueForLabelExistingInVocabulary() { - final List terms = generateTerms(10); + final List terms = generateTerms(2); addTermsAndSave(new HashSet<>(terms), vocabulary); final String label = terms.get(0).getLabel().get(Environment.LANGUAGE); @@ -226,7 +220,7 @@ void existsInVocabularyReturnsTrueForLabelExistingInVocabulary() { @Test void existsInVocabularyReturnsFalseForUnknownLabel() { - final List terms = generateTerms(10); + final List terms = generateTerms(4); addTermsAndSave(new HashSet<>(terms), vocabulary); assertFalse(sut.existsInVocabulary("unknown label", vocabulary, Environment.LANGUAGE)); @@ -234,7 +228,7 @@ void existsInVocabularyReturnsFalseForUnknownLabel() { @Test void existsInVocabularyReturnsTrueWhenLabelDiffersOnlyInCase() { - final List terms = generateTerms(10); + final List terms = generateTerms(2); addTermsAndSave(terms, vocabulary); final String label = terms.get(0).getLabel().get(Environment.LANGUAGE).toLowerCase(); @@ -255,7 +249,7 @@ void existsInVocabularyReturnsFalseForLabelExistingInAnotherLanguageInTheVocabul @Test void findAllFullGetsAllTermsInVocabulary() { - final List terms = generateTerms(10); + final List terms = generateTerms(4); addTermsAndSave(terms, vocabulary); final List result = sut.findAllFull(vocabulary); @@ -265,7 +259,7 @@ void findAllFullGetsAllTermsInVocabulary() { @Test void findAllFullReturnsAllTermsFromVocabularyOrderedByLabel() { - final List terms = generateTerms(10); + final List terms = generateTerms(4); addTermsAndSave(terms, vocabulary); final List result = sut.findAllFull(vocabulary); @@ -275,7 +269,7 @@ void findAllFullReturnsAllTermsFromVocabularyOrderedByLabel() { @Test void findAllIncludingImportedReturnsTermsInVocabularyAndImportedVocabularies() { - final List terms = generateTerms(10); + final List terms = generateTerms(4); addTermsAndSave(terms, vocabulary); final Vocabulary parent = Generator.generateVocabularyWithId(); vocabulary.setImportedVocabularies(Collections.singleton(parent.getUri())); @@ -323,7 +317,7 @@ void updateUpdatesTermInVocabularyContext() { term.setGlossary(vocabulary.getGlossary().getUri()); em.merge(vocabulary.getGlossary(), descriptorFactory.glossaryDescriptor(vocabulary)); em.persist(term, descriptorFactory.termDescriptor(vocabulary)); - addTermInVocabularyRelationship(term, vocabulary.getUri()); + Generator.addTermInVocabularyRelationship(term, vocabulary.getUri(), em); }); final String updatedLabel = "Updated label"; @@ -404,7 +398,7 @@ void findAllRootsIncludingImportsGetsRootTermsFromVocabularyImportChain() { @Test void findAllRootsIncludingImportsReturnsVocabularyRootTermsWhenVocabularyDoesNotImportAnyOther() { - final List terms = generateTerms(10); + final List terms = generateTerms(4); addTermsAndSave(new HashSet<>(terms), vocabulary); final List result = sut @@ -537,8 +531,8 @@ void updateSupportsReferencingParentTermInDifferentVocabulary() { term.addParentTerm(parent); em.persist(parent, descriptorFactory.termDescriptor(parentVoc)); em.persist(term, descriptorFactory.termDescriptor(vocabulary)); - addTermInVocabularyRelationship(term, vocabulary.getUri()); - addTermInVocabularyRelationship(parent, parentVoc.getUri()); + Generator.addTermInVocabularyRelationship(term, vocabulary.getUri(), em); + Generator.addTermInVocabularyRelationship(parent, parentVoc.getUri(), em); }); final Term toUpdate = sut.find(term.getUri()).get(); @@ -571,9 +565,9 @@ void updateSupportsSettingNewParentFromAnotherDifferentVocabulary() { em.persist(parentTwo, descriptorFactory.termDescriptor(parentTwoVoc)); em.persist(term, descriptorFactory.termDescriptor(vocabulary)); parentTwo.setGlossary(parentTwoVoc.getGlossary().getUri()); - addTermInVocabularyRelationship(term, vocabulary.getUri()); - addTermInVocabularyRelationship(parentOne, parentOneVoc.getUri()); - addTermInVocabularyRelationship(parentTwo, parentTwoVoc.getUri()); + Generator.addTermInVocabularyRelationship(term, vocabulary.getUri(), em); + Generator.addTermInVocabularyRelationship(parentOne, parentOneVoc.getUri(), em); + Generator.addTermInVocabularyRelationship(parentTwo, parentTwoVoc.getUri(), em); }); em.getEntityManagerFactory().getCache().evictAll(); @@ -636,7 +630,7 @@ private void persistTerms(String lang, String... labels) { vocabulary.getGlossary().addRootTerm(parent); em.merge(vocabulary.getGlossary(), descriptorFactory.glossaryDescriptor(vocabulary)); em.persist(parent, descriptorFactory.termDescriptor(vocabulary)); - addTermInVocabularyRelationship(parent, vocabulary.getUri()); + Generator.addTermInVocabularyRelationship(parent, vocabulary.getUri(), em); })); } @@ -769,21 +763,21 @@ void updateAllowsSettingMultipleTermParentsFromMultipleVocabularies() { @Test void findAllRootsRetrievesRootTermsAndTermsSpecifiedByProvidedIdentifiers() { - final List terms = generateTerms(10); + final List terms = generateTerms(4); addTermsAndSave(new HashSet<>(terms), vocabulary); final Term childToReturn = Generator.generateTermWithId(vocabulary.getUri()); childToReturn.addParentTerm(terms.get(0)); transactional(() -> em.persist(childToReturn, descriptorFactory.termDescriptor(childToReturn))); final List results = sut - .findAllRoots(vocabulary, PageRequest.of(0, 5), Collections.singleton(childToReturn.getUri())); + .findAllRoots(vocabulary, PageRequest.of(0, terms.size() / 2), Collections.singleton(childToReturn.getUri())); assertFalse(results.isEmpty()); assertThat(results, hasItem(new TermDto(childToReturn))); } @Test void findAllRootsIncludingImportsRetrievesRootTermsAndTermsSpecifiedByProvidedIdentifiers() { - final List terms = generateTerms(10); + final List terms = generateTerms(4); addTermsAndSave(new HashSet<>(terms), vocabulary); final Vocabulary parentVoc = Generator.generateVocabularyWithId(); final Term parentTermInParentVoc = Generator.generateTermWithId(parentVoc.getUri()); @@ -798,7 +792,7 @@ void findAllRootsIncludingImportsRetrievesRootTermsAndTermsSpecifiedByProvidedId }); final List results = sut - .findAllRootsIncludingImports(vocabulary, PageRequest.of(0, 5), + .findAllRootsIncludingImports(vocabulary, PageRequest.of(0, terms.size() / 2), Collections.singleton(childTermInParentVoc.getUri())); assertFalse(results.isEmpty()); assertThat(results, hasItem(new TermDto(childTermInParentVoc))); @@ -917,7 +911,7 @@ void updateClearsPossiblyStaleTermDtoFromCache() { transactional(() -> { em.merge(vocabulary.getGlossary(), descriptorFactory.glossaryDescriptor(vocabulary)); em.persist(term, descriptorFactory.termDescriptor(vocabulary)); - addTermInVocabularyRelationship(term, vocabulary.getUri()); + Generator.addTermInVocabularyRelationship(term, vocabulary.getUri(), em); }); final List dto = sut.findAllRoots(vocabulary, Constants.DEFAULT_PAGE_SPEC, Collections.emptyList()); assertEquals(1, dto.size()); @@ -933,7 +927,7 @@ void updateClearsPossiblyStaleTermDtoFromCache() { @Test void loadsSubTermsForIncludedTermsLoadedWhenFetchingRoots() { enableRdfsInference(em); - final List rootTerms = generateTerms(10); + final List rootTerms = generateTerms(4); addTermsAndSave(rootTerms, vocabulary); final Term term = Generator.generateTermWithId(vocabulary.getUri()); // Make it last @@ -962,7 +956,7 @@ void loadsSubTermsForIncludedTermsLoadedWhenFetchingRoots() { @Test void findAllRootsLoadsSubTermsForAncestorsOfIncludedTerms() { enableRdfsInference(em); - final List rootTerms = generateTerms(10); + final List rootTerms = generateTerms(4); addTermsAndSave(rootTerms, vocabulary); final Term term = Generator.generateTermWithId(vocabulary.getUri()); term.setGlossary(vocabulary.getGlossary().getUri()); @@ -993,7 +987,7 @@ void findAllRootsLoadsSubTermsForAncestorsOfIncludedTerms() { */ @Test void findAllRootsEnsuresIncludedTermsAreNotDuplicatedInResult() { - final List rootTerms = generateTerms(10); + final List rootTerms = generateTerms(4); addTermsAndSave(rootTerms, vocabulary); rootTerms.sort(Comparator.comparing(Term::getPrimaryLabel)); final Term toInclude = rootTerms.get(0); @@ -1039,7 +1033,7 @@ private Document getDocument(final File... files) { @Test void findAllGetsAllTermsInVocabulary() { - final List terms = generateTerms(10); + final List terms = generateTerms(4); addTermsAndSave(terms, vocabulary); final List result = sut.findAll(vocabulary); @@ -1089,7 +1083,7 @@ void persistWithParentEvictsParentsSubTermsCache() { parent.setGlossary(vocabulary.getGlossary().getUri()); em.persist(parent, descriptorFactory.termDescriptor(vocabulary)); em.merge(vocabulary.getGlossary(), descriptorFactory.glossaryDescriptor(vocabulary)); - addTermInVocabularyRelationship(parent, vocabulary.getUri()); + Generator.addTermInVocabularyRelationship(parent, vocabulary.getUri(), em); }); final List rootsBefore = sut.findAllRoots(vocabulary, Constants.DEFAULT_PAGE_SPEC, Collections.emptyList()); From c744d7aa39ed720e89ed93061bb20588e9ea5d81 Mon Sep 17 00:00:00 2001 From: Martin Ledvinka Date: Mon, 16 Jan 2023 10:37:03 +0100 Subject: [PATCH 20/23] [Enhancement #194] Add popis-dat-model.ttl and skos.rdf locally so that they are not downloaded for every test in which they are used. --- .../kbss/termit/environment/Environment.java | 6 +- .../resources/ontologies/popis-dat-model.ttl | 538 ++++++++++++++++++ src/test/resources/ontologies/skos.rdf | 468 +++++++++++++++ 3 files changed, 1008 insertions(+), 4 deletions(-) create mode 100644 src/test/resources/ontologies/popis-dat-model.ttl create mode 100644 src/test/resources/ontologies/skos.rdf diff --git a/src/test/java/cz/cvut/kbss/termit/environment/Environment.java b/src/test/java/cz/cvut/kbss/termit/environment/Environment.java index 6f0305e21..3e81b9b06 100644 --- a/src/test/java/cz/cvut/kbss/termit/environment/Environment.java +++ b/src/test/java/cz/cvut/kbss/termit/environment/Environment.java @@ -41,7 +41,6 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.HashSet; @@ -163,12 +162,11 @@ public static void addModelStructureForRdfsInference(EntityManager em) { final Repository repo = em.unwrap(Repository.class); try (final RepositoryConnection conn = repo.getConnection()) { conn.begin(); - // Using the accent-less version of popis dat ontology URL to prevent encoding issues on Windows - conn.add(new URL("http://onto.fel.cvut.cz/ontologies/slovnik/agendovy/popis-dat/model"), BASE_URI, + conn.add(Environment.class.getClassLoader().getResourceAsStream("ontologies/popis-dat-model.ttl"), BASE_URI, RDFFormat.TURTLE); conn.add(new File("ontology/termit-model.ttl"), BASE_URI, RDFFormat.TURTLE); conn.add(new File("rulesets/rules-termit-spin.ttl"), BASE_URI, RDFFormat.TURTLE); - conn.add(new URL("https://www.w3.org/TR/skos-reference/skos.rdf"), "", RDFFormat.RDFXML); + conn.add(Environment.class.getClassLoader().getResourceAsStream("ontologies/skos.rdf"), "", RDFFormat.RDFXML); conn.commit(); } catch (IOException e) { throw new RuntimeException("Unable to load TermIt model for import.", e); diff --git a/src/test/resources/ontologies/popis-dat-model.ttl b/src/test/resources/ontologies/popis-dat-model.ttl new file mode 100644 index 000000000..180b9a5a1 --- /dev/null +++ b/src/test/resources/ontologies/popis-dat-model.ttl @@ -0,0 +1,538 @@ +@prefix rdf: . +@prefix owl: . +@prefix a-popis-dat-pojem: . +@prefix xsd: . +@prefix rdfs: . +@prefix a-popis-dat: . + +a-popis-dat-pojem:je-pojmem + a owl:ObjectProperty , ; + rdfs:domain a-popis-dat-pojem:term ; + rdfs:subPropertyOf a-popis-dat-pojem:je-záznamem . + +a-popis-dat-pojem:číselník + a , owl:Class ; + rdfs:subClassOf a-popis-dat-pojem:databázová-tabulka . + +a-popis-dat-pojem:má-heslo + a owl:DatatypeProperty , ; + rdfs:domain a-popis-dat-pojem:uživatel ; + rdfs:range rdfs:Literal ; + rdfs:subPropertyOf . + +a-popis-dat-pojem:má-posledního-editora + a ; + rdfs:subPropertyOf a-popis-dat-pojem:má-editora . + +a-popis-dat-pojem:má-datový-typ + a owl:ObjectProperty , ; + rdfs:domain a-popis-dat-pojem:atribut-databázové-tabulky ; + rdfs:subPropertyOf . + +a-popis-dat-pojem:záznam + a , owl:Class ; + rdfs:subClassOf , . + +a-popis-dat-pojem:je-záznamem-datové-sady + a owl:ObjectProperty , ; + rdfs:range a-popis-dat-pojem:datová-sada ; + rdfs:subPropertyOf a-popis-dat-pojem:je-záznamem . + +a-popis-dat-pojem:legislativní-slovník + a , owl:Class ; + rdfs:subClassOf a-popis-dat-pojem:dokumentový-slovník . + +a-popis-dat-pojem:poskytuje-záznam + a owl:ObjectProperty , ; + rdfs:domain a-popis-dat-pojem:zdroj-záznamu ; + rdfs:range a-popis-dat-pojem:záznam ; + rdfs:subPropertyOf . + +a-popis-dat-pojem:statistický-atribut-záznamu + a , owl:Class ; + rdfs:subClassOf a-popis-dat-pojem:atribut-záznamu . + +a-popis-dat-pojem:funkce + a , owl:Class ; + rdfs:subClassOf . + +a-popis-dat-pojem:má-dokumentový-slovník + a ; + owl:inverseOf a-popis-dat-pojem:popisuje-dokument; + rdfs:subPropertyOf . + +a-popis-dat-pojem:má-atribut + a owl:ObjectProperty , ; + rdfs:range a-popis-dat-pojem:atribut ; + owl:inverseOf a-popis-dat-pojem:je-atributem; + rdfs:subPropertyOf . + +a-popis-dat-pojem:soubor + a , owl:Class ; + rdfs:subClassOf a-popis-dat-pojem:zdroj . + +a-popis-dat-pojem:je-datovým-atributem-záznamu + a owl:ObjectProperty , ; + rdfs:domain a-popis-dat-pojem:datový-atribut-záznamu ; + rdfs:subPropertyOf a-popis-dat-pojem:je-atributem-záznamu . + +a-popis-dat-pojem:zdroj + a , owl:Class ; + rdfs:subClassOf . + +a-popis-dat-pojem:má-referenční-atribut-záznamu + a ; + owl:inverseOf a-popis-dat-pojem:je-referenčním-atributem-záznamu ; + rdfs:subPropertyOf a-popis-dat-pojem:má-atribut-záznamu . + +a-popis-dat-pojem:je-referenčním-atributem-záznamu + a owl:ObjectProperty , ; + rdfs:domain a-popis-dat-pojem:referenční-atribut-záznamu ; + rdfs:subPropertyOf a-popis-dat-pojem:je-atributem-záznamu . + +a-popis-dat-pojem:má-atribut-databázové-tabulky + a owl:ObjectProperty , ; + rdfs:domain a-popis-dat-pojem:databázová-tabulka ; + rdfs:range a-popis-dat-pojem:atribut-databázové-tabulky ; + rdfs:subPropertyOf a-popis-dat-pojem:má-atribut . + +a-popis-dat-pojem:dokument + a , owl:Class ; + rdfs:subClassOf a-popis-dat-pojem:zdroj . + +a-popis-dat-pojem:má-číselníkovou-hodnotu + a owl:ObjectProperty , ; + rdfs:domain a-popis-dat-pojem:číselník ; + rdfs:range a-popis-dat-pojem:číselníková-hodnota ; + rdfs:subPropertyOf a-popis-dat-pojem:má-atribut-databázové-tabulky . + +a-popis-dat-pojem:term + a , owl:Class ; + rdfs:subClassOf a-popis-dat-pojem:záznam . + +a-popis-dat-pojem:je-pojmem-ze-slovníku + a owl:ObjectProperty , ; + rdfs:range a-popis-dat-pojem:slovník ; + rdfs:subPropertyOf a-popis-dat-pojem:je-pojmem . + + + a owl:Class . + +a-popis-dat-pojem:dokumentový-slovník + a , owl:Class ; + rdfs:subClassOf a-popis-dat-pojem:slovník . + +a-popis-dat-pojem:má-datum-a-čas-poslední-modifikace + a ; + rdfs:subPropertyOf a-popis-dat-pojem:má-datum-a-čas-modifikace . + +a-popis-dat-pojem:je-záznamem + a owl:ObjectProperty , ; + rdfs:domain a-popis-dat-pojem:záznam ; + rdfs:range ; + rdfs:subPropertyOf . + +a-popis-dat-pojem:datový-atribut-záznamu + a , owl:Class ; + rdfs:subClassOf a-popis-dat-pojem:atribut-záznamu . + +a-popis-dat-pojem:atribut + a , owl:Class ; + rdfs:subClassOf . + +a-popis-dat-pojem:zdroj-záznamu + a , owl:Class ; + rdfs:subClassOf a-popis-dat-pojem:zdroj-dat . + +a-popis-dat-pojem:databázová-tabulka-jako-zdroj-dat + a , owl:Class ; + rdfs:subClassOf a-popis-dat-pojem:dotaz-sql-databáze , a-popis-dat-pojem:databázová-tabulka . + +a-popis-dat-pojem:má-příjmení + a owl:DatatypeProperty , ; + rdfs:domain ; + rdfs:range rdfs:Literal ; + rdfs:subPropertyOf . + +a-popis-dat-pojem:má-model + a owl:ObjectProperty , ; + rdfs:domain a-popis-dat-pojem:slovník ; + rdfs:range a-popis-dat-pojem:model ; + rdfs:subPropertyOf . + +a-popis-dat-pojem:je-v-kontextu + a ; + rdfs:subPropertyOf . + +a-popis-dat-pojem:má-datum-a-čas-modifikace + a owl:DatatypeProperty , ; + rdfs:domain ; + rdfs:range rdfs:Literal ; + rdfs:subPropertyOf . + +a-popis-dat-pojem:je-statistickým-atributem-záznamu + a owl:ObjectProperty , ; + rdfs:domain a-popis-dat-pojem:statistický-atribut-záznamu ; + rdfs:subPropertyOf a-popis-dat-pojem:je-atributem-záznamu . + +a-popis-dat-pojem:má-atribut-záznamu + a ; + owl:inverseOf a-popis-dat-pojem:je-atributem-záznamu ; + rdfs:subPropertyOf a-popis-dat-pojem:má-atribut . + +a-popis-dat-pojem:datový-slovník + a , owl:Class ; + rdfs:subClassOf a-popis-dat-pojem:slovník . + +a-popis-dat-pojem:má-statistický-atribut-záznamu + a ; + owl:inverseOf a-popis-dat-pojem:je-statistickým-atributem-záznamu ; + rdfs:subPropertyOf a-popis-dat-pojem:má-atribut-záznamu . + +a-popis-dat-pojem:má-hodnotu-z-číselníku + a owl:ObjectProperty , ; + rdfs:domain a-popis-dat-pojem:atribut-databázové-tabulky ; + rdfs:range a-popis-dat-pojem:číselník ; + rdfs:subPropertyOf . + +a-popis-dat-pojem:definuje-funkci + a owl:ObjectProperty , ; + rdfs:domain a-popis-dat-pojem:atribut-databázové-tabulky ; + rdfs:range a-popis-dat-pojem:funkce ; + rdfs:subPropertyOf . + +a-popis-dat-pojem:referenční-atribut-záznamu + a , owl:Class ; + rdfs:subClassOf a-popis-dat-pojem:atribut-záznamu . + +a-popis-dat-pojem:je-v-kontextu-datové-sady + a owl:ObjectProperty , ; + rdfs:range a-popis-dat-pojem:datová-sada ; + rdfs:subPropertyOf a-popis-dat-pojem:je-v-kontextu . + +a-popis-dat-pojem:dokument-legislativní-povahy + a , owl:Class ; + rdfs:subClassOf a-popis-dat-pojem:dokument . + +a-popis-dat-pojem:atribut-záznamu + a , owl:Class ; + rdfs:subClassOf a-popis-dat-pojem:term , a-popis-dat-pojem:atribut . + + + a owl:Class . + +a-popis-dat-pojem:číselníková-hodnota + a , owl:Class ; + rdfs:subClassOf a-popis-dat-pojem:atribut-databázové-tabulky . + +a-popis-dat-pojem:je-atributem-databázové-tabulky + a owl:ObjectProperty , ; + rdfs:domain a-popis-dat-pojem:atribut-databázové-tabulky ; + rdfs:range a-popis-dat-pojem:databázová-tabulka ; + rdfs:subPropertyOf a-popis-dat-pojem:je-atributem . + +a-popis-dat-pojem:dotaz-sql-databáze + a , owl:Class ; + rdfs:subClassOf a-popis-dat-pojem:zdroj-dat . + + + a owl:Class . + +a-popis-dat-pojem:má-autora + a ; + rdfs:subPropertyOf a-popis-dat-pojem:má-editora . + +a-popis-dat-pojem:má-editora + a owl:ObjectProperty , ; + rdfs:range a-popis-dat-pojem:uživatel ; + rdfs:subPropertyOf . + +a-popis-dat-pojem:importuje-slovník + a owl:ObjectProperty , ; + rdfs:domain a-popis-dat-pojem:slovník ; + rdfs:range a-popis-dat-pojem:slovník ; + rdfs:subPropertyOf . + +a-popis-dat-pojem:má-glosář + a owl:ObjectProperty , ; + rdfs:domain a-popis-dat-pojem:slovník ; + rdfs:range a-popis-dat-pojem:glosář ; + rdfs:subPropertyOf . + +a-popis-dat-pojem:poskytuje-atribut-záznamu + a owl:ObjectProperty , ; + rdfs:domain a-popis-dat-pojem:zdroj-atributu-záznamu ; + rdfs:range a-popis-dat-pojem:atribut-záznamu ; + rdfs:subPropertyOf . + +a-popis-dat:model a a-popis-dat-pojem:model , owl:Ontology ; + rdfs:label "Vocabulary for data description - model"@en , "Slovník popis dat - model"@cs ; + + "Michal Med" ; + + ; + + "Slovník popis dat - model"@cs , "Vocabulary for data description - model"@en ; + + "13.11.2019" ; + + "Specifikace"@cs , "Specification"@en ; + + "a-popis-dat-pojem" ; + + "http://onto.fel.cvut.cz/ontologies/slovnik/agendovy/popis-dat/pojem/" ; + owl:imports , a-popis-dat:glosář ; + owl:versionIRI . + +a-popis-dat-pojem:zdroj-datové-sady + a , owl:Class ; + rdfs:subClassOf . + +a-popis-dat-pojem:model + a , owl:Class ; + rdfs:subClassOf . + +a-popis-dat-pojem:databáze + a , owl:Class ; + rdfs:subClassOf a-popis-dat-pojem:zdroj . + +a-popis-dat-pojem:je-atributem + a owl:ObjectProperty , ; + rdfs:domain a-popis-dat-pojem:atribut ; + rdfs:range ; + rdfs:subPropertyOf . + +a-popis-dat-pojem:atribut-databázové-tabulky + a , owl:Class ; + rdfs:subClassOf a-popis-dat-pojem:atribut . + +a-popis-dat-pojem:geometrický-atribut + a , owl:Class ; + rdfs:subClassOf a-popis-dat-pojem:atribut . + +a-popis-dat-pojem:katalogizuje-datovou-sadu + a owl:ObjectProperty , ; + rdfs:domain a-popis-dat-pojem:katalog ; + rdfs:range a-popis-dat-pojem:datová-sada ; + rdfs:subPropertyOf . + +a-popis-dat-pojem:obsahuje-kořenový-pojem + a ; + rdfs:subPropertyOf , a-popis-dat-pojem:obsahuje-pojem . + +a-popis-dat-pojem:obsahuje-pojem + a owl:ObjectProperty , ; + rdfs:domain a-popis-dat-pojem:glosář ; + rdfs:range a-popis-dat-pojem:term ; + owl:inverseOf ; + rdfs:subPropertyOf . + +a-popis-dat-pojem:má-soubor + a owl:ObjectProperty , ; + rdfs:domain a-popis-dat-pojem:dokument ; + rdfs:range a-popis-dat-pojem:soubor ; + owl:inverseOf a-popis-dat-pojem:je-částí-dokumentu; + rdfs:subPropertyOf . + +a-popis-dat-pojem:datová-sada + a , owl:Class ; + rdfs:subClassOf , a-popis-dat-pojem:zdroj , a-popis-dat-pojem:kontext . + + + a owl:Class . + +a-popis-dat-pojem:poskytuje-datovou-sadu + a owl:ObjectProperty , ; + rdfs:domain a-popis-dat-pojem:zdroj-datové-sady ; + rdfs:range a-popis-dat-pojem:datová-sada ; + rdfs:subPropertyOf . + +a-popis-dat-pojem:glosář + a , owl:Class ; + rdfs:subClassOf , . + +a-popis-dat-pojem:diagram + a , owl:Class ; + rdfs:subClassOf , . + +a-popis-dat-pojem:je-částí-dokumentu + a ; + rdfs:subPropertyOf . + +a-popis-dat-pojem:má-křestní-jméno + a owl:DatatypeProperty , ; + rdfs:domain ; + rdfs:range rdfs:Literal ; + rdfs:subPropertyOf . + +a-popis-dat-pojem:je-atributem-záznamu + a owl:ObjectProperty , ; + rdfs:domain a-popis-dat-pojem:atribut-záznamu ; + rdfs:range a-popis-dat-pojem:záznam ; + rdfs:subPropertyOf a-popis-dat-pojem:je-atributem . + +a-popis-dat-pojem:slovník + a , owl:Class ; + rdfs:subClassOf a-popis-dat-pojem:datová-sada . + +a-popis-dat-pojem:má-datový-atribut-záznamu + a ; + owl:inverseOf a-popis-dat-pojem:je-datovým-atributem-záznamu ; + rdfs:subPropertyOf a-popis-dat-pojem:má-atribut-záznamu . + +a-popis-dat-pojem:katalog + a , owl:Class ; + rdfs:subClassOf a-popis-dat-pojem:datová-sada . + +a-popis-dat-pojem:popisuje-legislativní-dokument + a owl:ObjectProperty , ; + rdfs:domain a-popis-dat-pojem:legislativní-slovník ; + rdfs:range a-popis-dat-pojem:dokument-legislativní-povahy ; + rdfs:subPropertyOf a-popis-dat-pojem:popisuje-dokument . + +a-popis-dat-pojem:popisuje-dokument + a owl:ObjectProperty , ; + rdfs:domain a-popis-dat-pojem:dokumentový-slovník ; + rdfs:range a-popis-dat-pojem:dokument ; + rdfs:subPropertyOf . + +a-popis-dat-pojem:uživatel + a , owl:Class ; + rdfs:subClassOf . + +a-popis-dat-pojem:zdroj-dat + a , owl:Class ; + rdfs:subClassOf . + + + a owl:Class . + + + a owl:Class . + +a-popis-dat-pojem:má-zdroj + a owl:ObjectProperty , ; + rdfs:domain ; + rdfs:range a-popis-dat-pojem:zdroj ; + rdfs:subPropertyOf . + +a-popis-dat-pojem:má-uživatelské-jméno + a owl:DatatypeProperty , ; + rdfs:domain a-popis-dat-pojem:uživatel ; + rdfs:range rdfs:Literal ; + rdfs:subPropertyOf . + + +a-popis-dat-pojem:verze-objektu + a , owl:Class ; + rdfs:subClassOf . + +a-popis-dat-pojem:verze-slovníku + a , owl:Class ; + rdfs:subClassOf a-popis-dat-pojem:verze-objektu , a-popis-dat-pojem:slovník . + +a-popis-dat-pojem:verze-modelu + a , owl:Class ; + rdfs:subClassOf a-popis-dat-pojem:verze-objektu , a-popis-dat-pojem:model . + +a-popis-dat-pojem:verze-glosáře + a , owl:Class ; + rdfs:subClassOf a-popis-dat-pojem:verze-objektu , a-popis-dat-pojem:glosář . + +a-popis-dat-pojem:verze-pojmu + a , owl:Class ; + rdfs:subClassOf a-popis-dat-pojem:verze-objektu , a-popis-dat-pojem:term . + +a-popis-dat-pojem:je-verzí + a owl:ObjectProperty , ; + rdfs:domain a-popis-dat-pojem:verze-objektu . + +a-popis-dat-pojem:je-verzí-slovníku + a owl:ObjectProperty , ; + rdfs:subPropertyOf a-popis-dat-pojem:je-verzí ; + rdfs:domain a-popis-dat-pojem:verze-slovníku . + +a-popis-dat-pojem:je-verzí-modelu + a owl:ObjectProperty , ; + rdfs:subPropertyOf a-popis-dat-pojem:je-verzí ; + rdfs:domain a-popis-dat-pojem:verze-modelu . + +a-popis-dat-pojem:je-verzí-glosáře + a owl:ObjectProperty , ; + rdfs:subPropertyOf a-popis-dat-pojem:je-verzí ; + rdfs:domain a-popis-dat-pojem:verze-glosáře . + +a-popis-dat-pojem:je-verzí-pojmu + a owl:ObjectProperty , ; + rdfs:subPropertyOf a-popis-dat-pojem:je-verzí ; + rdfs:domain a-popis-dat-pojem:verze-pojmu . + +a-popis-dat-pojem:má-verzi-glosáře + a owl:ObjectProperty , ; + rdfs:subPropertyOf a-popis-dat-pojem:má-glosář ; + rdfs:domain a-popis-dat-pojem:slovník ; + rdfs:range a-popis-dat-pojem:glosář . + +a-popis-dat-pojem:má-verzi-modelu + a owl:ObjectProperty , ; + rdfs:subPropertyOf a-popis-dat-pojem:má-model ; + rdfs:domain a-popis-dat-pojem:slovník ; + rdfs:range a-popis-dat-pojem:model . + +a-popis-dat-pojem:má-datum-a-čas-vytvoření-verze + a ; + rdfs:domain a-popis-dat-pojem:verze-objektu ; + rdfs:subPropertyOf a-popis-dat-pojem:má-datum-a-čas-modifikace . + +a-popis-dat-pojem:databázová-tabulka + a , owl:Class ; + rdfs:subClassOf . + +a-popis-dat-pojem:zdroj-atributu-záznamu + a , owl:Class ; + rdfs:subClassOf a-popis-dat-pojem:zdroj-dat . + +a-popis-dat-pojem:agendový-slovník + a , owl:Class ; + rdfs:subClassOf a-popis-dat-pojem:slovník . + +a-popis-dat-pojem:kontext + a , owl:Class ; + rdfs:subClassOf . + +a-popis-dat-pojem:má-datum-a-čas-vytvoření + a ; + rdfs:subPropertyOf a-popis-dat-pojem:má-datum-a-čas-modifikace . + +a-popis-dat-pojem:změna + a , owl:Class . + +a-popis-dat-pojem:vytvoření-entity + a , owl:Class ; + rdfs:subClassOf a-popis-dat-pojem:změna . + + +a-popis-dat-pojem:úprava-entity + a , owl:Class ; + rdfs:subClassOf a-popis-dat-pojem:změna . + +a-popis-dat-pojem:má-změněnou-entitu + a owl:ObjectProperty , ; + rdfs:domain a-popis-dat-pojem:změna ; + rdfs:range ; + rdfs:subPropertyOf . + +a-popis-dat-pojem:má-změněný-atribut + a ; + rdfs:domain a-popis-dat-pojem:úprava-entity ; + rdfs:subPropertyOf . + +a-popis-dat-pojem:má-novou-hodnotu + a ; + rdfs:domain a-popis-dat-pojem:úprava-entity ; + rdfs:subPropertyOf . + +a-popis-dat-pojem:má-původní-hodnotu + a ; + rdfs:domain a-popis-dat-pojem:úprava-entity ; + rdfs:subPropertyOf . diff --git a/src/test/resources/ontologies/skos.rdf b/src/test/resources/ontologies/skos.rdf new file mode 100644 index 000000000..1ddeb9a5c --- /dev/null +++ b/src/test/resources/ontologies/skos.rdf @@ -0,0 +1,468 @@ + + + + + SKOS Vocabulary + Dave Beckett + Nikki Rogers + Participants in W3C's Semantic Web Deployment Working Group. + An RDF vocabulary for describing the basic structure and content of concept schemes such as thesauri, classification schemes, subject heading lists, taxonomies, 'folksonomies', other types of controlled vocabulary, and also concept schemes embedded in glossaries and terminologies. + Alistair Miles + Sean Bechhofer + + + + Concept + + An idea or notion; a unit of thought. + + + + + Concept Scheme + + A set of concepts, optionally including statements about semantic relationships between those concepts. + A concept scheme may be defined to include concepts from different sources. + Thesauri, classification schemes, subject heading lists, taxonomies, 'folksonomies', and other types of controlled vocabulary are all examples of concept schemes. Concept schemes are also embedded in glossaries and terminologies. + + + + + + + Collection + + A meaningful collection of concepts. + Labelled collections can be used where you would like a set of concepts to be displayed under a 'node label' in the hierarchy. + + + + + + + + + Ordered Collection + + An ordered collection of concepts, where both the grouping and the ordering are meaningful. + Ordered collections can be used where you would like a set of concepts to be displayed in a specific order, and optionally under a 'node label'. + + + + + + + is in scheme + + Relates a resource (for example a concept) to a concept scheme in which it is included. + A concept may be a member of more than one concept scheme. + + + + + + + + + has top concept + + Relates, by convention, a concept scheme to a concept which is topmost in the broader/narrower concept hierarchies for that scheme, providing an entry point to these hierarchies. + + + + + + + + + + + + + is top concept in scheme + + Relates a concept to the concept scheme that it is a top level concept of. + + + + + + + + + + + + + preferred label + + The preferred lexical label for a resource, in a given language. + + + + + + A resource has no more than one value of skos:prefLabel per language tag, and no more than one value of skos:prefLabel without language tag. + + The range of skos:prefLabel is the class of RDF plain literals. + + skos:prefLabel, skos:altLabel and skos:hiddenLabel are pairwise + disjoint properties. + + + + + alternative label + + An alternative lexical label for a resource. + Acronyms, abbreviations, spelling variants, and irregular plural/singular forms may be included among the alternative labels for a concept. Mis-spelled terms are normally included as hidden labels (see skos:hiddenLabel). + + + + + + The range of skos:altLabel is the class of RDF plain literals. + + skos:prefLabel, skos:altLabel and skos:hiddenLabel are pairwise disjoint properties. + + + + + hidden label + + A lexical label for a resource that should be hidden when generating visual displays of the resource, but should still be accessible to free text search operations. + + + + + + The range of skos:hiddenLabel is the class of RDF plain literals. + + skos:prefLabel, skos:altLabel and skos:hiddenLabel are pairwise disjoint properties. + + + + + notation + + A notation, also known as classification code, is a string of characters such as "T58.5" or "303.4833" used to uniquely identify a concept within the scope of a given concept scheme. + By convention, skos:notation is used with a typed literal in the object position of the triple. + + + + + + + note + + A general note, for any purpose. + This property may be used directly, or as a super-property for more specific note types. + + + + + + + change note + + A note about a modification to a concept. + + + + + + + + + definition + + A statement or formal explanation of the meaning of a concept. + + + + + + + + + editorial note + + A note for an editor, translator or maintainer of the vocabulary. + + + + + + + + + example + + An example of the use of a concept. + + + + + + + + + history note + + A note about the past state/use/meaning of a concept. + + + + + + + + + scope note + + A note that helps to clarify the meaning and/or the use of a concept. + + + + + + + + + is in semantic relation with + + Links a concept to a concept related by meaning. + This property should not be used directly, but as a super-property for all properties denoting a relationship of meaning between concepts. + + + + + + + + + + + has broader + + Relates a concept to a concept that is more general in meaning. + Broader concepts are typically rendered as parents in a concept hierarchy (tree). + By convention, skos:broader is only used to assert an immediate (i.e. direct) hierarchical link between two conceptual resources. + + + + + + + + + + + has narrower + + Relates a concept to a concept that is more specific in meaning. + By convention, skos:broader is only used to assert an immediate (i.e. direct) hierarchical link between two conceptual resources. + Narrower concepts are typically rendered as children in a concept hierarchy (tree). + + + + + + + + + + + has related + + Relates a concept to a concept with which there is an associative semantic relationship. + + + + + + + + skos:related is disjoint with skos:broaderTransitive + + + + + has broader transitive + + skos:broaderTransitive is a transitive superproperty of skos:broader. + By convention, skos:broaderTransitive is not used to make assertions. Rather, the properties can be used to draw inferences about the transitive closure of the hierarchical relation, which is useful e.g. when implementing a simple query expansion algorithm in a search application. + + + + + + + + + + + + + has narrower transitive + + skos:narrowerTransitive is a transitive superproperty of skos:narrower. + By convention, skos:narrowerTransitive is not used to make assertions. Rather, the properties can be used to draw inferences about the transitive closure of the hierarchical relation, which is useful e.g. when implementing a simple query expansion algorithm in a search application. + + + + + + + + + + + + + has member + + Relates a collection to one of its members. + + + + + + + + + + + + + + + + + + has member list + + Relates an ordered collection to the RDF list containing its members. + + + + + + + + + + For any resource, every item in the list given as the value of the + skos:memberList property is also a value of the skos:member property. + + + + + is in mapping relation with + + Relates two concepts coming, by convention, from different schemes, and that have comparable meanings + These concept mapping relations mirror semantic relations, and the data model defined below is similar (with the exception of skos:exactMatch) to the data model defined for semantic relations. A distinct vocabulary is provided for concept mapping relations, to provide a convenient way to differentiate links within a concept scheme from links between concept schemes. However, this pattern of usage is not a formal requirement of the SKOS data model, and relies on informal definitions of best practice. + + + + + + + + + has broader match + + skos:broadMatch is used to state a hierarchical mapping link between two conceptual resources in different concept schemes. + + + + + + + + + + + + + has narrower match + + skos:narrowMatch is used to state a hierarchical mapping link between two conceptual resources in different concept schemes. + + + + + + + + + + + + + has related match + + skos:relatedMatch is used to state an associative mapping link between two conceptual resources in different concept schemes. + + + + + + + + + + + + + has exact match + + skos:exactMatch is used to link two concepts, indicating a high degree of confidence that the concepts can be used interchangeably across a wide range of information retrieval applications. skos:exactMatch is a transitive property, and is a sub-property of skos:closeMatch. + + + + + + + + + + skos:exactMatch is disjoint with each of the properties skos:broadMatch and skos:relatedMatch. + + + + + has close match + + skos:closeMatch is used to link two concepts that are sufficiently similar that they can be used interchangeably in some information retrieval applications. In order to avoid the possibility of "compound errors" when combining mappings across more than two concept schemes, skos:closeMatch is not declared to be a transitive property. + + + + + + + + + + From a027d549a6beda8d1caf17521bed1e5b4498aca8 Mon Sep 17 00:00:00 2001 From: Martin Ledvinka Date: Mon, 16 Jan 2023 11:08:37 +0100 Subject: [PATCH 21/23] [Enhancement #194] Reuse test setup in BaseDaoTest. Remove unnecessary sleep in ResourceDaoTest. --- .../cvut/kbss/termit/persistence/dao/BaseDaoTest.java | 10 ++-------- .../kbss/termit/persistence/dao/ResourceDaoTest.java | 3 +-- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/test/java/cz/cvut/kbss/termit/persistence/dao/BaseDaoTest.java b/src/test/java/cz/cvut/kbss/termit/persistence/dao/BaseDaoTest.java index a97516b64..ca98f8bd3 100644 --- a/src/test/java/cz/cvut/kbss/termit/persistence/dao/BaseDaoTest.java +++ b/src/test/java/cz/cvut/kbss/termit/persistence/dao/BaseDaoTest.java @@ -21,10 +21,8 @@ import cz.cvut.kbss.termit.exception.PersistenceException; import cz.cvut.kbss.termit.model.Term; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.annotation.DirtiesContext; import java.util.Collections; import java.util.List; @@ -33,12 +31,9 @@ import java.util.stream.IntStream; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.*; import static org.junit.jupiter.api.Assertions.*; -@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) -@Tag("dao") class BaseDaoTest extends BaseDaoTestRunner { @Autowired @@ -61,8 +56,7 @@ void findAllRetrievesAllExistingInstances() { }).collect(Collectors.toList()); transactional(() -> sut.persist(terms)); final List result = sut.findAll(); - assertEquals(terms.size(), result.size()); - assertTrue(terms.containsAll(result)); + assertThat(result, hasItems(terms.toArray(new Term[] {}))); } @Test diff --git a/src/test/java/cz/cvut/kbss/termit/persistence/dao/ResourceDaoTest.java b/src/test/java/cz/cvut/kbss/termit/persistence/dao/ResourceDaoTest.java index e307f308d..192f3a51b 100644 --- a/src/test/java/cz/cvut/kbss/termit/persistence/dao/ResourceDaoTest.java +++ b/src/test/java/cz/cvut/kbss/termit/persistence/dao/ResourceDaoTest.java @@ -315,12 +315,11 @@ void removeRefreshesLastModifiedValue() { } @Test - void updateRefreshesLastModifiedValue() throws Exception { + void updateRefreshesLastModifiedValue() { final Resource resource = generateResource(); final long before = sut.getLastModified(); final String newLabel = "New label"; resource.setLabel(newLabel); - Thread.sleep(100); // force time to move on transactional(() -> sut.update(resource)); final Optional result = sut.find(resource.getUri()); assertTrue(result.isPresent()); From ad2dd4e8377a870efaa6eb0d8d063f612f23754c Mon Sep 17 00:00:00 2001 From: Martin Ledvinka Date: Mon, 16 Jan 2023 13:28:29 +0100 Subject: [PATCH 22/23] Allow only admins to run text analysis on all vocabularies. --- .../java/cz/cvut/kbss/termit/rest/VocabularyController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 2ca85f373..aae4201f2 100644 --- a/src/main/java/cz/cvut/kbss/termit/rest/VocabularyController.java +++ b/src/main/java/cz/cvut/kbss/termit/rest/VocabularyController.java @@ -206,7 +206,7 @@ public void runTextAnalysisOnAllTerms(@PathVariable String vocabularyIdFragment, */ @GetMapping(value = "/text-analysis") @ResponseStatus(HttpStatus.ACCEPTED) - @PreAuthorize("hasRole('" + SecurityConstants.ROLE_FULL_USER + "')") + @PreAuthorize("hasRole('" + SecurityConstants.ROLE_ADMIN + "')") public void runTextAnalysisOnAllVocabularies() { vocabularyService.runTextAnalysisOnAllVocabularies(); } From 5668adb36fa14cd44bc26e7376b54d0ebbb6f2e4 Mon Sep 17 00:00:00 2001 From: Martin Ledvinka Date: Sat, 21 Jan 2023 17:38:12 +0100 Subject: [PATCH 23/23] [2.16.0] Bump version. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 24e5e065f..8c021c103 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ termit - 2.15.0 + 2.16.0 TermIt Terminology manager based on Semantic Web technologies. ${packaging}