Skip to content

Commit

Permalink
Merge pull request #282 from kbss-cvut/development
Browse files Browse the repository at this point in the history
[3.1.3] Release
  • Loading branch information
ledsoft authored Aug 5, 2024
2 parents ca4ee3f + 548fd6e commit 3cff9fd
Show file tree
Hide file tree
Showing 30 changed files with 699 additions and 370 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
</parent>

<artifactId>termit</artifactId>
<version>3.1.2</version>
<version>3.1.3</version>
<name>TermIt</name>
<description>Terminology manager based on Semantic Web technologies.</description>
<packaging>${packaging}</packaging>
Expand Down
30 changes: 27 additions & 3 deletions src/main/java/cz/cvut/kbss/termit/persistence/dao/DataDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import cz.cvut.kbss.termit.util.Configuration;
import cz.cvut.kbss.termit.util.Configuration.Persistence;
import cz.cvut.kbss.termit.util.TypeAwareResource;
import jakarta.annotation.Nullable;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.model.ValueFactory;
import org.eclipse.rdf4j.repository.RepositoryConnection;
Expand All @@ -41,7 +42,13 @@

import java.io.ByteArrayOutputStream;
import java.net.URI;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

@Repository
Expand Down Expand Up @@ -139,13 +146,30 @@ public Optional<RdfsResource> find(URI id) {
/**
* Gets the {@link RDFS#LABEL} of a resource with the specified identifier.
* <p>
* Note that the label has to have matching language tag or no language tag at all (matching tag is preferred).
* Note that the label has to have language tag matching the configured persistence unit language
* or no language tag at all (matching tag is preferred).
*
* @param id Resource ({@link RDFS#RESOURCE}) identifier
* @return Matching resource identifier (if found)
*/
public Optional<String> getLabel(URI id) {
return getLabel(id, null);
}

/**
* Gets the {@link RDFS#LABEL} of a resource with the specified identifier.
* <p>
* Note that the label has to have matching language tag or no language tag at all (matching tag is preferred).
*
* @param id Resource ({@link RDFS#RESOURCE}) identifier
* @param language Label language, if null, configured persistence unit language is used instead
* @return Matching resource identifier (if found)
*/
public Optional<String> getLabel(URI id, @Nullable String language) {
Objects.requireNonNull(id);
if(language == null) {
language = config.getLanguage();
}
if (!id.isAbsolute()) {
return Optional.of(id.toString());
}
Expand All @@ -159,7 +183,7 @@ public Optional<String> getLabel(URI id) {
String.class)
.setParameter("x", id).setParameter("has-label", RDFS_LABEL)
.setParameter("has-title", URI.create(DC.Terms.TITLE))
.setParameter("tag", config.getLanguage(), null).getSingleResult());
.setParameter("tag", language, null).getSingleResult());
} catch (NoResultException | NoUniqueResultException e) {
return Optional.empty();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import cz.cvut.kbss.jopa.model.EntityManager;
import cz.cvut.kbss.termit.exception.PersistenceException;
import cz.cvut.kbss.termit.model.PasswordChangeRequest;
import cz.cvut.kbss.termit.util.Configuration;
import cz.cvut.kbss.termit.model.UserAccount;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

Expand All @@ -13,19 +13,16 @@
@Repository
public class PasswordChangeRequestDao extends BaseDao<PasswordChangeRequest> {

private final Configuration.Persistence persistenceConfig;

@Autowired
public PasswordChangeRequestDao(EntityManager em, Configuration configuration) {
public PasswordChangeRequestDao(EntityManager em) {
super(PasswordChangeRequest.class, em);
this.persistenceConfig = configuration.getPersistence();
}

public List<PasswordChangeRequest> findAllByUsername(String username) {
Objects.requireNonNull(username);
public List<PasswordChangeRequest> findAllByUserAccount(UserAccount userAccount) {
Objects.requireNonNull(userAccount);
try {
return em.createQuery("SELECT DISTINCT t FROM " + type.getSimpleName() + " t WHERE t.userAccount.username = :username", type)
.setParameter("username", username, persistenceConfig.getLanguage())
return em.createQuery("SELECT DISTINCT t FROM " + type.getSimpleName() + " t WHERE t.userAccount = :userAccount", type)
.setParameter("userAccount", userAccount)
.getResultList();
} catch (RuntimeException e) {
throw new PersistenceException(e);
Expand Down
21 changes: 15 additions & 6 deletions src/main/java/cz/cvut/kbss/termit/persistence/dao/TermDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,8 @@ public List<TermDto> findAllIncludingImported(Vocabulary vocabulary) {

/**
* Loads a page of root terms (terms without a parent) contained in the specified vocabulary.
* <p>
* Terms with a label in the instance language are prepended.
*
* @param vocabulary Vocabulary whose root terms should be returned
* @param pageSpec Page specification
Expand All @@ -401,13 +403,14 @@ public List<TermDto> findAllRoots(Vocabulary vocabulary, Pageable pageSpec, Coll
Objects.requireNonNull(vocabulary);
Objects.requireNonNull(pageSpec);
TypedQuery<TermDto> query = em.createNativeQuery("SELECT DISTINCT ?term WHERE {" +
"SELECT DISTINCT ?term ?hasLocaleLabel WHERE {" +
"GRAPH ?context { " +
"?term a ?type ;" +
"?hasLabel ?label ." +
"?vocabulary ?hasGlossary/?hasTerm ?term ." +
"FILTER (lang(?label) = ?labelLang) ." +
"BIND((lang(?label) = ?labelLang) as ?hasLocaleLabel) ." +
"FILTER (?term NOT IN (?included))" +
"}} ORDER BY " + orderSentence("?label"),
"}} ORDER BY DESC(?hasLocaleLabel) lang(?label) " + orderSentence("?label") + "}",
TermDto.class);
query = setCommonFindAllRootsQueryParams(query, false);
try {
Expand Down Expand Up @@ -453,6 +456,8 @@ private static String r(String string, String from, String to) {

/**
* Loads a page of root terms (terms without a parent).
* <p>
* Terms with a label in the instance language are prepended.
*
* @param pageSpec Page specification
* @param includeTerms Identifiers of terms which should be a part of the result. Optional
Expand All @@ -462,13 +467,14 @@ private static String r(String string, String from, String to) {
public List<TermDto> findAllRoots(Pageable pageSpec, Collection<URI> includeTerms) {
Objects.requireNonNull(pageSpec);
TypedQuery<TermDto> query = em.createNativeQuery("SELECT DISTINCT ?term WHERE {" +
"SELECT DISTINCT ?term ?hasLocaleLabel WHERE {" +
"?term a ?type ; " +
"?hasLabel ?label . " +
"?vocabulary ?hasGlossary/?hasTerm ?term . " +
"FILTER (lang(?label) = ?labelLang) . " +
"BIND((lang(?label) = ?labelLang) as ?hasLocaleLabel) ." +
"FILTER (?term NOT IN (?included)) . " +
"FILTER NOT EXISTS {?term a ?snapshot .} " +
"} ORDER BY " + orderSentence("?label"),
"} ORDER BY DESC(?hasLocaleLabel) lang(?label) " + orderSentence("?label") + "}",
TermDto.class);
query = setCommonFindAllRootsQueryParams(query, false);
try {
Expand Down Expand Up @@ -533,6 +539,8 @@ private void recursivelyLoadParentTermSubTerms(TermDto term) {
* <p>
* This method basically does a transitive closure of the vocabulary import relationship and retrieves a page of
* root terms from this closure.
* <p>
* Terms with a label in the instance language are prepended.
*
* @param vocabulary The last vocabulary in the vocabulary import chain
* @param pageSpec Page specification
Expand All @@ -544,13 +552,14 @@ public List<TermDto> findAllRootsIncludingImports(Vocabulary vocabulary, Pageabl
Objects.requireNonNull(vocabulary);
Objects.requireNonNull(pageSpec);
TypedQuery<TermDto> query = em.createNativeQuery("SELECT DISTINCT ?term WHERE {" +
"SELECT DISTINCT ?term ?hasLocaleLabel WHERE {" +
"?term a ?type ;" +
"?hasLabel ?label ." +
"?vocabulary ?imports* ?parent ." +
"?parent ?hasGlossary/?hasTerm ?term ." +
"FILTER (lang(?label) = ?labelLang) ." +
"BIND((lang(?label) = ?labelLang) as ?hasLocaleLabel) ." +
"FILTER (?term NOT IN (?included))" +
"} ORDER BY " + orderSentence("?label"),
"} ORDER BY DESC(?hasLocaleLabel) lang(?label) " + orderSentence("?label") + "}",
TermDto.class);
query = setCommonFindAllRootsQueryParams(query, true);
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import cz.cvut.kbss.termit.model.validation.ValidationResult;
import cz.cvut.kbss.termit.persistence.context.VocabularyContextMapper;
import cz.cvut.kbss.termit.util.Configuration;
import cz.cvut.kbss.termit.util.Constants;
import cz.cvut.kbss.termit.util.Utils;
import org.apache.jena.rdf.model.Literal;
import org.apache.jena.rdf.model.Model;
Expand Down Expand Up @@ -113,7 +114,7 @@ private Model initValidationModel(com.github.sgov.server.Validator validator, St
// 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())
.toList()
);
final Model validationModel = com.github.sgov.server.Validator.getRulesModel(rules);
loadOverrideRules(validationModel, language);
Expand Down Expand Up @@ -152,7 +153,8 @@ public List<ValidationResult> validate(final Collection<URI> vocabularyIris) {
final MultilingualString messages = new MultilingualString(result.getMessages().stream()
.map(RDFNode::asLiteral)
.collect(Collectors.toMap(
Literal::getLanguage,
lit -> lit.getLanguage().isBlank() ?
Constants.DEFAULT_LANGUAGE : lit.getLanguage(),
Literal::getLexicalForm)));

return new ValidationResult()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,15 @@
import cz.cvut.kbss.termit.model.UserAccount;
import cz.cvut.kbss.termit.security.SecurityConstants;
import cz.cvut.kbss.termit.service.business.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Profile;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
Expand All @@ -41,12 +45,12 @@
* Available only if internal security is used.
*/
@ConditionalOnProperty(prefix = "termit.security", name = "provider", havingValue = "internal", matchIfMissing = true)
@Tag(name = "Admin User Registration", description = "Allows admins to register new users.")
@RestController
@RequestMapping("/users")
@Profile("admin-registration-only")
@RequestMapping("/admin/users")
public class AdminBasedRegistrationController {

private static final Logger LOG = LoggerFactory.getLogger(FreeRegistrationController.class);
private static final Logger LOG = LoggerFactory.getLogger(AdminBasedRegistrationController.class);

private final UserService userService;

Expand All @@ -56,11 +60,17 @@ public AdminBasedRegistrationController(UserService userService) {
LOG.debug("Instantiating admin-based registration controller.");
}

@Operation(security = {@SecurityRequirement(name="bearer-key")},
description = "Creates a new user account. If the password is blank, the account is locked, and an email will be sent to the new user with a link to create a password.")
@ApiResponses({
@ApiResponse(responseCode = "201", description = "User created"),
@ApiResponse(responseCode = "409", description = "User data are invalid")
})
@PreAuthorize("hasRole('" + SecurityConstants.ROLE_ADMIN + "')")
@PostMapping(consumes = {MediaType.APPLICATION_JSON_VALUE, JsonLd.MEDIA_TYPE})
public ResponseEntity<Void> createUser(@RequestBody UserAccount user) {
userService.persist(user);
LOG.info("User {} successfully registered.", user);
userService.adminCreateUser(user);
LOG.info("User {} successfully registered by {}.", user, userService.getCurrent().getUsername());
return new ResponseEntity<>(HttpStatus.CREATED);
}
}
14 changes: 11 additions & 3 deletions src/main/java/cz/cvut/kbss/termit/rest/DataController.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,12 @@
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.net.URI;
import java.util.List;
Expand Down Expand Up @@ -92,8 +97,11 @@ public RdfsResource getById(@Parameter(description = "Identifier of the resource
})
@GetMapping(value = "/label")
public String getLabel(@Parameter(description = "Resource identifier.")
@RequestParam("iri") URI id) {
return dataService.getLabel(id).orElseThrow(
@RequestParam("iri") URI id,
@Parameter(description = "Label language")
@RequestParam(value = "language", required = false) String language
) {
return dataService.getLabel(id, language).orElseThrow(
() -> new NotFoundException("Resource with id " + id + " not found or it has no matching label."));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import cz.cvut.kbss.jsonld.JsonLd;
import cz.cvut.kbss.termit.dto.PasswordChangeDto;
import cz.cvut.kbss.termit.service.business.PasswordChangeService;
import cz.cvut.kbss.termit.service.business.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
Expand Down Expand Up @@ -30,11 +30,11 @@
public class PasswordChangeController {
private static final Logger LOG = LoggerFactory.getLogger(PasswordChangeController.class);

private final PasswordChangeService passwordChangeService;
private final UserService userService;

@Autowired
public PasswordChangeController(PasswordChangeService passwordChangeService) {
this.passwordChangeService = passwordChangeService;
public PasswordChangeController(UserService userService) {
this.userService = userService;
}

@Operation(description = "Requests a password reset for the specified username.")
Expand All @@ -47,7 +47,7 @@ public PasswordChangeController(PasswordChangeService passwordChangeService) {
public ResponseEntity<Void> requestPasswordReset(
@Parameter(description = "Username of the user") @RequestBody String username) {
LOG.info("Password reset requested for user {}.", username);
passwordChangeService.requestPasswordReset(username);
userService.requestPasswordReset(username);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}

Expand All @@ -62,7 +62,7 @@ public ResponseEntity<Void> changePassword(
@Parameter(
description = "Token with URI for password reset") @RequestBody PasswordChangeDto passwordChangeDto) {
LOG.info("Password change requested with token {}", passwordChangeDto.getToken());
passwordChangeService.changePassword(passwordChangeDto);
userService.changePassword(passwordChangeDto);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
}
Loading

0 comments on commit 3cff9fd

Please sign in to comment.