From 0cf265142e77da5ff38ed771ab78afbb7117e6fe Mon Sep 17 00:00:00 2001 From: Milan Kuchtiak Date: Wed, 3 Jun 2026 15:01:06 +0200 Subject: [PATCH] Issue 1343: add PUT and DELETE endpoint methods to ClarinLicenseLabel REST repository (ufal/clarin-dspace#1357) * Issue 1343: add PUT and DELETE endpoint methods to ClarinLicenseLabelRest repository * resolve Copilot comments * fixed PUT request in ClarinLicenseLabelRestRepository * added check for Clarin License Label -> Label string to be shorter that 5 characters * implement coorrect put method in ClarinLicenseLabelRestRepository * add constraints to license_label table: made label UNIQUE, make license_label not deletable when used in clarin licenses * fixed IT test failure * change order of deleting objects in test cleanup(): delete license objects before license_label objects (to satisfy license_label constraints) * resolve PR Copilot comments, fixed failing ClarinWorkspaceItemRestRepositoryIT * prevent creating duplicate Clarin License Labels in REST API * not necessary to trim label twice * minor fixes, suggested by Copilot * Rename SQL migration files to use today's date (2026.06.01) --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> (cherry picked from commit b041c904b9b43770c668bc21086a407a6c5e3d1c) --- .../clarin/ClarinLicenseLabelServiceImpl.java | 5 + .../clarin/ClarinLicenseServiceImpl.java | 5 + .../content/dao/clarin/ClarinLicenseDAO.java | 2 + .../dao/clarin/ClarinLicenseLabelDAO.java | 5 + .../dao/impl/clarin/ClarinLicenseDAOImpl.java | 20 ++ .../clarin/ClarinLicenseLabelDAOImpl.java | 17 ++ .../clarin/ClarinLicenseLabelService.java | 10 + .../service/clarin/ClarinLicenseService.java | 10 + ...6_2026.06.01__license_label_constraint.sql | 18 ++ ...6_2026.06.01__license_label_constraint.sql | 18 ++ .../util/AbstractBuilderCleanupUtil.java | 2 + .../ClarinLicenseLabelNotFoundException.java | 27 +++ .../ClarinLicenseLabelRestRepository.java | 109 ++++++++- .../ClarinLicenseLabelRestRepositoryIT.java | 222 +++++++++++++++++- .../ClarinWorkspaceItemRestRepositoryIT.java | 13 +- .../dspace/app/rest/ProvenanceServiceIT.java | 21 +- 16 files changed, 464 insertions(+), 40 deletions(-) create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2026.06.01__license_label_constraint.sql create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2026.06.01__license_label_constraint.sql create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/ClarinLicenseLabelNotFoundException.java diff --git a/dspace-api/src/main/java/org/dspace/content/clarin/ClarinLicenseLabelServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/clarin/ClarinLicenseLabelServiceImpl.java index fce90020aafb..a56be61d6942 100644 --- a/dspace-api/src/main/java/org/dspace/content/clarin/ClarinLicenseLabelServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/clarin/ClarinLicenseLabelServiceImpl.java @@ -77,6 +77,11 @@ public List findAll(Context context) throws SQLException, Au return clarinLicenseLabelDAO.findAll(context, ClarinLicenseLabel.class); } + @Override + public ClarinLicenseLabel findByLabel(Context context, String label) throws SQLException { + return clarinLicenseLabelDAO.findByLabel(context, label); + } + @Override public void delete(Context context, ClarinLicenseLabel license) throws SQLException, AuthorizeException { if (!authorizeService.isAdmin(context)) { diff --git a/dspace-api/src/main/java/org/dspace/content/clarin/ClarinLicenseServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/clarin/ClarinLicenseServiceImpl.java index 10f209df7d04..e87f9365ccb8 100644 --- a/dspace-api/src/main/java/org/dspace/content/clarin/ClarinLicenseServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/clarin/ClarinLicenseServiceImpl.java @@ -98,6 +98,11 @@ public List findByNameLike(Context context, String name) throws S return clarinLicenseDAO.findByNameLike(context, name); } + @Override + public List findByLabel(Context context, String label) throws SQLException { + return clarinLicenseDAO.findByLabel(context, label); + } + @Override public void addLicenseMetadataToItem(Context context, ClarinLicense clarinLicense, Item item) throws SQLException { if (Objects.isNull(clarinLicense) || Objects.isNull(item)) { diff --git a/dspace-api/src/main/java/org/dspace/content/dao/clarin/ClarinLicenseDAO.java b/dspace-api/src/main/java/org/dspace/content/dao/clarin/ClarinLicenseDAO.java index 99147af64e65..9054947a9054 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/clarin/ClarinLicenseDAO.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/clarin/ClarinLicenseDAO.java @@ -28,4 +28,6 @@ public interface ClarinLicenseDAO extends GenericDAO { List findByNameLike(Context context, String name) throws SQLException; + List findByLabel(Context context, String label) throws SQLException; + } diff --git a/dspace-api/src/main/java/org/dspace/content/dao/clarin/ClarinLicenseLabelDAO.java b/dspace-api/src/main/java/org/dspace/content/dao/clarin/ClarinLicenseLabelDAO.java index 1abd25b7a96a..d9444244e1c3 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/clarin/ClarinLicenseLabelDAO.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/clarin/ClarinLicenseLabelDAO.java @@ -7,7 +7,10 @@ */ package org.dspace.content.dao.clarin; +import java.sql.SQLException; + import org.dspace.content.clarin.ClarinLicenseLabel; +import org.dspace.core.Context; import org.dspace.core.GenericDAO; /** @@ -19,4 +22,6 @@ * @author Milan Majchrak (milan.majchrak at dataquest.sk) */ public interface ClarinLicenseLabelDAO extends GenericDAO { + + ClarinLicenseLabel findByLabel(Context context, String label) throws SQLException; } diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/clarin/ClarinLicenseDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/clarin/ClarinLicenseDAOImpl.java index 24bbe180307c..93887a6918e2 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/clarin/ClarinLicenseDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/clarin/ClarinLicenseDAOImpl.java @@ -12,9 +12,13 @@ import javax.persistence.Query; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; +import javax.persistence.criteria.SetJoin; import org.dspace.content.clarin.ClarinLicense; +import org.dspace.content.clarin.ClarinLicenseLabel; +import org.dspace.content.clarin.ClarinLicenseLabel_; import org.dspace.content.clarin.ClarinLicense_; import org.dspace.content.dao.clarin.ClarinLicenseDAO; import org.dspace.core.AbstractHibernateDAO; @@ -54,4 +58,20 @@ public List findByNameLike(Context context, String name) throws S criteriaQuery.orderBy(criteriaBuilder.asc(clarinLicenseRoot.get(ClarinLicense_.name))); return list(context, criteriaQuery, false, ClarinLicense.class, -1, -1); } + + @Override + public List findByLabel(Context context, String label) throws SQLException { + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, ClarinLicense.class); + Root clarinLicenseRoot = criteriaQuery.from(ClarinLicense.class); + + SetJoin labelJoin = + clarinLicenseRoot.joinSet(ClarinLicense_.CLARIN_LICENSE_LABELS); + + Predicate labelPredicate = criteriaBuilder.equal(labelJoin.get(ClarinLicenseLabel_.LABEL), label); + + criteriaQuery.select(clarinLicenseRoot).where(labelPredicate); + + return list(context, criteriaQuery, false, ClarinLicense.class, -1, -1); + } } diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/clarin/ClarinLicenseLabelDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/clarin/ClarinLicenseLabelDAOImpl.java index 1bf2179a3935..cb52af7c683d 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/clarin/ClarinLicenseLabelDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/clarin/ClarinLicenseLabelDAOImpl.java @@ -7,9 +7,16 @@ */ package org.dspace.content.dao.impl.clarin; +import java.sql.SQLException; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Root; + import org.dspace.content.clarin.ClarinLicenseLabel; +import org.dspace.content.clarin.ClarinLicenseLabel_; import org.dspace.content.dao.clarin.ClarinLicenseLabelDAO; import org.dspace.core.AbstractHibernateDAO; +import org.dspace.core.Context; /** * Hibernate implementation of the Database Access Object interface class for the Clarin License Label object. @@ -23,4 +30,14 @@ public class ClarinLicenseLabelDAOImpl extends AbstractHibernateDAO criteriaQuery = getCriteriaQuery(criteriaBuilder, ClarinLicenseLabel.class); + Root cllRoot = criteriaQuery.from(ClarinLicenseLabel.class); + criteriaQuery.select(cllRoot); + criteriaQuery.where(criteriaBuilder.equal(cllRoot.get(ClarinLicenseLabel_.label), label)); + return uniqueResult(context, criteriaQuery, true, ClarinLicenseLabel.class); + } } diff --git a/dspace-api/src/main/java/org/dspace/content/service/clarin/ClarinLicenseLabelService.java b/dspace-api/src/main/java/org/dspace/content/service/clarin/ClarinLicenseLabelService.java index adb56ecc238d..32e794622aad 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/clarin/ClarinLicenseLabelService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/clarin/ClarinLicenseLabelService.java @@ -52,6 +52,16 @@ ClarinLicenseLabel create(Context context, ClarinLicenseLabel clarinLicenseLabel */ ClarinLicenseLabel find(Context context, int valueId) throws SQLException; + /** + * Find the clarin license label object by label name + * + * @param context DSpace context object + * @param label label name of the searching clarin license label object + * @return found clarin license label object or null + * @throws SQLException if database error + */ + ClarinLicenseLabel findByLabel(Context context, String label) throws SQLException; + /** * Find all clarin license label objects * @param context DSpace context object diff --git a/dspace-api/src/main/java/org/dspace/content/service/clarin/ClarinLicenseService.java b/dspace-api/src/main/java/org/dspace/content/service/clarin/ClarinLicenseService.java index 93fbe88df3cb..1053d5741471 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/clarin/ClarinLicenseService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/clarin/ClarinLicenseService.java @@ -77,6 +77,16 @@ public interface ClarinLicenseService { */ List findByNameLike(Context context, String name) throws SQLException; + /** + * Find Clarin Licenses by the license label. + * + * @param context DSpace context object + * @param label the license label + * @return List of clarin licenses which contain the specified license label. + * @throws SQLException if database error + */ + List findByLabel(Context context, String label) throws SQLException; + void addLicenseMetadataToItem(Context context, ClarinLicense clarinLicense, Item item) throws SQLException; void clearLicenseMetadataFromItem(Context context, Item item) throws SQLException; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2026.06.01__license_label_constraint.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2026.06.01__license_label_constraint.sql new file mode 100644 index 000000000000..01bd6a493d3c --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2026.06.01__license_label_constraint.sql @@ -0,0 +1,18 @@ +-- +-- The contents of this file are subject to the license and copyright +-- detailed in the LICENSE and NOTICE files at the root of the source +-- tree and available online at +-- +-- http://www.dspace.org/license/ +-- + +ALTER TABLE license_label_extended_mapping + DROP CONSTRAINT IF EXISTS license_label_license_label_extended_mapping_fk; + +-- here the "ON DELETE RESTRICT" clause (default clause) is used, which prevents deletion of a license_label record +-- when there are any license_label_extended_mapping records that reference it +ALTER TABLE license_label_extended_mapping + ADD CONSTRAINT license_label_license_label_extended_mapping_fk FOREIGN KEY (label_id) REFERENCES license_label(label_id); + +ALTER TABLE license_label DROP CONSTRAINT IF EXISTS license_label_label_unique; +ALTER TABLE license_label ADD CONSTRAINT license_label_label_unique UNIQUE(label); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2026.06.01__license_label_constraint.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2026.06.01__license_label_constraint.sql new file mode 100644 index 000000000000..01bd6a493d3c --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2026.06.01__license_label_constraint.sql @@ -0,0 +1,18 @@ +-- +-- The contents of this file are subject to the license and copyright +-- detailed in the LICENSE and NOTICE files at the root of the source +-- tree and available online at +-- +-- http://www.dspace.org/license/ +-- + +ALTER TABLE license_label_extended_mapping + DROP CONSTRAINT IF EXISTS license_label_license_label_extended_mapping_fk; + +-- here the "ON DELETE RESTRICT" clause (default clause) is used, which prevents deletion of a license_label record +-- when there are any license_label_extended_mapping records that reference it +ALTER TABLE license_label_extended_mapping + ADD CONSTRAINT license_label_license_label_extended_mapping_fk FOREIGN KEY (label_id) REFERENCES license_label(label_id); + +ALTER TABLE license_label DROP CONSTRAINT IF EXISTS license_label_label_unique; +ALTER TABLE license_label ADD CONSTRAINT license_label_label_unique UNIQUE(label); diff --git a/dspace-api/src/test/java/org/dspace/builder/util/AbstractBuilderCleanupUtil.java b/dspace-api/src/test/java/org/dspace/builder/util/AbstractBuilderCleanupUtil.java index 7ff2ff720017..00190464b5dd 100644 --- a/dspace-api/src/test/java/org/dspace/builder/util/AbstractBuilderCleanupUtil.java +++ b/dspace-api/src/test/java/org/dspace/builder/util/AbstractBuilderCleanupUtil.java @@ -17,6 +17,7 @@ import org.dspace.builder.BitstreamFormatBuilder; import org.dspace.builder.BundleBuilder; import org.dspace.builder.ClaimedTaskBuilder; +import org.dspace.builder.ClarinLicenseBuilder; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; import org.dspace.builder.EPersonBuilder; @@ -85,6 +86,7 @@ private void initMap() { map.put(SiteBuilder.class.getName(), new ArrayList<>()); map.put(ProcessBuilder.class.getName(), new ArrayList<>()); map.put(PreviewContentBuilder.class.getName(), new ArrayList<>()); + map.put(ClarinLicenseBuilder.class.getName(), new ArrayList<>()); } /** diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/ClarinLicenseLabelNotFoundException.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/ClarinLicenseLabelNotFoundException.java new file mode 100644 index 000000000000..88da6a6f24aa --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/ClarinLicenseLabelNotFoundException.java @@ -0,0 +1,27 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.exception; + +import javax.ws.rs.NotFoundException; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +/** + * Exception thrown when Clarin License Label not found + * + * @author Milan Kuchtiak + */ +@ResponseStatus(HttpStatus.NOT_FOUND) +public class ClarinLicenseLabelNotFoundException extends NotFoundException { + + public ClarinLicenseLabelNotFoundException(String message) { + super(message); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClarinLicenseLabelRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClarinLicenseLabelRestRepository.java index 061ef5807529..f6c59fcd3f4d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClarinLicenseLabelRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClarinLicenseLabelRestRepository.java @@ -13,14 +13,19 @@ import java.sql.SQLException; import java.util.List; import java.util.Objects; +import java.util.Optional; +import javax.servlet.http.HttpServletRequest; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import org.dspace.app.rest.exception.ClarinLicenseLabelNotFoundException; import org.dspace.app.rest.exception.DSpaceBadRequestException; -import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.ClarinLicenseLabelRest; import org.dspace.authorize.AuthorizeException; +import org.dspace.content.clarin.ClarinLicense; import org.dspace.content.clarin.ClarinLicenseLabel; import org.dspace.content.service.clarin.ClarinLicenseLabelService; +import org.dspace.content.service.clarin.ClarinLicenseService; import org.dspace.core.Context; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; @@ -36,9 +41,17 @@ @Component(ClarinLicenseLabelRest.CATEGORY + "." + ClarinLicenseLabelRest.NAME) public class ClarinLicenseLabelRestRepository extends DSpaceRestRepository { + private static final int MAX_LABEL_LENGTH = 5; + + @Autowired + ClarinLicenseService clarinLicenseService; + @Autowired ClarinLicenseLabelService clarinLicenseLabelService; + @Autowired + ObjectMapper objectMapper; + @Override public ClarinLicenseLabelRest findOne(Context context, Integer id) { ClarinLicenseLabel clarinLicenseLabel; @@ -72,7 +85,7 @@ protected ClarinLicenseLabelRest createAndReturn(Context context) // parse request body ClarinLicenseLabelRest clarinLicenseLabelRest; try { - clarinLicenseLabelRest = new ObjectMapper().readValue( + clarinLicenseLabelRest = objectMapper.readValue( getRequestService().getCurrentRequest().getHttpServletRequest().getInputStream(), ClarinLicenseLabelRest.class ); @@ -80,27 +93,99 @@ protected ClarinLicenseLabelRest createAndReturn(Context context) throw new DSpaceBadRequestException("error parsing request body", excIO); } - // validate fields - if (isBlank(clarinLicenseLabelRest.getLabel()) || isBlank(clarinLicenseLabelRest.getTitle())) { - throw new UnprocessableEntityException("CLARIN License Label title, label, icon cannot be null or empty"); + checkLabelAndTitle(clarinLicenseLabelRest); + if (clarinLicenseLabelService.findByLabel(context, clarinLicenseLabelRest.getLabel().trim()) != null) { + throw new DSpaceBadRequestException("Clarin License Label with label " + clarinLicenseLabelRest.getLabel() + + " already exists"); } // create ClarinLicenseLabel clarinLicenseLabel; clarinLicenseLabel = clarinLicenseLabelService.create(context); -// if (Objects.nonNull(clarinLicenseLabelRest.getId())) { -// clarinLicenseLabel.setId(clarinLicenseLabelRest.getId()); -// } - clarinLicenseLabel.setLabel(clarinLicenseLabelRest.getLabel()); - clarinLicenseLabel.setTitle(clarinLicenseLabelRest.getTitle()); - clarinLicenseLabel.setIcon(clarinLicenseLabelRest.getIcon()); - clarinLicenseLabel.setExtended(clarinLicenseLabelRest.isExtended()); + updateClarinLicenseLabel(clarinLicenseLabel, clarinLicenseLabelRest); clarinLicenseLabelService.update(context, clarinLicenseLabel); // return return converter.toRest(clarinLicenseLabel, utils.obtainProjection()); } + @Override + @PreAuthorize("hasAuthority('ADMIN')") + public ClarinLicenseLabelRest put(Context context, + HttpServletRequest request, + String apiCategory, + String model, + Integer id, + JsonNode jsonNode) throws SQLException, AuthorizeException { + ClarinLicenseLabel clarinLicenseLabel = clarinLicenseLabelService.find(context, id); + if (Objects.isNull(clarinLicenseLabel)) { + throw new ClarinLicenseLabelNotFoundException("Clarin License Label with id " + id + " was not found"); + } + + // parse request body + ClarinLicenseLabelRest clarinLicenseLabelRest; + try { + clarinLicenseLabelRest = objectMapper.treeToValue(jsonNode, ClarinLicenseLabelRest.class); + } catch (IOException excIO) { + throw new DSpaceBadRequestException("error parsing request body", excIO); + } + + checkLabelAndTitle(clarinLicenseLabelRest); + + ClarinLicenseLabel clarinLicenseLabelWithSameLabel = clarinLicenseLabelService.findByLabel(context, + clarinLicenseLabelRest.getLabel().trim()); + if (clarinLicenseLabelWithSameLabel != null && !clarinLicenseLabelWithSameLabel.getID().equals(id)) { + throw new DSpaceBadRequestException("Clarin License Label with label " + clarinLicenseLabelRest.getLabel() + + " already exists"); + } + + clarinLicenseLabel.setId(id); + updateClarinLicenseLabel(clarinLicenseLabel, clarinLicenseLabelRest); + + clarinLicenseLabelService.update(context, clarinLicenseLabel); + + return converter.toRest(clarinLicenseLabel, utils.obtainProjection()); + } + + @Override + @PreAuthorize("hasAuthority('ADMIN')") + public void delete(Context context, Integer id) throws AuthorizeException { + ClarinLicenseLabel clarinLicenseLabel; + try { + clarinLicenseLabel = clarinLicenseLabelService.find(context, id); + if (Objects.isNull(clarinLicenseLabel)) { + throw new ClarinLicenseLabelNotFoundException("Clarin License Label with id " + id + " was not found"); + } + List licenses = clarinLicenseService.findByLabel(context, clarinLicenseLabel.getLabel()); + if (!licenses.isEmpty()) { + throw new DSpaceBadRequestException("Clarin License Label " + clarinLicenseLabel.getLabel() + + " is in use and cannot be deleted"); + } + clarinLicenseLabelService.delete(context, clarinLicenseLabel); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + private void checkLabelAndTitle(ClarinLicenseLabelRest clarinLicenseLabelRest) { + String label = Optional.ofNullable(clarinLicenseLabelRest.getLabel()).map(String::trim).orElse(null); + // validate fields + if (isBlank(label) || isBlank(clarinLicenseLabelRest.getTitle())) { + throw new DSpaceBadRequestException("CLARIN License Label title and label cannot be null or empty"); + } + if (label.length() > MAX_LABEL_LENGTH) { + throw new DSpaceBadRequestException( + "CLARIN License Label -> label string cannot be longer than " + MAX_LABEL_LENGTH + " characters"); + } + } + + private static void updateClarinLicenseLabel(ClarinLicenseLabel clarinLicenseLabel, + ClarinLicenseLabelRest clarinLicenseLabelRest) { + clarinLicenseLabel.setLabel(clarinLicenseLabelRest.getLabel().trim()); + clarinLicenseLabel.setTitle(clarinLicenseLabelRest.getTitle()); + clarinLicenseLabel.setIcon(clarinLicenseLabelRest.getIcon()); + clarinLicenseLabel.setExtended(clarinLicenseLabelRest.isExtended()); + } @Override public Class getDomainClass() { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinLicenseLabelRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinLicenseLabelRestRepositoryIT.java index 207142d58e7d..2b59e2030263 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinLicenseLabelRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinLicenseLabelRestRepositoryIT.java @@ -10,13 +10,17 @@ import static com.jayway.jsonpath.JsonPath.read; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.util.Objects; +import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import com.fasterxml.jackson.databind.ObjectMapper; @@ -25,9 +29,12 @@ import org.dspace.app.rest.model.ClarinLicenseLabelRest; import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.ClarinLicenseBuilder; import org.dspace.builder.ClarinLicenseLabelBuilder; +import org.dspace.content.clarin.ClarinLicense; import org.dspace.content.clarin.ClarinLicenseLabel; import org.dspace.content.service.clarin.ClarinLicenseLabelService; +import org.dspace.content.service.clarin.ClarinLicenseService; import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Before; @@ -41,12 +48,18 @@ */ public class ClarinLicenseLabelRestRepositoryIT extends AbstractControllerIntegrationTest { + @Autowired + ClarinLicenseService clarinLicenseService; + @Autowired ClarinLicenseLabelService clarinLicenseLabelService; @Autowired ClarinLicenseLabelConverter clarinLicenseLabelConverter; + @Autowired + private ObjectMapper objectMapper; + ClarinLicenseLabel firstCLicenseLabel; ClarinLicenseLabel secondCLicenseLabel; ClarinLicenseLabel thirdCLicenseLabel; @@ -107,24 +120,18 @@ public void findAll() throws Exception { @Test public void create() throws Exception { - // create a new clarin license label - context.turnOffAuthorisationSystem(); - ClarinLicenseLabel clarinLicenseLabel = ClarinLicenseLabelBuilder.createClarinLicenseLabel(context).build(); - clarinLicenseLabel.setLabel("new"); - clarinLicenseLabel.setExtended(true); - clarinLicenseLabel.setTitle("New CLL"); - clarinLicenseLabel.setIcon(new byte[100]); - - ClarinLicenseLabelRest clarinLicenseLabelRest = clarinLicenseLabelConverter.convert(clarinLicenseLabel, - Projection.DEFAULT); - context.restoreAuthSystemState(); + ClarinLicenseLabelRest clarinLicenseLabelRest = new ClarinLicenseLabelRest(); + clarinLicenseLabelRest.setLabel("new"); + clarinLicenseLabelRest.setExtended(true); + clarinLicenseLabelRest.setTitle("New CLL"); + clarinLicenseLabelRest.setIcon(new byte[100]); // id of created clarin license AtomicReference idRef = new AtomicReference<>(); String authTokenAdmin = getAuthToken(admin.getEmail(), password); try { getClient(authTokenAdmin).perform(post("/api/core/clarinlicenselabels") - .content(new ObjectMapper().writeValueAsBytes(clarinLicenseLabelRest)) + .content(objectMapper.writeValueAsBytes(clarinLicenseLabelRest)) .contentType(org.springframework.http.MediaType.APPLICATION_JSON)) .andExpect(status().isCreated()) .andExpect(jsonPath("$.label", is(clarinLicenseLabelRest.getLabel()))) @@ -146,4 +153,195 @@ public void create() throws Exception { } } } + + @Test + public void createWithLongLabel() throws Exception { + ClarinLicenseLabelRest clarinLicenseLabelRest = new ClarinLicenseLabelRest(); + clarinLicenseLabelRest.setLabel("LONG_LABEL"); + clarinLicenseLabelRest.setExtended(true); + clarinLicenseLabelRest.setTitle("LONG CLL"); + clarinLicenseLabelRest.setIcon(new byte[100]); + + String authTokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(authTokenAdmin).perform(post("/api/core/clarinlicenselabels") + .content(objectMapper.writeValueAsBytes(clarinLicenseLabelRest)) + .contentType(org.springframework.http.MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + } + + @Test + public void createWithDuplicateLabel() throws Exception { + ClarinLicenseLabelRest clarinLicenseLabelRest = new ClarinLicenseLabelRest(); + clarinLicenseLabelRest.setLabel(firstCLicenseLabel.getLabel()); + clarinLicenseLabelRest.setExtended(true); + clarinLicenseLabelRest.setTitle("Title 1"); + clarinLicenseLabelRest.setIcon(new byte[100]); + + String authTokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(authTokenAdmin).perform(post("/api/core/clarinlicenselabels") + .content(objectMapper.writeValueAsBytes(clarinLicenseLabelRest)) + .contentType(org.springframework.http.MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + } + + @Test + public void updateOk() throws Exception { + String authTokenAdmin = getAuthToken(admin.getEmail(), password); + Integer clarinLicenseLabelId = null; + try { + context.turnOffAuthorisationSystem(); + ClarinLicenseLabel clarinLicenseLabel = ClarinLicenseLabelBuilder.createClarinLicenseLabel(context).build(); + clarinLicenseLabel.setLabel("CLL"); + clarinLicenseLabel.setExtended(true); + clarinLicenseLabel.setTitle("CLL Title4"); + clarinLicenseLabelService.update(context, clarinLicenseLabel); + + clarinLicenseLabelId = Objects.requireNonNull(clarinLicenseLabel.getID()); + context.restoreAuthSystemState(); + + ClarinLicenseLabelRest clarinLicenseLabelRest = clarinLicenseLabelConverter.convert(clarinLicenseLabel, + Projection.DEFAULT); + clarinLicenseLabelRest.setLabel("UPDATED CLL"); + clarinLicenseLabelRest.setTitle("Updated CLL Title"); + clarinLicenseLabelRest.setExtended(false); + + // test if the id from the path is used instead of the id from the body + clarinLicenseLabelRest.setId(999); + + // check if update ends with Bad Request since the label length is greater than 5 + getClient(authTokenAdmin).perform(put("/api/core/clarinlicenselabels/" + clarinLicenseLabelId) + .content(objectMapper.writeValueAsBytes(clarinLicenseLabelRest)) + .contentType(org.springframework.http.MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + + // set the label to valid value and check if the update is successful + clarinLicenseLabelRest.setLabel(" CLL-X "); + getClient(authTokenAdmin).perform(put("/api/core/clarinlicenselabels/" + clarinLicenseLabelId) + .content(objectMapper.writeValueAsBytes(clarinLicenseLabelRest)) + .contentType(org.springframework.http.MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(clarinLicenseLabelId))) + .andExpect(jsonPath("$.label", is("CLL-X"))) + .andExpect(jsonPath("$.title", is("Updated CLL Title"))) + .andExpect(jsonPath("$.extended", is(false))) + .andExpect(jsonPath("$.icon", nullValue())) + .andExpect(jsonPath("$.type", is(ClarinLicenseLabelRest.NAME))); + + getClient(authTokenAdmin).perform(get("/api/core/clarinlicenselabels/" + clarinLicenseLabelId)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(clarinLicenseLabelId))) + .andExpect(jsonPath("$.label", is("CLL-X"))) + .andExpect(jsonPath("$.title", is("Updated CLL Title"))) + .andExpect(jsonPath("$.extended", is(false))) + .andExpect(jsonPath("$.icon", nullValue())) + .andExpect(jsonPath("$.type", is(ClarinLicenseLabelRest.NAME))); + } finally { + ClarinLicenseLabelBuilder.deleteClarinLicenseLabel(clarinLicenseLabelId); + } + } + + @Test + public void updateNotFound() throws Exception { + String authTokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(authTokenAdmin).perform(put("/api/core/clarinlicenselabels/999") + .content("{}") + .contentType(org.springframework.http.MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + @Test + public void updateInvalidBody() throws Exception { + String authTokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(authTokenAdmin).perform(put("/api/core/clarinlicenselabels/" + firstCLicenseLabel.getID()) + .content("{\"label\": \"lbl\", \"invalid_property\": 0}") + .contentType(org.springframework.http.MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + } + + @Test + public void updateMissingTitle() throws Exception { + String authTokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(authTokenAdmin).perform(put("/api/core/clarinlicenselabels/" + firstCLicenseLabel.getID()) + .content("{\"label\": \"test label\"}") + .contentType(org.springframework.http.MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + } + + @Test + public void updateNotAuthorized() throws Exception { + getClient().perform(put("/api/core/clarinlicenselabels/999") + .content("{}") + .contentType(org.springframework.http.MediaType.APPLICATION_JSON)) + .andExpect(status().isUnauthorized()); + } + + @Test + public void updateWithDuplicateLabel() throws Exception { + ClarinLicenseLabelRest clarinLicenseLabelRest = new ClarinLicenseLabelRest(); + clarinLicenseLabelRest.setLabel(firstCLicenseLabel.getLabel()); + clarinLicenseLabelRest.setExtended(true); + clarinLicenseLabelRest.setTitle("Title 1"); + clarinLicenseLabelRest.setIcon(new byte[100]); + + String authTokenAdmin = getAuthToken(admin.getEmail(), password); + + getClient(authTokenAdmin).perform(put("/api/core/clarinlicenselabels/" + secondCLicenseLabel.getID()) + .content(objectMapper.writeValueAsBytes(clarinLicenseLabelRest)) + .contentType(org.springframework.http.MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + + // set the label to the same value as the secondCLicenseLabel and check if the update is successful + clarinLicenseLabelRest.setLabel(secondCLicenseLabel.getLabel()); + getClient(authTokenAdmin).perform(put("/api/core/clarinlicenselabels/" + secondCLicenseLabel.getID()) + .content(objectMapper.writeValueAsBytes(clarinLicenseLabelRest)) + .contentType(org.springframework.http.MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + } + + @Test + public void deleteNotAuthorized() throws Exception { + getClient().perform(delete("/api/core/clarinlicenselabels/" + firstCLicenseLabel.getID())) + .andExpect(status().isUnauthorized()); + } + + @Test + public void deleteOk() throws Exception { + String authTokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(authTokenAdmin).perform(delete("/api/core/clarinlicenselabels/" + firstCLicenseLabel.getID())) + .andExpect(status().isNoContent()); + } + + @Test + public void deleteNotFound() throws Exception { + String authTokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(authTokenAdmin).perform(delete("/api/core/clarinlicenselabels/999")) + .andExpect(status().isNotFound()); + } + + @Test + public void deleteForLabelUsed() throws Exception { + // create ClarinLicense + context.turnOffAuthorisationSystem(); + ClarinLicense firstCLicense = ClarinLicenseBuilder.createClarinLicense(context).build(); + firstCLicense.setName("CL Name1"); + firstCLicense.setConfirmation(ClarinLicense.Confirmation.NOT_REQUIRED); + firstCLicense.setDefinition("CL Definition1"); + firstCLicense.setRequiredInfo("CL Req1"); + // add ClarinLicenseLabels to the ClarinLicense + firstCLicense.setLicenseLabels(Set.of(firstCLicenseLabel, thirdCLicenseLabel)); + clarinLicenseService.update(context, firstCLicense); + context.restoreAuthSystemState(); + + String authTokenAdmin = getAuthToken(admin.getEmail(), password); + + getClient(authTokenAdmin).perform(delete("/api/core/clarinlicenselabels/" + firstCLicenseLabel.getID())) + .andExpect(status().isBadRequest()); + getClient(authTokenAdmin).perform(delete("/api/core/clarinlicenselabels/" + thirdCLicenseLabel.getID())) + .andExpect(status().isBadRequest()); + + context.turnOffAuthorisationSystem(); + clarinLicenseService.delete(context, firstCLicense); + context.restoreAuthSystemState(); + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinWorkspaceItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinWorkspaceItemRestRepositoryIT.java index aae481283540..b54e9084ba24 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinWorkspaceItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinWorkspaceItemRestRepositoryIT.java @@ -638,7 +638,7 @@ public void addClarinLicenseToWI() throws Exception { String clarinLicenseName = "Test Clarin License"; // 2. Create clarin license with clarin license label - ClarinLicense clarinLicense = createClarinLicense(clarinLicenseName, "Test Def", "Test R Info", + ClarinLicense clarinLicense = createClarinLicense(clarinLicenseName, "Test Def", "Test R Info", "lbl", Confirmation.NOT_REQUIRED); // creating replace operation @@ -688,7 +688,7 @@ public void removeClarinLicenseFromWI() throws Exception { List replaceOperations = new ArrayList(); // 2. Create Clarin License String clarinLicenseName = "Test Clarin License"; - ClarinLicense clarinLicense = createClarinLicense(clarinLicenseName, "Test Def", "Test R Info", + ClarinLicense clarinLicense = createClarinLicense(clarinLicenseName, "Test Def", "Test R Info", "lbl", Confirmation.NOT_REQUIRED); context.restoreAuthSystemState(); @@ -763,10 +763,11 @@ public void updateClarinLicenseInWI() throws Exception { String updateClarinLicenseName = "Updated Clarin License"; // 2. Create Clarin Licenses - ClarinLicense clarinLicense = createClarinLicense(clarinLicenseName, "Test Def", "Test R Info", + ClarinLicense clarinLicense = createClarinLicense(clarinLicenseName, "Test Def", "Test R Info", "lbl1", Confirmation.NOT_REQUIRED); ClarinLicense updatedClarinLicense = - createClarinLicense(updateClarinLicenseName, "Test Def2", "Test R Info2", Confirmation.NOT_REQUIRED); + createClarinLicense(updateClarinLicenseName, "Test Def2", "Test R Info2", "lbl2", + Confirmation.NOT_REQUIRED); context.restoreAuthSystemState(); // Creating replace operation @@ -1257,7 +1258,7 @@ private ClarinLicenseLabel createClarinLicenseLabel(String label, boolean extend /** * Create ClarinLicense object with ClarinLicenseLabel object for testing purposes. */ - private ClarinLicense createClarinLicense(String name, String definition, String requiredInfo, + private ClarinLicense createClarinLicense(String name, String definition, String requiredInfo, String label, Confirmation confirmation) throws SQLException, AuthorizeException { ClarinLicense clarinLicense = ClarinLicenseBuilder.createClarinLicense(context).build(); clarinLicense.setConfirmation(confirmation); @@ -1267,7 +1268,7 @@ private ClarinLicense createClarinLicense(String name, String definition, String // add ClarinLicenseLabels to the ClarinLicense HashSet clarinLicenseLabels = new HashSet<>(); - ClarinLicenseLabel clarinLicenseLabel = createClarinLicenseLabel("lbl", false, "Test Title"); + ClarinLicenseLabel clarinLicenseLabel = createClarinLicenseLabel(label, false, label + " Title"); clarinLicenseLabels.add(clarinLicenseLabel); clarinLicense.setLicenseLabels(clarinLicenseLabels); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ProvenanceServiceIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ProvenanceServiceIT.java index da7cd789ccca..1f0c83d55462 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ProvenanceServiceIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ProvenanceServiceIT.java @@ -121,9 +121,9 @@ public void destroy() throws Exception { @Test public void updateLicenseTest() throws Exception { Bitstream bitstream = createBitstream(item, Constants.LICENSE_BUNDLE_NAME); - ClarinLicense clarinLicense1 = createClarinLicense("Test 1", "Test Def"); + ClarinLicense clarinLicense1 = createClarinLicense("Test 1", "Test Def", "LBL_1"); ClarinLicenseResourceMapping mapping = createResourceMapping(clarinLicense1, bitstream); - ClarinLicense clarinLicense2 = createClarinLicense("Test 2", "Test Def"); + ClarinLicense clarinLicense2 = createClarinLicense("Test 2", "Test Def", "LBL_2"); String token = getAuthToken(admin.getEmail(), password); getClient(token).perform(put("/api/core/items/" + item.getID() + "/bundles") @@ -139,7 +139,7 @@ public void updateLicenseTest() throws Exception { @Test public void addLicenseTest() throws Exception { - ClarinLicense clarinLicense = createClarinLicense("Test", "Test Def"); + ClarinLicense clarinLicense = createClarinLicense("Test", "Test Def", "LBL"); String token = getAuthToken(admin.getEmail(), password); getClient(token).perform(put("/api/core/items/" + item.getID() + "/bundles") @@ -153,7 +153,7 @@ public void addLicenseTest() throws Exception { @Test public void removeLicenseTest() throws Exception { Bitstream bitstream = createBitstream(item, Constants.LICENSE_BUNDLE_NAME); - ClarinLicense clarinLicense = createClarinLicense("Test", "Test Def"); + ClarinLicense clarinLicense = createClarinLicense("Test", "Test Def", "LBL"); ClarinLicenseResourceMapping mapping = createResourceMapping(clarinLicense, bitstream); String token = getAuthToken(admin.getEmail(), password); @@ -478,14 +478,14 @@ private ClarinLicenseLabel createClarinLicenseLabel(String label, boolean extend return clarinLicenseLabel; } - private ClarinLicense createClarinLicense(String name, String definition) + private ClarinLicense createClarinLicense(String name, String definition, String label) throws SQLException, AuthorizeException { context.turnOffAuthorisationSystem(); ClarinLicense clarinLicense = ClarinLicenseBuilder.createClarinLicense(context).build(); clarinLicense.setDefinition(definition); clarinLicense.setName(name); HashSet clarinLicenseLabels = new HashSet<>(); - ClarinLicenseLabel clarinLicenseLabel = createClarinLicenseLabel("lbl", false, "Test Title"); + ClarinLicenseLabel clarinLicenseLabel = createClarinLicenseLabel(label, false, label + " Title"); clarinLicenseLabels.add(clarinLicenseLabel); clarinLicense.setLicenseLabels(clarinLicenseLabels); clarinLicenseService.update(context, clarinLicense); @@ -498,11 +498,12 @@ private void deleteClarinLicenseLable(Integer id) throws Exception { } private void deleteClarinLicense(ClarinLicense license) throws Exception { - int size = license.getLicenseLabels().size(); - for (int i = 0; i < size; i++) { - deleteClarinLicenseLable(license.getLicenseLabels().get(i).getID()); - } + // first delete license, then labels, because of the foreign key constraint + List clarinLicenseLabels = license.getLicenseLabels(); ClarinLicenseBuilder.deleteClarinLicense(license.getID()); + for (ClarinLicenseLabel clarinLicenseLabel : clarinLicenseLabels) { + deleteClarinLicenseLable(clarinLicenseLabel.getID()); + } } private Collection createCollection() {