diff --git a/doc/release-notes/11710-get-available-dataverses-api.md b/doc/release-notes/11710-get-available-dataverses-api.md new file mode 100644 index 00000000000..ac33c581848 --- /dev/null +++ b/doc/release-notes/11710-get-available-dataverses-api.md @@ -0,0 +1,5 @@ +### New API endpoint for retrieving a list of Dataverse Collections to which a given Dataset or Dataverse Collection may be linked + +-The end point also takes in a search term which currently must be part of the collections' names. +-The user calling this API must have Link Dataset or Link Dataverse permission on the Dataverse Collections returned. +-If the Collection has already been linked to the given Dataset or Collection, it will not be returned. diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index 19a4d79c5ae..a12dfa151c9 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -732,6 +732,50 @@ Note: you must have "Add Dataset" permission in the given collection to invoke t .. _featured-collections: +List Dataverse Collections to Which a Given Dataset or Dataverse Collection May Be Linked +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The user may provide a search term to limit the list of Dataverse Collections returned. The search term will be compared to the name of the Dataverse Collections. +The response is a JSON array of the ids, aliases, and names of the Dataverse collections to which a given Dataset or Dataverse Collection may be linked: + +For a given Dataverse Collection: + +.. code-block:: bash + + export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + export SERVER_URL=https://demo.dataverse.org + export OBJECT_TYPE=dataverse + export ID=collectionAlias + export SEARCH_TERM=searchOn + + curl -H "X-Dataverse-key:$API_TOKEN" -X GET "$SERVER_URL/api/dataverses/$ID/$OBJECT_TYPE/linkingDataverses?searchTerm=$SEARCH_TERM" + +The fully expanded example above (without environment variables) looks like this: + +.. code-block:: bash + + curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -X GET "https://demo.dataverse.org/api/dataverses/collectionAlias/dataverse/linkingDataverses?searchTerm=searchOn" + +For a given Dataset: + +.. code-block:: bash + + export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + export SERVER_URL=https://demo.dataverse.org + export OBJECT_TYPE=dataset + export PERSISTENT_IDENTIFIER=doi:10.5072/FK2/J8SJZB + export SEARCH_TERM=searchOn + + curl -H "X-Dataverse-key:$API_TOKEN" -X GET "$SERVER_URL/api/dataverses/:persistentId/$OBJECT_TYPE/linkingDataverses?searchTerm=SEARCH_TERM&persistentId=$PERSISTENT_IDENTIFIER" + +The fully expanded example above (without environment variables) looks like this: + +.. code-block:: bash + + curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -X GET "https://demo.dataverse.org/api/dataverses/:persistentId/dataset/linkingDataverses?searchTerm=searchOn&persistentId=doi:10.5072/FK2/J8SJZB" + +You may also add an optional "alreadyLinked=true" parameter to return collections which are already linked to the given Dataset or Dataverse Collection. + List Featured Collections for a Dataverse Collection ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetLinkingDataverse.java b/src/main/java/edu/harvard/iq/dataverse/DatasetLinkingDataverse.java index dec07a09643..28b061ffa2a 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DatasetLinkingDataverse.java +++ b/src/main/java/edu/harvard/iq/dataverse/DatasetLinkingDataverse.java @@ -9,6 +9,7 @@ import jakarta.persistence.Id; import jakarta.persistence.Index; import jakarta.persistence.JoinColumn; +import jakarta.persistence.NamedNativeQuery; import jakarta.persistence.NamedQueries; import jakarta.persistence.NamedQuery; import jakarta.persistence.OneToOne; @@ -35,6 +36,18 @@ @NamedQuery(name = "DatasetLinkingDataverse.findIdsByLinkingDataverseId", query = "SELECT o.dataset.id FROM DatasetLinkingDataverse AS o WHERE o.linkingDataverse.id = :linkingDataverseId") }) + + @NamedNativeQuery( + name = "DatasetLinkingDataverse.findByDatasetIdAndLinkingDataverseName", + query = """ + select o.linkingDataverse_id from DatasetLinkingDataverse as o + LEFT JOIN dataverse dv ON dv.id = o.linkingDataverse_id + WHERE o.dataset_id =? AND ((LOWER(dv.name) LIKE ? and ((SUBSTRING(LOWER(dv.name),0,(LENGTH(dv.name)-9)) LIKE ?) + or (SUBSTRING(LOWER(dv.name),0,(LENGTH(dv.name)-9)) LIKE ?))) + or (LOWER(dv.name) NOT LIKE ? and ((LOWER(dv.name) LIKE ?) + or (LOWER(dv.name) LIKE ?))))""" + ) + public class DatasetLinkingDataverse implements Serializable { private static final long serialVersionUID = 1L; @Id diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetLinkingServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/DatasetLinkingServiceBean.java index 39c82bfa3f1..6b0f8af6590 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DatasetLinkingServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/DatasetLinkingServiceBean.java @@ -5,6 +5,7 @@ */ package edu.harvard.iq.dataverse; +import jakarta.ejb.EJB; import java.util.ArrayList; import java.util.List; import java.util.logging.Logger; @@ -13,7 +14,6 @@ import jakarta.persistence.EntityManager; import jakarta.persistence.NoResultException; import jakarta.persistence.PersistenceContext; -import jakarta.persistence.Query; import jakarta.persistence.TypedQuery; /** @@ -28,6 +28,9 @@ public class DatasetLinkingServiceBean implements java.io.Serializable { @PersistenceContext(unitName = "VDCNet-ejbPU") private EntityManager em; + @EJB + DataverseServiceBean dataverseService; + public List findLinkedDatasets(Long dataverseId) { @@ -41,13 +44,42 @@ public List findLinkedDatasets(Long dataverseId) { } public List findLinkingDataverses(Long datasetId) { + return findLinkingDataverses(datasetId, ""); + } + + public List findLinkingDataverses(Long datasetId, String searchTerm) { List retList = new ArrayList<>(); - TypedQuery typedQuery = em.createNamedQuery("DatasetLinkingDataverse.findByDatasetId", DatasetLinkingDataverse.class) - .setParameter("datasetId", datasetId); - for (DatasetLinkingDataverse datasetLinkingDataverse : typedQuery.getResultList()) { - retList.add(datasetLinkingDataverse.getLinkingDataverse()); + if (searchTerm == null || searchTerm.isEmpty()) { + TypedQuery typedQuery = em.createNamedQuery("DatasetLinkingDataverse.findByDatasetId", DatasetLinkingDataverse.class) + .setParameter("datasetId", datasetId); + for (DatasetLinkingDataverse datasetLinkingDataverse : typedQuery.getResultList()) { + retList.add(datasetLinkingDataverse.getLinkingDataverse()); + } + return retList; + + } else { + + String pattern = searchTerm.toLowerCase(); + + String pattern1 = pattern + "%"; + String pattern2 = "% " + pattern + "%"; + + // Adjust the queries for very short, 1 and 2-character patterns: + if (pattern.length() == 1) { + pattern1 = pattern; + pattern2 = pattern + " %"; + } + TypedQuery typedQuery + = em.createNamedQuery("DatasetLinkingDataverse.findByDatasetIdAndLinkingDataverseName", Long.class) + .setParameter(1, datasetId).setParameter(2, "%dataverse").setParameter(3, pattern1) + .setParameter(4, pattern2).setParameter(5, "%dataverse").setParameter(6, pattern1).setParameter(7, pattern2); + + for (Long id : typedQuery.getResultList()) { + retList.add(dataverseService.find(id)); + } + return retList; } - return retList; + } public void save(DatasetLinkingDataverse datasetLinkingDataverse) { diff --git a/src/main/java/edu/harvard/iq/dataverse/DataverseLinkingDataverse.java b/src/main/java/edu/harvard/iq/dataverse/DataverseLinkingDataverse.java index 3030922ea5e..bf2326f0e06 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DataverseLinkingDataverse.java +++ b/src/main/java/edu/harvard/iq/dataverse/DataverseLinkingDataverse.java @@ -13,6 +13,7 @@ import jakarta.persistence.Id; import jakarta.persistence.Index; import jakarta.persistence.JoinColumn; +import jakarta.persistence.NamedNativeQuery; import jakarta.persistence.NamedQueries; import jakarta.persistence.NamedQuery; import jakarta.persistence.OneToOne; @@ -39,6 +40,16 @@ @NamedQuery(name = "DataverseLinkingDataverse.findIdsByLinkingDataverseId", query = "SELECT o.dataverse.id FROM DataverseLinkingDataverse AS o WHERE o.linkingDataverse.id = :linkingDataverseId") }) + @NamedNativeQuery( + name = "DataverseLinkingDataverse.findByDataverseIdAndLinkingDataverseName", + query = """ + select o.linkingDataverse_id from DataverseLinkingDataverse as o + LEFT JOIN dataverse dv ON dv.id = o.linkingDataverse_id + WHERE o.dataverse_id =? AND ((LOWER(dv.name) LIKE ? and ((SUBSTRING(LOWER(dv.name),0,(LENGTH(dv.name)-9)) LIKE ?) + or (SUBSTRING(LOWER(dv.name),0,(LENGTH(dv.name)-9)) LIKE ?))) + or (LOWER(dv.name) NOT LIKE ? and ((LOWER(dv.name) LIKE ?) + or (LOWER(dv.name) LIKE ?))))""" + ) public class DataverseLinkingDataverse implements Serializable { private static final long serialVersionUID = 1L; @Id diff --git a/src/main/java/edu/harvard/iq/dataverse/DataverseLinkingServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/DataverseLinkingServiceBean.java index 834ff96e392..9f1bcde4c0e 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DataverseLinkingServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/DataverseLinkingServiceBean.java @@ -43,12 +43,41 @@ public List findLinkedDataverses(Long linkingDataverseId) { } public List findLinkingDataverses(Long dataverseId) { + + return findLinkingDataverses(dataverseId, ""); + } + + public List findLinkingDataverses(Long dataverseId, String searchTerm) { List retList = new ArrayList<>(); - TypedQuery typedQuery = em.createNamedQuery("DataverseLinkingDataverse.findByDataverseId", DataverseLinkingDataverse.class) - .setParameter("dataverseId", dataverseId); - for (DataverseLinkingDataverse dataverseLinkingDataverse : typedQuery.getResultList()) { - retList.add(dataverseLinkingDataverse.getLinkingDataverse()); + if (searchTerm == null || searchTerm.isEmpty()) { + TypedQuery typedQuery = em.createNamedQuery("DataverseLinkingDataverse.findByDataverseId", DataverseLinkingDataverse.class) + .setParameter("dataverseId", dataverseId); + for (DataverseLinkingDataverse dataverseLinkingDataverse : typedQuery.getResultList()) { + retList.add(dataverseLinkingDataverse.getLinkingDataverse()); + } + + } else { + + String pattern = searchTerm.toLowerCase(); + + String pattern1 = pattern + "%"; + String pattern2 = "% " + pattern + "%"; + + // Adjust the queries for very short, 1 and 2-character patterns: + if (pattern.length() == 1) { + pattern1 = pattern; + pattern2 = pattern + " %"; + } + TypedQuery typedQuery + = em.createNamedQuery("DataverseLinkingDataverse.findByDataverseIdAndLinkingDataverseName", Long.class) + .setParameter(1, dataverseId).setParameter(2, "%dataverse").setParameter(3, pattern1) + .setParameter(4, pattern2).setParameter(5, "%dataverse").setParameter(6, pattern1).setParameter(7, pattern2); + + for (Long id : typedQuery.getResultList()) { + retList.add(dataverseService.find(id)); + } } + return retList; } diff --git a/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java index c14711060af..57bc0bc5450 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java @@ -505,17 +505,28 @@ public List filterByAliasQuery(String filterQuery) { return ret; } - public List filterDataversesForLinking(String query, DataverseRequest req, Dataset dataset) { + public List filterDataversesForLinking(String query, DataverseRequest req, DvObject dvo) { List dataverseList = new ArrayList<>(); List results = filterDataversesByNamePattern(query); - - if (results == null || results.size() == 0) { - return null; + + if (results == null || results.isEmpty()) { + return null; + } + + Dataset linkedDataset = null; + Dataverse linkedDataverse = null; + List alreadyLinkeddv_ids; + + if ((dvo instanceof Dataset)) { + linkedDataset = (Dataset) dvo; + alreadyLinkeddv_ids = em.createNativeQuery("SELECT linkingdataverse_id FROM datasetlinkingdataverse WHERE dataset_id = " + linkedDataset.getId()).getResultList(); + } else { + linkedDataverse = (Dataverse) dvo; + alreadyLinkeddv_ids = em.createNativeQuery("SELECT linkingdataverse_id FROM dataverselinkingdataverse WHERE dataverse_id = " + linkedDataverse.getId()).getResultList(); } - List alreadyLinkeddv_ids = em.createNativeQuery("SELECT linkingdataverse_id FROM datasetlinkingdataverse WHERE dataset_id = " + dataset.getId()).getResultList(); List remove = new ArrayList<>(); if (alreadyLinkeddv_ids != null && !alreadyLinkeddv_ids.isEmpty()) { @@ -523,10 +534,15 @@ public List filterDataversesForLinking(String query, DataverseRequest remove.add(removeIt); }); } - + + if (dvo instanceof Dataverse dataverse) { + remove.add(dataverse); + } + for (Dataverse res : results) { if (!remove.contains(res)) { - if (this.permissionService.requestOn(req, res).has(Permission.LinkDataset)) { + if ((linkedDataset != null && this.permissionService.requestOn(req, res).has(Permission.LinkDataset)) + || (linkedDataverse != null && this.permissionService.requestOn(req, res).has(Permission.LinkDataverse))) { dataverseList.add(res); } } @@ -534,6 +550,54 @@ public List filterDataversesForLinking(String query, DataverseRequest return dataverseList; } + + public List removeUnlinkableDataverses(List allWithPerms, DvObject dvo) { + List dataverseList = new ArrayList<>(); + Dataset linkedDataset = null; + Dataverse linkedDataverse = null; + List alreadyLinkeddv_ids; + + if ((dvo instanceof Dataset)) { + linkedDataset = (Dataset) dvo; + alreadyLinkeddv_ids = em.createNativeQuery("SELECT linkingdataverse_id FROM datasetlinkingdataverse WHERE dataset_id = " + linkedDataset.getId()).getResultList(); + } else { + linkedDataverse = (Dataverse) dvo; + alreadyLinkeddv_ids = em.createNativeQuery("SELECT linkingdataverse_id FROM dataverselinkingdataverse WHERE dataverse_id = " + linkedDataverse.getId()).getResultList(); + } + + List remove = new ArrayList<>(); + + if (alreadyLinkeddv_ids != null && !alreadyLinkeddv_ids.isEmpty()) { + alreadyLinkeddv_ids.stream().map((testDVId) -> this.find(testDVId)).forEachOrdered((removeIt) -> { + remove.add(removeIt); + }); + } + + + if (dvo instanceof Dataverse dataverse) { + remove.add(dataverse); + } + + DvObject testDVO = dvo; + //Remove DVO's parent up to Root + while (testDVO != null) { + if (testDVO.getOwner() == null) { + break; // we are at the root; which by definition is metadata block root, regardless of the value + } + remove.add((Dataverse) testDVO.getOwner()); + testDVO = testDVO.getOwner(); + } + + for (Dataverse res : allWithPerms) { + if (!remove.contains(res)) { + dataverseList.add(res); + } + } + + return dataverseList; + } + + public List filterDataversesForUnLinking(String query, DataverseRequest req, Dataset dataset) { List alreadyLinkeddv_ids = em.createNativeQuery("SELECT linkingdataverse_id FROM datasetlinkingdataverse WHERE dataset_id = " + dataset.getId()).getResultList(); List dataverseList = new ArrayList<>(); diff --git a/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java index e00e303356e..d492991bb62 100644 --- a/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java @@ -96,7 +96,7 @@ public class PermissionServiceBean { DatasetVersionFilesServiceBean datasetVersionFilesServiceBean; private static final String LIST_ALL_DATAVERSES_SUPERUSER_HAS_PERMISSION = """ - SELECT id, name, alias FROM DATAVERSE + SELECT id, name, alias FROM DATAVERSE dv """; private static final String LIST_ALL_DATAVERSES_USER_HAS_PERMISSION = """ @@ -105,7 +105,7 @@ WITH grouplist AS ( WHERE explicitgroup_authenticateduser.containedauthenticatedusers_id = @USERID ) - SELECT * FROM DATAVERSE WHERE id IN ( + SELECT * FROM DATAVERSE dv WHERE id IN ( SELECT definitionpoint_id FROM roleassignment WHERE roleassignment.assigneeidentifier IN ( @@ -161,8 +161,24 @@ AND EXISTS (SELECT id FROM dataverserole WHERE dataverserole.id = roleassignment AND @IPRANGESQL ) ) - ) + ) """; + + private static final String AND = """ + and + """; + + private static final String WHERE = """ + where + """; + + private static final String SEARCH_PARAMS = """ + ((LOWER(dv.name) LIKE ? and ((SUBSTRING(LOWER(dv.name),0,(LENGTH(dv.name)-9)) LIKE ?) + or (SUBSTRING(LOWER(dv.name),0,(LENGTH(dv.name)-9)) LIKE ?))) + or (LOWER(dv.name) NOT LIKE ? and ((LOWER(dv.name) LIKE ?) + or (LOWER(dv.name) LIKE ?)))) + """; + /** * A request-level permission query (e.g includes IP ras). */ @@ -921,12 +937,21 @@ private boolean hasUnrestrictedReleasedFiles(DatasetVersion targetDatasetVersion Long result = em.createQuery(criteriaQuery).getSingleResult(); return result > 0; } - + public List findPermittedCollections(DataverseRequest request, AuthenticatedUser user, Permission permission) { - return findPermittedCollections(request, user, 1 << permission.ordinal()); + return findPermittedCollections(request, user, 1 << permission.ordinal(), ""); } + public List findPermittedCollections(DataverseRequest request, AuthenticatedUser user, Permission permission, String searchTerm) { + return findPermittedCollections(request, user, 1 << permission.ordinal(), searchTerm); + } + public List findPermittedCollections(DataverseRequest request, AuthenticatedUser user, int permissionBit) { + return findPermittedCollections(request, user, permissionBit, ""); + } + + + public List findPermittedCollections(DataverseRequest request, AuthenticatedUser user, int permissionBit, String searchTerm) { if (user != null) { // IP Group - Only check IP if a User is calling for themself String ipRangeSQL = "FALSE"; @@ -958,13 +983,64 @@ public List findPermittedCollections(DataverseRequest request, Authen String sqlCode; if (user.isSuperuser()) { sqlCode = LIST_ALL_DATAVERSES_SUPERUSER_HAS_PERMISSION; + + if (searchTerm == null || searchTerm.isEmpty()) { + return em.createNativeQuery(sqlCode, Dataverse.class).getResultList(); + } else { + sqlCode = LIST_ALL_DATAVERSES_SUPERUSER_HAS_PERMISSION.concat(WHERE).concat(SEARCH_PARAMS); + + String pattern = searchTerm.toLowerCase(); + String pattern1 = pattern + "%"; + String pattern2 = "% " + pattern + "%"; + + // Adjust the queries for very short, 1 + if (pattern.length() == 1) { + pattern1 = pattern; + pattern2 = pattern + " %"; + } + Query query = em.createNativeQuery(sqlCode, Dataverse.class); + query.setParameter(1, "%dataverse"); + query.setParameter(2, pattern1); + query.setParameter(3, pattern2); + query.setParameter(4, "%dataverse"); + query.setParameter(5, pattern1); + query.setParameter(6, pattern2); + return query.getResultList(); + + } } else { - sqlCode = LIST_ALL_DATAVERSES_USER_HAS_PERMISSION - .replace("@USERID", String.valueOf(user.getId())) - .replace("@PERMISSIONBIT", String.valueOf(permissionBit)) - .replace("@IPRANGESQL", ipRangeSQL); + if (searchTerm == null || searchTerm.isEmpty()) { + sqlCode = LIST_ALL_DATAVERSES_USER_HAS_PERMISSION + .replace("@USERID", String.valueOf(user.getId())) + .replace("@PERMISSIONBIT", String.valueOf(permissionBit)) + .replace("@IPRANGESQL", ipRangeSQL); + return em.createNativeQuery(sqlCode, Dataverse.class).getResultList(); + } else { + String pattern = searchTerm.toLowerCase(); + String pattern1 = pattern + "%"; + String pattern2 = "% " + pattern + "%"; + + // Adjust the queries for very short, 1 + if (pattern.length() == 1) { + pattern1 = pattern; + pattern2 = pattern + " %"; + } + + sqlCode = LIST_ALL_DATAVERSES_USER_HAS_PERMISSION.concat(AND).concat(SEARCH_PARAMS) + .replace("@USERID", String.valueOf(user.getId())) + .replace("@PERMISSIONBIT", String.valueOf(permissionBit)) + .replace("@IPRANGESQL", ipRangeSQL); + + Query query = em.createNativeQuery(sqlCode, Dataverse.class); + query.setParameter(1, "%dataverse"); + query.setParameter(2, pattern1); + query.setParameter(3, pattern2); + query.setParameter(4, "%dataverse"); + query.setParameter(5, pattern1); + query.setParameter(6, pattern2); + return query.getResultList(); + } } - return em.createNativeQuery(sqlCode, Dataverse.class).getResultList(); } return null; } diff --git a/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java b/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java index 46e8263da15..0ce84844289 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java @@ -615,11 +615,12 @@ protected DvObject findDvo(@NotNull final String id) throws WrappedResponse { * * @param dvIdtf * @param type + * @param testForReleased * @return DvObject if type matches or throw exception * @throws WrappedResponse */ @NotNull - protected DvObject findDvoByIdAndFeaturedItemTypeOrDie(@NotNull final String dvIdtf, String type) throws WrappedResponse { + protected DvObject findDvoByIdAndTypeOrDie(@NotNull final String dvIdtf, String type, boolean testForReleased) throws WrappedResponse { try { DataverseFeaturedItem.TYPES dvType = DataverseFeaturedItem.getDvType(type); DvObject dvObject = null; @@ -652,7 +653,12 @@ protected DvObject findDvoByIdAndFeaturedItemTypeOrDie(@NotNull final String dvI } } } - DataverseFeaturedItem.validateTypeAndDvObject(dvIdtf, dvObject, dvType); + if (testForReleased){ + DataverseFeaturedItem.validateTypeAndDvObject(dvIdtf, dvObject, dvType); + } + if (dvObject == null) { + throw new WrappedResponse(notFound(BundleUtil.getStringFromBundle("find.dvo.error.dvObjectNotFound", Collections.singletonList(dvIdtf)))); + } return dvObject; } catch (IllegalArgumentException e) { throw new WrappedResponse(error(Response.Status.BAD_REQUEST, e.getMessage())); diff --git a/src/main/java/edu/harvard/iq/dataverse/api/DataverseFeaturedItems.java b/src/main/java/edu/harvard/iq/dataverse/api/DataverseFeaturedItems.java index 30c3146fbfb..7fbdd79e3c3 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/DataverseFeaturedItems.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/DataverseFeaturedItems.java @@ -63,7 +63,7 @@ public Response updateFeaturedItem(@Context ContainerRequestContext crc, if (dataverseFeaturedItem == null) { throw new WrappedResponse(error(Response.Status.NOT_FOUND, MessageFormat.format(BundleUtil.getStringFromBundle("dataverseFeaturedItems.errors.notFound"), id))); } - DvObject dvObject = (dvObjectIdtf != null) ? findDvoByIdAndFeaturedItemTypeOrDie(dvObjectIdtf, type) : null; + DvObject dvObject = (dvObjectIdtf != null) ? findDvoByIdAndTypeOrDie(dvObjectIdtf, type, true) : null; UpdatedDataverseFeaturedItemDTO updatedDataverseFeaturedItemDTO = UpdatedDataverseFeaturedItemDTO.fromFormData(content, displayOrder, keepFile, imageFileInputStream, contentDispositionHeader, type, dvObject); return ok(json(execCommand(new UpdateDataverseFeaturedItemCommand(createDataverseRequest(getRequestUser(crc)), dataverseFeaturedItem, updatedDataverseFeaturedItemDTO)))); } catch (WrappedResponse e) { diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java b/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java index ae82ff46522..7322a8c9341 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java @@ -15,6 +15,7 @@ import edu.harvard.iq.dataverse.authorization.groups.impl.explicit.ExplicitGroup; import edu.harvard.iq.dataverse.authorization.groups.impl.explicit.ExplicitGroupProvider; import edu.harvard.iq.dataverse.authorization.groups.impl.explicit.ExplicitGroupServiceBean; +import edu.harvard.iq.dataverse.authorization.groups.impl.ipaddress.ip.IpAddress; import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; import edu.harvard.iq.dataverse.authorization.users.User; import edu.harvard.iq.dataverse.dataset.DatasetType; @@ -119,7 +120,7 @@ public class Dataverses extends AbstractApiBean { @EJB DataverseFeaturedItemServiceBean dataverseFeaturedItemServiceBean; - + @POST @AuthRequired public Response addRoot(@Context ContainerRequestContext crc, String body) { @@ -714,7 +715,7 @@ public Response getDataverse(@Context ContainerRequestContext crc, @PathParam("i return response(req -> { Dataverse dataverse = execCommand(new GetDataverseCommand(req, findDataverseOrDie(idtf))); boolean hideEmail = settingsService.isTrueForKey(SettingsServiceBean.Key.ExcludeEmailFromExport, false); - return ok(json(dataverse, hideEmail, returnOwners, returnChildCount ? dataverseService.getChildCount(dataverse) : null)); + return ok(json(dataverse, hideEmail, returnOwners, false, returnChildCount ? dataverseService.getChildCount(dataverse) : null)); }, getRequestUser(crc)); } @@ -1771,6 +1772,36 @@ public Response linkDataverse(@Context ContainerRequestContext crc, @PathParam(" return ex.getResponse(); } } + + @GET + @AuthRequired + @Produces(MediaType.APPLICATION_JSON) + @Path("{identifier}/{type}/linkingDataverses") + public Response getLinkingDataverseList(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf, @QueryParam("searchTerm") String searchTerm, @QueryParam("alreadyLinking") boolean alreadyLinking, @PathParam("type") String type) { + + try { + + DvObject dvObject = findDvoByIdAndTypeOrDie(dvIdtf, type, false); + List dataversesForLinking = execCommand(new GetLinkingDataverseListCommand( + createDataverseRequest(getRequestUser(crc)), + dvObject, + searchTerm, + alreadyLinking + )); + + JsonArrayBuilder dvBuilder = Json.createArrayBuilder(); + if (dataversesForLinking != null && !dataversesForLinking.isEmpty()) { + for (Dataverse dv : dataversesForLinking) { + dvBuilder.add(json(dv, true)); + } + } + return ok(dvBuilder); + } catch (WrappedResponse wr) { + return wr.getResponse(); + } + } + + @GET @AuthRequired @@ -1811,7 +1842,7 @@ public Response createFeaturedItem(@Context ContainerRequestContext crc, try { dataverse = findDataverseOrDie(dvIdtf); if (dvObjectIdtf != null) { - dvObject = findDvoByIdAndFeaturedItemTypeOrDie(dvObjectIdtf, type); + dvObject = findDvoByIdAndTypeOrDie(dvObjectIdtf, type, true); } } catch (WrappedResponse wr) { return wr.getResponse(); @@ -1899,7 +1930,7 @@ public Response updateFeaturedItems( // ignore dvObject if the id is missing or an empty string DvObject dvObject = dvObjectIdtf.get(i) != null && !dvObjectIdtf.get(i).isEmpty() - ? findDvoByIdAndFeaturedItemTypeOrDie(dvObjectIdtf.get(i), types.get(i)) : null; + ? findDvoByIdAndTypeOrDie(dvObjectIdtf.get(i), types.get(i), true) : null; if (ids.get(i) == 0) { newItems.add(NewDataverseFeaturedItemDTO.fromFormData( contents.get(i), displayOrders.get(i), fileInputStream, contentDisposition, types.get(i), dvObject)); diff --git a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/GetLinkingDataverseListCommand.java b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/GetLinkingDataverseListCommand.java new file mode 100644 index 00000000000..466871b88e4 --- /dev/null +++ b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/GetLinkingDataverseListCommand.java @@ -0,0 +1,113 @@ + +package edu.harvard.iq.dataverse.engine.command.impl; + +import edu.harvard.iq.dataverse.Dataset; +import edu.harvard.iq.dataverse.Dataverse; +import edu.harvard.iq.dataverse.DvObject; +import edu.harvard.iq.dataverse.authorization.Permission; +import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; +import edu.harvard.iq.dataverse.authorization.users.User; +import edu.harvard.iq.dataverse.engine.command.AbstractCommand; +import edu.harvard.iq.dataverse.engine.command.CommandContext; +import edu.harvard.iq.dataverse.engine.command.DataverseRequest; +import edu.harvard.iq.dataverse.engine.command.RequiredPermissions; +import edu.harvard.iq.dataverse.engine.command.exception.CommandException; +import edu.harvard.iq.dataverse.engine.command.exception.IllegalCommandException; +import edu.harvard.iq.dataverse.util.BundleUtil; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; + +/** + * + * @author stephenkraffmiller + */ +@RequiredPermissions({}) +public class GetLinkingDataverseListCommand extends AbstractCommand> { + + private final String searchTerm; + private final DvObject dvObject; + private final boolean alreadyLinked; + + public GetLinkingDataverseListCommand(DataverseRequest aRequest, DvObject dvObject, String searchTerm) { + super(aRequest, dvObject); + this.searchTerm = searchTerm; + this.dvObject = dvObject; + this.alreadyLinked = false; + } + + public GetLinkingDataverseListCommand(DataverseRequest aRequest, DvObject dvObject, boolean alreadyLinked) { + super(aRequest, dvObject); + this.searchTerm = ""; + this.dvObject = dvObject; + this.alreadyLinked = alreadyLinked; + } + + public GetLinkingDataverseListCommand(DataverseRequest aRequest, DvObject dvObject, String searchTerm, boolean alreadyLinked) { + super(aRequest, dvObject); + this.searchTerm = searchTerm; + this.dvObject = dvObject; + this.alreadyLinked = alreadyLinked; + } + + + + @Override + public List execute(CommandContext ctxt) throws CommandException { + + User requestUser = (User) getRequest().getUser(); + AuthenticatedUser authUser; + if (!requestUser.isAuthenticated()) { + throw new IllegalCommandException(BundleUtil.getStringFromBundle("dataverse.link.user"), this); + } else { + authUser = (AuthenticatedUser) requestUser; + } + List dataversesForLinking; + String searchParam; + if (searchTerm != null) { + searchParam = searchTerm; + } else { + searchParam = ""; + } + + //Find Permitted Collections now takes a Search Term to filter down collections the user may link + Permission permToCheck; + if (dvObject instanceof Dataset) { + permToCheck = Permission.LinkDataset; + } else { + permToCheck = Permission.LinkDataverse; + } + //depending on the already linked boolean the command will return a list of Dataverses available for linking + // or a list of dataverses to which the object has already been linked - for the unlink function + if (!alreadyLinked) { + dataversesForLinking = ctxt.permissions().findPermittedCollections(getRequest(), authUser, permToCheck, searchParam); + //Don't bother with checking for already linked if there are none to be tested. + if (dataversesForLinking == null || dataversesForLinking.isEmpty()) { + return dataversesForLinking; + } + return ctxt.dataverses().removeUnlinkableDataverses(dataversesForLinking, dvObject); + } else { + List dataversesAlreadyLinked; + List dataversesAlreadyLinkedCanUnlink = new ArrayList<>(); + //new ArrayList<>(); + if (dvObject instanceof Dataset) { + dataversesAlreadyLinked = ctxt.dsLinking().findLinkingDataverses(dvObject.getId(), searchParam); + for (Dataverse dv : dataversesAlreadyLinked) { + if (ctxt.permissions().hasPermissionsFor(getRequest(), dv, EnumSet.of(Permission.LinkDataset))) { + dataversesAlreadyLinkedCanUnlink.add(dv); + } + return dataversesAlreadyLinkedCanUnlink; + } + } else { + dataversesAlreadyLinked = ctxt.dvLinking().findLinkingDataverses(dvObject.getId(), searchParam); + for (Dataverse dv : dataversesAlreadyLinked) { + if (ctxt.permissions().hasPermissionsFor(getRequest(), dv, EnumSet.of(Permission.LinkDataverse))) { + dataversesAlreadyLinkedCanUnlink.add(dv); + } + } + return dataversesAlreadyLinkedCanUnlink; + } + return null; + } + } +} diff --git a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java index 1a2697180e0..8b0ea201aa3 100644 --- a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java +++ b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java @@ -291,22 +291,35 @@ public static JsonObjectBuilder json(Workflow wf){ return bld; } + + public static JsonObjectBuilder json(Dataverse dv, boolean minimal) { + if (!minimal){ + return json(dv, false, false, false, null); + } else { + return json(dv, false, false, true, null); + } + } public static JsonObjectBuilder json(Dataverse dv) { - return json(dv, false, false, null); + return json(dv, false, false, false, null); } //TODO: Once we upgrade to Java EE 8 we can remove objects from the builder, and this email removal can be done in a better place. - public static JsonObjectBuilder json(Dataverse dv, Boolean hideEmail, Boolean returnOwners, Long childCount) { + public static JsonObjectBuilder json(Dataverse dv, Boolean hideEmail, Boolean returnOwners, Boolean minimal, Long childCount) { JsonObjectBuilder bld = jsonObjectBuilder() .add("id", dv.getId()) .add("alias", dv.getAlias()) - .add("name", dv.getName()) - .add("affiliation", dv.getAffiliation()); - if(!hideEmail) { + .add("name", dv.getName()); + //minimal refers to only returning the id alias and name for + //used in selecting collections available for linking + if (minimal) { + return bld; + } + bld.add("affiliation", dv.getAffiliation()); + if (!hideEmail) { bld.add("dataverseContacts", JsonPrinter.json(dv.getDataverseContacts())); } - if (returnOwners){ + if (returnOwners) { bld.add("isPartOf", getOwnersFromDvObject(dv)); } bld.add("permissionRoot", dv.isPermissionRoot()) @@ -323,8 +336,8 @@ public static JsonObjectBuilder json(Dataverse dv, Boolean hideEmail, Boolean re if (dv.getDataverseTheme() != null) { bld.add("theme", JsonPrinter.json(dv.getDataverseTheme())); } - if(dv.getStorageDriverId() != null) { - bld.add("storageDriverLabel", DataAccess.getStorageDriverLabelFor(dv.getStorageDriverId())); + if (dv.getStorageDriverId() != null) { + bld.add("storageDriverLabel", DataAccess.getStorageDriverLabelFor(dv.getStorageDriverId())); } if (dv.getFilePIDsEnabled() != null) { bld.add("filePIDsEnabled", dv.getFilePIDsEnabled()); @@ -333,7 +346,7 @@ public static JsonObjectBuilder json(Dataverse dv, Boolean hideEmail, Boolean re bld.add("isReleased", dv.isReleased()); List inputLevels = dv.getDataverseFieldTypeInputLevels(); - if(!inputLevels.isEmpty()) { + if (!inputLevels.isEmpty()) { bld.add("inputLevels", JsonPrinter.jsonDataverseFieldTypeInputLevels(inputLevels)); } @@ -341,7 +354,6 @@ public static JsonObjectBuilder json(Dataverse dv, Boolean hideEmail, Boolean re bld.add("childCount", childCount); } addDatasetFileCountLimit(dv, bld); - return bld; } diff --git a/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java index 45f9f8a1ce0..eb247ce6c7f 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java @@ -46,6 +46,7 @@ import io.restassured.path.json.JsonPath; import org.hamcrest.CoreMatchers; import org.hamcrest.Matchers; +import static org.hamcrest.Matchers.greaterThan; public class DataversesIT { @@ -678,6 +679,235 @@ public void testImportDDI() throws IOException, InterruptedException { Response deleteUserResponse = UtilIT.deleteUser(username); assertEquals(200, deleteUserResponse.getStatusCode()); } + + @Test + public void testGetLinkableDataverses(){ + Response createUser = UtilIT.createRandomUser(); + String apiToken = UtilIT.getApiTokenFromResponse(createUser); + String username = UtilIT.getUsernameFromResponse(createUser); + + Response createDataverseResponse = UtilIT.createRandomDataverse(apiToken); + String dataverseAlias = UtilIT.getAliasFromResponse(createDataverseResponse); + + //Create dataverse for linking + Response createDataverseResponseForLinking = UtilIT.createRandomDataverse(apiToken); + String dataverseAliasForLinking = UtilIT.getAliasFromResponse(createDataverseResponseForLinking); + + + Response publishDataverse = UtilIT.publishDataverseViaNativeApi(dataverseAlias, apiToken); + assertEquals(200, publishDataverse.getStatusCode()); + + publishDataverse = UtilIT.publishDataverseViaNativeApi(dataverseAliasForLinking, apiToken); + assertEquals(200, publishDataverse.getStatusCode()); + + Response createDatasetResponse = UtilIT.createRandomDatasetViaNativeApi(dataverseAlias, apiToken); + createDatasetResponse.prettyPrint(); + String datasetPersistentId = UtilIT.getDatasetPersistentIdFromResponse(createDatasetResponse); + + Integer datasetId = UtilIT.getDatasetIdFromResponse(createDatasetResponse); + UtilIT.publishDatasetViaNativeApi(datasetPersistentId, "major", apiToken); + + Response getLinkableDataverses = UtilIT.getLinkableDataverses("dataset", datasetPersistentId, apiToken, dataverseAliasForLinking); + getLinkableDataverses.prettyPrint(); + getLinkableDataverses.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data[0].alias", equalTo(dataverseAliasForLinking)); + + Response getLinkableDataversesForDataverse = UtilIT.getLinkableDataverses("dataverse", dataverseAlias, apiToken, dataverseAliasForLinking); + getLinkableDataversesForDataverse.prettyPrint(); + getLinkableDataverses.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data[0].alias", equalTo(dataverseAliasForLinking)); + + //Should be able to get based on a partial alias... + // Partial must include the first part of the name + String searchTerm = dataverseAliasForLinking.substring(0, 7); + + Response getLinkableDataversesForDataversePartial = UtilIT.getLinkableDataverses("dataverse", dataverseAlias, apiToken, searchTerm); + getLinkableDataversesForDataversePartial.prettyPrint(); + getLinkableDataversesForDataversePartial.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data[0].alias", equalTo(dataverseAliasForLinking)); + + //if I give a blank search term i should get the one that I have perms on + Response getLinkableDataversesForDataverseBlank = UtilIT.getLinkableDataverses("dataverse", dataverseAlias, apiToken, ""); + getLinkableDataversesForDataverseBlank.prettyPrint(); + getLinkableDataversesForDataverseBlank.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data[0].alias", equalTo(dataverseAliasForLinking)); + + + //Try with empty string search term + searchTerm = ""; + getLinkableDataversesForDataversePartial = UtilIT.getLinkableDataverses("dataverse", dataverseAlias, apiToken, searchTerm); + getLinkableDataversesForDataversePartial.prettyPrint(); + getLinkableDataversesForDataversePartial.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data[0].alias", equalTo(dataverseAliasForLinking)); + + //Should get same result if search term is null + getLinkableDataversesForDataversePartial = UtilIT.getLinkableDataverses("dataverse", dataverseAlias, apiToken, null); + getLinkableDataversesForDataversePartial.prettyPrint(); + getLinkableDataversesForDataversePartial.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data[0].alias", equalTo(dataverseAliasForLinking)); + + // Create a child of the linking DV + Response createLevel1a = UtilIT.createSubDataverse(UtilIT.getRandomDvAlias() + "-level1a", null, apiToken, dataverseAliasForLinking); + createLevel1a.prettyPrint(); + String childLinking = UtilIT.getAliasFromResponse(createLevel1a); + + //Try to link the child - should not link to parent - should link to "uncle" + getLinkableDataversesForDataversePartial = UtilIT.getLinkableDataverses("dataverse", childLinking, apiToken, searchTerm); + getLinkableDataversesForDataversePartial.prettyPrint(); + getLinkableDataversesForDataversePartial.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data[0].alias", equalTo(dataverseAlias)); + + //Try with bad target alias + searchTerm = ""; + Response getLinkableDataversesForDataverseBadId = UtilIT.getLinkableDataverses("dataverse", "junque@#", apiToken, searchTerm); + getLinkableDataversesForDataverseBadId.prettyPrint(); + getLinkableDataversesForDataverseBadId.then().assertThat() + .statusCode(NOT_FOUND.getStatusCode()); + + // create new user and dataverse - the new dataverse should not be available to the first user for linking... + Response createUserTwo = UtilIT.createRandomUser(); + String apiTokenTwo = UtilIT.getApiTokenFromResponse(createUserTwo); + String usernameTwo = UtilIT.getUsernameFromResponse(createUserTwo); + + //Create dataverse that should be unavailable for linking + Response createDataverseResponseUnavailableForLinking = UtilIT.createRandomDataverse(apiTokenTwo); + createDataverseResponseUnavailableForLinking.prettyPrint(); + String dataverseAliasUnavailableForLinking = UtilIT.getAliasFromResponse(createDataverseResponseUnavailableForLinking); + publishDataverse = UtilIT.publishDataverseViaNativeApi(dataverseAliasUnavailableForLinking, apiTokenTwo); + publishDataverse.prettyPrint(); + assertEquals(200, publishDataverse.getStatusCode()); + + //user 3 will not have permissions + Response createUserThree = UtilIT.createRandomUser(); + String apiTokenThree = UtilIT.getApiTokenFromResponse(createUserThree); + String usernameThree = UtilIT.getUsernameFromResponse(createUserThree); + + Response getUnavailableForDatasetBadSearchTerm = UtilIT.getLinkableDataverses("dataset", datasetPersistentId, apiToken, "xxQQQ-Batman"); + getUnavailableForDatasetBadSearchTerm.prettyPrint(); + getUnavailableForDatasetBadSearchTerm.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data.size()", equalTo(0)); + + Response getUnavailableForDataset = UtilIT.getLinkableDataverses("dataset", datasetPersistentId, apiToken, dataverseAliasUnavailableForLinking); + getUnavailableForDataset.prettyPrint(); + getUnavailableForDataset.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data.size()", equalTo(0)); + + Response getGuestUnavailableForDataset = UtilIT.getLinkableDataverses("dataset", datasetPersistentId, "", dataverseAliasUnavailableForLinking); + getGuestUnavailableForDataset.prettyPrint(); + getGuestUnavailableForDataset.then().assertThat() + .statusCode(FORBIDDEN.getStatusCode()); + + Response getUnavailableForDataverse = UtilIT.getLinkableDataverses("dataverse", dataverseAlias, apiToken, dataverseAliasUnavailableForLinking); + getUnavailableForDataverse.prettyPrint(); + getUnavailableForDataverse.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data.size()", equalTo(0)); + + Response getNoPermsOnAnyCollection = UtilIT.getLinkableDataverses("dataverse", dataverseAlias, apiTokenThree, ""); + getNoPermsOnAnyCollection.prettyPrint(); + getNoPermsOnAnyCollection.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data.size()", equalTo(0)); + + //now link a dataset and see that it's unavailable in the future + + Response makeSuperUser = UtilIT.setSuperuserStatus(username, Boolean.TRUE); + + Response linkDataset = UtilIT.linkDataset(datasetPersistentId, dataverseAliasForLinking, apiToken); + linkDataset.prettyPrint(); + linkDataset.then().assertThat() + .statusCode(OK.getStatusCode()); + + //set user api back to non-super user so perms are limited + UtilIT.setSuperuserStatus(username, Boolean.FALSE); + + //should get an empty list because dataset is already linked + getLinkableDataverses = UtilIT.getLinkableDataverses("dataset", datasetPersistentId, apiToken, dataverseAliasForLinking); + getLinkableDataverses.prettyPrint(); + getLinkableDataverses.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data.size()", equalTo(0)); + + //if you ask for already linked you should get one + getLinkableDataverses = UtilIT.getLinkableDataverses("dataset", datasetPersistentId, apiToken, dataverseAliasForLinking, true); + + getLinkableDataverses.prettyPrint(); + getLinkableDataverses.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data.size()", equalTo(1)); + + //if you ask for already linked you should get one unless you don't have perms + getLinkableDataverses = UtilIT.getLinkableDataverses("dataset", datasetPersistentId, apiTokenThree, dataverseAliasForLinking, true); + + getLinkableDataverses.prettyPrint(); + getLinkableDataverses.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data.size()", equalTo(0)); + + //if you ask for already linked you should get one unless there's a bad search terms + getLinkableDataverses = UtilIT.getLinkableDataverses("dataset", datasetPersistentId, apiToken, "QQQBatmanSymbol", true); + + getLinkableDataverses.prettyPrint(); + getLinkableDataverses.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data.size()", equalTo(0)); + + //set user api back to super user for cleanup + UtilIT.setSuperuserStatus(username, Boolean.TRUE); + + //Create dataverse that should be available for super user + Response createDataverseResponseAvailableForLinkingBySuperUser = UtilIT.createRandomDataverse(apiTokenTwo); + createDataverseResponseAvailableForLinkingBySuperUser.prettyPrint(); + String dataverseAliasAvailableForLinkingBySuperUser = UtilIT.getAliasFromResponse(createDataverseResponseAvailableForLinkingBySuperUser); + publishDataverse = UtilIT.publishDataverseViaNativeApi(dataverseAliasAvailableForLinkingBySuperUser, apiToken); + publishDataverse.prettyPrint(); + assertEquals(200, publishDataverse.getStatusCode()); + + //First see that the super user gets dataverses to link to + getLinkableDataverses = UtilIT.getLinkableDataverses("dataset", datasetPersistentId, apiToken, dataverseAliasAvailableForLinkingBySuperUser); + getLinkableDataverses.prettyPrint(); + getLinkableDataverses.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data.size()", equalTo(1)); + + // Clean up + Response destroyDatasetResponse = UtilIT.destroyDataset(datasetId, apiToken); + assertEquals(200, destroyDatasetResponse.getStatusCode()); + + Response deleteDataverseResponseSuper = UtilIT.deleteDataverse(dataverseAliasAvailableForLinkingBySuperUser, apiToken); + assertEquals(200, deleteDataverseResponseSuper.getStatusCode()); + + Response deleteDataverseResponse = UtilIT.deleteDataverse(dataverseAlias, apiToken); + assertEquals(200, deleteDataverseResponse.getStatusCode()); + + deleteDataverseResponse = UtilIT.deleteDataverse(childLinking, apiToken); + assertEquals(200, deleteDataverseResponse.getStatusCode()); + + deleteDataverseResponse = UtilIT.deleteDataverse(dataverseAliasForLinking, apiToken); + assertEquals(200, deleteDataverseResponse.getStatusCode()); + + deleteDataverseResponse = UtilIT.deleteDataverse(dataverseAliasUnavailableForLinking, apiToken); + assertEquals(200, deleteDataverseResponse.getStatusCode()); + + Response deleteUserResponse = UtilIT.deleteUser(usernameTwo); + assertEquals(200, deleteUserResponse.getStatusCode()); + + deleteUserResponse = UtilIT.deleteUser(usernameThree); + assertEquals(200, deleteUserResponse.getStatusCode()); + + deleteUserResponse = UtilIT.deleteUser(username); + assertEquals(200, deleteUserResponse.getStatusCode()); + + } @Test public void testImport() throws IOException, InterruptedException { @@ -2363,17 +2593,39 @@ public void testUpdateInputLevelDisplayOnCreateOverride() { } @Test - public void testCreateAndGetTemplates() { + public void testCreateAndGetTemplates() throws JsonParseException { Response createUserResponse = UtilIT.createRandomUser(); String apiToken = UtilIT.getApiTokenFromResponse(createUserResponse); Response createSecondUserResponse = UtilIT.createRandomUser(); String secondApiToken = UtilIT.getApiTokenFromResponse(createSecondUserResponse); - + + /* + We need to make this a non-inherited metadatablocks so the get template will only get templates from current dv + */ + Response createDataverseResponse = UtilIT.createRandomDataverse(apiToken); createDataverseResponse.then().assertThat().statusCode(CREATED.getStatusCode()); String dataverseAlias = UtilIT.getAliasFromResponse(createDataverseResponse); + String newName = "New Test Dataverse Name"; + String newAffiliation = "New Test Dataverse Affiliation"; + String newDataverseType = Dataverse.DataverseType.TEACHING_COURSES.toString(); + String[] newContactEmails = new String[]{"new_email@dataverse.com"}; + String[] newInputLevelNames = new String[]{"geographicCoverage"}; + String[] newFacetIds = new String[]{"contributorName"}; + String[] newMetadataBlockNames = new String[]{"citation", "geospatial", "biomedical"}; + + //Giving the new Dataverse updated metadatablocks so that it will not inherit templates + Response updateDataverseResponse = UtilIT.updateDataverse( + dataverseAlias, dataverseAlias, newName, newAffiliation, newDataverseType, newContactEmails, newInputLevelNames, + null, newMetadataBlockNames, apiToken, + Boolean.FALSE, Boolean.FALSE, null + ); + + updateDataverseResponse.then().assertThat() + .statusCode(OK.getStatusCode()); + // Create a template String jsonString = """ @@ -2411,6 +2663,7 @@ public void testCreateAndGetTemplates() { jsonString, apiToken ); + createTemplateResponse.then().assertThat().statusCode(OK.getStatusCode()) .body("data.name", equalTo("Dataverse template")) .body("data.isDefault", equalTo(true)) diff --git a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java index 9c0cc213df0..c11f66aa749 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -4758,6 +4758,59 @@ static Response updateDataverseFeaturedItem(long featuredItemId, String apiToken) { return updateDataverseFeaturedItem(featuredItemId, content, displayOrder, keepFile, pathToFile, null, null, apiToken); } + + static Response getLinkableDataverses(String type, String dvObjectId, String apiToken, String searchTerm) { + return getLinkableDataverses(type, dvObjectId, apiToken, searchTerm, false); + } + + static Response getLinkableDataverses(String type, String dvObjectId, String apiToken, String searchTerm, boolean alreadyLinked) { + + String idInPath = dvObjectId; // Assume it's a number to start. + String optionalQueryParam = ""; // If idOrPersistentId is a number we'll just put it in the path. + if (type.equals("dataset")) { + if (!NumberUtils.isCreatable(idInPath)) { + idInPath = ":persistentId"; + if (searchTerm == null || searchTerm.isEmpty() ) { + optionalQueryParam = "?persistentId=" + dvObjectId; + } else { + optionalQueryParam = "&persistentId=" + dvObjectId; + } + } + } + + if (alreadyLinked){ + if (optionalQueryParam.isEmpty() && (searchTerm == null || searchTerm.isEmpty())){ + optionalQueryParam = "?alreadyLinking=true"; + } else { + optionalQueryParam = optionalQueryParam + "&alreadyLinking=true"; + } + } + + if (searchTerm == null) { + if (!apiToken.isEmpty()) { + return given() + .header(API_TOKEN_HTTP_HEADER, apiToken) + .get("/api/dataverses/" + idInPath + "/" + type + "/linkingDataverses" + optionalQueryParam); + + } else { + return given() + .get("/api/dataverses/" + idInPath + "/" + type + "/linkingDataverses" + optionalQueryParam); + } + + } else { + if (!apiToken.isEmpty()) { + return given() + .header(API_TOKEN_HTTP_HEADER, apiToken) + .get("/api/dataverses/" + idInPath + "/" + type + "/linkingDataverses?searchTerm=" + searchTerm + optionalQueryParam); + + } else { + return given() + .get("/api/dataverses/" + idInPath + "/" + type + "/linkingDataverses?searchTerm=" + searchTerm + optionalQueryParam); + } + + } + } + static Response updateDataverseFeaturedItem(long featuredItemId, String content, int displayOrder, diff --git a/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/GetLinkingDataverseListCommandTest.java b/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/GetLinkingDataverseListCommandTest.java new file mode 100644 index 00000000000..7f167370c63 --- /dev/null +++ b/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/GetLinkingDataverseListCommandTest.java @@ -0,0 +1,163 @@ +package edu.harvard.iq.dataverse.engine.command.impl; + +import edu.harvard.iq.dataverse.*; +import edu.harvard.iq.dataverse.authorization.Permission; +import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; +import edu.harvard.iq.dataverse.engine.command.CommandContext; +import edu.harvard.iq.dataverse.engine.command.DataverseRequest; +import edu.harvard.iq.dataverse.engine.command.exception.CommandException; +import edu.harvard.iq.dataverse.engine.command.exception.IllegalCommandException; +import edu.harvard.iq.dataverse.util.BundleUtil; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@ExtendWith(MockitoExtension.class) +public class GetLinkingDataverseListCommandTest { + + @Mock + private DataverseRequest dataverseRequest; + @Mock + private CommandContext commandContext; + @Mock + private PermissionServiceBean permissionService; + @Mock + private DataverseServiceBean dataverseService; + @Mock + private AuthenticatedUser authenticatedUser; + + @BeforeEach + public void setUp() { + Mockito.when(dataverseRequest.getUser()).thenReturn(authenticatedUser); + } + + @Test + public void execute_shouldThrowException_whenUserIsNotAuthenticated() { + // Arrange + Mockito.when(authenticatedUser.isAuthenticated()).thenReturn(false); + DvObject dvObject = new Dataset(); + GetLinkingDataverseListCommand sut = new GetLinkingDataverseListCommand(dataverseRequest, dvObject, ""); + + // Act & Assert + assertThrows(IllegalCommandException.class, () -> { + sut.execute(commandContext); + }, BundleUtil.getStringFromBundle("dataverse.link.user")); + } + + @Test + public void execute_shouldReturnPermittedList_forLinkingDataset() throws CommandException { + // Arrange + String searchTerm = "test"; + Dataset datasetToLink = new Dataset(); + List permittedList = Arrays.asList(new Dataverse(), new Dataverse()); + + Mockito.when(commandContext.permissions()).thenReturn(permissionService); + Mockito.when(commandContext.dataverses()).thenReturn(dataverseService); + Mockito.when(authenticatedUser.isAuthenticated()).thenReturn(true); + Mockito.when(permissionService.findPermittedCollections(dataverseRequest, authenticatedUser, Permission.LinkDataset, searchTerm)) + .thenReturn(permittedList); + Mockito.when(dataverseService.removeUnlinkableDataverses(permittedList, datasetToLink)) + .thenReturn(permittedList); // Assume none are removed for this test + + GetLinkingDataverseListCommand sut = new GetLinkingDataverseListCommand(dataverseRequest, datasetToLink, searchTerm); + + // Act + List result = sut.execute(commandContext); + + // Assert + assertEquals(2, result.size()); + assertEquals(permittedList, result); + Mockito.verify(permissionService).findPermittedCollections(dataverseRequest, authenticatedUser, Permission.LinkDataset, searchTerm); + Mockito.verify(dataverseService).removeUnlinkableDataverses(permittedList, datasetToLink); + } + + @Test + public void execute_shouldReturnPermittedList_forLinkingDataverse() throws CommandException { + // Arrange + String searchTerm = "test"; + Dataverse dataverseToLink = new Dataverse(); + List permittedList = Collections.singletonList(new Dataverse()); + + Mockito.when(commandContext.permissions()).thenReturn(permissionService); + Mockito.when(commandContext.dataverses()).thenReturn(dataverseService); + Mockito.when(authenticatedUser.isAuthenticated()).thenReturn(true); + Mockito.when(permissionService.findPermittedCollections(dataverseRequest, authenticatedUser, Permission.LinkDataverse, searchTerm)) + .thenReturn(permittedList); + Mockito.when(dataverseService.removeUnlinkableDataverses(permittedList, dataverseToLink)) + .thenReturn(permittedList); + + GetLinkingDataverseListCommand sut = new GetLinkingDataverseListCommand(dataverseRequest, dataverseToLink, searchTerm); + + // Act + List result = sut.execute(commandContext); + + // Assert + assertEquals(1, result.size()); + assertEquals(permittedList, result); + Mockito.verify(permissionService).findPermittedCollections(dataverseRequest, authenticatedUser, Permission.LinkDataverse, searchTerm); + Mockito.verify(dataverseService).removeUnlinkableDataverses(permittedList, dataverseToLink); + } + + @Test + public void execute_shouldUseEmptyString_whenSearchTermIsNull() throws CommandException { + // Arrange + String searchTerm = null; + Dataset datasetToLink = new Dataset(); + List expectedList = Collections.emptyList(); + + Mockito.when(commandContext.permissions()).thenReturn(permissionService); + // if there are none found then the remove unlinkable doesn't get run + // Mockito.when(commandContext.dataverses()).thenReturn(dataverseService); + Mockito.when(authenticatedUser.isAuthenticated()).thenReturn(true); + // Note: verify that an empty string "" is passed instead of null + Mockito.when(permissionService.findPermittedCollections(dataverseRequest, authenticatedUser, Permission.LinkDataset, "")) + .thenReturn(expectedList); + // if there are none found then the remove unlinkable doesn't get run + // Mockito.when(dataverseService.removeUnlinkableDataverses(expectedList, datasetToLink)).thenReturn(expectedList); + + GetLinkingDataverseListCommand sut = new GetLinkingDataverseListCommand(dataverseRequest, datasetToLink, searchTerm); + + // Act + List result = sut.execute(commandContext); + + // Assert + assertTrue(result.isEmpty()); + Mockito.verify(permissionService).findPermittedCollections(dataverseRequest, authenticatedUser, Permission.LinkDataset, ""); + } + + @Test + public void execute_shouldReturnEmptyList_whenNoPermittedCollectionsFound() throws CommandException { + // Arrange + String searchTerm = "nonexistent"; + Dataset datasetToLink = new Dataset(); + List emptyList = Collections.emptyList(); + + Mockito.when(commandContext.permissions()).thenReturn(permissionService); + // if there are none found then the remove unlinkable doesn't get run + // Mockito.when(commandContext.dataverses()).thenReturn(dataverseService); + Mockito.when(authenticatedUser.isAuthenticated()).thenReturn(true); + Mockito.when(permissionService.findPermittedCollections(dataverseRequest, authenticatedUser, Permission.LinkDataset, searchTerm)) + .thenReturn(emptyList); + // if there are none found then the remove unlinkable doesn't get run + // Mockito.when(dataverseService.removeUnlinkableDataverses(emptyList, datasetToLink)).thenReturn(emptyList); + + GetLinkingDataverseListCommand sut = new GetLinkingDataverseListCommand(dataverseRequest, datasetToLink, searchTerm); + + // Act + List result = sut.execute(commandContext); + + // Assert + assertTrue(result.isEmpty()); + } +}