diff --git a/src/main/java/cz/cvut/kbss/termit/service/MessageFormatter.java b/src/main/java/cz/cvut/kbss/termit/service/MessageFormatter.java new file mode 100644 index 000000000..10b147a60 --- /dev/null +++ b/src/main/java/cz/cvut/kbss/termit/service/MessageFormatter.java @@ -0,0 +1,48 @@ +package cz.cvut.kbss.termit.service; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.lang.NonNull; + +import java.text.MessageFormat; +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.Objects; +import java.util.ResourceBundle; + +/** + * Formats internationalized messages. + *

+ * The message bundles should be located in {@code src/main/resources/i18n} and be called {@code messages}. + */ +public class MessageFormatter { + + private static final Logger LOG = LoggerFactory.getLogger(MessageFormatter.class); + + private final ResourceBundle messages; + + public MessageFormatter(@NonNull String lang) { + Objects.requireNonNull(lang); + final Locale locale = new Locale(lang); + this.messages = ResourceBundle.getBundle("i18n/messages", locale); + LOG.info("Loaded message bundle '{}_{}'.", messages.getBaseBundleName(), locale); + } + + /** + * Formats message with the specified identifier using the specified parameters. + * + * @param messageId Message identifier + * @param params Parameters to substitute into the message string + * @return Formatted message + */ + public String formatMessage(@NonNull String messageId, Object... params) { + Objects.requireNonNull(messageId); + try { + final MessageFormat formatter = new MessageFormat(messages.getString(messageId)); + return formatter.format(params); + } catch (MissingResourceException e) { + LOG.error("No message found for message id '{}'. Returning the message id.", messageId, e); + return messageId; + } + } +} diff --git a/src/main/java/cz/cvut/kbss/termit/service/repository/VocabularyRepositoryService.java b/src/main/java/cz/cvut/kbss/termit/service/repository/VocabularyRepositoryService.java index d00b5c125..71c2c51bb 100644 --- a/src/main/java/cz/cvut/kbss/termit/service/repository/VocabularyRepositoryService.java +++ b/src/main/java/cz/cvut/kbss/termit/service/repository/VocabularyRepositoryService.java @@ -28,16 +28,19 @@ import cz.cvut.kbss.termit.model.Glossary; import cz.cvut.kbss.termit.model.Model; import cz.cvut.kbss.termit.model.Vocabulary; +import cz.cvut.kbss.termit.model.resource.Document; import cz.cvut.kbss.termit.model.validation.ValidationResult; import cz.cvut.kbss.termit.persistence.dao.BaseAssetDao; import cz.cvut.kbss.termit.persistence.dao.VocabularyDao; import cz.cvut.kbss.termit.persistence.dao.skos.SKOSImporter; import cz.cvut.kbss.termit.service.IdentifierResolver; +import cz.cvut.kbss.termit.service.MessageFormatter; import cz.cvut.kbss.termit.service.snapshot.SnapshotProvider; import cz.cvut.kbss.termit.util.Configuration; import cz.cvut.kbss.termit.util.Constants; import cz.cvut.kbss.termit.util.Utils; import cz.cvut.kbss.termit.workspace.EditableVocabularies; +import jakarta.validation.Validator; import org.apache.tika.Tika; import org.apache.tika.metadata.Metadata; import org.apache.tika.metadata.TikaCoreProperties; @@ -52,11 +55,14 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; -import jakarta.validation.Validator; import java.io.IOException; import java.net.URI; import java.time.Instant; -import java.util.*; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; import java.util.stream.Collectors; @CacheConfig(cacheNames = "vocabularies") @@ -137,6 +143,7 @@ protected void prePersist(@NotNull Vocabulary instance) { } verifyIdentifierUnique(instance); initGlossaryAndModel(instance); + initDocument(instance); if (instance.getDocument() != null) { instance.getDocument().setVocabulary(null); } @@ -154,6 +161,19 @@ private void initGlossaryAndModel(Vocabulary vocabulary) { } } + private void initDocument(Vocabulary vocabulary) { + if (vocabulary.getDocument() != null) { + return; + } + final Document doc = new Document(); + doc.setUri(idResolver.generateIdentifier(vocabulary.getUri().toString(), + Constants.DEFAULT_DOCUMENT_IRI_COMPONENT)); + doc.setLabel( + new MessageFormatter(config.getPersistence().getLanguage()).formatMessage("vocabulary.document.label", + vocabulary.getLabel())); + vocabulary.setDocument(doc); + } + @Override protected void preUpdate(@NotNull Vocabulary instance) { super.preUpdate(instance); 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 2468b1d12..c33e0969c 100644 --- a/src/main/java/cz/cvut/kbss/termit/util/Constants.java +++ b/src/main/java/cz/cvut/kbss/termit/util/Constants.java @@ -65,8 +65,8 @@ public class Constants { /** * Path to directory containing queries used by the system. *

- * The path should be relative to the classpath, so that queries from it can be loaded using {@link - * ClassLoader#getResourceAsStream(String)}. + * The path should be relative to the classpath, so that queries from it can be loaded using + * {@link ClassLoader#getResourceAsStream(String)}. */ public static final String QUERY_DIRECTORY = "query"; @@ -88,9 +88,16 @@ public class Constants { */ public static final String DEFAULT_MODEL_IRI_COMPONENT = "model"; + /** + * Default identifier component for {@link cz.cvut.kbss.termit.model.resource.Document}. + *

+ * This component is appended to the containing vocabulary identifier to form the document identifier. + */ + public static final String DEFAULT_DOCUMENT_IRI_COMPONENT = "document"; + /** * Default language when none is specified in configuration. - * + *

* Used mainly for resolving internationalized templates. */ public static final String DEFAULT_LANGUAGE = "en"; diff --git a/src/main/resources/i18n/messages_cs.properties b/src/main/resources/i18n/messages_cs.properties new file mode 100644 index 000000000..40f0c75ae --- /dev/null +++ b/src/main/resources/i18n/messages_cs.properties @@ -0,0 +1 @@ +vocabulary.document.label=Dokument pro {0} diff --git a/src/main/resources/i18n/messages_en.properties b/src/main/resources/i18n/messages_en.properties new file mode 100644 index 000000000..105c6c25c --- /dev/null +++ b/src/main/resources/i18n/messages_en.properties @@ -0,0 +1 @@ +vocabulary.document.label=Document for {0} diff --git a/src/test/java/cz/cvut/kbss/termit/service/MessageFormatterTest.java b/src/test/java/cz/cvut/kbss/termit/service/MessageFormatterTest.java new file mode 100644 index 000000000..a4aee2eb4 --- /dev/null +++ b/src/test/java/cz/cvut/kbss/termit/service/MessageFormatterTest.java @@ -0,0 +1,40 @@ +package cz.cvut.kbss.termit.service; + +import cz.cvut.kbss.termit.environment.Environment; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class MessageFormatterTest { + + @ParameterizedTest + @MethodSource("i18nFormattingValues") + void formatsMessageWithSpecifiedIdUsingSpecifiedValues(String language, String expectedMessage) { + final MessageFormatter sut = new MessageFormatter(language); + final String vocabularyLabel = "Test"; + assertEquals(expectedMessage, sut.formatMessage("vocabulary.document.label", vocabularyLabel)); + } + + static Stream i18nFormattingValues() { + return Stream.of( + Arguments.of("en", "Document for Test"), + Arguments.of("cs", "Dokument pro Test") + ); + } + + @Test + void formatMessageReturnsMessageIdAndLogsErrorWhenMessageIsNotFound() { + final String unknownMessageId = "unknownMessage"; + final MessageFormatter sut = new MessageFormatter(Environment.LANGUAGE); + assertDoesNotThrow(() -> { + final String result = sut.formatMessage(unknownMessageId, "12345"); + assertEquals(unknownMessageId, result); + }); + } +} diff --git a/src/test/java/cz/cvut/kbss/termit/service/repository/VocabularyRepositoryServiceTest.java b/src/test/java/cz/cvut/kbss/termit/service/repository/VocabularyRepositoryServiceTest.java index e1d401d3c..49419d1d6 100644 --- a/src/test/java/cz/cvut/kbss/termit/service/repository/VocabularyRepositoryServiceTest.java +++ b/src/test/java/cz/cvut/kbss/termit/service/repository/VocabularyRepositoryServiceTest.java @@ -27,6 +27,7 @@ import cz.cvut.kbss.termit.model.UserAccount; import cz.cvut.kbss.termit.model.Vocabulary; import cz.cvut.kbss.termit.model.changetracking.PersistChangeRecord; +import cz.cvut.kbss.termit.model.resource.Document; import cz.cvut.kbss.termit.persistence.context.DescriptorFactory; import cz.cvut.kbss.termit.service.BaseServiceTestRunner; import cz.cvut.kbss.termit.service.IdentifierResolver; @@ -383,4 +384,26 @@ void updateEnsuresReferenceToAccessControlListIsPreserved() { assertEquals(update.getLabel(), result.getLabel()); assertEquals(vocabulary.getAcl(), result.getAcl()); } + + @Test + void importCreatesDocumentAssociatedWithVocabulary() { + final String skos = + "@prefix skos : . " + + "@prefix dc : . " + + " a skos:ConceptScheme ; dc:title \"Test\"@en . " + + " a skos:Concept ; skos:inScheme . "; + + + final MultipartFile mf = new MockMultipartFile( + "file", + "thesaurus", + "text/turtle", + skos.getBytes(StandardCharsets.UTF_8) + ); + + final Vocabulary v = sut.importVocabulary(true, mf); + assertNotNull(v.getDocument()); + assertNotNull(em.find(Document.class, v.getDocument().getUri())); + assertEquals("Document for " + v.getLabel(), v.getDocument().getLabel()); + } }