From 2c03617bfb5889a7c2b88cffeca68ce6d3bde096 Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Thu, 7 Aug 2025 09:37:23 -0400 Subject: [PATCH 01/65] #11710 dummy endpoint to start --- .../java/edu/harvard/iq/dataverse/api/Dataverses.java | 9 +++++++++ 1 file changed, 9 insertions(+) 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 6b7e783a3eb..1523703893a 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java @@ -1763,6 +1763,15 @@ public Response linkDataverse(@Context ContainerRequestContext crc, @PathParam(" return ex.getResponse(); } } + + @GET + @AuthRequired + @Path("/linkingDataverses/{searchTerm}") + public Response getLinkingDataverseList(){ + return null; + } + + @GET @AuthRequired From d37725bc61606e524e52cce60e26bad9d04b24d6 Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Thu, 7 Aug 2025 13:20:05 -0400 Subject: [PATCH 02/65] #11710 update path --- src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 1523703893a..dba634e8ba9 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java @@ -1766,8 +1766,9 @@ public Response linkDataverse(@Context ContainerRequestContext crc, @PathParam(" @GET @AuthRequired - @Path("/linkingDataverses/{searchTerm}") - public Response getLinkingDataverseList(){ + @Path("{identifier}/linkingDataverses/{searchTerm}") + public Response getLinkingDataverseList(@PathParam("identifier") String dvIdtf, @PathParam("searchTerm") String searchTerm){ + return null; } From 59db4ff859fca947516827f0d3e2dc7c10af2c7b Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Thu, 7 Aug 2025 16:24:38 -0400 Subject: [PATCH 03/65] #11710 add api functionality and tests --- .../iq/dataverse/DataverseServiceBean.java | 23 +++++++++++++++---- .../iq/dataverse/api/AbstractApiBean.java | 2 +- .../dataverse/api/DataverseFeaturedItems.java | 2 +- .../harvard/iq/dataverse/api/Dataverses.java | 20 ++++++++++++---- .../iq/dataverse/api/DataversesIT.java | 22 ++++++++++++++++++ .../edu/harvard/iq/dataverse/api/UtilIT.java | 12 ++++++++++ 6 files changed, 69 insertions(+), 12 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java index c14711060af..23d94338992 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java @@ -505,18 +505,29 @@ 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) { + 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<>(); + List remove = new ArrayList<>(); if (alreadyLinkeddv_ids != null && !alreadyLinkeddv_ids.isEmpty()) { alreadyLinkeddv_ids.stream().map((testDVId) -> this.find(testDVId)).forEachOrdered((removeIt) -> { @@ -526,9 +537,11 @@ public List filterDataversesForLinking(String query, DataverseRequest 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); } + } } 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 76ef91fbd3a..f99727b16db 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java @@ -601,7 +601,7 @@ protected DvObject findDvo(@NotNull final String id) throws WrappedResponse { * @throws WrappedResponse */ @NotNull - protected DvObject findDvoByIdAndFeaturedItemTypeOrDie(@NotNull final String dvIdtf, String type) throws WrappedResponse { + protected DvObject findDvoByIdAndTypeOrDie(@NotNull final String dvIdtf, String type) throws WrappedResponse { try { DataverseFeaturedItem.TYPES dvType = DataverseFeaturedItem.getDvType(type); DvObject dvObject = null; 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..00f1aa76e7e 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) : 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 dba634e8ba9..ae49e34d693 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java @@ -1767,9 +1767,19 @@ public Response linkDataverse(@Context ContainerRequestContext crc, @PathParam(" @GET @AuthRequired @Path("{identifier}/linkingDataverses/{searchTerm}") - public Response getLinkingDataverseList(@PathParam("identifier") String dvIdtf, @PathParam("searchTerm") String searchTerm){ - - return null; + public Response getLinkingDataverseList(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf, @PathParam("searchTerm") String searchTerm, @FormDataParam("type") String type){ + //first determine what you are linking based on identifier and type + try{ + DvObject dvObject = findDvoByIdAndTypeOrDie(dvIdtf, type); + List dataversesForLinking = dataverseService.filterDataversesForLinking(searchTerm, createDataverseRequest(getRequestUser(crc)), dvObject); + JsonArrayBuilder dvBuilder = Json.createArrayBuilder(); + for (Dataverse dv : dataversesForLinking) { + dvBuilder.add(dv.getAlias()); + } + return ok(dvBuilder); + } catch (WrappedResponse wr) { + return wr.getResponse(); + } } @@ -1813,7 +1823,7 @@ public Response createFeaturedItem(@Context ContainerRequestContext crc, try { dataverse = findDataverseOrDie(dvIdtf); if (dvObjectIdtf != null) { - dvObject = findDvoByIdAndFeaturedItemTypeOrDie(dvObjectIdtf, type); + dvObject = findDvoByIdAndTypeOrDie(dvObjectIdtf, type); } } catch (WrappedResponse wr) { return wr.getResponse(); @@ -1901,7 +1911,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)) : 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/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java index 3e1a160c9f2..4d9f8aa9509 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java @@ -677,6 +677,28 @@ public void testImportDDI() throws IOException, InterruptedException { Response deleteUserResponse = UtilIT.deleteUser(username); assertEquals(200, deleteUserResponse.getStatusCode()); } + + @Test + public void testGetLinkableDataverses(){ + Response createUser = UtilIT.createRandomUser(); + String username = UtilIT.getUsernameFromResponse(createUser); + Response makeSuperUser = UtilIT.makeSuperUser(username); + assertEquals(200, makeSuperUser.getStatusCode()); + String apiToken = UtilIT.getApiTokenFromResponse(createUser); + + Response createDataverseResponse = UtilIT.createRandomDataverse(apiToken); + String dataverseAlias = UtilIT.getAliasFromResponse(createDataverseResponse); + + Response publishDataverse = UtilIT.publishDataverseViaNativeApi(dataverseAlias, apiToken); + assertEquals(200, publishDataverse.getStatusCode()); + + Response createDatasetResponse = UtilIT.createRandomDatasetViaNativeApi(dataverseAlias, apiToken); + createDatasetResponse.prettyPrint(); + String datasetPersistentId = UtilIT.getDatasetPersistentIdFromResponse(createDatasetResponse); + + Response getLinkableDataverses = UtilIT.getLinkableDataverses("dataset", datasetPersistentId, apiToken, dataverseAlias); + getLinkableDataverses.prettyPrint(); + } @Test public void testImport() throws IOException, InterruptedException { 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 24f2adbb3ed..77b4303393e 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -4773,6 +4773,18 @@ 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 dataverseAlias) { + return given() + .header(API_TOKEN_HTTP_HEADER, apiToken) + .contentType("application/json") + .get("/api/dataverses/" + dataverseAlias + "/featuredItems"); + + /* + {identifier}/linkingDataverses/{searchTerm} + */ + + } static Response updateDataverseFeaturedItem(long featuredItemId, String content, int displayOrder, From 1203c435472d980fc648d3b25940f8d2d6cad5a2 Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Fri, 8 Aug 2025 16:54:52 -0400 Subject: [PATCH 04/65] #11710 fix test --- .../harvard/iq/dataverse/api/Dataverses.java | 14 +++++++++-- .../iq/dataverse/api/DataversesIT.java | 9 +++++++- .../edu/harvard/iq/dataverse/api/UtilIT.java | 23 +++++++++++-------- 3 files changed, 34 insertions(+), 12 deletions(-) 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 ae49e34d693..21b67d1c3c9 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java @@ -1766,9 +1766,16 @@ public Response linkDataverse(@Context ContainerRequestContext crc, @PathParam(" @GET @AuthRequired - @Path("{identifier}/linkingDataverses/{searchTerm}") - public Response getLinkingDataverseList(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf, @PathParam("searchTerm") String searchTerm, @FormDataParam("type") String type){ + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Path("{identifier}/{type}/linkingDataverses/{searchTerm}") + public Response getLinkingDataverseList(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf, @PathParam("searchTerm") String searchTerm, @PathParam("type") String type){ //first determine what you are linking based on identifier and type + System.out.print("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); + System.out.print("in dataverses method"); + System.out.print("id: " + dvIdtf); + System.out.print("searchTerm: " + searchTerm); + System.out.print("type: " + type); try{ DvObject dvObject = findDvoByIdAndTypeOrDie(dvIdtf, type); List dataversesForLinking = dataverseService.filterDataversesForLinking(searchTerm, createDataverseRequest(getRequestUser(crc)), dvObject); @@ -1779,6 +1786,9 @@ public Response getLinkingDataverseList(@Context ContainerRequestContext crc, @P return ok(dvBuilder); } catch (WrappedResponse wr) { return wr.getResponse(); + } catch (Exception e){ + return error(Status.BAD_REQUEST, e.getLocalizedMessage()); + } } 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 4d9f8aa9509..e13d83f6f88 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java @@ -696,8 +696,15 @@ public void testGetLinkableDataverses(){ createDatasetResponse.prettyPrint(); String datasetPersistentId = UtilIT.getDatasetPersistentIdFromResponse(createDatasetResponse); - Response getLinkableDataverses = UtilIT.getLinkableDataverses("dataset", datasetPersistentId, apiToken, dataverseAlias); + Integer datasetId = UtilIT.getDatasetIdFromResponse(createDatasetResponse); + UtilIT.publishDatasetViaNativeApi(datasetPersistentId, "major", apiToken); + + System.out.print("After pub dataset"); + + Response getLinkableDataverses = UtilIT.getLinkableDataverses("dataset", datasetPersistentId, apiToken, "dv0"); getLinkableDataverses.prettyPrint(); + getLinkableDataverses.then().assertThat() + .statusCode(OK.getStatusCode()); } @Test 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 77b4303393e..f28c0b3e640 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -4774,16 +4774,21 @@ static Response updateDataverseFeaturedItem(long featuredItemId, return updateDataverseFeaturedItem(featuredItemId, content, displayOrder, keepFile, pathToFile, null, null, apiToken); } - static Response getLinkableDataverses (String type, String dvObjectId, String apiToken, String dataverseAlias) { - return given() + static Response getLinkableDataverses (String type, String dvObjectId, String apiToken, String searchTerm) { + + 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"; + optionalQueryParam = "?persistentId=" + dvObjectId; + } + } + + return given() .header(API_TOKEN_HTTP_HEADER, apiToken) - .contentType("application/json") - .get("/api/dataverses/" + dataverseAlias + "/featuredItems"); - - /* - {identifier}/linkingDataverses/{searchTerm} - */ - + .get("/api/dataverses/" + idInPath + "/" + type + "/linkingDataverses/" + searchTerm + optionalQueryParam); + } static Response updateDataverseFeaturedItem(long featuredItemId, String content, From 8e8b084c038a0b103314e203e5b735ea624330f3 Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Mon, 11 Aug 2025 08:49:06 -0400 Subject: [PATCH 05/65] #11710 add "mini" dv to json printer --- .../java/edu/harvard/iq/dataverse/api/Dataverses.java | 2 +- .../harvard/iq/dataverse/util/json/JsonPrinter.java | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) 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 21b67d1c3c9..a6aa24f417b 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java @@ -1781,7 +1781,7 @@ public Response getLinkingDataverseList(@Context ContainerRequestContext crc, @P List dataversesForLinking = dataverseService.filterDataversesForLinking(searchTerm, createDataverseRequest(getRequestUser(crc)), dvObject); JsonArrayBuilder dvBuilder = Json.createArrayBuilder(); for (Dataverse dv : dataversesForLinking) { - dvBuilder.add(dv.getAlias()); + dvBuilder.add(json(dv, true)); } return ok(dvBuilder); } catch (WrappedResponse wr) { 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 592a893083c..ec9abdfa7cc 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 @@ -269,6 +269,17 @@ public static JsonObjectBuilder json(Workflow wf){ return bld; } + + public static JsonObjectBuilder json(Dataverse dv, Boolean mini) { + if (!mini){ + return json(dv, false, false, null); + } else { + return jsonObjectBuilder() + .add("id", dv.getId()) + .add("alias", dv.getAlias()) + .add("name", dv.getName()); + } + } public static JsonObjectBuilder json(Dataverse dv) { return json(dv, false, false, null); From bb40ca4b40e35a9f2315606913f2c309af363213 Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Mon, 11 Aug 2025 14:42:05 -0400 Subject: [PATCH 06/65] #11710 fix templates test for existing databases/templates --- .../iq/dataverse/api/DataversesIT.java | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) 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 e13d83f6f88..53643e3d5a5 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java @@ -2314,17 +2314,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"}; + + // Assert that the error is returned for having both MetadataBlockNames and inheritMetadataBlocksFromParent + 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 = """ From 791edfb2a4237eb28ae2ef71f9dd76b3e23d4a33 Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Tue, 12 Aug 2025 09:47:49 -0400 Subject: [PATCH 07/65] #11710 code cleanup --- src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 53643e3d5a5..889f9589819 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java @@ -2320,6 +2320,7 @@ public void testCreateAndGetTemplates() throws JsonParseException { 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 */ @@ -2342,10 +2343,9 @@ public void testCreateAndGetTemplates() throws JsonParseException { null, newMetadataBlockNames, apiToken, Boolean.FALSE, Boolean.FALSE, null ); + updateDataverseResponse.then().assertThat() .statusCode(OK.getStatusCode()); - - // Create a template @@ -2384,6 +2384,7 @@ public void testCreateAndGetTemplates() throws JsonParseException { jsonString, apiToken ); + createTemplateResponse.then().assertThat().statusCode(OK.getStatusCode()) .body("data.name", equalTo("Dataverse template")) .body("data.usageCount", equalTo(0)) From 61dae90a8f203b6dca0df9c5317e70c40df40d24 Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Tue, 12 Aug 2025 10:51:39 -0400 Subject: [PATCH 08/65] #11710 add test for perms --- .../harvard/iq/dataverse/api/Dataverses.java | 6 +-- .../iq/dataverse/api/DataversesIT.java | 46 ++++++++++++++++--- 2 files changed, 40 insertions(+), 12 deletions(-) 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 a6aa24f417b..f4788120ae5 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java @@ -1771,11 +1771,7 @@ public Response linkDataverse(@Context ContainerRequestContext crc, @PathParam(" @Path("{identifier}/{type}/linkingDataverses/{searchTerm}") public Response getLinkingDataverseList(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf, @PathParam("searchTerm") String searchTerm, @PathParam("type") String type){ //first determine what you are linking based on identifier and type - System.out.print("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); - System.out.print("in dataverses method"); - System.out.print("id: " + dvIdtf); - System.out.print("searchTerm: " + searchTerm); - System.out.print("type: " + type); + try{ DvObject dvObject = findDvoByIdAndTypeOrDie(dvIdtf, type); List dataversesForLinking = dataverseService.filterDataversesForLinking(searchTerm, createDataverseRequest(getRequestUser(crc)), dvObject); 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 889f9589819..d35270de1a3 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java @@ -681,30 +681,62 @@ public void testImportDDI() throws IOException, InterruptedException { @Test public void testGetLinkableDataverses(){ Response createUser = UtilIT.createRandomUser(); - String username = UtilIT.getUsernameFromResponse(createUser); - Response makeSuperUser = UtilIT.makeSuperUser(username); - assertEquals(200, makeSuperUser.getStatusCode()); String apiToken = UtilIT.getApiTokenFromResponse(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); - - System.out.print("After pub dataset"); - Response getLinkableDataverses = UtilIT.getLinkableDataverses("dataset", datasetPersistentId, apiToken, "dv0"); + Response getLinkableDataverses = UtilIT.getLinkableDataverses("dataset", datasetPersistentId, apiToken, dataverseAliasForLinking); getLinkableDataverses.prettyPrint(); getLinkableDataverses.then().assertThat() - .statusCode(OK.getStatusCode()); + .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)); + + // 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); + + //Create dataverse that should be unavailable for linking + Response createDataverseResponseUnavailableForLinking = UtilIT.createRandomDataverse(apiTokenTwo); + createDataverseResponseUnavailableForLinking.prettyPrint(); + String dataverseAliasUnavailableForLinking = UtilIT.getAliasFromResponse(createDataverseResponseUnavailableForLinking); + + Response getUnavailableForDataset = UtilIT.getLinkableDataverses("dataset", datasetPersistentId, apiToken, dataverseAliasUnavailableForLinking); + getUnavailableForDataset.prettyPrint(); + getUnavailableForDataset.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data.size()", equalTo(0)); + + Response getUnavailableForDataverse = UtilIT.getLinkableDataverses("dataverse", dataverseAlias, apiToken, dataverseAliasUnavailableForLinking); + getUnavailableForDataverse.prettyPrint(); + getUnavailableForDataverse.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data.size()", equalTo(0)); + + } @Test From 32d06ed75bbb7f06a25d856a37963978855cc8a2 Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Tue, 12 Aug 2025 11:02:07 -0400 Subject: [PATCH 09/65] #11710 add publish to test --- src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java | 2 ++ 1 file changed, 2 insertions(+) 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 d35270de1a3..678616d7db8 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java @@ -723,6 +723,8 @@ public void testGetLinkableDataverses(){ Response createDataverseResponseUnavailableForLinking = UtilIT.createRandomDataverse(apiTokenTwo); createDataverseResponseUnavailableForLinking.prettyPrint(); String dataverseAliasUnavailableForLinking = UtilIT.getAliasFromResponse(createDataverseResponseUnavailableForLinking); + publishDataverse = UtilIT.publishDataverseViaNativeApi(dataverseAliasUnavailableForLinking, apiTokenTwo); + assertEquals(200, publishDataverse.getStatusCode()); Response getUnavailableForDataset = UtilIT.getLinkableDataverses("dataset", datasetPersistentId, apiToken, dataverseAliasUnavailableForLinking); getUnavailableForDataset.prettyPrint(); From 59572c9485e49f2d00d1857034819c07b7337a47 Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Tue, 12 Aug 2025 14:20:51 -0400 Subject: [PATCH 10/65] #11710 code cleanup --- src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java | 1 - src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java | 1 - 2 files changed, 2 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java index 23d94338992..b8ce8cb1a6b 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java @@ -541,7 +541,6 @@ public List filterDataversesForLinking(String query, DataverseRequest || (linkedDataverse != null && this.permissionService.requestOn(req, res).has(Permission.LinkDataverse))) { dataverseList.add(res); } - } } 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 f4788120ae5..8bfa3594602 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java @@ -1767,7 +1767,6 @@ public Response linkDataverse(@Context ContainerRequestContext crc, @PathParam(" @GET @AuthRequired @Produces(MediaType.APPLICATION_JSON) - @Consumes(MediaType.MULTIPART_FORM_DATA) @Path("{identifier}/{type}/linkingDataverses/{searchTerm}") public Response getLinkingDataverseList(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf, @PathParam("searchTerm") String searchTerm, @PathParam("type") String type){ //first determine what you are linking based on identifier and type From d6625a4692ecfbf06ab026379e35afedaa4e1deb Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Tue, 12 Aug 2025 14:53:07 -0400 Subject: [PATCH 11/65] #11710 test that linked dv's are removed from list --- .../iq/dataverse/api/DataversesIT.java | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) 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 678616d7db8..5358debacda 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java @@ -682,7 +682,8 @@ public void testImportDDI() throws IOException, InterruptedException { 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); @@ -737,7 +738,25 @@ public void testGetLinkableDataverses(){ getUnavailableForDataverse.then().assertThat() .statusCode(OK.getStatusCode()) .body("data.size()", equalTo(0)); - + + //now link a dataverse 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 it 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)); } From 12f69ffaae42873c9008238e4fd9dfc7c013fd5a Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Tue, 12 Aug 2025 15:00:04 -0400 Subject: [PATCH 12/65] #11710 clean up comments --- src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 5358debacda..7782dea8fea 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java @@ -739,7 +739,7 @@ public void testGetLinkableDataverses(){ .statusCode(OK.getStatusCode()) .body("data.size()", equalTo(0)); - //now link a dataverse and see that it's unavailable in the future + //now link a dataset and see that it's unavailable in the future Response makeSuperUser = UtilIT.setSuperuserStatus(username, Boolean.TRUE); @@ -748,7 +748,7 @@ public void testGetLinkableDataverses(){ linkDataset.then().assertThat() .statusCode(OK.getStatusCode()); - //set it back to non-super user so perms are limited + //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 From 8456b90a1cc015ff3f935217bdf79649f43f93c6 Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Tue, 12 Aug 2025 15:30:32 -0400 Subject: [PATCH 13/65] Update native-api.rst --- doc/sphinx-guides/source/api/native-api.rst | 44 +++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index 8de396e14b3..53270476437 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 that a given Dataset or Dataverse Collection may be linked to +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The user may provide a search term to limit the list of Dataverse Collections returned +The response is a JSON array of the ids, aliases, and names of the Dataverse collections that a given Dataset or Dataverse Collection may be linked to: + +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/$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/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/$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/searchOn?persistentId=doi:10.5072/FK2/J8SJZB" + + + List Featured Collections for a Dataverse Collection ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From bb74b24000878104063f0ddf816290388c8aafcc Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Tue, 12 Aug 2025 15:32:50 -0400 Subject: [PATCH 14/65] Update native-api.rst --- doc/sphinx-guides/source/api/native-api.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index 53270476437..9a861c353d7 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -732,11 +732,11 @@ Note: you must have "Add Dataset" permission in the given collection to invoke t .. _featured-collections: -List Dataverse Collections that a given Dataset or Dataverse Collection may be linked to -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +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 response is a JSON array of the ids, aliases, and names of the Dataverse collections that a given Dataset or Dataverse Collection may be linked to: +The user may provide a search term to limit the list of Dataverse Collections returned. +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: From 01de521e93b58c5dd7cc89cc6edf5795a8d60434 Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Tue, 12 Aug 2025 16:53:21 -0400 Subject: [PATCH 15/65] #11710 add test for partial search term --- .../edu/harvard/iq/dataverse/api/Dataverses.java | 12 +++++++----- .../edu/harvard/iq/dataverse/api/DataversesIT.java | 12 +++++++++++- 2 files changed, 18 insertions(+), 6 deletions(-) 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 8bfa3594602..4d11cc0152a 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java @@ -1771,14 +1771,16 @@ public Response linkDataverse(@Context ContainerRequestContext crc, @PathParam(" public Response getLinkingDataverseList(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf, @PathParam("searchTerm") String searchTerm, @PathParam("type") String type){ //first determine what you are linking based on identifier and type - try{ - DvObject dvObject = findDvoByIdAndTypeOrDie(dvIdtf, type); - List dataversesForLinking = dataverseService.filterDataversesForLinking(searchTerm, createDataverseRequest(getRequestUser(crc)), dvObject); - JsonArrayBuilder dvBuilder = Json.createArrayBuilder(); + try { + DvObject dvObject = findDvoByIdAndTypeOrDie(dvIdtf, type); + List dataversesForLinking = dataverseService.filterDataversesForLinking(searchTerm, createDataverseRequest(getRequestUser(crc)), dvObject); + JsonArrayBuilder dvBuilder = Json.createArrayBuilder(); + if (dataversesForLinking != null && !dataversesForLinking.isEmpty()) { for (Dataverse dv : dataversesForLinking) { dvBuilder.add(json(dv, true)); } - return ok(dvBuilder); + } + return ok(dvBuilder); } catch (WrappedResponse wr) { return wr.getResponse(); } catch (Exception e){ 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 7782dea8fea..3967666d798 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java @@ -715,7 +715,17 @@ public void testGetLinkableDataverses(){ 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, 5); + + Response getLinkableDataversesForDataversePartial = UtilIT.getLinkableDataverses("dataverse", dataverseAlias, apiToken, searchTerm); + getLinkableDataversesForDataversePartial.prettyPrint(); + getLinkableDataversesForDataversePartial.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data[0].alias", equalTo(dataverseAliasForLinking)); + // 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); From 9e391921948730c5d34663d2bd0bde74b34828df Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Tue, 12 Aug 2025 16:56:34 -0400 Subject: [PATCH 16/65] Update native-api.rst --- doc/sphinx-guides/source/api/native-api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index 9a861c353d7..22a77cf0d4a 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -735,7 +735,7 @@ Note: you must have "Add Dataset" permission in the given collection to invoke t 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 user must 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 and must include the beginning of the Dataverse Collections' names. 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: From 3157b2461f789f059329f58c1bdc142573fab551 Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Wed, 13 Aug 2025 10:32:35 -0400 Subject: [PATCH 17/65] Create 11710-get-available-dataverses-api.md --- doc/release-notes/11710-get-available-dataverses-api.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 doc/release-notes/11710-get-available-dataverses-api.md 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..6658d1d8fd5 --- /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 the start 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. From 250e0e4c46107f0de3d106579260786993402614 Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Wed, 13 Aug 2025 10:45:31 -0400 Subject: [PATCH 18/65] #11710 add cleanup after tests --- .../iq/dataverse/api/DataversesIT.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) 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 3967666d798..34a5c6a6cec 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java @@ -729,6 +729,7 @@ public void testGetLinkableDataverses(){ // 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); @@ -767,6 +768,29 @@ public void testGetLinkableDataverses(){ 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); + + + // Clean up + Response destroyDatasetResponse = UtilIT.destroyDataset(datasetId, apiToken); + assertEquals(200, destroyDatasetResponse.getStatusCode()); + + Response deleteDataverseResponse = UtilIT.deleteDataverse(dataverseAlias, 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(username); + assertEquals(200, deleteUserResponse.getStatusCode()); } From 9be5fb38f77b150f6a7d22be46cba109ac8e089f Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Wed, 13 Aug 2025 14:21:17 -0400 Subject: [PATCH 19/65] Update native-api.rst --- doc/sphinx-guides/source/api/native-api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index 22a77cf0d4a..2cdcbc65eb4 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -766,7 +766,7 @@ For a given 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/$SEARCH_TERM?persistentId=$PERSISTENT_IDENTIFIER"" + curl -H "X-Dataverse-key:$API_TOKEN" -X GET "$SERVER_URL/api/dataverses/:persistentId/$OBJECT_TYPE/linkingDataverses/$SEARCH_TERM?persistentId=$PERSISTENT_IDENTIFIER" The fully expanded example above (without environment variables) looks like this: From a451f375b7bf9b497d997d1e188aef19b6ac6683 Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Mon, 18 Aug 2025 15:32:50 -0400 Subject: [PATCH 20/65] #11710 change search term to query param --- src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java | 4 ++-- src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) 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 22f12ca06ff..b771275a112 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java @@ -1775,8 +1775,8 @@ public Response linkDataverse(@Context ContainerRequestContext crc, @PathParam(" @GET @AuthRequired @Produces(MediaType.APPLICATION_JSON) - @Path("{identifier}/{type}/linkingDataverses/{searchTerm}") - public Response getLinkingDataverseList(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf, @PathParam("searchTerm") String searchTerm, @PathParam("type") String type){ + @Path("{identifier}/{type}/linkingDataverses") + public Response getLinkingDataverseList(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf, @QueryParam("searchTerm") String searchTerm, @PathParam("type") String type){ //first determine what you are linking based on identifier and type try { 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 f28c0b3e640..fc2adc16682 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -4781,13 +4781,13 @@ static Response getLinkableDataverses (String type, String dvObjectId, String ap if (type.equals("dataset")) { if (!NumberUtils.isCreatable(idInPath)) { idInPath = ":persistentId"; - optionalQueryParam = "?persistentId=" + dvObjectId; + optionalQueryParam = "&persistentId=" + dvObjectId; } } return given() .header(API_TOKEN_HTTP_HEADER, apiToken) - .get("/api/dataverses/" + idInPath + "/" + type + "/linkingDataverses/" + searchTerm + optionalQueryParam); + .get("/api/dataverses/" + idInPath + "/" + type + "/linkingDataverses?searchTerm=" + searchTerm + optionalQueryParam); } static Response updateDataverseFeaturedItem(long featuredItemId, From 645dd38079c39632b0c07d6b5f8721fa0a2d63ad Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Mon, 18 Aug 2025 16:12:04 -0400 Subject: [PATCH 21/65] #11710 test for empty search --- .../edu/harvard/iq/dataverse/DataverseServiceBean.java | 4 ++++ .../java/edu/harvard/iq/dataverse/api/DataversesIT.java | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java index b8ce8cb1a6b..6e3a3dcc1c8 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java @@ -535,6 +535,10 @@ public List filterDataversesForLinking(String query, DataverseRequest }); } + if (dvo instanceof Dataverse dataverse){ + remove.add(dataverse); + } + for (Dataverse res : results) { if (!remove.contains(res)) { if ((linkedDataset != null && this.permissionService.requestOn(req, res).has(Permission.LinkDataset)) 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 34a5c6a6cec..167aa4ad756 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java @@ -721,6 +721,15 @@ public void testGetLinkableDataverses(){ String searchTerm = dataverseAliasForLinking.substring(0, 5); Response getLinkableDataversesForDataversePartial = UtilIT.getLinkableDataverses("dataverse", dataverseAlias, apiToken, searchTerm); + getLinkableDataversesForDataversePartial.prettyPrint(); + getLinkableDataversesForDataversePartial.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()) From 694cd3431cf4fc5c92e405551d873df1242dd8bb Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Tue, 19 Aug 2025 10:13:03 -0400 Subject: [PATCH 22/65] Update native-api.rst --- doc/sphinx-guides/source/api/native-api.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index 2e0d7a6f230..0a208967f07 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -735,7 +735,7 @@ Note: you must have "Add Dataset" permission in the given collection to invoke t List Dataverse Collections to which a given Dataset or Dataverse Collection may be linked ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The user must 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 and must include the beginning of the Dataverse Collections' names. +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 and must include the beginning of the Dataverse Collections' names. 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: @@ -748,13 +748,13 @@ For a given Dataverse Collection: export ID=collectionAlias export SEARCH_TERM=searchOn - curl -H "X-Dataverse-key:$API_TOKEN" -X GET "$SERVER_URL/api/dataverses/$ID/$OBJECT_TYPE/linkingDataverses/$SEARCH_TERM" + 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/searchOn" + 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: @@ -766,13 +766,13 @@ For a given 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/$SEARCH_TERM?persistentId=$PERSISTENT_IDENTIFIER" + 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/searchOn?persistentId=doi:10.5072/FK2/J8SJZB" + 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" From 5c1ce14f5abae0ce350ca113caa5deb89153794b Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Tue, 26 Aug 2025 11:33:42 -0400 Subject: [PATCH 23/65] #11719 combine perms and search filter for query --- .../iq/dataverse/DataverseServiceBean.java | 41 +++++++++++++++++++ .../iq/dataverse/PermissionServiceBean.java | 24 +++++++++-- .../harvard/iq/dataverse/api/Dataverses.java | 18 +++++++- .../iq/dataverse/api/DataversesIT.java | 17 ++++++-- 4 files changed, 91 insertions(+), 9 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java index 6e3a3dcc1c8..85d4179a6d9 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java @@ -550,6 +550,47 @@ 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); + } else { + //dataset is always owned by a dataverse + remove.add((Dataverse)dvo.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 f1099c0a439..778bf23586a 100644 --- a/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java @@ -101,7 +101,7 @@ WITH grouplist AS ( WHERE explicitgroup_authenticateduser.containedauthenticatedusers_id = @USERID ) - SELECT * FROM DATAVERSE WHERE id IN ( + SELECT * FROM DATAVERSE WHERE id IN ( SELECT definitionpoint_id FROM roleassignment WHERE roleassignment.assigneeidentifier IN ( @@ -157,7 +157,7 @@ AND EXISTS (SELECT id FROM dataverserole WHERE dataverserole.id = roleassignment AND @IPRANGESQL ) ) - ) + ) @SEARCHCLAUSE """; /** * A request-level permission query (e.g includes IP ras). @@ -921,8 +921,16 @@ private boolean hasUnrestrictedReleasedFiles(DatasetVersion targetDatasetVersion public List findPermittedCollections(DataverseRequest request, AuthenticatedUser user, Permission permission) { 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"; @@ -950,16 +958,26 @@ public List findPermittedCollections(DataverseRequest request, Authen } } } + + String searchClause = ""; + if (!searchTerm.isEmpty()){ + searchClause = " AND ((LOWER(DATAVERSE.alias) LIKE '%@ALIAS%') OR (LOWER(DATAVERSE.name) LIKE '%@NAME%') OR (LOWER(DATAVERSE.affiliation) LIKE '%@AFFILIATION%')) " + .replace("@ALIAS", searchTerm.toLowerCase()) + .replace("@NAME", searchTerm.toLowerCase()) + .replace("@AFFILIATION", searchTerm.toLowerCase()); + } String sqlCode = LIST_ALL_DATAVERSES_USER_HAS_PERMISSION .replace("@USERID", String.valueOf(user.getId())) .replace("@PERMISSIONBIT", String.valueOf(permissionBit)) - .replace("@IPRANGESQL", ipRangeSQL); + .replace("@IPRANGESQL", ipRangeSQL) + .replace("@SEARCHCLAUSE", searchClause); return em.createNativeQuery(sqlCode, Dataverse.class).getResultList(); } return null; } + /** * Calculates the complete list of role assignments for a given user on a DvObject. * This includes roles assigned directly to the user and roles inherited from any groups 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 5522b35906b..bf332a5bb3d 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; @@ -1781,8 +1782,21 @@ public Response getLinkingDataverseList(@Context ContainerRequestContext crc, @P try { DvObject dvObject = findDvoByIdAndTypeOrDie(dvIdtf, type); - List dataversesForLinking = dataverseService.filterDataversesForLinking(searchTerm, createDataverseRequest(getRequestUser(crc)), dvObject); - JsonArrayBuilder dvBuilder = Json.createArrayBuilder(); + // List dataversesForLinking = dataverseService.filterDataversesForLinking(searchTerm, createDataverseRequest(getRequestUser(crc)), dvObject); + // public List findPermittedCollections(DataverseRequest request, AuthenticatedUser user, Permission permission, String searchTerm) { + + AuthenticatedUser requestUser = (AuthenticatedUser)getRequestUser(crc); + List dataversesForLinking = new ArrayList<>(); + + if ((dvObject instanceof Dataset)) { + dataversesForLinking = permissionService.findPermittedCollections( new DataverseRequest(requestUser, (IpAddress) null), requestUser, Permission.LinkDataset, searchTerm); + } else { + dataversesForLinking = permissionService.findPermittedCollections( new DataverseRequest(requestUser, (IpAddress) null), requestUser, Permission.LinkDataverse, searchTerm); + + } + + dataversesForLinking = dataverseService.removeUnlinkableDataverses(dataversesForLinking, dvObject); + JsonArrayBuilder dvBuilder = Json.createArrayBuilder(); if (dataversesForLinking != null && !dataversesForLinking.isEmpty()) { for (Dataverse dv : dataversesForLinking) { dvBuilder.add(json(dv, true)); 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 167aa4ad756..9ba53f2e8b6 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java @@ -683,7 +683,7 @@ 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); @@ -718,7 +718,7 @@ public void testGetLinkableDataverses(){ //Should be able to get based on a partial alias... // Partial must include the first part of the name - String searchTerm = dataverseAliasForLinking.substring(0, 5); + String searchTerm = dataverseAliasForLinking.substring(0, 7); Response getLinkableDataversesForDataversePartial = UtilIT.getLinkableDataverses("dataverse", dataverseAlias, apiToken, searchTerm); getLinkableDataversesForDataversePartial.prettyPrint(); @@ -745,15 +745,21 @@ public void testGetLinkableDataverses(){ createDataverseResponseUnavailableForLinking.prettyPrint(); String dataverseAliasUnavailableForLinking = UtilIT.getAliasFromResponse(createDataverseResponseUnavailableForLinking); publishDataverse = UtilIT.publishDataverseViaNativeApi(dataverseAliasUnavailableForLinking, apiTokenTwo); + publishDataverse.prettyPrint(); assertEquals(200, publishDataverse.getStatusCode()); - Response getUnavailableForDataset = UtilIT.getLinkableDataverses("dataset", datasetPersistentId, apiToken, dataverseAliasUnavailableForLinking); + //user 3 will not have permissions + Response createUserThree = UtilIT.createRandomUser(); + String apiTokenThree = UtilIT.getApiTokenFromResponse(createUserThree); + String usernameThree = UtilIT.getUsernameFromResponse(createUserThree); + + Response getUnavailableForDataset = UtilIT.getLinkableDataverses("dataset", datasetPersistentId, apiTokenThree, dataverseAliasUnavailableForLinking); getUnavailableForDataset.prettyPrint(); getUnavailableForDataset.then().assertThat() .statusCode(OK.getStatusCode()) .body("data.size()", equalTo(0)); - Response getUnavailableForDataverse = UtilIT.getLinkableDataverses("dataverse", dataverseAlias, apiToken, dataverseAliasUnavailableForLinking); + Response getUnavailableForDataverse = UtilIT.getLinkableDataverses("dataverse", dataverseAlias, apiTokenThree, dataverseAliasUnavailableForLinking); getUnavailableForDataverse.prettyPrint(); getUnavailableForDataverse.then().assertThat() .statusCode(OK.getStatusCode()) @@ -798,6 +804,9 @@ public void testGetLinkableDataverses(){ 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()); From 2bcfa709ec04aaca0c5166f3086ebdcedcbd370c Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Tue, 26 Aug 2025 14:55:13 -0400 Subject: [PATCH 24/65] #11710 refactor query update --- .../harvard/iq/dataverse/PermissionServiceBean.java | 12 ++---------- .../edu/harvard/iq/dataverse/api/DataversesIT.java | 10 ++++++++-- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java index 778bf23586a..6fe5db974eb 100644 --- a/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java @@ -157,7 +157,7 @@ AND EXISTS (SELECT id FROM dataverserole WHERE dataverserole.id = roleassignment AND @IPRANGESQL ) ) - ) @SEARCHCLAUSE + ) AND ((LOWER(DATAVERSE.alias) LIKE '%@SEARCHTERM%') OR (LOWER(DATAVERSE.name) LIKE '%@SEARCHTERM%') OR (LOWER(DATAVERSE.affiliation) LIKE '%@SEARCHTERM%')) """; /** * A request-level permission query (e.g includes IP ras). @@ -959,19 +959,11 @@ public List findPermittedCollections(DataverseRequest request, Authen } } - String searchClause = ""; - if (!searchTerm.isEmpty()){ - searchClause = " AND ((LOWER(DATAVERSE.alias) LIKE '%@ALIAS%') OR (LOWER(DATAVERSE.name) LIKE '%@NAME%') OR (LOWER(DATAVERSE.affiliation) LIKE '%@AFFILIATION%')) " - .replace("@ALIAS", searchTerm.toLowerCase()) - .replace("@NAME", searchTerm.toLowerCase()) - .replace("@AFFILIATION", searchTerm.toLowerCase()); - } - String sqlCode = LIST_ALL_DATAVERSES_USER_HAS_PERMISSION .replace("@USERID", String.valueOf(user.getId())) .replace("@PERMISSIONBIT", String.valueOf(permissionBit)) .replace("@IPRANGESQL", ipRangeSQL) - .replace("@SEARCHCLAUSE", searchClause); + .replace("@SEARCHTERM", searchTerm); return em.createNativeQuery(sqlCode, Dataverse.class).getResultList(); } return null; 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 9ba53f2e8b6..64c88197494 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java @@ -753,17 +753,23 @@ public void testGetLinkableDataverses(){ String apiTokenThree = UtilIT.getApiTokenFromResponse(createUserThree); String usernameThree = UtilIT.getUsernameFromResponse(createUserThree); - Response getUnavailableForDataset = UtilIT.getLinkableDataverses("dataset", datasetPersistentId, apiTokenThree, dataverseAliasUnavailableForLinking); + Response getUnavailableForDataset = UtilIT.getLinkableDataverses("dataset", datasetPersistentId, apiToken, dataverseAliasUnavailableForLinking); getUnavailableForDataset.prettyPrint(); getUnavailableForDataset.then().assertThat() .statusCode(OK.getStatusCode()) .body("data.size()", equalTo(0)); - Response getUnavailableForDataverse = UtilIT.getLinkableDataverses("dataverse", dataverseAlias, apiTokenThree, dataverseAliasUnavailableForLinking); + 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 From c688965eb4a64f4faacc70a164be62e35ee39c41 Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Wed, 27 Aug 2025 15:00:45 -0400 Subject: [PATCH 25/65] #11710 separate perms test from lookup --- .../iq/dataverse/DataverseServiceBean.java | 51 ++++++++++++++++++ .../iq/dataverse/PermissionServiceBean.java | 14 ++--- .../iq/dataverse/api/AbstractApiBean.java | 6 ++- .../dataverse/api/DataverseFeaturedItems.java | 2 +- .../harvard/iq/dataverse/api/Dataverses.java | 53 ++++++++++--------- 5 files changed, 88 insertions(+), 38 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java index 85d4179a6d9..d3f96da107e 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java @@ -687,6 +687,57 @@ public List filterDataversesByNamePattern(String pattern) { return searchResults; } + /* + This method takes a search parameter and expands it into a list of + Dataverses with matching names. + The search is performed on the name with the trailing word "dataverse" + stripped (if present). This way the search on "data" (or on "da" pr + "dat") does NOT return almost every dataverse in the database - since + most of them have names that end in "... Dataverse". + The query isn't pretty, but it works, and it's still EJB QL (and NOT a + native query). + */ + public List filterDataversesByNameAliasPattern(String pattern) { + + pattern = pattern.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 + " %"; + } + /*if (pattern.length() == 2) { + pattern2 = pattern + "%"; + }*/ + + + String qstr = "select dv from Dataverse dv " + + "where (LOWER(dv.name) LIKE :dataverse and ((SUBSTRING(LOWER(dv.name),0,(LENGTH(dv.name)-9)) LIKE :pattern1) " + + " or (SUBSTRING(LOWER(dv.name),0,(LENGTH(dv.name)-9)) LIKE :pattern2))) " + + "or (LOWER(dv.name) NOT LIKE :dataverse and ((LOWER(dv.name) LIKE :pattern1) " + + " or (LOWER(dv.name) LIKE :pattern2)) " + + "or (LOWER(dv.alias) LIKE :pattern1) " + + " or (LOWER(dv.alias) LIKE :pattern2))) " + + "order by dv.alias"; + + List searchResults = null; + + try { + searchResults = em.createQuery(qstr, Dataverse.class) + .setParameter("dataverse", "%dataverse") + .setParameter("pattern1", pattern1) + .setParameter("pattern2", pattern2) + .getResultList(); + } catch (Exception ex) { + searchResults = null; + } + + return searchResults; + } + /** * Used to identify and properly display Harvested objects on the dataverse page. * diff --git a/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java index 6fe5db974eb..a4111963ea2 100644 --- a/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java @@ -157,7 +157,7 @@ AND EXISTS (SELECT id FROM dataverserole WHERE dataverserole.id = roleassignment AND @IPRANGESQL ) ) - ) AND ((LOWER(DATAVERSE.alias) LIKE '%@SEARCHTERM%') OR (LOWER(DATAVERSE.name) LIKE '%@SEARCHTERM%') OR (LOWER(DATAVERSE.affiliation) LIKE '%@SEARCHTERM%')) + ) """; /** * A request-level permission query (e.g includes IP ras). @@ -922,15 +922,8 @@ public List findPermittedCollections(DataverseRequest request, Authen 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) { + public List findPermittedCollections(DataverseRequest request, AuthenticatedUser user, int permissionBit) { if (user != null) { // IP Group - Only check IP if a User is calling for themself String ipRangeSQL = "FALSE"; @@ -962,8 +955,7 @@ public List findPermittedCollections(DataverseRequest request, Authen String sqlCode = LIST_ALL_DATAVERSES_USER_HAS_PERMISSION .replace("@USERID", String.valueOf(user.getId())) .replace("@PERMISSIONBIT", String.valueOf(permissionBit)) - .replace("@IPRANGESQL", ipRangeSQL) - .replace("@SEARCHTERM", searchTerm); + .replace("@IPRANGESQL", ipRangeSQL); 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 f99727b16db..938d7fc9081 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java @@ -601,7 +601,7 @@ protected DvObject findDvo(@NotNull final String id) throws WrappedResponse { * @throws WrappedResponse */ @NotNull - protected DvObject findDvoByIdAndTypeOrDie(@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; @@ -634,7 +634,9 @@ protected DvObject findDvoByIdAndTypeOrDie(@NotNull final String dvIdtf, String } } } - DataverseFeaturedItem.validateTypeAndDvObject(dvIdtf, dvObject, dvType); + if (testForReleased){ + DataverseFeaturedItem.validateTypeAndDvObject(dvIdtf, dvObject, dvType); + } 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 00f1aa76e7e..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) ? findDvoByIdAndTypeOrDie(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 bf332a5bb3d..f59cfbc3bab 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java @@ -70,6 +70,7 @@ import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.StreamingOutput; +import java.sql.PreparedStatement; import org.glassfish.jersey.media.multipart.FormDataBodyPart; import org.glassfish.jersey.media.multipart.FormDataContentDisposition; import org.glassfish.jersey.media.multipart.FormDataParam; @@ -120,7 +121,7 @@ public class Dataverses extends AbstractApiBean { @EJB DataverseFeaturedItemServiceBean dataverseFeaturedItemServiceBean; - + @POST @AuthRequired public Response addRoot(@Context ContainerRequestContext crc, String body) { @@ -1777,37 +1778,41 @@ public Response linkDataverse(@Context ContainerRequestContext crc, @PathParam(" @AuthRequired @Produces(MediaType.APPLICATION_JSON) @Path("{identifier}/{type}/linkingDataverses") - public Response getLinkingDataverseList(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf, @QueryParam("searchTerm") String searchTerm, @PathParam("type") String type){ + public Response getLinkingDataverseList(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf, @QueryParam("searchTerm") String searchTerm, @PathParam("type") String type) { //first determine what you are linking based on identifier and type + + AuthenticatedUser requestUser = (AuthenticatedUser) getRequestUser(crc); + DataverseRequest dvReq = new DataverseRequest(requestUser, (IpAddress) null); + List dataversesForLinking; + dataversesForLinking = permissionService.findPermittedCollections(dvReq, requestUser, Permission.LinkDataset); try { - DvObject dvObject = findDvoByIdAndTypeOrDie(dvIdtf, type); - // List dataversesForLinking = dataverseService.filterDataversesForLinking(searchTerm, createDataverseRequest(getRequestUser(crc)), dvObject); - // public List findPermittedCollections(DataverseRequest request, AuthenticatedUser user, Permission permission, String searchTerm) { - - AuthenticatedUser requestUser = (AuthenticatedUser)getRequestUser(crc); - List dataversesForLinking = new ArrayList<>(); - - if ((dvObject instanceof Dataset)) { - dataversesForLinking = permissionService.findPermittedCollections( new DataverseRequest(requestUser, (IpAddress) null), requestUser, Permission.LinkDataset, searchTerm); - } else { - dataversesForLinking = permissionService.findPermittedCollections( new DataverseRequest(requestUser, (IpAddress) null), requestUser, Permission.LinkDataverse, searchTerm); - - } - - dataversesForLinking = dataverseService.removeUnlinkableDataverses(dataversesForLinking, dvObject); - JsonArrayBuilder dvBuilder = Json.createArrayBuilder(); - if (dataversesForLinking != null && !dataversesForLinking.isEmpty()) { + + DvObject dvObject = findDvoByIdAndTypeOrDie(dvIdtf, type, false); + List dataversesForLinkingSearch = new ArrayList(); + dataversesForLinkingSearch = dataverseService.filterDataversesByNameAliasPattern(searchTerm); + + List mergedWithSearch = new ArrayList<>(); + dataversesForLinking = dataverseService.removeUnlinkableDataverses(dataversesForLinking, dvObject); + if (!dataversesForLinkingSearch.isEmpty()) { for (Dataverse dv : dataversesForLinking) { + if (dataversesForLinkingSearch.contains(dv)) { + mergedWithSearch.add(dv); + } + } + } + JsonArrayBuilder dvBuilder = Json.createArrayBuilder(); + if (!mergedWithSearch.isEmpty()) { + for (Dataverse dv : mergedWithSearch) { dvBuilder.add(json(dv, true)); } } - return ok(dvBuilder); + return ok(dvBuilder); } catch (WrappedResponse wr) { return wr.getResponse(); - } catch (Exception e){ + } catch (Exception e) { return error(Status.BAD_REQUEST, e.getLocalizedMessage()); - + } } @@ -1852,7 +1857,7 @@ public Response createFeaturedItem(@Context ContainerRequestContext crc, try { dataverse = findDataverseOrDie(dvIdtf); if (dvObjectIdtf != null) { - dvObject = findDvoByIdAndTypeOrDie(dvObjectIdtf, type); + dvObject = findDvoByIdAndTypeOrDie(dvObjectIdtf, type, true); } } catch (WrappedResponse wr) { return wr.getResponse(); @@ -1940,7 +1945,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() - ? findDvoByIdAndTypeOrDie(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)); From 96c8b25c2efcaf58ebe47e62fb1356eed56bfdc2 Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Wed, 27 Aug 2025 16:28:46 -0400 Subject: [PATCH 26/65] #11710 remove unused method --- .../iq/dataverse/DataverseServiceBean.java | 51 ------------------- .../harvard/iq/dataverse/api/Dataverses.java | 12 ++--- 2 files changed, 6 insertions(+), 57 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java index d3f96da107e..85d4179a6d9 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java @@ -687,57 +687,6 @@ public List filterDataversesByNamePattern(String pattern) { return searchResults; } - /* - This method takes a search parameter and expands it into a list of - Dataverses with matching names. - The search is performed on the name with the trailing word "dataverse" - stripped (if present). This way the search on "data" (or on "da" pr - "dat") does NOT return almost every dataverse in the database - since - most of them have names that end in "... Dataverse". - The query isn't pretty, but it works, and it's still EJB QL (and NOT a - native query). - */ - public List filterDataversesByNameAliasPattern(String pattern) { - - pattern = pattern.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 + " %"; - } - /*if (pattern.length() == 2) { - pattern2 = pattern + "%"; - }*/ - - - String qstr = "select dv from Dataverse dv " - + "where (LOWER(dv.name) LIKE :dataverse and ((SUBSTRING(LOWER(dv.name),0,(LENGTH(dv.name)-9)) LIKE :pattern1) " - + " or (SUBSTRING(LOWER(dv.name),0,(LENGTH(dv.name)-9)) LIKE :pattern2))) " - + "or (LOWER(dv.name) NOT LIKE :dataverse and ((LOWER(dv.name) LIKE :pattern1) " - + " or (LOWER(dv.name) LIKE :pattern2)) " - + "or (LOWER(dv.alias) LIKE :pattern1) " - + " or (LOWER(dv.alias) LIKE :pattern2))) " - + "order by dv.alias"; - - List searchResults = null; - - try { - searchResults = em.createQuery(qstr, Dataverse.class) - .setParameter("dataverse", "%dataverse") - .setParameter("pattern1", pattern1) - .setParameter("pattern2", pattern2) - .getResultList(); - } catch (Exception ex) { - searchResults = null; - } - - return searchResults; - } - /** * Used to identify and properly display Harvested objects on the dataverse page. * 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 f59cfbc3bab..512b7b00fab 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java @@ -1780,17 +1780,17 @@ public Response linkDataverse(@Context ContainerRequestContext crc, @PathParam(" @Path("{identifier}/{type}/linkingDataverses") public Response getLinkingDataverseList(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf, @QueryParam("searchTerm") String searchTerm, @PathParam("type") String type) { //first determine what you are linking based on identifier and type - - AuthenticatedUser requestUser = (AuthenticatedUser) getRequestUser(crc); - DataverseRequest dvReq = new DataverseRequest(requestUser, (IpAddress) null); - List dataversesForLinking; - dataversesForLinking = permissionService.findPermittedCollections(dvReq, requestUser, Permission.LinkDataset); try { DvObject dvObject = findDvoByIdAndTypeOrDie(dvIdtf, type, false); List dataversesForLinkingSearch = new ArrayList(); - dataversesForLinkingSearch = dataverseService.filterDataversesByNameAliasPattern(searchTerm); + dataversesForLinkingSearch = dataverseService.filterDataversesByNamePattern(searchTerm); + + AuthenticatedUser requestUser = (AuthenticatedUser) getRequestUser(crc); + DataverseRequest dvReq = new DataverseRequest(requestUser, (IpAddress) null); + List dataversesForLinking; + dataversesForLinking = permissionService.findPermittedCollections(dvReq, requestUser, Permission.LinkDataset); List mergedWithSearch = new ArrayList<>(); dataversesForLinking = dataverseService.removeUnlinkableDataverses(dataversesForLinking, dvObject); From ec3f8aad2f144e4fb8ecc10680a93a18be3eb1aa Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Thu, 28 Aug 2025 09:40:43 -0400 Subject: [PATCH 27/65] #11710 fix release note --- doc/release-notes/11710-get-available-dataverses-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/release-notes/11710-get-available-dataverses-api.md b/doc/release-notes/11710-get-available-dataverses-api.md index 6658d1d8fd5..ac33c581848 100644 --- a/doc/release-notes/11710-get-available-dataverses-api.md +++ b/doc/release-notes/11710-get-available-dataverses-api.md @@ -1,5 +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 the start of the collections' names. +-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. From 239af1b84408371081140b4f63200ccc486ea686 Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Thu, 28 Aug 2025 09:48:37 -0400 Subject: [PATCH 28/65] Update native-api.rst --- doc/sphinx-guides/source/api/native-api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index 0a208967f07..327e31124f6 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -735,7 +735,7 @@ Note: you must have "Add Dataset" permission in the given collection to invoke t 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 and must include the beginning of the Dataverse Collections' names. +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: From 85a3dccdc6df01c18dc333a40aac0dbe48b80937 Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Thu, 28 Aug 2025 09:54:36 -0400 Subject: [PATCH 29/65] #11710 some code cleanup --- .../iq/dataverse/DataverseServiceBean.java | 47 +++++++++---------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java index 85d4179a6d9..bc91cb4e081 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java @@ -510,15 +510,15 @@ public List filterDataversesForLinking(String query, DataverseRequest List dataverseList = new ArrayList<>(); List results = filterDataversesByNamePattern(query); - + if (results == null || results.isEmpty()) { - return null; + 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(); @@ -527,22 +527,22 @@ public List filterDataversesForLinking(String query, DataverseRequest alreadyLinkeddv_ids = em.createNativeQuery("SELECT linkingdataverse_id FROM dataverselinkingdataverse WHERE dataverse_id = " + linkedDataverse.getId()).getResultList(); } - List remove = new ArrayList<>(); + 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){ + + if (dvo instanceof Dataverse dataverse) { remove.add(dataverse); } - + for (Dataverse res : results) { if (!remove.contains(res)) { if ((linkedDataset != null && this.permissionService.requestOn(req, res).has(Permission.LinkDataset)) - || (linkedDataverse != null && this.permissionService.requestOn(req, res).has(Permission.LinkDataverse))) { + || (linkedDataverse != null && this.permissionService.requestOn(req, res).has(Permission.LinkDataverse))) { dataverseList.add(res); } } @@ -550,44 +550,43 @@ public List filterDataversesForLinking(String query, DataverseRequest return dataverseList; } - - - public List removeUnlinkableDataverses (List allWithPerms, DvObject dvo){ + + 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(); + 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(); + alreadyLinkeddv_ids = em.createNativeQuery("SELECT linkingdataverse_id FROM dataverselinkingdataverse WHERE dataverse_id = " + linkedDataverse.getId()).getResultList(); } - List remove = new ArrayList<>(); + 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){ + + if (dvo instanceof Dataverse dataverse) { remove.add(dataverse); - } else { + } else { //dataset is always owned by a dataverse - remove.add((Dataverse)dvo.getOwner()); + remove.add((Dataverse) dvo.getOwner()); } - + for (Dataverse res : allWithPerms) { if (!remove.contains(res)) { - dataverseList.add(res); + dataverseList.add(res); } } - - return dataverseList; + + return dataverseList; } From 286402f94bc6ebe5b8c7a04b765fd1c905cae787 Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Thu, 28 Aug 2025 12:25:24 -0400 Subject: [PATCH 30/65] #11710 skip filter if search term empty --- .../harvard/iq/dataverse/api/Dataverses.java | 20 +++++++++++++------ .../iq/dataverse/api/DataversesIT.java | 9 ++++++++- 2 files changed, 22 insertions(+), 7 deletions(-) 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 512b7b00fab..97bc961c6b2 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java @@ -1785,8 +1785,7 @@ public Response getLinkingDataverseList(@Context ContainerRequestContext crc, @P DvObject dvObject = findDvoByIdAndTypeOrDie(dvIdtf, type, false); List dataversesForLinkingSearch = new ArrayList(); - dataversesForLinkingSearch = dataverseService.filterDataversesByNamePattern(searchTerm); - + AuthenticatedUser requestUser = (AuthenticatedUser) getRequestUser(crc); DataverseRequest dvReq = new DataverseRequest(requestUser, (IpAddress) null); List dataversesForLinking; @@ -1794,13 +1793,22 @@ public Response getLinkingDataverseList(@Context ContainerRequestContext crc, @P List mergedWithSearch = new ArrayList<>(); dataversesForLinking = dataverseService.removeUnlinkableDataverses(dataversesForLinking, dvObject); - if (!dataversesForLinkingSearch.isEmpty()) { - for (Dataverse dv : dataversesForLinking) { - if (dataversesForLinkingSearch.contains(dv)) { - mergedWithSearch.add(dv); + + //Only do search lookup if search term is there. Otherwise just include the collections based on perms + if (!searchTerm.isEmpty()) { + dataversesForLinkingSearch = dataverseService.filterDataversesByNamePattern(searchTerm); + if (!dataversesForLinkingSearch.isEmpty()) { + for (Dataverse dv : dataversesForLinking) { + if (dataversesForLinkingSearch.contains(dv)) { + mergedWithSearch.add(dv); + } } } + } else { + //search term empty then add all based on perms + mergedWithSearch.addAll(dataversesForLinking); } + JsonArrayBuilder dvBuilder = Json.createArrayBuilder(); if (!mergedWithSearch.isEmpty()) { for (Dataverse dv : mergedWithSearch) { 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 64c88197494..5de3fbb6620 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java @@ -724,7 +724,14 @@ public void testGetLinkableDataverses(){ getLinkableDataversesForDataversePartial.prettyPrint(); getLinkableDataversesForDataversePartial.then().assertThat() .statusCode(OK.getStatusCode()) - .body("data[0].alias", equalTo(dataverseAliasForLinking)); + .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 From 5ed86a1cde6f20d6b4fd8b6a2d9a232b66ffea1e Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Fri, 5 Sep 2025 13:39:01 -0400 Subject: [PATCH 31/65] Update doc/sphinx-guides/source/api/native-api.rst Co-authored-by: Philip Durbin --- doc/sphinx-guides/source/api/native-api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index 327e31124f6..6d6b25b10ef 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -732,7 +732,7 @@ 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 +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. From a3065927348fd44a637bd0c85c370620e9e2f3ce Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Fri, 5 Sep 2025 13:39:52 -0400 Subject: [PATCH 32/65] Update src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java Co-authored-by: Guillermo Portas --- .../java/edu/harvard/iq/dataverse/PermissionServiceBean.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java index a4111963ea2..303bc87d116 100644 --- a/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java @@ -101,7 +101,7 @@ WITH grouplist AS ( WHERE explicitgroup_authenticateduser.containedauthenticatedusers_id = @USERID ) - SELECT * FROM DATAVERSE WHERE id IN ( + SELECT * FROM DATAVERSE WHERE id IN ( SELECT definitionpoint_id FROM roleassignment WHERE roleassignment.assigneeidentifier IN ( From 6cc2f6b05eb8835702bb0d9b1ca1776a0bb658d4 Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Fri, 5 Sep 2025 13:40:45 -0400 Subject: [PATCH 33/65] Update src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java Co-authored-by: Guillermo Portas --- .../java/edu/harvard/iq/dataverse/PermissionServiceBean.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java index 303bc87d116..7deae81160a 100644 --- a/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java @@ -157,7 +157,7 @@ AND EXISTS (SELECT id FROM dataverserole WHERE dataverserole.id = roleassignment AND @IPRANGESQL ) ) - ) + ) """; /** * A request-level permission query (e.g includes IP ras). From f7f0d26ace4f0ebc7de0a6b7671452ec71614121 Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Fri, 5 Sep 2025 13:41:05 -0400 Subject: [PATCH 34/65] Update src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java Co-authored-by: Guillermo Portas --- .../java/edu/harvard/iq/dataverse/PermissionServiceBean.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java index 7deae81160a..742505acdf5 100644 --- a/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java @@ -961,7 +961,6 @@ public List findPermittedCollections(DataverseRequest request, Authen return null; } - /** * Calculates the complete list of role assignments for a given user on a DvObject. * This includes roles assigned directly to the user and roles inherited from any groups From f071e214345e4015fe64d880ae807f57ff633d4b Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Fri, 5 Sep 2025 13:41:30 -0400 Subject: [PATCH 35/65] Update src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java Co-authored-by: Guillermo Portas --- .../java/edu/harvard/iq/dataverse/PermissionServiceBean.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java index 742505acdf5..7ae24a08c27 100644 --- a/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java @@ -922,7 +922,6 @@ public List findPermittedCollections(DataverseRequest request, Authen return findPermittedCollections(request, user, 1 << permission.ordinal()); } - public List findPermittedCollections(DataverseRequest request, AuthenticatedUser user, int permissionBit) { if (user != null) { // IP Group - Only check IP if a User is calling for themself From 5d8eabba0813d53c57fe3c627ef615b5b203e4ca Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Fri, 5 Sep 2025 14:01:30 -0400 Subject: [PATCH 36/65] Update src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java Co-authored-by: Guillermo Portas --- .../java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 18896df32ad..cc9d85d0ad8 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,7 +291,7 @@ public static JsonObjectBuilder json(Workflow wf){ return bld; } - public static JsonObjectBuilder json(Dataverse dv, Boolean mini) { + public static JsonObjectBuilder json(Dataverse dv, boolean minimal) { if (!mini){ return json(dv, false, false, null); } else { From e975a68289e791ec28694e206128692255eacf72 Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Fri, 5 Sep 2025 14:03:30 -0400 Subject: [PATCH 37/65] Update src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java Co-authored-by: Guillermo Portas --- src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1e79706b7f7..8b0f5323659 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java @@ -797,7 +797,7 @@ public void testGetLinkableDataverses(){ .statusCode(OK.getStatusCode()) .body("data.size()", equalTo(0)); - //set user api back to super user for cleanup + //set user api back to super user for cleanup UtilIT.setSuperuserStatus(username, Boolean.TRUE); From 70b16d596821ded7bf5a4a12cdc6b2a7b81e1886 Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Fri, 5 Sep 2025 14:42:25 -0400 Subject: [PATCH 38/65] #11710 fix typo --- .../java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 cc9d85d0ad8..779cb84e9cc 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 @@ -292,7 +292,7 @@ public static JsonObjectBuilder json(Workflow wf){ } public static JsonObjectBuilder json(Dataverse dv, boolean minimal) { - if (!mini){ + if (!minimal){ return json(dv, false, false, null); } else { return jsonObjectBuilder() From 238db74dca3de4dfd0b6b68c828430bdd5c000ae Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Mon, 8 Sep 2025 14:10:38 -0400 Subject: [PATCH 39/65] #11710 move logic in json printer --- .../harvard/iq/dataverse/api/Dataverses.java | 2 +- .../iq/dataverse/util/json/JsonPrinter.java | 90 +++++++++---------- 2 files changed, 46 insertions(+), 46 deletions(-) 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 97bc961c6b2..b522d63bcb0 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java @@ -716,7 +716,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)); } 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 779cb84e9cc..ae84ef18ec7 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 @@ -293,65 +293,65 @@ public static JsonObjectBuilder json(Workflow wf){ public static JsonObjectBuilder json(Dataverse dv, boolean minimal) { if (!minimal){ - return json(dv, false, false, null); + return json(dv, false, false, false, null); } else { - return jsonObjectBuilder() - .add("id", dv.getId()) - .add("alias", dv.getAlias()) - .add("name", dv.getName()); + 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) { - bld.add("dataverseContacts", JsonPrinter.json(dv.getDataverseContacts())); - } - if (returnOwners){ - bld.add("isPartOf", getOwnersFromDvObject(dv)); - } - bld.add("permissionRoot", dv.isPermissionRoot()) - .add("description", dv.getDescription()) - .add("dataverseType", dv.getDataverseType().name()) - .add("isMetadataBlockRoot", dv.isMetadataBlockRoot()) - .add("isFacetRoot", dv.isFacetRoot()); - if (dv.getOwner() != null) { - bld.add("ownerId", dv.getOwner().getId()); - } - if (dv.getCreateDate() != null) { - bld.add("creationDate", Util.getDateTimeFormat().format(dv.getCreateDate())); - } - if (dv.getDataverseTheme() != null) { - bld.add("theme", JsonPrinter.json(dv.getDataverseTheme())); - } - if(dv.getStorageDriverId() != null) { - bld.add("storageDriverLabel", DataAccess.getStorageDriverLabelFor(dv.getStorageDriverId())); - } - if (dv.getFilePIDsEnabled() != null) { - bld.add("filePIDsEnabled", dv.getFilePIDsEnabled()); - } - bld.add("effectiveRequiresFilesToPublishDataset", dv.getEffectiveRequiresFilesToPublishDataset()); - bld.add("isReleased", dv.isReleased()); + .add("name", dv.getName()); + //minimal refers to only returning the id alias and name for + //use in selecting collections available for linking + if (!minimal) { + bld.add("affiliation", dv.getAffiliation()); + if (!hideEmail) { + bld.add("dataverseContacts", JsonPrinter.json(dv.getDataverseContacts())); + } + if (returnOwners) { + bld.add("isPartOf", getOwnersFromDvObject(dv)); + } + bld.add("permissionRoot", dv.isPermissionRoot()) + .add("description", dv.getDescription()) + .add("dataverseType", dv.getDataverseType().name()) + .add("isMetadataBlockRoot", dv.isMetadataBlockRoot()) + .add("isFacetRoot", dv.isFacetRoot()); + if (dv.getOwner() != null) { + bld.add("ownerId", dv.getOwner().getId()); + } + if (dv.getCreateDate() != null) { + bld.add("creationDate", Util.getDateTimeFormat().format(dv.getCreateDate())); + } + if (dv.getDataverseTheme() != null) { + bld.add("theme", JsonPrinter.json(dv.getDataverseTheme())); + } + if (dv.getStorageDriverId() != null) { + bld.add("storageDriverLabel", DataAccess.getStorageDriverLabelFor(dv.getStorageDriverId())); + } + if (dv.getFilePIDsEnabled() != null) { + bld.add("filePIDsEnabled", dv.getFilePIDsEnabled()); + } + bld.add("effectiveRequiresFilesToPublishDataset", dv.getEffectiveRequiresFilesToPublishDataset()); + bld.add("isReleased", dv.isReleased()); - List inputLevels = dv.getDataverseFieldTypeInputLevels(); - if(!inputLevels.isEmpty()) { - bld.add("inputLevels", JsonPrinter.jsonDataverseFieldTypeInputLevels(inputLevels)); - } + List inputLevels = dv.getDataverseFieldTypeInputLevels(); + if (!inputLevels.isEmpty()) { + bld.add("inputLevels", JsonPrinter.jsonDataverseFieldTypeInputLevels(inputLevels)); + } - if (childCount != null) { - bld.add("childCount", childCount); + if (childCount != null) { + bld.add("childCount", childCount); + } + addDatasetFileCountLimit(dv, bld); } - addDatasetFileCountLimit(dv, bld); - return bld; } From 5791664ad4d69b1573ac09d3ce3492a0bce8c816 Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Wed, 10 Sep 2025 09:49:15 -0400 Subject: [PATCH 40/65] #11710 fix comment from copy --- src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 8b0f5323659..d0ae3f4d8b9 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java @@ -2455,7 +2455,7 @@ public void testCreateAndGetTemplates() throws JsonParseException { String[] newFacetIds = new String[]{"contributorName"}; String[] newMetadataBlockNames = new String[]{"citation", "geospatial", "biomedical"}; - // Assert that the error is returned for having both MetadataBlockNames and inheritMetadataBlocksFromParent + //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, From cfb841cf049b71934f7276e8d96067325f14d745 Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Wed, 10 Sep 2025 10:57:28 -0400 Subject: [PATCH 41/65] #11710 handle guest users --- .../edu/harvard/iq/dataverse/api/Dataverses.java | 13 ++++++++++--- .../edu/harvard/iq/dataverse/api/DataversesIT.java | 5 +++++ .../java/edu/harvard/iq/dataverse/api/UtilIT.java | 12 +++++++++--- 3 files changed, 24 insertions(+), 6 deletions(-) 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 b522d63bcb0..16415e0c155 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java @@ -1786,10 +1786,18 @@ public Response getLinkingDataverseList(@Context ContainerRequestContext crc, @P DvObject dvObject = findDvoByIdAndTypeOrDie(dvIdtf, type, false); List dataversesForLinkingSearch = new ArrayList(); - AuthenticatedUser requestUser = (AuthenticatedUser) getRequestUser(crc); + User requestUser = (User) getRequestUser(crc); + AuthenticatedUser authUser; + if (!requestUser.isAuthenticated()) { + throw new WrappedResponse(error(Response.Status.BAD_REQUEST, + BundleUtil.getStringFromBundle("dataverse.link.user"))); + } else { + authUser = (AuthenticatedUser) requestUser; + } + DataverseRequest dvReq = new DataverseRequest(requestUser, (IpAddress) null); List dataversesForLinking; - dataversesForLinking = permissionService.findPermittedCollections(dvReq, requestUser, Permission.LinkDataset); + dataversesForLinking = permissionService.findPermittedCollections(dvReq, authUser, Permission.LinkDataset); List mergedWithSearch = new ArrayList<>(); dataversesForLinking = dataverseService.removeUnlinkableDataverses(dataversesForLinking, dvObject); @@ -1820,7 +1828,6 @@ public Response getLinkingDataverseList(@Context ContainerRequestContext crc, @P return wr.getResponse(); } catch (Exception e) { return error(Status.BAD_REQUEST, e.getLocalizedMessage()); - } } 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 d0ae3f4d8b9..a47672076b3 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java @@ -766,6 +766,11 @@ public void testGetLinkableDataverses(){ .statusCode(OK.getStatusCode()) .body("data.size()", equalTo(0)); + Response getGuestUnavailableForDataset = UtilIT.getLinkableDataverses("dataset", datasetPersistentId, "", dataverseAliasUnavailableForLinking); + getGuestUnavailableForDataset.prettyPrint(); + getGuestUnavailableForDataset.then().assertThat() + .statusCode(BAD_REQUEST.getStatusCode()); + Response getUnavailableForDataverse = UtilIT.getLinkableDataverses("dataverse", dataverseAlias, apiToken, dataverseAliasUnavailableForLinking); getUnavailableForDataverse.prettyPrint(); getUnavailableForDataverse.then().assertThat() 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 0eb4a79a3eb..00dbbd7dada 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -4729,10 +4729,16 @@ static Response getLinkableDataverses (String type, String dvObjectId, String ap optionalQueryParam = "&persistentId=" + dvObjectId; } } + + if (!apiToken.isEmpty()) { + return given() + .header(API_TOKEN_HTTP_HEADER, apiToken) + .get("/api/dataverses/" + idInPath + "/" + type + "/linkingDataverses?searchTerm=" + searchTerm + optionalQueryParam); - 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, From 26661fc17bb2e53bc1aff4dc1e51f1443e2e820c Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Wed, 10 Sep 2025 11:50:26 -0400 Subject: [PATCH 42/65] #11710 fix pass bad alias --- .../edu/harvard/iq/dataverse/api/AbstractApiBean.java | 3 +++ .../edu/harvard/iq/dataverse/api/DataversesIT.java | 11 +++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) 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 938d7fc9081..12cbe045f8c 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java @@ -637,6 +637,9 @@ protected DvObject findDvoByIdAndTypeOrDie(@NotNull final String dvIdtf, String 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/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java index a47672076b3..9657ebd8fd9 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java @@ -740,8 +740,15 @@ public void testGetLinkableDataverses(){ getLinkableDataversesForDataversePartial.prettyPrint(); getLinkableDataversesForDataversePartial.then().assertThat() .statusCode(OK.getStatusCode()) - .body("data[0].alias", equalTo(dataverseAliasForLinking)); - + .body("data[0].alias", equalTo(dataverseAliasForLinking)); + + //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); From 886256c0da2b7f41a3773a572a9684b53fd2c2d9 Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Wed, 10 Sep 2025 13:48:16 -0400 Subject: [PATCH 43/65] #11710 remove parents from available linking DV --- .../iq/dataverse/DataverseServiceBean.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java index bc91cb4e081..57bc0bc5450 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/DataverseServiceBean.java @@ -572,13 +572,21 @@ public List removeUnlinkableDataverses(List allWithPerms, remove.add(removeIt); }); } + if (dvo instanceof Dataverse dataverse) { remove.add(dataverse); - } else { - //dataset is always owned by a dataverse - remove.add((Dataverse) dvo.getOwner()); - } + } + + 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)) { From 76333aaf50b1d6964aea321300c8b332ee4037a4 Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Wed, 10 Sep 2025 14:34:21 -0400 Subject: [PATCH 44/65] #11710 allow search term to be null --- .../harvard/iq/dataverse/api/Dataverses.java | 2 +- .../iq/dataverse/api/DataversesIT.java | 9 ++++- .../edu/harvard/iq/dataverse/api/UtilIT.java | 38 ++++++++++++++----- 3 files changed, 37 insertions(+), 12 deletions(-) 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 16415e0c155..9e78f527258 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java @@ -1803,7 +1803,7 @@ public Response getLinkingDataverseList(@Context ContainerRequestContext crc, @P dataversesForLinking = dataverseService.removeUnlinkableDataverses(dataversesForLinking, dvObject); //Only do search lookup if search term is there. Otherwise just include the collections based on perms - if (!searchTerm.isEmpty()) { + if (searchTerm != null && !searchTerm.isEmpty()) { dataversesForLinkingSearch = dataverseService.filterDataversesByNamePattern(searchTerm); if (!dataversesForLinkingSearch.isEmpty()) { for (Dataverse dv : dataversesForLinking) { 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 9657ebd8fd9..2333faccf79 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java @@ -740,7 +740,14 @@ public void testGetLinkableDataverses(){ getLinkableDataversesForDataversePartial.prettyPrint(); getLinkableDataversesForDataversePartial.then().assertThat() .statusCode(OK.getStatusCode()) - .body("data[0].alias", equalTo(dataverseAliasForLinking)); + .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)); //Try with bad target alias searchTerm = ""; 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 79a71441e26..5f5416e08cc 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -4729,28 +4729,46 @@ static Response updateDataverseFeaturedItem(long featuredItemId, return updateDataverseFeaturedItem(featuredItemId, content, displayOrder, keepFile, pathToFile, null, null, apiToken); } - static Response getLinkableDataverses (String type, String dvObjectId, String apiToken, String searchTerm) { + static Response getLinkableDataverses(String type, String dvObjectId, String apiToken, String searchTerm) { 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"; - optionalQueryParam = "&persistentId=" + dvObjectId; + if (searchTerm == null) { + optionalQueryParam = "?persistentId=" + dvObjectId; + } else { + optionalQueryParam = "&persistentId=" + dvObjectId; + } } } - - if (!apiToken.isEmpty()) { - return given() - .header(API_TOKEN_HTTP_HEADER, apiToken) - .get("/api/dataverses/" + idInPath + "/" + type + "/linkingDataverses?searchTerm=" + searchTerm + optionalQueryParam); + + 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 { - return given() - .get("/api/dataverses/" + idInPath + "/" + type + "/linkingDataverses?searchTerm=" + searchTerm + optionalQueryParam); - } + 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, From bafa0d7204ef46938b211de6309de8703b0358a4 Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Wed, 10 Sep 2025 15:30:05 -0400 Subject: [PATCH 45/65] #11710 add test for linking child --- .../harvard/iq/dataverse/api/DataversesIT.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) 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 2333faccf79..3fbd7cff23f 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java @@ -690,6 +690,11 @@ public void testGetLinkableDataverses(){ //Create dataverse for linking Response createDataverseResponseForLinking = UtilIT.createRandomDataverse(apiToken); String dataverseAliasForLinking = UtilIT.getAliasFromResponse(createDataverseResponseForLinking); + + // Create a child of the linking DV + Response createLevel1a = UtilIT.createSubDataverse(UtilIT.getRandomDvAlias() + "-level1a", null, apiToken, dataverseAliasForLinking); + createLevel1a.prettyPrint(); + String childLinking = UtilIT.getAliasFromResponse(createLevel1a); Response publishDataverse = UtilIT.publishDataverseViaNativeApi(dataverseAlias, apiToken); assertEquals(200, publishDataverse.getStatusCode()); @@ -747,7 +752,14 @@ public void testGetLinkableDataverses(){ getLinkableDataversesForDataversePartial.prettyPrint(); getLinkableDataversesForDataversePartial.then().assertThat() .statusCode(OK.getStatusCode()) - .body("data[0].alias", equalTo(dataverseAliasForLinking)); + .body("data[0].alias", equalTo(dataverseAliasForLinking)); + + //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 = ""; @@ -827,6 +839,9 @@ public void testGetLinkableDataverses(){ 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()); From 5774d950d2de93e778fd5799faff98ea127795d6 Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Wed, 10 Sep 2025 15:50:55 -0400 Subject: [PATCH 46/65] #11710 remove generic exception --- src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) 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 9e78f527258..a8743190114 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java @@ -1826,9 +1826,8 @@ public Response getLinkingDataverseList(@Context ContainerRequestContext crc, @P return ok(dvBuilder); } catch (WrappedResponse wr) { return wr.getResponse(); - } catch (Exception e) { - return error(Status.BAD_REQUEST, e.getLocalizedMessage()); - } + } + } From 97fae8b8988b7febca885c3c775224aab76719bc Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Thu, 11 Sep 2025 08:32:16 -0400 Subject: [PATCH 47/65] #11710 combine search and perms query --- .../iq/dataverse/PermissionServiceBean.java | 56 +++++++++++++++++-- .../harvard/iq/dataverse/api/Dataverses.java | 13 ++++- 2 files changed, 62 insertions(+), 7 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java index 70e8145835b..f6ef9a1d5bf 100644 --- a/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java @@ -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,7 +161,7 @@ AND EXISTS (SELECT id FROM dataverserole WHERE dataverserole.id = roleassignment AND @IPRANGESQL ) ) - ) + ) @SEARCHPREDICATE """; /** * A request-level permission query (e.g includes IP ras). @@ -921,12 +921,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"; @@ -959,15 +968,52 @@ public List findPermittedCollections(DataverseRequest request, Authen if (user.isSuperuser()) { sqlCode = LIST_ALL_DATAVERSES_SUPERUSER_HAS_PERMISSION; } else { + System.out.print("searchTerm: " + searchTerm); sqlCode = LIST_ALL_DATAVERSES_USER_HAS_PERMISSION .replace("@USERID", String.valueOf(user.getId())) .replace("@PERMISSIONBIT", String.valueOf(permissionBit)) - .replace("@IPRANGESQL", ipRangeSQL); + .replace("@IPRANGESQL", ipRangeSQL) + .replace("@SEARCHPREDICATE", getPredicateFromSearchTerm(searchTerm)); } + System.out.print(sqlCode); return em.createNativeQuery(sqlCode, Dataverse.class).getResultList(); } return null; } + + private String getPredicateFromSearchTerm(String searchTerm){ + + if (searchTerm == null || searchTerm.isEmpty()){ + System.out.print("search term null or empty"); + return ""; + } + + 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 + " %"; + } + /*if (pattern.length() == 2) { + pattern2 = pattern + "%"; + }*/ + + + String qstr = "and ((LOWER(dv.name) LIKE @DATAVERSE and ((SUBSTRING(LOWER(dv.name),0,(LENGTH(dv.name)-9)) LIKE @PATTERN1) " + + " or (SUBSTRING(LOWER(dv.name),0,(LENGTH(dv.name)-9)) LIKE @PATTERN2))) " + + " or (LOWER(dv.name) NOT LIKE @DATAVERSE and ((LOWER(dv.name) LIKE @PATTERN1) " + + " or (LOWER(dv.name) LIKE @PATTERN2)))) "; + qstr = qstr.replace("@DATAVERSE", "'%dataverse'") + .replace("@PATTERN1", "'" + pattern1 + "'") + .replace("@PATTERN2", "'" + pattern2 + "'"); + + System.out.print("Query String: " + qstr); + return qstr; + } /** * Calculates the complete list of role assignments for a given user on a DvObject. 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 a8743190114..3078afd7419 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java @@ -1797,12 +1797,19 @@ public Response getLinkingDataverseList(@Context ContainerRequestContext crc, @P DataverseRequest dvReq = new DataverseRequest(requestUser, (IpAddress) null); List dataversesForLinking; - dataversesForLinking = permissionService.findPermittedCollections(dvReq, authUser, Permission.LinkDataset); + String searchParam; + if(searchTerm != null){ + searchParam = searchTerm; + } else { + searchParam = ""; + } + dataversesForLinking = permissionService.findPermittedCollections(dvReq, authUser, Permission.LinkDataset, searchParam); List mergedWithSearch = new ArrayList<>(); dataversesForLinking = dataverseService.removeUnlinkableDataverses(dataversesForLinking, dvObject); //Only do search lookup if search term is there. Otherwise just include the collections based on perms + /* if (searchTerm != null && !searchTerm.isEmpty()) { dataversesForLinkingSearch = dataverseService.filterDataversesByNamePattern(searchTerm); if (!dataversesForLinkingSearch.isEmpty()) { @@ -1816,7 +1823,9 @@ public Response getLinkingDataverseList(@Context ContainerRequestContext crc, @P //search term empty then add all based on perms mergedWithSearch.addAll(dataversesForLinking); } - + */ + mergedWithSearch.addAll(dataversesForLinking); + JsonArrayBuilder dvBuilder = Json.createArrayBuilder(); if (!mergedWithSearch.isEmpty()) { for (Dataverse dv : mergedWithSearch) { From ce946d02dcd8e73feb3ff8f4f487b1441ee3b8b1 Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Thu, 11 Sep 2025 10:41:36 -0400 Subject: [PATCH 48/65] #11710 move dv creation for test --- .../edu/harvard/iq/dataverse/api/DataversesIT.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) 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 3fbd7cff23f..d11d9158f2a 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java @@ -691,10 +691,6 @@ public void testGetLinkableDataverses(){ Response createDataverseResponseForLinking = UtilIT.createRandomDataverse(apiToken); String dataverseAliasForLinking = UtilIT.getAliasFromResponse(createDataverseResponseForLinking); - // Create a child of the linking DV - Response createLevel1a = UtilIT.createSubDataverse(UtilIT.getRandomDvAlias() + "-level1a", null, apiToken, dataverseAliasForLinking); - createLevel1a.prettyPrint(); - String childLinking = UtilIT.getAliasFromResponse(createLevel1a); Response publishDataverse = UtilIT.publishDataverseViaNativeApi(dataverseAlias, apiToken); assertEquals(200, publishDataverse.getStatusCode()); @@ -752,7 +748,12 @@ public void testGetLinkableDataverses(){ getLinkableDataversesForDataversePartial.prettyPrint(); getLinkableDataversesForDataversePartial.then().assertThat() .statusCode(OK.getStatusCode()) - .body("data[0].alias", equalTo(dataverseAliasForLinking)); + .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); From 3abc4d04d99cae36ff10d723e6018cf7bfc37149 Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Thu, 11 Sep 2025 12:23:20 -0400 Subject: [PATCH 49/65] #11710 parametize the query --- .../iq/dataverse/PermissionServiceBean.java | 83 +++++++++---------- .../harvard/iq/dataverse/api/Dataverses.java | 1 - .../iq/dataverse/api/DataversesIT.java | 6 ++ 3 files changed, 46 insertions(+), 44 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java index f6ef9a1d5bf..1fb0d52e9fc 100644 --- a/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java @@ -161,8 +161,16 @@ AND EXISTS (SELECT id FROM dataverserole WHERE dataverserole.id = roleassignment AND @IPRANGESQL ) ) - ) @SEARCHPREDICATE + ) """; + + private static final String SEARCH_PARAMS = """ + 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 ?)))) + """; + /** * A request-level permission query (e.g includes IP ras). */ @@ -968,52 +976,41 @@ public List findPermittedCollections(DataverseRequest request, Authen if (user.isSuperuser()) { sqlCode = LIST_ALL_DATAVERSES_SUPERUSER_HAS_PERMISSION; } else { - System.out.print("searchTerm: " + searchTerm); - sqlCode = LIST_ALL_DATAVERSES_USER_HAS_PERMISSION - .replace("@USERID", String.valueOf(user.getId())) - .replace("@PERMISSIONBIT", String.valueOf(permissionBit)) - .replace("@IPRANGESQL", ipRangeSQL) - .replace("@SEARCHPREDICATE", getPredicateFromSearchTerm(searchTerm)); + 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(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(); + } } - System.out.print(sqlCode); - return em.createNativeQuery(sqlCode, Dataverse.class).getResultList(); } return null; } - - private String getPredicateFromSearchTerm(String searchTerm){ - - if (searchTerm == null || searchTerm.isEmpty()){ - System.out.print("search term null or empty"); - return ""; - } - - 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 + " %"; - } - /*if (pattern.length() == 2) { - pattern2 = pattern + "%"; - }*/ - - - String qstr = "and ((LOWER(dv.name) LIKE @DATAVERSE and ((SUBSTRING(LOWER(dv.name),0,(LENGTH(dv.name)-9)) LIKE @PATTERN1) " - + " or (SUBSTRING(LOWER(dv.name),0,(LENGTH(dv.name)-9)) LIKE @PATTERN2))) " - + " or (LOWER(dv.name) NOT LIKE @DATAVERSE and ((LOWER(dv.name) LIKE @PATTERN1) " - + " or (LOWER(dv.name) LIKE @PATTERN2)))) "; - qstr = qstr.replace("@DATAVERSE", "'%dataverse'") - .replace("@PATTERN1", "'" + pattern1 + "'") - .replace("@PATTERN2", "'" + pattern2 + "'"); - - System.out.print("Query String: " + qstr); - return qstr; - } /** * Calculates the complete list of role assignments for a given user on a DvObject. 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 3078afd7419..0abfed3d4ba 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java @@ -70,7 +70,6 @@ import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.StreamingOutput; -import java.sql.PreparedStatement; import org.glassfish.jersey.media.multipart.FormDataBodyPart; import org.glassfish.jersey.media.multipart.FormDataContentDisposition; import org.glassfish.jersey.media.multipart.FormDataParam; 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 d11d9158f2a..e849a32a513 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java @@ -787,6 +787,12 @@ public void testGetLinkableDataverses(){ 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() From cadf11dd75c6c5e8389d64508bf659629f6c5d83 Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Thu, 11 Sep 2025 14:04:18 -0400 Subject: [PATCH 50/65] #11710 Code Cleanup --- .../harvard/iq/dataverse/api/Dataverses.java | 33 ++++++------------- 1 file changed, 10 insertions(+), 23 deletions(-) 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 0abfed3d4ba..36e92f0f1a1 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java @@ -1783,7 +1783,6 @@ public Response getLinkingDataverseList(@Context ContainerRequestContext crc, @P try { DvObject dvObject = findDvoByIdAndTypeOrDie(dvIdtf, type, false); - List dataversesForLinkingSearch = new ArrayList(); User requestUser = (User) getRequestUser(crc); AuthenticatedUser authUser; @@ -1802,32 +1801,20 @@ public Response getLinkingDataverseList(@Context ContainerRequestContext crc, @P } else { searchParam = ""; } - dataversesForLinking = permissionService.findPermittedCollections(dvReq, authUser, Permission.LinkDataset, searchParam); - - List mergedWithSearch = new ArrayList<>(); - dataversesForLinking = dataverseService.removeUnlinkableDataverses(dataversesForLinking, dvObject); - - //Only do search lookup if search term is there. Otherwise just include the collections based on perms - /* - if (searchTerm != null && !searchTerm.isEmpty()) { - dataversesForLinkingSearch = dataverseService.filterDataversesByNamePattern(searchTerm); - if (!dataversesForLinkingSearch.isEmpty()) { - for (Dataverse dv : dataversesForLinking) { - if (dataversesForLinkingSearch.contains(dv)) { - mergedWithSearch.add(dv); - } - } - } + //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 { - //search term empty then add all based on perms - mergedWithSearch.addAll(dataversesForLinking); + permToCheck = Permission.LinkDataverse; } - */ - mergedWithSearch.addAll(dataversesForLinking); + + dataversesForLinking = permissionService.findPermittedCollections(dvReq, authUser, permToCheck, searchParam); + dataversesForLinking = dataverseService.removeUnlinkableDataverses(dataversesForLinking, dvObject); JsonArrayBuilder dvBuilder = Json.createArrayBuilder(); - if (!mergedWithSearch.isEmpty()) { - for (Dataverse dv : mergedWithSearch) { + if (!dataversesForLinking.isEmpty()) { + for (Dataverse dv : dataversesForLinking) { dvBuilder.add(json(dv, true)); } } From d45d0b984e69711061a3d9b83977b77ad6eed6bc Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Thu, 11 Sep 2025 14:50:32 -0400 Subject: [PATCH 51/65] #11710 code cleanup --- .../java/edu/harvard/iq/dataverse/api/AbstractApiBean.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 6b5c4bcf0b2..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 findDvoByIdAndTypeOrDie(@NotNull final String dvIdtf, String type, Boolean testForReleased) 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; From 8d7c44140327f44d3939103bf00075f3ce54935d Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Thu, 11 Sep 2025 15:54:11 -0400 Subject: [PATCH 52/65] #11710 implement command --- .../harvard/iq/dataverse/api/Dataverses.java | 37 ++--------- .../impl/GetLinkingDataverseListCommand.java | 66 +++++++++++++++++++ .../iq/dataverse/api/DataversesIT.java | 2 +- 3 files changed, 74 insertions(+), 31 deletions(-) create mode 100644 src/main/java/edu/harvard/iq/dataverse/engine/command/impl/GetLinkingDataverseListCommand.java 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 36e92f0f1a1..b65c53b0ded 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java @@ -1783,35 +1783,12 @@ public Response getLinkingDataverseList(@Context ContainerRequestContext crc, @P try { DvObject dvObject = findDvoByIdAndTypeOrDie(dvIdtf, type, false); - - User requestUser = (User) getRequestUser(crc); - AuthenticatedUser authUser; - if (!requestUser.isAuthenticated()) { - throw new WrappedResponse(error(Response.Status.BAD_REQUEST, - BundleUtil.getStringFromBundle("dataverse.link.user"))); - } else { - authUser = (AuthenticatedUser) requestUser; - } - - DataverseRequest dvReq = new DataverseRequest(requestUser, (IpAddress) null); - 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; - } + List dataversesForLinking = execCommand(new GetLinkingDataverseListCommand( + createDataverseRequest(getRequestUser(crc)), + dvObject, + searchTerm + )); - dataversesForLinking = permissionService.findPermittedCollections(dvReq, authUser, permToCheck, searchParam); - dataversesForLinking = dataverseService.removeUnlinkableDataverses(dataversesForLinking, dvObject); - JsonArrayBuilder dvBuilder = Json.createArrayBuilder(); if (!dataversesForLinking.isEmpty()) { for (Dataverse dv : dataversesForLinking) { @@ -1821,8 +1798,8 @@ public Response getLinkingDataverseList(@Context ContainerRequestContext crc, @P return ok(dvBuilder); } catch (WrappedResponse wr) { return wr.getResponse(); - } - + } + } 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..2710940c813 --- /dev/null +++ b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/GetLinkingDataverseListCommand.java @@ -0,0 +1,66 @@ + +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.List; + +/** + * + * @author stephenkraffmiller + */ +@RequiredPermissions({}) +public class GetLinkingDataverseListCommand extends AbstractCommand> { + + private final String searchTerm; + private final DvObject dvObject; + + public GetLinkingDataverseListCommand(DataverseRequest aRequest, DvObject dvObject, String searchTerm) { + super(aRequest, dvObject); + this.searchTerm = searchTerm; + this.dvObject = dvObject; + } + + @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; + } + + dataversesForLinking = ctxt.permissions().findPermittedCollections(getRequest(), authUser, permToCheck, searchParam); + return ctxt.dataverses().removeUnlinkableDataverses(dataversesForLinking, dvObject); + + } + +} 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 e849a32a513..c72a32a92f2 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java @@ -802,7 +802,7 @@ public void testGetLinkableDataverses(){ Response getGuestUnavailableForDataset = UtilIT.getLinkableDataverses("dataset", datasetPersistentId, "", dataverseAliasUnavailableForLinking); getGuestUnavailableForDataset.prettyPrint(); getGuestUnavailableForDataset.then().assertThat() - .statusCode(BAD_REQUEST.getStatusCode()); + .statusCode(FORBIDDEN.getStatusCode()); Response getUnavailableForDataverse = UtilIT.getLinkableDataverses("dataverse", dataverseAlias, apiToken, dataverseAliasUnavailableForLinking); getUnavailableForDataverse.prettyPrint(); From 3943cb201d9c262f616acb611caf2c03f530b7c4 Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Thu, 11 Sep 2025 15:57:14 -0400 Subject: [PATCH 53/65] #11710 remove unused comment --- src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java | 2 -- 1 file changed, 2 deletions(-) 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 b65c53b0ded..c2cb22bc195 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java @@ -1778,7 +1778,6 @@ public Response linkDataverse(@Context ContainerRequestContext crc, @PathParam(" @Produces(MediaType.APPLICATION_JSON) @Path("{identifier}/{type}/linkingDataverses") public Response getLinkingDataverseList(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf, @QueryParam("searchTerm") String searchTerm, @PathParam("type") String type) { - //first determine what you are linking based on identifier and type try { @@ -1799,7 +1798,6 @@ public Response getLinkingDataverseList(@Context ContainerRequestContext crc, @P } catch (WrappedResponse wr) { return wr.getResponse(); } - } From 70c7430fb20b56d6cf47ba5c06fafd9c6fd4067f Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Fri, 12 Sep 2025 14:45:22 -0400 Subject: [PATCH 54/65] #11710 simplify logic --- .../iq/dataverse/util/json/JsonPrinter.java | 79 ++++++++++--------- 1 file changed, 40 insertions(+), 39 deletions(-) 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 9c7c519c1ef..46aa2dfff20 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 @@ -310,48 +310,49 @@ public static JsonObjectBuilder json(Dataverse dv, Boolean hideEmail, Boolean re .add("alias", dv.getAlias()) .add("name", dv.getName()); //minimal refers to only returning the id alias and name for - //use in selecting collections available for linking - if (!minimal) { - bld.add("affiliation", dv.getAffiliation()); - if (!hideEmail) { - bld.add("dataverseContacts", JsonPrinter.json(dv.getDataverseContacts())); - } - if (returnOwners) { - bld.add("isPartOf", getOwnersFromDvObject(dv)); - } - bld.add("permissionRoot", dv.isPermissionRoot()) - .add("description", dv.getDescription()) - .add("dataverseType", dv.getDataverseType().name()) - .add("isMetadataBlockRoot", dv.isMetadataBlockRoot()) - .add("isFacetRoot", dv.isFacetRoot()); - if (dv.getOwner() != null) { - bld.add("ownerId", dv.getOwner().getId()); - } - if (dv.getCreateDate() != null) { - bld.add("creationDate", Util.getDateTimeFormat().format(dv.getCreateDate())); - } - if (dv.getDataverseTheme() != null) { - bld.add("theme", JsonPrinter.json(dv.getDataverseTheme())); - } - if (dv.getStorageDriverId() != null) { - bld.add("storageDriverLabel", DataAccess.getStorageDriverLabelFor(dv.getStorageDriverId())); - } - if (dv.getFilePIDsEnabled() != null) { - bld.add("filePIDsEnabled", dv.getFilePIDsEnabled()); - } - bld.add("effectiveRequiresFilesToPublishDataset", dv.getEffectiveRequiresFilesToPublishDataset()); - bld.add("isReleased", dv.isReleased()); + //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) { + bld.add("isPartOf", getOwnersFromDvObject(dv)); + } + bld.add("permissionRoot", dv.isPermissionRoot()) + .add("description", dv.getDescription()) + .add("dataverseType", dv.getDataverseType().name()) + .add("isMetadataBlockRoot", dv.isMetadataBlockRoot()) + .add("isFacetRoot", dv.isFacetRoot()); + if (dv.getOwner() != null) { + bld.add("ownerId", dv.getOwner().getId()); + } + if (dv.getCreateDate() != null) { + bld.add("creationDate", Util.getDateTimeFormat().format(dv.getCreateDate())); + } + if (dv.getDataverseTheme() != null) { + bld.add("theme", JsonPrinter.json(dv.getDataverseTheme())); + } + if (dv.getStorageDriverId() != null) { + bld.add("storageDriverLabel", DataAccess.getStorageDriverLabelFor(dv.getStorageDriverId())); + } + if (dv.getFilePIDsEnabled() != null) { + bld.add("filePIDsEnabled", dv.getFilePIDsEnabled()); + } + bld.add("effectiveRequiresFilesToPublishDataset", dv.getEffectiveRequiresFilesToPublishDataset()); + bld.add("isReleased", dv.isReleased()); - List inputLevels = dv.getDataverseFieldTypeInputLevels(); - if (!inputLevels.isEmpty()) { - bld.add("inputLevels", JsonPrinter.jsonDataverseFieldTypeInputLevels(inputLevels)); - } + List inputLevels = dv.getDataverseFieldTypeInputLevels(); + if (!inputLevels.isEmpty()) { + bld.add("inputLevels", JsonPrinter.jsonDataverseFieldTypeInputLevels(inputLevels)); + } - if (childCount != null) { - bld.add("childCount", childCount); - } - addDatasetFileCountLimit(dv, bld); + if (childCount != null) { + bld.add("childCount", childCount); } + addDatasetFileCountLimit(dv, bld); return bld; } From 47d7e3dd506234ab7bb133dc0358bd340ac2d4a7 Mon Sep 17 00:00:00 2001 From: GPortas Date: Mon, 15 Sep 2025 11:36:19 +0100 Subject: [PATCH 55/65] Added: GetLinkingDataverseListCommand unit test --- .../GetLinkingDataverseListCommandTest.java | 161 ++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 src/test/java/edu/harvard/iq/dataverse/engine/command/impl/GetLinkingDataverseListCommandTest.java 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..50636474be8 --- /dev/null +++ b/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/GetLinkingDataverseListCommandTest.java @@ -0,0 +1,161 @@ +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); + 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); + 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); + Mockito.when(commandContext.dataverses()).thenReturn(dataverseService); + Mockito.when(authenticatedUser.isAuthenticated()).thenReturn(true); + Mockito.when(permissionService.findPermittedCollections(dataverseRequest, authenticatedUser, Permission.LinkDataset, searchTerm)) + .thenReturn(emptyList); + 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()); + } +} From 72e78a5f0d6c18f638910abde802061fa7e9a517 Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Tue, 16 Sep 2025 09:56:31 -0400 Subject: [PATCH 56/65] #11710 short circuit test for unlinkable --- .../engine/command/impl/GetLinkingDataverseListCommand.java | 4 ++++ 1 file changed, 4 insertions(+) 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 index 2710940c813..c47c6215640 100644 --- 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 @@ -59,6 +59,10 @@ public List execute(CommandContext ctxt) throws CommandException { } 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); } From 844ca2c9c37706851a06b20281abea434accac02 Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Tue, 16 Sep 2025 10:23:37 -0400 Subject: [PATCH 57/65] #11710 comment out unused mocks --- .../impl/GetLinkingDataverseListCommandTest.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) 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 index 50636474be8..7f167370c63 100644 --- 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 @@ -117,13 +117,14 @@ public void execute_shouldUseEmptyString_whenSearchTermIsNull() throws CommandEx List expectedList = Collections.emptyList(); Mockito.when(commandContext.permissions()).thenReturn(permissionService); - Mockito.when(commandContext.dataverses()).thenReturn(dataverseService); + // 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); - Mockito.when(dataverseService.removeUnlinkableDataverses(expectedList, datasetToLink)) - .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); @@ -143,12 +144,13 @@ public void execute_shouldReturnEmptyList_whenNoPermittedCollectionsFound() thro List emptyList = Collections.emptyList(); Mockito.when(commandContext.permissions()).thenReturn(permissionService); - Mockito.when(commandContext.dataverses()).thenReturn(dataverseService); + // 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); - Mockito.when(dataverseService.removeUnlinkableDataverses(emptyList, datasetToLink)) - .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); From bd900fb4a1488c68285232056f78ce5ba6b7204e Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Tue, 16 Sep 2025 13:12:57 -0400 Subject: [PATCH 58/65] #11710 another null check --- src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 c2cb22bc195..f5816c36852 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java @@ -1789,7 +1789,7 @@ public Response getLinkingDataverseList(@Context ContainerRequestContext crc, @P )); JsonArrayBuilder dvBuilder = Json.createArrayBuilder(); - if (!dataversesForLinking.isEmpty()) { + if (dataversesForLinking != null && !dataversesForLinking.isEmpty()) { for (Dataverse dv : dataversesForLinking) { dvBuilder.add(json(dv, true)); } From 8756f5c084135f0b8b7044594f93f25374b1c8cd Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Tue, 16 Sep 2025 14:50:40 -0400 Subject: [PATCH 59/65] #11710 fix super user query/run --- .../iq/dataverse/PermissionServiceBean.java | 39 +++++++++++++++++-- .../iq/dataverse/api/DataversesIT.java | 20 +++++++++- 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java index 1fb0d52e9fc..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 = """ @@ -163,9 +163,17 @@ AND EXISTS (SELECT id FROM dataverserole WHERE dataverserole.id = roleassignment ) ) """; + + private static final String AND = """ + and + """; + + private static final String WHERE = """ + where + """; private static final String SEARCH_PARAMS = """ - and ((LOWER(dv.name) LIKE ? and ((SUBSTRING(LOWER(dv.name),0,(LENGTH(dv.name)-9)) LIKE ?) + ((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 ?)))) @@ -975,6 +983,31 @@ 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 { if (searchTerm == null || searchTerm.isEmpty()) { sqlCode = LIST_ALL_DATAVERSES_USER_HAS_PERMISSION @@ -993,7 +1026,7 @@ public List findPermittedCollections(DataverseRequest request, Authen pattern2 = pattern + " %"; } - sqlCode = LIST_ALL_DATAVERSES_USER_HAS_PERMISSION.concat(SEARCH_PARAMS) + 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); 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 c72a32a92f2..bbe1291f90f 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java @@ -45,6 +45,7 @@ import io.restassured.path.json.JsonPath; import org.hamcrest.CoreMatchers; import org.hamcrest.Matchers; +import static org.hamcrest.Matchers.greaterThan; public class DataversesIT { @@ -837,11 +838,28 @@ public void testGetLinkableDataverses(){ //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()); From 5c8a4eb685bd4927572763da157b161ecc4ec520 Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Thu, 18 Sep 2025 15:43:24 -0400 Subject: [PATCH 60/65] #11710 add already linking functionality --- .../dataverse/DatasetLinkingServiceBean.java | 1 - .../harvard/iq/dataverse/api/Dataverses.java | 5 ++- .../impl/GetLinkingDataverseListCommand.java | 43 +++++++++++++++---- .../iq/dataverse/api/DataversesIT.java | 8 ++++ .../edu/harvard/iq/dataverse/api/UtilIT.java | 14 +++++- 5 files changed, 58 insertions(+), 13 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetLinkingServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/DatasetLinkingServiceBean.java index 39c82bfa3f1..514ecb1dc4d 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DatasetLinkingServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/DatasetLinkingServiceBean.java @@ -13,7 +13,6 @@ import jakarta.persistence.EntityManager; import jakarta.persistence.NoResultException; import jakarta.persistence.PersistenceContext; -import jakarta.persistence.Query; import jakarta.persistence.TypedQuery; /** 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 f5816c36852..7322a8c9341 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java @@ -1777,7 +1777,7 @@ public Response linkDataverse(@Context ContainerRequestContext crc, @PathParam(" @AuthRequired @Produces(MediaType.APPLICATION_JSON) @Path("{identifier}/{type}/linkingDataverses") - public Response getLinkingDataverseList(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf, @QueryParam("searchTerm") String searchTerm, @PathParam("type") String type) { + public Response getLinkingDataverseList(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf, @QueryParam("searchTerm") String searchTerm, @QueryParam("alreadyLinking") boolean alreadyLinking, @PathParam("type") String type) { try { @@ -1785,7 +1785,8 @@ public Response getLinkingDataverseList(@Context ContainerRequestContext crc, @P List dataversesForLinking = execCommand(new GetLinkingDataverseListCommand( createDataverseRequest(getRequestUser(crc)), dvObject, - searchTerm + searchTerm, + alreadyLinking )); JsonArrayBuilder dvBuilder = Json.createArrayBuilder(); 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 index c47c6215640..de8e5a62938 100644 --- 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 @@ -25,13 +25,31 @@ public class GetLinkingDataverseListCommand extends AbstractCommand execute(CommandContext ctxt) throws CommandException { @@ -57,14 +75,21 @@ public List execute(CommandContext ctxt) throws CommandException { } else { permToCheck = Permission.LinkDataverse; } - - 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; + //dependin 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 { + if (dvObject instanceof Dataset) {; + return ctxt.dsLinking().findLinkingDataverses(dvObject.getId()); + } else { + return ctxt.dvLinking().findLinkingDataverses(dvObject.getId()); + } } - return ctxt.dataverses().removeUnlinkableDataverses(dataversesForLinking, dvObject); - } - } 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 bbe1291f90f..86b082d79ae 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java @@ -836,6 +836,14 @@ public void testGetLinkableDataverses(){ .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)); + //set user api back to super user for cleanup UtilIT.setSuperuserStatus(username, Boolean.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 5f5416e08cc..47b8deaf0e4 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -4730,19 +4730,31 @@ static Response updateDataverseFeaturedItem(long featuredItemId, } 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) { + if (searchTerm == null || searchTerm.isEmpty() ) { optionalQueryParam = "?persistentId=" + dvObjectId; } else { optionalQueryParam = "&persistentId=" + dvObjectId; } } } + + if (alreadyLinked){ + if (optionalQueryParam.isEmpty()){ + optionalQueryParam = "?alreadyLinking=true"; + } else { + optionalQueryParam = optionalQueryParam + "&alreadyLinking=true"; + } + } if (searchTerm == null) { if (!apiToken.isEmpty()) { From eb4ed35865a9cd7d7555805d4a7da8eb21818090 Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Thu, 18 Sep 2025 16:41:47 -0400 Subject: [PATCH 61/65] #11710 typo --- .../engine/command/impl/GetLinkingDataverseListCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index de8e5a62938..793175d26b7 100644 --- 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 @@ -85,7 +85,7 @@ public List execute(CommandContext ctxt) throws CommandException { } return ctxt.dataverses().removeUnlinkableDataverses(dataversesForLinking, dvObject); } else { - if (dvObject instanceof Dataset) {; + if (dvObject instanceof Dataset) { return ctxt.dsLinking().findLinkingDataverses(dvObject.getId()); } else { return ctxt.dvLinking().findLinkingDataverses(dvObject.getId()); From d4dbd3a23cec5a82d791d2e5a33ed1e0c50257c6 Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Fri, 19 Sep 2025 16:24:51 -0400 Subject: [PATCH 62/65] #11710 add search term to already linked --- .../iq/dataverse/DatasetLinkingDataverse.java | 13 ++++++ .../dataverse/DatasetLinkingServiceBean.java | 45 ++++++++++++++++--- .../impl/GetLinkingDataverseListCommand.java | 2 +- .../iq/dataverse/api/DataversesIT.java | 10 ++++- .../edu/harvard/iq/dataverse/api/UtilIT.java | 2 +- 5 files changed, 64 insertions(+), 8 deletions(-) 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 514ecb1dc4d..eb54d239e11 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; @@ -27,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) { @@ -40,13 +44,44 @@ 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 + " %"; + } + System.out.print("pattern1: " + pattern1); + System.out.print("pattern2: " + pattern2); + 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/engine/command/impl/GetLinkingDataverseListCommand.java b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/GetLinkingDataverseListCommand.java index 793175d26b7..c20e545982c 100644 --- 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 @@ -86,7 +86,7 @@ public List execute(CommandContext ctxt) throws CommandException { return ctxt.dataverses().removeUnlinkableDataverses(dataversesForLinking, dvObject); } else { if (dvObject instanceof Dataset) { - return ctxt.dsLinking().findLinkingDataverses(dvObject.getId()); + return ctxt.dsLinking().findLinkingDataverses(dvObject.getId(), searchParam); } else { return ctxt.dvLinking().findLinkingDataverses(dvObject.getId()); } 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 86b082d79ae..fe9d9b74de0 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java @@ -842,7 +842,15 @@ public void testGetLinkableDataverses(){ getLinkableDataverses.prettyPrint(); getLinkableDataverses.then().assertThat() .statusCode(OK.getStatusCode()) - .body("data.size()", equalTo(1)); + .body("data.size()", equalTo(1)); + + //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); 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 47b8deaf0e4..83c14a84a76 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -4749,7 +4749,7 @@ static Response getLinkableDataverses(String type, String dvObjectId, String api } if (alreadyLinked){ - if (optionalQueryParam.isEmpty()){ + if (optionalQueryParam.isEmpty() && (searchTerm == null || searchTerm.isEmpty())){ optionalQueryParam = "?alreadyLinking=true"; } else { optionalQueryParam = optionalQueryParam + "&alreadyLinking=true"; From b63555f0941b9993a722d06d6b1d44602eaf5ca9 Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Mon, 22 Sep 2025 10:26:48 -0400 Subject: [PATCH 63/65] #11710 add perms to find already linked --- .../dataverse/DatasetLinkingServiceBean.java | 2 - .../dataverse/DataverseLinkingDataverse.java | 11 ++++++ .../DataverseLinkingServiceBean.java | 37 +++++++++++++++++-- .../impl/GetLinkingDataverseListCommand.java | 23 +++++++++++- .../iq/dataverse/api/DataversesIT.java | 4 +- 5 files changed, 67 insertions(+), 10 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetLinkingServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/DatasetLinkingServiceBean.java index eb54d239e11..6b0f8af6590 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DatasetLinkingServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/DatasetLinkingServiceBean.java @@ -69,8 +69,6 @@ public List findLinkingDataverses(Long datasetId, String searchTerm) pattern1 = pattern; pattern2 = pattern + " %"; } - System.out.print("pattern1: " + pattern1); - System.out.print("pattern2: " + pattern2); TypedQuery typedQuery = em.createNamedQuery("DatasetLinkingDataverse.findByDatasetIdAndLinkingDataverseName", Long.class) .setParameter(1, datasetId).setParameter(2, "%dataverse").setParameter(3, pattern1) 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/engine/command/impl/GetLinkingDataverseListCommand.java b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/GetLinkingDataverseListCommand.java index c20e545982c..077c420d669 100644 --- 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 @@ -14,6 +14,8 @@ 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; /** @@ -85,11 +87,28 @@ public List execute(CommandContext ctxt) throws CommandException { } return ctxt.dataverses().removeUnlinkableDataverses(dataversesForLinking, dvObject); } else { + List dataversesAlreadyLinked; + List dataversesAlreadyLinkedCanUnlink = new ArrayList<>(); + //new ArrayList<>(); if (dvObject instanceof Dataset) { - return ctxt.dsLinking().findLinkingDataverses(dvObject.getId(), searchParam); + 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; + } + //this.permissionService.requestOn(req, res).has(Permission.LinkDataverse) } else { - return ctxt.dvLinking().findLinkingDataverses(dvObject.getId()); + 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/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java index fe9d9b74de0..9423c475499 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java @@ -850,8 +850,8 @@ public void testGetLinkableDataverses(){ getLinkableDataverses.prettyPrint(); getLinkableDataverses.then().assertThat() .statusCode(OK.getStatusCode()) - .body("data.size()", equalTo(0)); - + .body("data.size()", equalTo(0)); + //set user api back to super user for cleanup UtilIT.setSuperuserStatus(username, Boolean.TRUE); From 44eec3b74cfca862f56a0fb50c121cb3dc0d5665 Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Mon, 22 Sep 2025 10:58:38 -0400 Subject: [PATCH 64/65] #11710 add test for without perms --- .../command/impl/GetLinkingDataverseListCommand.java | 3 +-- .../java/edu/harvard/iq/dataverse/api/DataversesIT.java | 8 ++++++++ 2 files changed, 9 insertions(+), 2 deletions(-) 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 index 077c420d669..466871b88e4 100644 --- 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 @@ -77,7 +77,7 @@ public List execute(CommandContext ctxt) throws CommandException { } else { permToCheck = Permission.LinkDataverse; } - //dependin on the already linked boolean the command will return a list of Dataverses available for linking + //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); @@ -98,7 +98,6 @@ public List execute(CommandContext ctxt) throws CommandException { } return dataversesAlreadyLinkedCanUnlink; } - //this.permissionService.requestOn(req, res).has(Permission.LinkDataverse) } else { dataversesAlreadyLinked = ctxt.dvLinking().findLinkingDataverses(dvObject.getId(), searchParam); for (Dataverse dv : dataversesAlreadyLinked) { 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 9423c475499..719c820f1de 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java @@ -844,6 +844,14 @@ public void testGetLinkableDataverses(){ .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); From 5cbf6285442f0b83da159368e59d80ef9f97a7ad Mon Sep 17 00:00:00 2001 From: Stephen Kraffmiller Date: Mon, 22 Sep 2025 16:59:03 -0400 Subject: [PATCH 65/65] Update native-api.rst --- doc/sphinx-guides/source/api/native-api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index 1fba99039b6..f8f84430ac6 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -774,7 +774,7 @@ The fully expanded example above (without environment variables) looks like this 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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~